L'interface en ligne de commande
- Leçon 17
- 45 min
- Module 6 : Du carnet Jupyter au projet facile à maintenir
Leçon 17 sur 18 · Python pour l'ingénierie de l'IA en pratiqueL'interface en ligne de commande
1. Bases de l'ordinateur et de l'environnement
2. Valeurs et flux de contrôle
3. Fonctions et structure du programme
4. Fichiers, données et services externes
5. Débogage et fiabilité
6. Du carnet Jupyter au projet facile à maintenir
Ce que vous saurez faire
Exécutez ticket-cleaner en une commande, avec le fichier d’entrée et les options sur la ligne de commande : python -m ticket_cleaner data/tickets.csv --status pending. Pour l’utiliser, personne n’a besoin de lire le code ou de définir des variables. --help (aide) explique chaque option.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient chaque étape, tâche et vérification de cette leçon, avec les instructions. Ses premières cellules créent le dossier ticket-cleaner tel qu’il est au début de cette leçon, avec les tests. Elles installent aussi httpx et pytest et téléchargent les fichiers de données. Le carnet fonctionne donc sans votre projet local. Lisez les explications ici et exécutez le code dans le carnet.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les arguments, les options et les codes de sortie, dans Les fichiers et le terminal.
- Intercepter les erreurs à la frontière du programme, dans
main(), selon Exceptions et journalisation. python -met__name__, dans Modules et importations, ettmp_path, dans Des tests ciblés.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet n’en a pas besoin)
count_tickets.pyqui lit sa source dansTICKETS_SOURCE, écrit des journaux et renvoie des codes de sortie. Vous l’avez créé dans Exceptions et journalisation : exercices guidés. S’il est différent, refaites cette section. Vous pouvez aussi copier les fichiers de la cellule de préparation du carnet de cette leçon. Elle contient chaque fichier tel qu’il est au début de cette leçon.- Les tests dans
tests/, qui réussissent avecpython -m pytest, selon Des tests ciblés : exercices guidés et À vous de jouer. pytest est dans l’environnement, selon Installer pytest. TICKETS_SOURCEn’est pas définie dans votre terminal. Supprimez-la avecunset TICKETS_SOURCE(macOS et Linux) ouRemove-Item Env:TICKETS_SOURCE(PowerShell), comme dans Configuration et secrets : variables d’environnement.data/tickets.csvetdata/tickets.json. Vous les avez téléchargés dans Lire et écrire des fichiers : obtenir le fichier de données. Si un fichier manque ou a changé, téléchargez-le à nouveau avec la même commandecurl. Les sommes de contrôle figurent dans la fiche du jeu de données.- Un terminal dans
~/projects/ticket-cleaner. Vous avez créé le dossier dans Les fichiers et le terminal : exercices guidés. - L’environnement du projet,
.venv, actif dans votre terminal (l’invite commence par(.venv)). Vous l’avez créé dans Isoler les dépendances : exercices guidés. S’il manque ou ne fonctionne plus, supprimez-le et recréez-le à partir du fichier de verrouillage. Suivez Recréer l’environnement à partir de zéro.
Testé avec Python 3.14.6 sur macOS. Le carnet a été testé dans Jupyter.
Arguments : ce qui change à chaque exécution
Section intitulée « Arguments : ce qui change à chaque exécution »Dans Les fichiers et le terminal, les commandes prenaient des arguments (cd projects) et des options (python3 --version). Votre programme peut aussi en prendre. Python place les mots de la commande dans une liste, sys.argv :
import sys
print(sys.argv)python show_args.py data/tickets.csv --status pending['show_args.py', 'data/tickets.csv', '--status', 'pending']Le premier élément est le script. Les autres sont les mots que vous avez tapés, sous forme de chaînes de caractères. Vous pourriez lire cette liste vous-même. Mais vous devriez aussi gérer les valeurs manquantes, les fautes de frappe dans les options et le texte d’aide. Le module argparse fait tout cela.
Quels paramètres deviennent des arguments, et lesquels restent dans les variables d’environnement? Demandez-vous : changent-ils à chaque exécution, ou dépendent-ils de l’endroit où le programme s’exécute?
| Paramètre | Change | C’est donc |
|---|---|---|
| Le fichier d’entrée | À chaque exécution | Un argument : data/tickets.csv |
| Le statut à compter | À chaque exécution | Une option : --status pending |
| Le fichier du rapport | Parfois | Une option avec une valeur par défaut : --output |
| L’URL du service, le jeton, le niveau de journalisation | Selon l’ordinateur ou le serveur | Des variables d’environnement, comme avant |
Exemple détaillé : argparse
Section intitulée « Exemple détaillé : argparse »Amira écrit un analyseur qui décrit la commande :
import argparsefrom pathlib import Path
parser = argparse.ArgumentParser( prog="ticket_cleaner", description="Check support-ticket records and write a summary report.",)parser.add_argument( "input", help="a .csv or .json file of tickets, or 'remote' to download them")parser.add_argument( "--status", default="open", choices=sorted(STATUSES), help="which tickets to count (default: open)",)parser.add_argument( "--output", type=Path, default=Path("reports/summary.json"), help="where to save the report (default: reports/summary.json)",)args = parser.parse_args()| Partie | Signification |
|---|---|
"input" |
Un argument positionnel : un nom sans --. Il est obligatoire, et sa position l’identifie. |
"--status" |
Une option : elle commence par -- et elle est facultative. |
default="open" |
La valeur utilisée quand la personne ne fournit pas l’option. |
choices=... |
Les seules valeurs permises. Toute autre valeur produit une erreur, avec un message qui énumère les choix. |
type=Path |
Convertit le texte en Path. Sans type, chaque valeur est une chaîne de caractères. |
help="..." |
Le texte que --help affiche. Indiquez ce que représente la valeur et sa valeur par défaut. |
parser.parse_args() |
Lit sys.argv. Renvoie un objet avec un attribut par argument : args.input, args.status, args.output. |
argparse ajoute -h et --help pour vous. Voici l’aide qu’il produit à partir de l’analyseur :
usage: ticket_cleaner [-h] [--status {closed,open,pending}] [--output OUTPUT] input
Check support-ticket records and write a summary report.
positional arguments: input a .csv or .json file of tickets, or 'remote' to download them
options: -h, --help show this help message and exit --status {closed,open,pending} which tickets to count (default: open) --output OUTPUT where to save the report (default: reports/summary.json)Les codes de sortie, au complet
Section intitulée « Les codes de sortie, au complet »Le programme a maintenant trois codes de sortie. Chacun a un sens différent pour la personne ou le programme qui l’exécute :
| Code de sortie | Signification | Exemple |
|---|---|---|
0 |
Réussite. Le rapport est écrit. Les enregistrements rejetés sont un résultat normal, pas un échec. | python -m ticket_cleaner data/tickets.csv |
1 |
Impossible de lire l’entrée. | Un fichier manque, ou le service est indisponible. |
2 |
La commande est incorrecte. argparse affiche l’utilisation et l’erreur, puis termine le programme pour vous. |
--status Pending, ou aucune entrée |
usage: ticket_cleaner [-h] [--status {closed,open,pending}] [--output OUTPUT] inputticket_cleaner: error: argument --status: invalid choice: 'Pending' (choose from 'closed', 'open', 'pending')Exécuter un paquet avec python -m
Section intitulée « Exécuter un paquet avec python -m »Un paquet peut s’exécuter comme un programme s’il contient un fichier nommé __main__.py. Ensuite, python -m ticket_cleaner exécute ce fichier depuis le dossier du projet. Cela remplace count_tickets.py : le programme fait maintenant partie du paquet. Le paquet est la seule chose à installer, à tester et à exécuter.
Le code du programme va dans un nouveau module, cli.py (CLI signifie interface en ligne de commande). __main__.py ne fait que le démarrer :
from ticket_cleaner.cli import main
raise SystemExit(main())import argparseimport loggingfrom pathlib import Path
import httpx
from ticket_cleaner.config import Settings, load_settingsfrom ticket_cleaner.files import read_rows, write_jsonfrom ticket_cleaner.parsing import split_recordsfrom ticket_cleaner.records import STATUSESfrom ticket_cleaner.remote import fetch_rowsfrom ticket_cleaner.report import build_summary
logger = logging.getLogger("ticket_cleaner")
def make_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="ticket_cleaner", description="Check support-ticket records and write a summary report.", ) parser.add_argument( "input", help="a .csv or .json file of tickets, or 'remote' to download them" ) parser.add_argument( "--status", default="open", choices=sorted(STATUSES), help="which tickets to count (default: open)", ) parser.add_argument( "--output", type=Path, default=Path("reports/summary.json"), help="where to save the report (default: reports/summary.json)", ) return parser
def load_rows(source: str, settings: Settings) -> list[dict]: """Return raw records from a file, or from the web service for "remote".""" if source == "remote": return fetch_rows(settings.tickets_url, token=settings.api_token) return read_rows(Path(source))
def main(argv: list[str] | None = None) -> int: args = make_parser().parse_args(argv) settings = load_settings() logging.basicConfig( level=settings.log_level, format="%(levelname)s %(name)s: %(message)s" ) logger.info("reading tickets from %s", args.input)
try: rows = load_rows(args.input, settings) except (OSError, ValueError, httpx.HTTPError) as error: logger.error("cannot read the tickets from %s: %s", args.input, error) return 1
tickets, rejected = split_records(rows) for record in rejected: logger.warning("row %d rejected: %s", record.row, "; ".join(record.problems))
summary = build_summary(tickets, rejected, args.status) write_json(summary, args.output) logger.info("wrote %s", args.output) print(f"{len(rows)} rows: {len(tickets)} valid, {len(rejected)} rejected.") print(f"Report: {args.output}") return 0Deux détails :
main(argv: list[str] | None = None).parse_args(None)litsys.argv, ce qui est le cas normal. Un test peut plutôt passer sa propre liste :main(["data/tickets.csv", "--status", "pending"]). Un test peut ainsi exécuter tout le programme sans terminal.config.pyperdsource. L’entrée est maintenant un argument. Supprimez donc le champsourcedeSettingset sa ligne dansload_settings. L’URL, le jeton et le niveau de journalisation restent.
Exercices guidés : du script à la commande
Section intitulée « Exercices guidés : du script à la commande »-
Créez
ticket_cleaner/cli.pyetticket_cleaner/__main__.pyavec le code ci-dessus. -
Dans
ticket_cleaner/config.py, supprimez le champsourceet la lignesource=.... -
Supprimez
count_tickets.py. Son rôle est maintenant danscli.py. -
Affichez l’aide :
Fenêtre de terminal python -m ticket_cleaner --helpVous voyez le texte d’aide de l’exemple détaillé. Dans certains terminaux, il est en couleur.
-
Exécutez le programme avec les valeurs par défaut :
Fenêtre de terminal python -m ticket_cleaner data/tickets.csvSortie (dernières lignes) WARNING ticket_cleaner: row 14 rejected: missing category15 rows: 9 valid, 6 rejected.Report: reports/summary.json -
Exécutez-le pour les billets de soutien en attente, avec un fichier de sortie différent :
Fenêtre de terminal python -m ticket_cleaner data/tickets.json --status pending --output reports/pending.jsonreports/pending.jsoncontient"selected": 2. -
Vérifiez les trois codes de sortie. Exécutez chaque commande, puis
echo $?(ou$LASTEXITCODE) :python -m ticket_cleaner data/tickets.csvdonne0,python -m ticket_cleaner data/nothing.csvdonne1, etpython -m ticket_cleaner data/tickets.csv --status Pendingdonne2.
Tester la commande
Section intitulée « Tester la commande »Comme main accepte une liste, un test peut exécuter tout le programme. Créez tests/test_cli.py :
import json
from ticket_cleaner.cli import main
def test_report_for_pending_tickets(tmp_path): output = tmp_path / "pending.json" exit_code = main( ["data/tickets.csv", "--status", "pending", "--output", str(output)] ) assert exit_code == 0 summary = json.loads(output.read_text(encoding="utf-8")) assert summary["selected"] == 2 assert len(summary["rejected"]) == 6
def test_missing_file_gives_exit_code_1(tmp_path): assert main([str(tmp_path / "missing.csv")]) == 1Le rapport va dans tmp_path, donc le test ne modifie jamais reports/. Exécutez python -m pytest. Tous les tests réussissent.
À vous de jouer : une option –strict
Section intitulée « À vous de jouer : une option –strict »La personne responsable du soutien veut utiliser ticket-cleaner dans une tâche automatique qui doit s’arrêter si les données ont des problèmes. Ajoutez une option --strict. Avec cette option, le programme écrit toujours le rapport. Mais il termine avec le code de sortie 3 si au moins un enregistrement a été rejeté. Sans cette option, le comportement ne change pas.
Utilisez action="store_true" : l’option ne prend aucune valeur, et args.strict vaut True quand la personne la fournit. Mettez à jour l’aide et ajoutez un test pour chaque cas.
Afficher une réponse
Dans make_parser :
parser.add_argument( "--strict", action="store_true", help="end with exit code 3 if any record is rejected", )À la fin de main, au lieu de return 0 :
if args.strict and rejected: return 3 return 0Tests :
def test_strict_with_rejected_records_gives_exit_code_3(tmp_path): output = tmp_path / "summary.json" assert main(["data/tickets.csv", "--strict", "--output", str(output)]) == 3 assert output.exists()
def test_strict_with_clean_data_gives_exit_code_0(tmp_path): clean = tmp_path / "clean.csv" clean.write_text("id,status,category,priority\nT-1,open,login,1\n", encoding="utf-8") assert main([str(clean), "--strict", "--output", str(tmp_path / "s.json")]) == 0Pourquoi 3 et non 1? Le code de sortie 1 signifie déjà « impossible de lire l’entrée ». Un code différent permet à la tâche automatique de distinguer les deux cas.
Erreur courante
Aucun module nommé ticket_cleaner.__main__
Symptôme : python -m ticket_cleaner data/tickets.csv s’arrête avec l’un de ces messages :
No module named ticket_cleaner.__main__; 'ticket_cleaner' is a package and cannot be directly executedNo module named ticket_cleanerCause : le premier message indique que le paquet n’a pas de fichier __main__.py, ou que ce fichier porte un autre nom. Par exemple, il pourrait s’appeler main.py ou __main__.py.txt. Le deuxième indique que Python ne trouve pas le paquet. Le terminal n’est pas dans le dossier du projet, ou l’environnement n’est pas actif.
Solution :
- Exécutez
ls ticket_cleaner. La liste doit contenir__main__.py, avec deux_avant et aprèsmain. - Exécutez
pwd. Le terminal doit être dansticket-cleaner, le dossier qui contientticket_cleaner. - Vérifiez que l’invite commence par
(.venv).
Vérifiez votre compréhension
Pourquoi le fichier d'entrée est-il un argument, mais le jeton du service une variable d'environnement?
Afficher la réponse
L’entrée change à chaque exécution, donc la personne la fournit dans la commande. Le jeton dépend de l’endroit où le programme s’exécute, et c’est un secret. Une commande peut être enregistrée dans l’historique du terminal, dans un script ou dans un journal, puis partagée. Une variable d’environnement ne fait pas partie de la commande.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Une tâche automatique exécute ticket-cleaner chaque nuit. Quel code de sortie indique que le rapport est prêt, et lequel indique que le service était indisponible?
Afficher la réponse
0 signifie que le rapport est écrit, même si certains enregistrements ont été rejetés (ils sont dans le rapport). 1 signifie que l’entrée n’a pas pu être lue, par exemple parce que le service était indisponible. Il n’y a donc pas de nouveau rapport. 2 signifie que la commande elle-même est incorrecte. Il faut donc corriger la configuration de la tâche.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
argparsedécrit une commande : arguments positionnels, options avec valeurs par défaut et choix, et texte d’aide. Il gère--helpet les commandes incorrectes pour vous.- Les valeurs qui changent à chaque exécution sont des arguments. Les valeurs qui dépendent de l’environnement, ainsi que les secrets, sont des variables d’environnement.
- Les codes de sortie indiquent ce qui s’est passé :
0réussite,1entrée impossible à lire,2commande incorrecte. __main__.pypermet d’exécuter un paquet avecpython -m package.main(argv=None)permet à un test d’exécuter tout le programme avec ses propres arguments.
Ensuite, vous ajoutez le formatage et l’analyse statique. Vous documentez comment préparer et exécuter le projet, puis prouvez que le README fonctionne sur une nouvelle copie.
Leçon suivante : Qualité et passation