Saltar al contenido principal

Primer script y validación de parámetros en Python

Metadatos de la lección

Autoría:
Ignacio Slater-Muñoz
Última actualización:
27 de marzo de 2026

Cambios recientes:

  • 8880728 · 27 de marzo de 2026 · ✨ feat(notes): add abstract slots and Python structured-output lesson ( GitLab / GitHub )
  • c2972b0 · 26 de marzo de 2026 · 🐛✅ fix(types): unblock astro check in CI ( GitLab / GitHub )
  • 29b0ae0 · 11 de marzo de 2026 · 📝✨ docs(scripting): add Nushell first-script lesson and inline code guidance ( GitLab / GitHub )

Encuentra el código de la lección:

Abstract

Esta lección introduce la construcción de un primer script en Python con interfaz de línea de comandos, poniendo el foco en la validación explícita de parámetros, el manejo básico de logging y la organización de un punto de entrada claro. A través de comparaciones con PowerShell, se destaca cómo Python traslada responsabilidades que en otros lenguajes están integradas al lenguaje hacia su biblioteca estándar, manteniendo la sintaxis del lenguaje simple a cambio de más trabajo manual en la definición del contrato del script.

Validar argumentos con argparse

En PowerShell, buena parte del «contrato» del script (parámetros obligatorios, validaciones, errores) está integrado en el lenguaje mediante atributos como [Parameter(Mandatory)] o [ValidateScript(...)]. Python, si bien es un lenguaje conocido por proveer una amplia gama de funcionalidades out-of-the-box, incorpora estas funcionalidades mediante su biblioteca estándar y no como características del lenguaje. Esto significa que escribir el contrato que deseamos requerirá más «pegamento» manual; aquí es donde cobra protagonismo argparse.

A través de argparse podemos declarar argumentos posicionales y opcionales, marcar algunos como obligatorios, asignarles valores por defecto, documentarlos automáticamente en la ayuda (--help) y validar su forma básica (por ejemplo, si son enteros, strings o rutas).

Para empezar, construiremos un bloque reutilizable: un validador que recibe un string y lo convierte a un Path, fallando con un error entendible si la ruta no es un directorio. La idea es que luego podamos usarlo en el parser como type=validated_directory.

Validador reusable para directorios
scripts-py/readme_gen/validated_directory.py
import argparse
from pathlib import Path


def validated_directory(value: str) -> Path:
    p = Path(value)
    if not p.is_dir():
        raise argparse.ArgumentTypeError(f"'{value}' is not a valid directory")
    return p

Detalles clave

  • Type hints: value: str indica que esperamos un string, y -> Path que devolvemos un Path. En Python esto es una anotación para herramientas (IDE, linters, mypy), no una restricción obligatoria en runtime.
  • Path(value): convierte el texto recibido (por ejemplo "./scripts") en un objeto de ruta multiplataforma, evitando manipular strings manualmente.
  • p.is_dir(): verifica si la ruta existe y corresponde a un directorio (no a un archivo). Si quieres permitir rutas inexistentes que se crearán después, este chequeo sería distinto.
  • raise argparse.ArgumentTypeError: es la excepción «oficial» para validadores de tipo en argparse. Hace que el parser muestre el mensaje de error y termine con un output de uso consistente.

Construyendo un parser con argparse

Ahora que ya contamos con un bloque reutilizable para validar directorios (nuestro validated_directory()), el siguiente paso es construir el parser de la línea de comandos. En PowerShell, este tipo de contrato suele quedar «pegado» al propio script vía atributos. En Python lo expresamos configurando un objeto que describe: qué banderas existen, qué tipo de datos aceptan, qué valores por defecto usan y qué errores deben mostrarse si la invocación es inválida.

En nuestro caso, el programa generará un README. Para eso necesitamos:

  • Un nombre obligatorio (--name) para personalizar el contenido.
  • Un modo verboso (-v/--verbose) para imprimir información extra.
  • Un directorio de salida opcional (-o/--out_dir), validado como directorio.

Todo esto se declara en una función build_parser(), que devuelve un ArgumentParser ya configurado.

Definiendo el parser
scripts-py/readme_gen/parser.py
import argparse

