Typer convierte funciones anotadas en comandos de terminal. Los parámetros obligatorios se vuelven argumentos, los valores predeterminados se vuelven opciones y los type hints controlan conversión, validación y ayuda. Es útil para herramientas internas que ya admiten dependencias externas.
Si necesitas una herramienta pequeña basada solo en la biblioteca estándar, consulta la guía de argparse.
Crear un comando
python -m pip install typer
from pathlib import Path
from typing import Annotated
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def contar(
archivo: Path,
omitir_vacias: Annotated[
bool, typer.Option("--omitir-vacias/--mantener-vacias")
] = True,
) -> None:
"""Cuenta líneas de un archivo de texto."""
if not archivo.is_file():
raise typer.BadParameter("el archivo no existe")
lineas = archivo.read_text(encoding="utf-8").splitlines()
total = sum(bool(linea.strip()) for linea in lineas) if omitir_vacias else len(lineas)
typer.echo(total)
if __name__ == "__main__":
app()
Ejecuta python app.py --help. La ayuda es parte de la interfaz pública, por lo que conviene escribir descripciones directas y mantener nombres estables.
Organizar y probar
Una herramienta grande puede agrupar acciones como usuarios crear y usuarios listar en objetos Typer separados. La función de comando debe interpretar entrada y mostrar salida. El acceso a archivos, las peticiones HTTP y las reglas de negocio deben vivir en módulos que puedan probarse sin la CLI.
No pases claves de API como opciones visibles, ya que el historial del shell puede conservarlas. Usa variables de entorno o una entrada oculta cuando corresponda.
from typer.testing import CliRunner
from app import app
runner = CliRunner()
def test_ayuda() -> None:
resultado = runner.invoke(app, ["--help"])
assert resultado.exit_code == 0
assert "Cuenta líneas" in resultado.stdout
Prueba también archivos ausentes y opciones incompatibles. La guía de pytest ayuda a organizar esos casos.
El tutorial oficial de Typer, consultado el 22 de julio de 2026, documenta argumentos, opciones, subcomandos y pruebas. Una CLI profesional también necesita códigos de salida predecibles, errores accionables y manejo seguro de secretos.
Argumentos, opciones y metadatos
Los argumentos identifican el recurso principal y las opciones ajustan el comportamiento. Evita demasiados valores posicionales, porque el usuario debe recordar su orden. Annotated conserva el tipo y los metadatos juntos:
def exportar(
origen: Annotated[Path, typer.Argument(help="Archivo JSON de entrada")],
salida: Annotated[
Path, typer.Option("--salida", "-o", help="Archivo de destino")
] = Path("informe.csv"),
limite: Annotated[
int, typer.Option(min=1, max=10_000, help="Máximo de filas")
] = 100,
) -> None:
...
Typer convierte valores antes de llamar a la función y rechaza enteros inválidos. La validación del dominio sigue en la aplicación: una ruta válida puede contener un esquema incorrecto.
Pares como --color/--sin-color hacen visibles ambos estados. Si existen tres modos, usa un enum. Mantén estables los nombres porque los scripts dependen de ellos.
Errores y códigos de salida
Usa typer.BadParameter para entradas inválidas. Para un fallo operativo, escribe en stderr y termina con un código no cero:
try:
filas = cargar(archivo)
except PermissionError:
typer.echo(f"No se puede leer {archivo}", err=True)
raise typer.Exit(code=2)
No muestres un traceback por un error rutinario, pero tampoco captures toda excepción y elimines el contexto. Los defectos inesperados deben seguir siendo observables. Documenta códigos distintos solo si otro programa puede actuar según ellos.
La salida es una interfaz. Envía datos a stdout y diagnósticos a stderr. Si habrá automatización, ofrece un modo --json estable en vez de exigir que los scripts interpreten tablas decoradas.
Organizar subcomandos
app = typer.Typer(no_args_is_help=True)
usuarios = typer.Typer(no_args_is_help=True)
@usuarios.command("listar")
def listar_usuarios(activos: bool = True) -> None:
for usuario in buscar_usuarios(activos=activos):
typer.echo(usuario.nombre)
app.add_typer(usuarios, name="usuarios")
Separa grupos en módulos cuando el archivo crezca. Crea la aplicación en un módulo de entrada pequeño para evitar importaciones circulares. Los comandos traducen valores hacia servicios normales de Python; los servicios no deben importar Typer.
Los callbacks sirven para opciones globales como --verbose. typer.Context puede transportar estado compartido, pero no conviertas context.obj en dependencia oculta de toda la lógica.
Configuración, prompts y secretos
Documenta la precedencia entre opción, variable de entorno, archivo y valor predeterminado. Una regla habitual es opción explícita, entorno, archivo y predeterminado.
Un prompt oculto protege una contraseña del historial, pero bloquea trabajos no interactivos. En CI usa secretos administrados o variables suministradas por la plataforma. Nunca escribas credenciales en logs.
Las confirmaciones protegen operaciones destructivas. Conserva la respuesta segura como valor inicial y ofrece --yes solo para automatización controlada.
Probar comportamiento
def test_archivo_ausente(tmp_path: Path) -> None:
ausente = tmp_path / "ausente.txt"
result = runner.invoke(app, ["count", str(ausente)])
assert result.exit_code != 0
assert "no existe" in result.output
def test_conteo(tmp_path: Path) -> None:
origen = tmp_path / "lineas.txt"
origen.write_text("una\n\ntres\n", encoding="utf-8")
result = runner.invoke(app, ["count", str(origen)])
assert result.exit_code == 0
assert result.stdout.strip() == "2"
Prueba la invocación pública, incluidos los nombres, y prueba directamente los servicios extraídos. La guía de pytest explica fixtures y organización.
Instalar como comando
[project.scripts]
herramientas-texto = "herramientas_texto.cli:app"
Tras instalar el proyecto, el usuario ejecuta herramientas-texto. Mantén ligero el módulo de entrada para que --help sea rápido. Declara versiones compatibles de Python y Typer y prueba el entry point instalado en CI.
Typer reduce el parsing repetitivo, pero no reemplaza el diseño de interfaz. Ayuda clara, nombres estables, salida determinista, secretos protegidos y pruebas convierten una utilidad cómoda en una CLI confiable.
Lista para publicar
Ejecuta cada comando con --help y revisa nombres, valores iniciales, ejemplos y terminología. Prueba tipos inválidos, archivos ausentes, permisos, entrada vacía y opciones incompatibles. Los diagnósticos deben ir a stderr y los datos para máquinas deben quedar limpios en stdout.
Instala el paquete en un entorno virtual nuevo, no solo desde el checkout. Ejecuta el comando desde otro directorio para descubrir dependencias accidentales del directorio actual. Prueba rutas con espacios y caracteres no ASCII en las plataformas compatibles.
Considera nombres, opciones, códigos de salida y campos JSON como API pública. Si los scripts pueden depender de una forma, deprecala antes de retirarla y documenta el cambio. Mantén una prueba integral del flujo de automatización principal.
Comprueba que Ctrl+C interrumpa pronto, que los temporales se limpien y que las escrituras importantes sean atómicas. Una interrupción no debe dejar un archivo que parezca completo.
Revisa también el tiempo de arranque. Importaciones pesadas en el módulo de entrada vuelven lento incluso --help. Carga clientes o modelos grandes dentro del servicio que realmente los necesita. El usuario percibe la latencia en cada invocación.
Por último, verifica la CLI sin color y con salida redirigida. La información esencial nunca debe depender de estilos visuales. Una interfaz sobria, predecible y documentada funciona tanto para personas como para pipelines.
Evolucionar sin romper scripts
Antes de renombrar una opción, busca su uso en documentación, CI y scripts internos. Mantén temporalmente el alias anterior, marca la forma preferida en la ayuda y elimina el alias solo en una versión comunicada. Cambiar el texto explicativo es sencillo; cambiar el significado de una opción existente no lo es.
Si añades un valor predeterminado nuevo, comprueba el efecto en invocaciones antiguas. Una opción aparentemente inocua puede cambiar archivos o seleccionar más registros. Los valores seguros y conservadores reducen sorpresas.
Versiona el esquema del modo JSON si habrá consumidores externos. Los mensajes para personas pueden mejorar, pero nombres de campos y tipos requieren compatibilidad. Incluye ejemplos reales pequeños en la documentación y pruebas de contrato.
Para archivos grandes, procesa por streaming cuando sea posible y muestra progreso solo en un terminal interactivo. La salida redirigida no debe mezclarse con animaciones. Permite desactivar progreso explícitamente.
Finalmente, decide cómo encuentra configuración y rutas relativas. Resolverlas desde el directorio actual suele ser más comprensible que hacerlo desde el paquete instalado, pero debe estar documentado. Una prueba desde otro directorio confirma el contrato.