Python : Comment créer une CLI

 

Dans ce tutoriel, vous allez apprendre à créer une interface en ligne de commande (CLI) en Python.

Trois bibliothèques sont au programme :

  • argparse, qui fait partie de la bibliothèque standard
  • click, qui s’utilise avec des décorateurs
  • typer, qui s’appuie sur les annotations de type (type hints)

Les exemples ont été testés avec Python 3.13, click 8.5.0 et typer 0.27.3.

Pourquoi créer une CLI ?

Une CLI permet de paramétrer un script sans toucher au code : on lui passe des arguments au lancement, comme à n’importe quelle commande du système. Le même script devient un outil réutilisable, que l’on peut appeler à la main, depuis un script bash ou dans un pipeline CI/CD.

argparse (bibliothèque standard)

argparse est inclus dans Python, aucune installation nécessaire.

Exemple basique

import argparse

parser = argparse.ArgumentParser(description="Un outil CLI simple")
parser.add_argument("nom", help="Votre nom")
parser.add_argument("-a", "--age", type=int, help="Votre âge")

args = parser.parse_args()

print(f"Bonjour {args.nom}!")
if args.age:
    print(f"Vous avez {args.age} ans.")

Utilisation :

python script.py Pierre --age 25
# Bonjour Pierre!
# Vous avez 25 ans.

Arguments positionnels et optionnels

import argparse

parser = argparse.ArgumentParser(description="Gestionnaire de fichiers")

# Argument positionnel (obligatoire)
parser.add_argument("fichier", help="Chemin du fichier")

# Arguments optionnels
parser.add_argument("-o", "--output", help="Fichier de sortie")
parser.add_argument("-v", "--verbose", action="store_true", help="Mode verbeux")
parser.add_argument("-n", "--nombre", type=int, default=10, help="Nombre de lignes (défaut: 10)")

args = parser.parse_args()

print(f"Fichier: {args.fichier}")
print(f"Output: {args.output}")
print(f"Verbose: {args.verbose}")
print(f"Nombre: {args.nombre}")

Un argument positionnel est obligatoire et repéré par sa position, un argument optionnel commence par - ou --. Avec action="store_true", l’argument devient un simple drapeau, qui vaut True s’il est présent. argparse génère aussi l’aide à partir de ces déclarations :

python script.py --help

Ce qui donne, avec Python 3.13 :

usage: script.py [-h] [-o OUTPUT] [-v] [-n NOMBRE] fichier

Gestionnaire de fichiers

positional arguments:
  fichier              Chemin du fichier

options:
  -h, --help           show this help message and exit
  -o, --output OUTPUT  Fichier de sortie
  -v, --verbose        Mode verbeux
  -n, --nombre NOMBRE  Nombre de lignes (défaut: 10)

Depuis Python 3.14, cette aide s’affiche en couleur.

Sous-commandes avec argparse

import argparse

parser = argparse.ArgumentParser(description="Gestionnaire de tâches")
subparsers = parser.add_subparsers(dest="commande", help="Commandes disponibles")

# Sous-commande "add"
parser_add = subparsers.add_parser("add", help="Ajouter une tâche")
parser_add.add_argument("tache", help="Description de la tâche")
parser_add.add_argument("-p", "--priorite", type=int, default=1, help="Priorité (1-5)")

# Sous-commande "list"
parser_list = subparsers.add_parser("list", help="Lister les tâches")
parser_list.add_argument("-a", "--all", action="store_true", help="Afficher toutes les tâches")

# Sous-commande "delete"
parser_delete = subparsers.add_parser("delete", help="Supprimer une tâche")
parser_delete.add_argument("id", type=int, help="ID de la tâche")

args = parser.parse_args()

if args.commande == "add":
    print(f"Ajout de la tâche: {args.tache} (priorité: {args.priorite})")
elif args.commande == "list":
    print(f"Liste des tâches (all={args.all})")
elif args.commande == "delete":
    print(f"Suppression de la tâche #{args.id}")
else:
    parser.print_help()

Utilisation :

python tasks.py add "Finir le rapport" -p 3
python tasks.py list --all
python tasks.py delete 5

Click

click est une bibliothèque qui simplifie la création de CLI avec des décorateurs.

Installation

pip install click

Exemple basique

import click

@click.command()
@click.argument("nom")
@click.option("-a", "--age", type=int, help="Votre âge")
def bonjour(nom, age):
    """Un outil CLI simple."""
    click.echo(f"Bonjour {nom}!")
    if age:
        click.echo(f"Vous avez {age} ans.")

if __name__ == "__main__":
    bonjour()

Options avancées avec Click

import click

@click.command()
@click.argument("fichier", type=click.Path(exists=True))
@click.option("-o", "--output", type=click.Path(), help="Fichier de sortie")
@click.option("-v", "--verbose", is_flag=True, help="Mode verbeux")
@click.option("-f", "--format", type=click.Choice(["json", "csv", "xml"]), default="json")
@click.option("-n", "--nombre", type=int, default=10, show_default=True)
def traiter(fichier, output, verbose, format, nombre):
    """Traite un fichier et génère une sortie."""
    if verbose:
        click.echo(f"Traitement de {fichier}...")

    click.echo(f"Format: {format}, Lignes: {nombre}")

    if output:
        click.echo(f"Écriture vers {output}")

if __name__ == "__main__":
    traiter()

Groupes de commandes avec Click

import click

@click.group()
def cli():
    """Gestionnaire de tâches."""
    pass

@cli.command()
@click.argument("tache")
@click.option("-p", "--priorite", type=int, default=1, help="Priorité (1-5)")
def add(tache, priorite):
    """Ajouter une nouvelle tâche."""
    click.echo(f"Ajout: {tache} (priorité: {priorite})")