from readme_gen.validated_directory import validated_directory


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="readme-gen",
        description="Generate a README.md from simple parameters.",
    )

    p.add_argument(
        "--name",
        required=True,
        help="Project name (used in the README title and sections).",
    )

    p.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Enable verbose output (extra messages during execution).",
    )

    p.add_argument(
        "-o",
        "--out_dir",
        type=validated_directory,
        default=".",
        help="Directory where README.md will be written (default: current directory).",
    )

    return p

Detalles clave

ArgumentParser() crea el objeto que concentra toda la definición de nuestra interfaz de línea de comandos. Al inicializarlo, podemos ajustar detalles como:

  • prog: el nombre que aparecerá en el texto de ayuda (útil si el script se ejecuta como comando).
  • description: un resumen que se muestra al usar --help, para dar contexto rápido.

La función clave es add_argument(): cada llamada registra un argumento. Lo interesante es que varios parámetros de add_argument() equivalen a partes del «contrato» :

  • required=True: el argumento es obligatorio. Si falta, argparse imprime un error, muestra la ayuda y termina el programa con un código de salida de error.
  • action="store_true": convierte una bandera en un booleano. Si la bandera aparece, el valor será True; si no aparece, será False . Esto es ideal para flags como --verbose.
  • type=... define cómo convertir el texto recibido a un tipo útil. Aquí usamos validated_directory, que transforma un string en Path y falla con un error claro si no es un directorio válido. Es importante notar que pasamos la función sin paréntesis: no la ejecutamos aquí, sino que entregamos una referencia para que argparse la invoque al procesar los argumentos.
  • default=".": valor por defecto cuando el usuario no entrega el argumento. En este ejemplo, el directorio de salida será el directorio actual.

Logging en Python con el módulo logging

En PowerShell es común escribir mensajes para depurar o diagnosticar usando Write-Verbose, Write-Warning, o Write-Error. Python ofrece algo similar (aunque con diferencias sutiles) 1 a través del módulo estándar logging: una forma centralizada de emitir mensajes con distintos niveles (DEBUG, INFO, WARNING, etc.) y decidir cuáles se muestran según el modo de ejecución.

La idea es simple: si se activa --verbose, mostramos detalles de depuración; si no, dejamos solo advertencias (o mensajes más importantes).

Configuración reutilizable de logging
scripts-py/readme_gen/app_logging.py
import logging


def setup_logging(verbose: bool) -> logging.Logger:
    level = logging.DEBUG if verbose else logging.WARNING
    logging.basicConfig(level=level, format="%(message)s")
    return logging.getLogger(__name__)

Detalles clave

Esta función define una política mínima de logging para toda la app:

  • level = ... selecciona el nivel global. Cuando verbose == True usamos logging.DEBUG (mensajes detallados); si no, usamos logging.WARNING (solo advertencias y más graves).
  • logging.basicConfig(...) aplica una configuración base. Aquí fijamos: level para filtrar mensajes y format="%(message)s" para imprimir solo el contenido del mensaje (sin prefijos como fecha, nombre del logger, etc.). La sintaxis de format corresponde al sistema de atributos de LogRecord .
  • logging.getLogger(__name__) entrega un logger nombrado. __name__ es una variable especial de Python que contiene el nombre del módulo actual (por ejemplo, readme_gen.app_logging). Esto permite que los mensajes indiquen de qué parte del programa vienen si luego cambiamos el formato, o si configuramos distintos niveles por módulo.

Punto de entrada y escritura del README

Ya tenemos las piezas principales: un parser (argparse) y una configuración de logging (logging). Ahora toca unir todo en un script ejecutable: parseamos argumentos, generamos el contenido y lo escribimos en README.md.

En PowerShell, normalmente devolveríamos un string y dejaríamos que quien consume el script decida si lo redirige a un archivo, a la salida estándar o a otro destino mediante el pipeline. En Python, lograr exactamente ese mismo estilo de composición es menos directo, así que aquí optaremos por un enfoque simple: escribir directamente a un archivo en el directorio de salida.

Uniendo todo en un script ejecutable
scripts-py/readme_gen/new_readme.py
from datetime import datetime
from pathlib import Path
import sys

from readme_gen.app_logging import setup_logging
from readme_gen.parser import build_parser


