Configuration et secrets
- Leçon 12
- 45 min
- Module 4 : Fichiers, données et services externes
Leçon 12 sur 18 · Python pour l'ingénierie de l'IA en pratiqueConfiguration et secrets
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 le même code avec une source, une URL ou un jeton différent, sans le modifier. Assurez-vous aussi qu’un jeton secret n’apparaît jamais dans votre code, votre historique Git ou vos sorties. Les services d’IA des prochains cours exigent tous une clé secrète. Cette leçon vous montre comment la gérer.
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 consignes. Ses premières cellules créent le dossier ticket-cleaner tel qu’il est au début de cette leçon. Elles installent aussi httpx 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 là-bas.
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
fetch_rowset le choix de la source, dans Utiliser un client HTTP simple.- Les services simulés avec
MockTransport, dans Utiliser un client HTTP simple : tester sans réseau. - Les classes de données, dans Les enregistrements structurés.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet et les encadrés de cette page n’en ont pas besoin)
count_tickets.py, qui charge les lignes depuis un fichier ou le service Web, etticket_cleaner/remote.py. Vous les avez créés dans Utiliser un client HTTP simple : exercice guidé. S’ils manquent ou 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.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 de nouveau avec la même commandecurl. Les sommes de contrôle figurent dans la fiche du jeu de données.- L’environnement
.venvdu projet, actif dans votre terminal (l’invite commence par(.venv)). Vous l’avez créé dans Isoler les dépendances : exercice guidé. 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. - httpx 0.28.1 dans cet environnement. Vous l’avez installé dans Isoler les dépendances : exemple détaillé. Vérifiez-le avec
python -c "import httpx; print(httpx.__version__)". S’il manque, exécutezpython -m pip install -r requirements-lock.txt.
Testé avec Python 3.14.6 et httpx 0.28.1 sur macOS, et Pyodide 314.0 (navigateur). Le carnet a été testé dans Jupyter.
La configuration n’est pas du code
Section intitulée « La configuration n’est pas du code »À la fin de la dernière leçon, count_tickets.py avait deux constantes que vous modifiiez à la main : SOURCE et TICKETS_URL. Elles constituent la configuration : des valeurs qui changent selon le lieu ou l’exécution, alors que le code reste identique. Un collègue veut le fichier CSV, le serveur de test exige une autre URL et le vrai service exige un jeton.
Si la configuration est dans le code, chaque changement exige une modification du code. Ces modifications entrent parfois par erreur dans un commit (enregistrement dans Git). Un secret dans le code se retrouve alors dans l’historique Git. Vous séparez donc les deux :
| Va dans le code | Va dans la configuration |
|---|---|
| Comment valider un enregistrement | Quel fichier ou quelle URL lire |
| Les statuts autorisés | L’adresse d’un serveur |
| Le format du rapport | Un jeton ou un mot de passe (un secret) |
| Une valeur par défaut sûre | Une valeur différente sur votre ordinateur et celui d’un collègue |
Variables d’environnement
Section intitulée « Variables d’environnement »Une variable d’environnement est un nom associé à une valeur textuelle que le terminal transmet à chaque programme qu’il démarre. Vous en avez déjà vu une : PATH. Les programmes les lisent, et elles ne sont dans aucun fichier de votre projet. Elles ne peuvent donc pas entrer dans un commit par accident.
Définissez-en une dans le terminal. Elle reste définie jusqu’à ce que vous fermiez ce terminal :
export TICKETS_SOURCE=remoteecho $TICKETS_SOURCE$env:TICKETS_SOURCE = "remote"$env:TICKETS_SOURCEexport TICKETS_SOURCE=remoteecho $TICKETS_SOURCEremotePour la supprimer, exécutez unset TICKETS_SOURCE sur macOS et Linux, ou Remove-Item Env:TICKETS_SOURCE dans PowerShell. Sur macOS et Linux, vous pouvez aussi définir une variable pour une seule commande : TICKETS_SOURCE=remote python count_tickets.py.
En Python, le module os lit les variables d’environnement. Il existe deux façons de les lire, et la différence compte :
| Code | Si la variable n’est pas définie |
|---|---|
os.environ.get("TICKETS_SOURCE", "data/tickets.csv") |
Renvoie la valeur par défaut, "data/tickets.csv". |
os.environ["TICKETS_SOURCE"] |
S’arrête avec KeyError: 'TICKETS_SOURCE' (erreur de clé). |
Utilisez .get() avec une valeur par défaut quand une valeur sûre existe. Utilisez [ ] seulement pour un paramètre indispensable au programme. Il s’arrêtera immédiatement en indiquant le nom du paramètre manquant.
Un secret est une valeur qui donne accès : un jeton, un mot de passe ou une clé d’API. Toute personne qui le possède peut agir en votre nom. Certains services vous facturent ce qu’elle fait. Quatre règles :
- Pas dans le code. Lisez-le depuis une variable d’environnement.
- Pas dans un fichier inclus dans un commit. Si vous gardez des secrets dans un fichier local, ajoutez d’abord son nom à
.gitignore. - Pas dans les sorties. Ne l’affichez pas et ne l’écrivez pas dans les journaux (logs). Une capture d’écran ou un journal partagé suffit à le divulguer.
- S’il est divulgué, remplacez-le. Accédez au service, révoquez l’ancien jeton et créez-en un nouveau. Supprimer le commit ne suffit pas : des copies de l’historique peuvent exister ailleurs. Le cours L’essentiel de Git et du développement en équipe montre comment garder les secrets hors d’un dépôt.
Un service Web reçoit généralement un jeton dans un en-tête de requête nommé Authorization (autorisation). Sa valeur contient Bearer (porteur), suivi du jeton. Le jeton voyage dans la requête et le serveur le vérifie. Le service répond 401 Unauthorized (non autorisé) si le jeton manque ou est incorrect.
Exemple détaillé : un module de paramètres
Section intitulée « Exemple détaillé : un module de paramètres »Amira place toute la configuration dans un seul module, config.py. Tous les autres modules y prennent leurs paramètres. Ainsi, un seul endroit lit l’environnement et une seule liste présente tous les paramètres du programme.
import osfrom dataclasses import dataclass, field
DEFAULT_URL = "https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/tickets.json"
@dataclass(frozen=True)class Settings: source: str tickets_url: str api_token: str | None = field(repr=False)
def load_settings() -> Settings: """Read the settings from environment variables, with safe defaults.""" return Settings( source=os.environ.get("TICKETS_SOURCE", "data/tickets.csv"), tickets_url=os.environ.get("TICKETS_URL", DEFAULT_URL), api_token=os.environ.get("TICKETS_API_TOKEN") or None, )| Partie | Pourquoi |
|---|---|
field(repr=False) |
Quand vous affichez un objet Settings, le jeton est omis. Afficher les paramètres pour les vérifier est courant et ne doit pas divulguer le secret. |
os.environ.get("TICKETS_API_TOKEN") or None |
Une variable définie comme une chaîne vide compte comme « aucun jeton ». |
| Aucune valeur par défaut pour le jeton | Il n’existe aucune valeur par défaut sûre pour un secret. Sans jeton, le programme n’en envoie pas. |
Exécutez du Python dans votre navigateur
Des paramètres sans divulgation
Le jeton est dans les paramètres, mais la ligne affichée ne le montre pas. Supprimez = field(repr=False) de la ligne api_token, puis exécutez le code de nouveau. Voyez ce qu’une ligne de journal divulguerait.
Tab ajoute quatre espaces. Appuyez sur Esc, puis sur Tab, pour quitter la zone. Ctrl+Enter exécute le code.
fetch_rows reçoit un paramètre token et envoie l’en-tête seulement quand un jeton est présent :
import httpx
def fetch_rows( url: str, token: str | None = None, client: httpx.Client | None = None) -> list[dict]: """Download raw records as JSON from a ticket service.""" headers = {"Authorization": f"Bearer {token}"} if token else {} client = client or httpx.Client(timeout=10.0) with client: response = client.get(url, headers=headers) response.raise_for_status() return response.json(){...} if token else {} est une expression conditionnelle : une valeur si la condition est vraie, une autre sinon. C’est une forme courte de if et else qui fournit une valeur.
Le script n’a plus de constantes et prend tout dans les paramètres :
from pathlib import Path
from ticket_cleaner.config import Settings, load_settingsfrom ticket_cleaner.files import read_rows, write_jsonfrom ticket_cleaner.parsing import split_recordsfrom ticket_cleaner.remote import fetch_rowsfrom ticket_cleaner.report import build_summary
OUTPUT_FILE = Path("reports/summary.json")
def load_rows(settings: Settings) -> list[dict]: """Return raw records from a file, or from the web service for "remote".""" if settings.source == "remote": return fetch_rows(settings.tickets_url, token=settings.api_token) return read_rows(Path(settings.source))
def main() -> None: settings = load_settings() print(f"Source: {settings.source}") rows = load_rows(settings) tickets, rejected = split_records(rows) for record in rejected: print(f"Row {record.row} rejected: {'; '.join(record.problems)}")
summary = build_summary(tickets, rejected, status="open") write_json(summary, OUTPUT_FILE) print(f"{len(rows)} rows: {len(tickets)} valid, {len(rejected)} rejected.") print(f"Report: {OUTPUT_FILE}")
if __name__ == "__main__": main()Documenter les paramètres
Section intitulée « Documenter les paramètres »Un paramètre qui existe seulement dans votre terminal est invisible pour la prochaine personne. Documentez les noms, mais pas les valeurs secrètes, dans un fichier nommé .env.example, dans le dossier du projet :
# Settings for ticket-cleaner. Set them as environment variables.# TICKETS_SOURCE: a .csv or .json file, or "remote". Default: data/tickets.csvTICKETS_SOURCE=data/tickets.csv# TICKETS_URL: the web service for "remote". Default: the course's public file.TICKETS_URL=# TICKETS_API_TOKEN: secret. Never write a real token in this file.TICKETS_API_TOKEN=Beaucoup de projets gardent aussi leurs vraies valeurs dans un fichier nommé .env. Un outil le charge au démarrage du programme. Si vous le faites, ajoutez .env à .gitignore avant d’y écrire quoi que ce soit. Ce cours définit les variables dans le terminal et n’a donc pas besoin de cet outil.
Exercice guidé : un programme, trois sources
Section intitulée « Exercice guidé : un programme, trois sources »-
Créez
ticket_cleaner/config.py, mettez à jourremote.pyet remplacezcount_tickets.pypar le code ci-dessus. -
Créez
.env.example. -
Assurez-vous que
TICKETS_SOURCEn’est pas définie (exécutez la commande de suppression ci-dessus), puis exécutez le script :Fenêtre de terminal python count_tickets.pyOutput (first and last lines) Source: data/tickets.csvRow 7 rejected: unknown status 'opne'...15 rows: 9 valid, 6 rejected.Report: reports/summary.json -
Définissez
TICKETS_SOURCEàdata/tickets.jsonet exécutez-le de nouveau. La première ligne estSource: data/tickets.json(source : data/tickets.json), et les comptes sont identiques. -
Définissez
TICKETS_SOURCEàremoteet exécutez-le de nouveau. Le programme télécharge le JSON. Les comptes sont identiques. -
Vérifiez qu’aucune exécution n’a perdu de données : dans
reports/summary.json,valid_recordsplus le nombre d’entrées dansrejectedvaut15.
À vous de jouer : un service qui exige un jeton
Section intitulée « À vous de jouer : un service qui exige un jeton »Le vrai service de billets de soutien d’une entreprise vérifie un jeton. Écrivez try_token.py : un service simulé qui répond 401 sans l’en-tête Authorization: Bearer test-token-123, et renvoie les billets avec celui-ci. Appelez ensuite fetch_rows deux fois, sans jeton puis avec le jeton. Lisez le jeton depuis la variable d’environnement TICKETS_API_TOKEN, pas depuis le code.
Afficher une réponse
import httpx
from ticket_cleaner.config import load_settingsfrom ticket_cleaner.remote import fetch_rows
URL = "https://tickets.example/api/tickets"
def mock_service(request: httpx.Request) -> httpx.Response: if request.headers.get("Authorization") != "Bearer test-token-123": return httpx.Response(401, text="missing or wrong token") return httpx.Response(200, json=[{"id": "T-2001", "status": "open"}])
def main() -> None: settings = load_settings() client = httpx.Client(transport=httpx.MockTransport(mock_service)) print(fetch_rows(URL, token=settings.api_token, client=client))
if __name__ == "__main__": main()Sans la variable, le programme s’arrête avec HTTPStatusError: Client error '401 Unauthorized' (erreur d’état HTTP : erreur client, non autorisé). Définissez TICKETS_API_TOKEN à test-token-123 (avec export ou $env:), puis exécutez-le de nouveau : il affiche la liste. La valeur du jeton est seulement dans votre terminal. Elle n’est ni dans le code, ni dans les sorties, ni dans un fichier inclus dans un commit.
Erreur courante
Un paramètre qui ne change rien
Symptôme : vous définissez TICKETS_SOURCE, mais le programme affiche encore Source: data/tickets.csv (source : data/tickets.csv).
Cause : une variable d’environnement appartient à un seul terminal. Habituellement, elle a été définie dans une autre fenêtre ou un autre onglet de terminal. Par exemple, dans le terminal de VS Code plutôt que dans un terminal séparé. Autres causes : une faute dans le nom (TICKET_SOURCE) ou, dans PowerShell, TICKETS_SOURCE=remote sans $env:, ce qui ne définit rien.
Solution :
- Dans le terminal où vous exécutez le programme, affichez la valeur :
echo $TICKETS_SOURCE(macOS et Linux) ou$env:TICKETS_SOURCE(PowerShell). Si la sortie est vide, la variable n’est pas définie dans ce terminal. - Définissez-la de nouveau dans ce terminal, avec le nom exact de
config.py. - Exécutez le programme de nouveau. La ligne
Source:(source) montre la valeur réellement utilisée par le programme. C’est pourquoimain()l’affiche.
Vérifiez votre compréhension
Un collègue vous demande votre jeton d'API dans une conversation pour exécuter le programme. Que faites-vous plutôt?
Afficher la réponse
Ne l’envoyez pas. Un jeton dans une conversation est un jeton divulgué. Le collègue obtient son propre jeton auprès du service et le définit comme TICKETS_API_TOKEN dans son terminal. .env.example lui indique le nom de la variable. Si vous l’avez déjà envoyé, révoquez le jeton auprès du service et créez-en un nouveau.
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
Vérification du module : pour chacune des trois sources, montrez que le programme conserve chaque enregistrement valide et signale chaque enregistrement invalide. Quels nombres comparez-vous?
Afficher la réponse
Exécutez le programme pour chaque source : data/tickets.csv, data/tickets.json et remote. Comparez ensuite trois nombres : les lignes d’entrée (15), valid_records dans le rapport (9) et les entrées dans rejected (6). Le nombre d’enregistrements valides plus celui des rejets doit égaler le nombre de lignes d’entrée. Chaque entrée rejetée doit avoir un numéro de ligne et une raison. Des nombres identiques pour les trois sources montrent que la source ne change pas le résultat.
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
- La configuration regroupe ce qui change selon l’exécution ou le lieu : sources, adresses et secrets. Gardez-la hors du code.
- Les variables d’environnement appartiennent à un seul terminal. Lisez-les avec
os.environ.get(name, default), dans un seul module de paramètres. - Les secrets ne vont jamais dans le code, les fichiers inclus dans un commit ou les sorties.
field(repr=False)empêche le jeton d’apparaître dans les paramètres affichés. - Documentez chaque paramètre dans
.env.example, sans vraies valeurs secrètes. - Si un secret est divulgué, révoquez-le auprès du service et créez-en un nouveau.
Vous pouvez maintenant lire des données depuis des fichiers et des services, et configurer le programme de façon sûre. Dans le prochain module, vous trouverez et corrigerez des erreurs, puis écrirez des tests qui s’exécutent automatiquement.
Leçon suivante : Lire les erreurs