@cli.command()
@click.option("-a", "--all", "show_all", is_flag=True, help="Afficher toutes les tâches")
def list(show_all):
    """Lister les tâches."""
    click.echo(f"Liste des tâches (all={show_all})")

@cli.command()
@click.argument("task_id", type=int)
@click.confirmation_option(prompt="Êtes-vous sûr de vouloir supprimer?")
def delete(task_id):
    """Supprimer une tâche."""
    click.echo(f"Suppression de la tâche #{task_id}")

if __name__ == "__main__":
    cli()

Couleurs et style avec Click

import click

@click.command()
def status():
    """Affiche le statut avec des couleurs."""
    click.secho("Succès!", fg="green", bold=True)
    click.secho("Attention!", fg="yellow")
    click.secho("Erreur!", fg="red", bold=True)

    # Barre de progression
    with click.progressbar(range(100), label="Traitement") as items:
        for item in items:
            # Simuler un traitement
            import time
            time.sleep(0.01)

if __name__ == "__main__":
    status()

Typer

typer utilise les annotations de type de Python pour générer automatiquement une CLI.

Installation

pip install typer

Exemple basique

import typer
from typing import Optional

def main(nom: str, age: Optional[int] = None):
    """Un outil CLI simple."""
    print(f"Bonjour {nom}!")
    if age:
        print(f"Vous avez {age} ans.")

if __name__ == "__main__":
    typer.run(main)

C’est tout ! Typer déduit les types des annotations et génère l’aide tout seul. Son installation ajoute aussi la bibliothèque Rich, qui met en forme l’aide et les messages d’erreur dans des encadrés.

Options avancées avec Typer

import typer
from typing import Optional
from pathlib import Path
from enum import Enum

class Format(str, Enum):
    json = "json"
    csv = "csv"
    xml = "xml"

def traiter(
    fichier: Path = typer.Argument(..., help="Chemin du fichier"),
    output: Optional[Path] = typer.Option(None, "--output", "-o", help="Fichier de sortie"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Mode verbeux"),
    format: Format = typer.Option(Format.json, "--format", "-f", help="Format de sortie"),
    nombre: int = typer.Option(10, "--nombre", "-n", help="Nombre de lignes"),
):
    """Traite un fichier et génère une sortie."""
    if verbose:
        typer.echo(f"Traitement de {fichier}...")

    typer.echo(f"Format: {format.value}, Lignes: {nombre}")

    if output:
        typer.echo(f"Écriture vers {output}")

if __name__ == "__main__":
    typer.run(traiter)

Application avec sous-commandes

import typer
from typing import Optional

app = typer.Typer(help="Gestionnaire de tâches")

@app.command()
def add(
    tache: str = typer.Argument(..., help="Description de la tâche"),
    priorite: int = typer.Option(1, "--priorite", "-p", min=1, max=5, help="Priorité"),
):
    """Ajouter une nouvelle tâche."""
    typer.echo(f"Ajout: {tache} (priorité: {priorite})")

@app.command()
def list(
    all: bool = typer.Option(False, "--all", "-a", help="Afficher toutes les tâches"),
):
    """Lister les tâches."""
    typer.echo(f"Liste des tâches (all={all})")

@app.command()
def delete(
    task_id: int = typer.Argument(..., help="ID de la tâche"),
    force: bool = typer.Option(False, "--force", "-f", help="Supprimer sans confirmation"),
):
    """Supprimer une tâche."""
    if not force:
        confirm = typer.confirm("Êtes-vous sûr?")
        if not confirm:
            raise typer.Abort()
    typer.echo(f"Suppression de la tâche #{task_id}")

if __name__ == "__main__":
    app()

Couleurs et style avec Typer

import typer

def main():
    typer.secho("Succès!", fg=typer.colors.GREEN, bold=True)
    typer.secho("Attention!", fg=typer.colors.YELLOW)
    typer.secho("Erreur!", fg=typer.colors.RED, bold=True)

    # Demander une entrée
    nom = typer.prompt("Quel est votre nom?")
    typer.echo(f"Bonjour {nom}!")

    # Confirmation
    if typer.confirm("Voulez-vous continuer?"):
        typer.echo("Continuation...")

if __name__ == "__main__":
    typer.run(main)

Rendre votre CLI installable

Pour distribuer votre CLI, créez un fichier pyproject.toml :

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mon-outil"
version = "1.0.0"
description = "Mon outil CLI"
dependencies = [
    "typer>=0.9.0",
]

[project.scripts]
mon-outil = "mon_package.cli:app"

Ici, le code de la CLI (l’objet app de Typer) se trouve dans mon_package/cli.py, à côté du pyproject.toml. Installez ensuite le projet avec :

pip install -e .

La commande mon-outil est alors disponible dans l’environnement où le paquet est installé (le venv actif, s’il y en a un).

Comparaison des bibliothèques

Fonctionnalité argparse click typer
Installation Inclus pip pip
Syntaxe Impérative Décorateurs Annotations de type
Courbe d’apprentissage Moyenne Facile Très facile
Sous-commandes Oui Oui Oui
Couleurs/style Non (aide en couleur depuis Python 3.14) Oui Oui
Autocomplétion Non Intégrée Intégrée
Validation Basique Avancée Avancée

Pour choisir : argparse suffit pour un script simple qui ne doit pas avoir de dépendance externe, click convient aux CLI complexes qui demandent beaucoup de personnalisation, et typer donne le code le plus court si vous utilisez déjà les annotations de type.

Voir aussi