Aller au contenu
English

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

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 JupyterColab · Kaggle · GitHub · en anglais

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 mainEnviron 45 minutes

Ce que vous devez savoir

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)

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 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

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 :

Terminal
export TICKETS_SOURCE=remote
echo $TICKETS_SOURCE
Sortie
remote

Pour 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 :

  1. Pas dans le code. Lisez-le depuis une variable d’environnement.
  2. Pas dans un fichier inclus dans un commit. Si vous gardez des secrets dans un fichier local, ajoutez d’abord son nom à .gitignore.
  3. 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.
  4. 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.

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.

ticket_cleaner/config.py
import os
from 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.

fetch_rows reçoit un paramètre token et envoie l’en-tête seulement quand un jeton est présent :

ticket_cleaner/remote.py
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 :

count_tickets.py
from pathlib import Path
from ticket_cleaner.config import Settings, load_settings
from ticket_cleaner.files import read_rows, write_json
from ticket_cleaner.parsing import split_records
from ticket_cleaner.remote import fetch_rows
from 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()

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 :

.env.example
# Settings for ticket-cleaner. Set them as environment variables.
# TICKETS_SOURCE: a .csv or .json file, or "remote". Default: data/tickets.csv
TICKETS_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.

  1. Créez ticket_cleaner/config.py, mettez à jour remote.py et remplacez count_tickets.py par le code ci-dessus.

  2. Créez .env.example.

  3. Assurez-vous que TICKETS_SOURCE n’est pas définie (exécutez la commande de suppression ci-dessus), puis exécutez le script :

    Fenêtre de terminal
    python count_tickets.py
    Output (first and last lines)
    Source: data/tickets.csv
    Row 7 rejected: unknown status 'opne'
    ...
    15 rows: 9 valid, 6 rejected.
    Report: reports/summary.json
  4. Définissez TICKETS_SOURCE à data/tickets.json et exécutez-le de nouveau. La première ligne est Source: data/tickets.json (source : data/tickets.json), et les comptes sont identiques.

  5. Définissez TICKETS_SOURCE à remote et exécutez-le de nouveau. Le programme télécharge le JSON. Les comptes sont identiques.

  6. Vérifiez qu’aucune exécution n’a perdu de données : dans reports/summary.json, valid_records plus le nombre d’entrées dans rejected vaut 15.

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
try_token.py
import httpx
from ticket_cleaner.config import load_settings
from 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 :

  1. 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.
  2. Définissez-la de nouveau dans ce terminal, avec le nom exact de config.py.
  3. Exécutez le programme de nouveau. La ligne Source: (source) montre la valeur réellement utilisée par le programme. C’est pourquoi main() 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.

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.

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