def write_readme(content: str, out_dir: Path) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    target = out_dir / "README.md"
    with target.open("w", encoding="utf-8") as f:
        f.write(content)


def main(argv: list[str]) -> int:
    args = build_parser().parse_args(argv)
    logger = setup_logging(args.verbose)
    logger.info("Creating README.md for project '%s'", args.name)

    content = (
        f"# {args.name}\n\n"
        f"Project initialized on {datetime.now():%Y-%m-%d %H:%M:%S}.\n\n"
        "Learn more about READMEs at https://www.makeareadme.com/.\n"
    )

    write_readme(content, args.out_dir)
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
Parseo de argumentos → logging → generación de contenido → escritura a README.md.

Detalles clave

Aquí están los puntos clave del script:

  • mkdir(parents=True, exist_ok=True) crea el directorio de salida si no existe. Con parents=True se crean también directorios intermedios, y con exist_ok=True no falla si ya existía.
  • with target.open(...) as f: abre el archivo de forma segura: al salir del bloque se cierra automáticamente, incluso si ocurre un error.
  • f.write(content) escribe el string completo al archivo.
  • parse_args(argv) toma la lista de strings (sin el nombre del programa) y la convierte en un objeto con atributos ( args.name , args.out_dir , etc.).
  • logger.info(...) emite un mensaje de diagnóstico. Si --verbose no está activo, este mensaje no se muestra (porque el nivel queda en WARNING ).
  • El bloque (f"...") concatena strings de manera legible: cada línea es un literal separado, pero Python los une en un solo string final.
  • return 0 y sys.exit(...) establecen el código de salida del proceso. Esto es importante para automatización (CI, scripts, tareas): 0 suele indicar éxito, y otros valores indican fallas.
  • if __name__ == "__main__": hace que el archivo se pueda ejecutar como script sin que main corra al importarlo como módulo. Además, sys.argv[1:] omite el primer argumento (el nombre del programa).

Uso

La opción -m le indica a Python que ejecute un módulo (o submódulo) usando el sistema de importaciones, en lugar de un archivo .py directo. Esto asegura que los imports funcionen correctamente dentro del paquete (readme_gen) y que el punto de entrada sea el bloque if __name__ == "__main__".
Ejecutando el módulo con -m
# Muestra la ayuda generada por argparse
python -m readme_gen.new_readme --help

# Genera un README en el directorio actual
python -m readme_gen.new_readme --name 'Utility Scripts - DIBS' --verbose

Conclusiones

En esta lección construimos un script completo en Python desde cero, recorriendo el camino habitual de una herramienta de línea de comandos: definición del contrato, validación de entradas, diagnóstico controlado y ejecución con un punto de entrada explícito.

A diferencia de PowerShell, donde muchas de estas decisiones están integradas en el propio lenguaje y su modelo de pipeline, Python delega estas responsabilidades en módulos bien definidos de su biblioteca estándar, lo que lleva a más «código pegamento» explícito.

El resultado no es solo un script funcional, sino una estructura que puede crecer: validadores reutilizables, parsers testeables, logging configurable y un flujo de ejecución claro.

Puntos clave

    • argparse permite declarar y validar el contrato de un script de forma explícita.
    • Los validadores personalizados convierten texto en tipos útiles antes de que la lógica principal se ejecute.
    • logging separa los mensajes de diagnóstico del flujo principal del programa.
    • El patrón if __name__ == "__main__" define un punto de entrada claro y reusable.

¿Qué nos llevamos?

Más allá de la sintaxis, esta lección busca dejar una idea central: escribir scripts no es solo hacer que algo funcione, sino definir contratos claros entre quien escribe la herramienta y quien la usa.

Python no ofrece un pipeline de objetos como PowerShell, pero compensa esa ausencia con una biblioteca estándar rica y coherente. Aprender a combinar estas piezas —parsers, validadores, logging y puntos de entrada— es clave para escribir herramientas pequeñas, pero bien diseñadas.

¿Con ganas de más?

Notas

  1. A diferencia de PowerShell, donde los cmdlets Write-_ envían objetos a distintos canales del pipeline (verbose, warning, error, etc.), el módulo logging se limita a emitir mensajes de texto con distintos niveles de severidad. El control fino no ocurre en el flujo de datos, sino en la configuración del logger (niveles, handlers y formato). Volver