MFormations
Modern Python Engineering

Chapitre 14

14 - CLI Python

> **Duree :** 3 semaines > **Objectif :** Maitriser la creation d'interfaces en ligne de commande en Python.

Cours 14 : CLI Python

1. Click

1.1 Commandes de base

import click

@click.command()
@click.argument("name")
@click.option("--greeting", default="Hello", help="Greeting message")
@click.option("--count", default=1, type=int, help="Number of times")
@click.option("--verbose", is_flag=True, help="Enable verbose output")
def hello(name, greeting, count, verbose):
    """Say hello to NAME."""
    for _ in range(count):
        click.echo(f"{greeting}, {name}!")
    if verbose:
        click.echo(f"Count: {count}")

if __name__ == "__main__":
    hello()

Utilisation :

python hello.py World --greeting Hi --count 3 --verbose

1.2 Groupes de commandes

@click.group()
def cli():
    pass

@cli.command()
@click.argument("project_name")
def init(project_name):
    click.echo(f"Initializing {project_name}...")

@cli.command()
def build():
    click.echo("Building...")

@cli.command()
def deploy():
    click.echo("Deploying...")

if __name__ == "__main__":
    cli()

1.3 Contexte et callbacks

@click.group()
@click.option("--debug/--no-debug", default=False)
@click.pass_context
def cli(ctx, debug):
    ctx.ensure_object(dict)
    ctx.obj["DEBUG"] = debug

@cli.command()
@click.pass_context
def sync(ctx):
    click.echo(f"Debug mode: {ctx.obj['DEBUG']}")

2. Typer

2.1 Typer avec type hints

from typer import Typer
import typer

app = Typer()

@app.command()
def hello(
    name: str,
    greeting: str = typer.Option("Hello", help="Greeting message"),
    count: int = typer.Option(1, help="Number of times"),
    verbose: bool = typer.Option(False, "--verbose", "-v"),
):
    for _ in range(count):
        print(f"{greeting}, {name}!")

@app.command()
def goodbye(name: str, formal: bool = False):
    if formal:
        print(f"Goodbye Ms. {name}.")
    else:
        print(f"Bye {name}!")

if __name__ == "__main__":
    app()

2.2 Auto-documentation

python app.py --help
python app.py hello --help

2.3 Shell completion

eval "$(python app.py --install-completion)"

3. Rich

3.1 Console et styles

from rich.console import Console
console = Console()
console.print("[bold green]Success![/bold green]")
console.rule("[blue]Section[/blue]")

3.2 Tables

from rich.table import Table

table = Table(title="Users")
table.add_column("ID", style="cyan")
table.add_column("Name", style="magenta")
table.add_row("1", "Alice")
table.add_row("2", "Bob")
console.print(table)

3.3 Progress bars

from rich.progress import Progress
import time

with Progress() as progress:
    task = progress.add_task("[cyan]Processing...", total=100)
    for i in range(100):
        time.sleep(0.02)
        progress.update(task, advance=1)

4. Textual

from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Button, Input, Static

class MyApp(App):
    CSS = '''
    Screen { align: center middle; }
    '''

    def compose(self) -> ComposeResult:
        yield Header()
        yield Input(placeholder="Enter your name...", id="name-input")
        yield Button("Submit", id="submit-btn")
        yield Static(id="hello")
        yield Footer()

    def on_button_pressed(self, event):
        name = self.query_one("#name-input", Input).value
        self.query_one("#hello", Static).update(f"Hello, {name}!")

if __name__ == "__main__":
    MyApp().run()

5. argparse

import argparse

parser = argparse.ArgumentParser(description="CLI tool")
parser.add_argument("input", help="Input file")
parser.add_argument("-o", "--output", default="output.json")
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
print(f"Processing {args.input}")

6. CLI Testing

from click.testing import CliRunner
from my_cli import hello

def test_hello():
    runner = CliRunner()
    result = runner.invoke(hello, ["--greeting", "Hi", "World"])
    assert result.exit_code == 0
    assert "Hi, World!" in result.output

7. Distribution

pipx install my-cli

8. Diagramme

Diagramme en cours de génération...