Cours Python · Module 12: Projet final

Portfolio GitHub

5 min de lecture
Dans cette leçon6

Un pisteur qui dit « j'ai vu un lion » sans la moindre photo ne convaincra personne. Un recruteur regarde un CV de la même façon : « je connais Python et FastAPI », c'est une affirmation, alors qu'un dépôt avec un projet qui fonctionne, c'est une preuve. Ton portfolio GitHub est ta carte de visite dans le monde de l'IT, et beaucoup de gens jugent un candidat dessus avant même de lire sa lettre de motivation. Montrons au monde ce dont tu es capable.

Structure du dépôt

La disposition des fichiers fait la première impression. Une structure claire dit « cette personne sait ce qu'elle fait » avant même que quelqu'un ouvre le code :

1ai-document-assistant/
2├── .github/
3│   ├── workflows/
4│   │   ├── test.yml
5│   │   └── deploy.yml
6│   └── ISSUE_TEMPLATE/
7├── app/
8│   ├── __init__.py
9│   ├── main.py
10│   ├── models/
11│   ├── services/
12│   ├── api/
13│   └── config.py
14├── tests/
15│   ├── unit/
16│   ├── integration/
17│   └── conftest.py
18├── docs/
19│   ├── architecture.md
20│   └── api.md
21├── docker-compose.yml
22├── Dockerfile
23├── requirements.txt
24├── README.md
25├── LICENSE
26└── .env.example

Le code de l'application se trouve dans app/, découpé en couches, les tests dans tests/, séparés en unitaires et intégration, et les workflows de CI dans .github/workflows/. Le fichier .env.example contient les noms des variables sans les vraies valeurs, et tu ajoutes .env lui-même au .gitignore. Le fichier LICENSE indique à quelles conditions les autres peuvent utiliser ton code. Sans licence, c'est le droit d'auteur complet qui s'applique, donc formellement personne ne peut copier ni modifier le code. Pour un projet de portfolio, la licence MIT est le choix le plus simple, et GitHub l'ajoute en un clic à la création du dépôt.

Variables d'environnement

D'où l'application tire-t-elle sa clé d'API si elle n'est pas dans le dépôt ? Du fichier .env, lu par la bibliothèque python-dotenv. La fonction load_dotenv() charge les variables du fichier dans l'environnement du processus, et os.getenv les lit :

1import os
2from dotenv import load_dotenv
3
4load_dotenv()  # charge les variables du fichier .env
5api_key = os.getenv("API_KEY")

Si la variable n'existe pas, os.getenv renvoie None au lieu de lever une exception. Le code reste le même entre ton ordinateur et le serveur, seul l'environnement change.

Historique des commits

Les recruteurs regardent aussi l'historique. La convention Conventional Commits standardise le format des messages : le préfixe feat: marque une nouvelle fonctionnalité, fix: une correction de bug, docs: des changements de documentation, chore: des tâches d'entretien. Le flux typique : une nouvelle branche, des changements, un commit, une Pull Request, puis la code review et le merge :

1git checkout -b feature/upload
2git commit -m "feat: add API endpoint"

Tu peux aussi créer la branche avec la commande plus récente git switch -c feature/upload, l'effet est le même. Même dans un projet solo, les Pull Requests montrent que tu sais comment on travaille en équipe.

Modèle de README

Le README d'un portfolio vend le projet. Les éléments clés sont le titre avec une description, les instructions d'installation, des exemples d'utilisation et la licence. Les badges en haut montrent les technologies et l'état des tests :

1# Nom du projet
2
3![Python](https://img.shields.io/badge/Python-3.12-blue)
4![FastAPI](https://img.shields.io/badge/FastAPI-0.100+-green)
5![Tests](https://github.com/user/repo/actions/workflows/test.yml/badge.svg)
6![License](https://img.shields.io/badge/License-MIT-yellow)
7
8> Une description courte et accrocheuse du projet (1-2 phrases)
9
10## Problème et solution
11
12**Problème :** Que résout le projet ?
13**Solution :** Comment le fait-il ?
14
15## Démo
16
17![Demo GIF](docs/demo.gif)
18
19[Démo en ligne](https://your-demo-url.com) | [Vidéo](https://youtube.com/...)
20
21## Stack technique
22
23- **Backend :** Python, FastAPI, SQLAlchemy
24- **AI/ML :** LlamaIndex, OpenAI, Qdrant
25- **Infrastructure :** Docker, GitHub Actions
26- **Tests :** pytest, httpx
27
28## Fonctionnalités
29
30- Fonctionnalité 1
31- Fonctionnalité 2
32- Fonctionnalité 3 (en cours)
33
34## Architecture
35
36```
37[Diagramme d'architecture]
38```
39
40## Démarrage rapide
41
42[Instructions d'installation]
43
44## Performance
45
46| Métrique | Valeur |
47|--------|-------|
48| Temps de réponse | <2s |
49| Précision | 92% |
50| Disponibilité | 99,9% |
51
52## Contribuer
53
54Les contributions sont les bienvenues ! Voir [CONTRIBUTING.md](CONTRIBUTING.md)
55
56## Licence
57
58MIT © [Ton nom](https://github.com/username)

La section « Problème et solution » est la plus importante, car elle répond à la question « pourquoi ce projet existe ». Les chiffres du tableau Performance sont l'endroit où mettre tes vraies mesures issues de l'évaluation, n'y inscris pas de valeurs que tu n'as pas mesurées. Le badge des tests récupère son état depuis GitHub Actions, donc vert signifie que les tests passent vraiment.

README de profil GitHub

GitHub affiche en haut de ton profil le README du dépôt qui porte le même nom que ton identifiant. C'est l'endroit pour une courte présentation :

1# Salut, je suis [Nom]
2
3Développeur Python | Passionné d'IA/ML | Je construis des trucs cool
4
5## Projets en cours
6
7- [AI Document Assistant](link) - Système RAG pour le Q&R sur documents
8- [Projet 2](link) - Description
9
10## Compétences
11
12```
13Python     ████████████████████  95%
14FastAPI    ███████████████████   90%
15ML/AI      ████████████████      80%
16Docker     ███████████████       75%
17```
18
19## Statistiques GitHub
20
21![Stats](https://github-readme-stats.vercel.app/api?username=yourusername)
22
23## Contact
24
25- LinkedIn : [lien]
26- Email : your@email.com

Les barres de compétences sont décoratives, prends-les avec du recul. Un lien vers un projet qu'on peut lancer fonctionne mieux. Les statistiques de github-readme-stats viennent d'un service externe parfois indisponible, alors ne bâtis pas tout ton profil dessus.

Astuces

Tu peux épingler jusqu'à six dépôts ou gists sur ton profil, choisis donc tes six meilleurs. Des commits réguliers montrent ton activité, et sur le graphe de contributions vert, la régularité compte plus que l'intensité. Chaque dépôt doit avoir un README, toujours. Si possible, ajoute une démo en ligne, car un lien qui fonctionne convainc plus qu'une description.

Mon conseil : trois projets soignés avec des tests et un README valent mieux que vingt ébauches abandonnées. Dans la dernière leçon, on préparera ta carrière.

Souviens-toi : ton portfolio, c'est l'album photo de ton expédition, la preuve que tu as vraiment traversé la savane.

Code de cette leçon : main.py
1# ===========================================
2# Safari Capstone : Mise en place et configuration du projet
3# ===========================================
4# Mets en place la structure complète du projet avec
5# la gestion de la configuration et la journalisation.
6
7import logging
8import json
9from dataclasses import dataclass, field, asdict
10from pathlib import Path
11from typing import Optional
12from datetime import datetime
13
14# --- Configuration ---
15@dataclass
16class AppConfig:
17    """Configuration de l'application pour le système safari."""
18    app_name: str = "Safari Wildlife Manager"
19    version: str = "1.0.0"
20    debug: bool = False
21    database_url: str = "sqlite:///safari.db"
22    api_port: int = 8000
23    log_level: str = "INFO"
24    max_observations_per_day: int = 1000
25
26    @classmethod
27    def from_dict(cls, data: dict) -> "AppConfig":
28        return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
29
30    def to_dict(self) -> dict:
31        return asdict(self)
32
33# --- Configuration de la journalisation ---
34def setup_logging(config: AppConfig):
35    """Configure la journalisation de l'application."""
36    logging.basicConfig(
37        level=getattr(logging, config.log_level),
38        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
39        datefmt="%Y-%m-%d %H:%M:%S"
40    )
41    logger = logging.getLogger(config.app_name)
42    logger.info(f"Logging initialized at {config.log_level} level")
43    return logger
44
45# --- Modèles de données ---
46@dataclass
47class Animal:
48    animal_id: str
49    species: str
50    name: str
51    weight_kg: float
52    region: str
53    status: str = "active"
54    tags: list[str] = field(default_factory=list)
55    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
56
57@dataclass
58class Observation:
59    obs_id: str
60    animal_id: str
61    latitude: float
62    longitude: float
63    observer: str
64    notes: str = ""
65    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
66
67# --- Amorçage de l'application ---
68config = AppConfig(debug=True, log_level="DEBUG")
69logger = setup_logging(config)
70
71print("=== Mise en place du projet safari ===")
72print(f"App : {config.app_name} v{config.version}")
73print(f"Debug : {config.debug}")
74print(f"Base de données : {config.database_url}")
75print(f"Port : {config.api_port}")
76
77logger.info("Application started")
78logger.debug("Debug mode is enabled")
79
80# Créer des données d'exemple
81simba = Animal("LN-001", "Lion", "Simba", 195.0, "Savanna", tags=["alpha", "collared"])
82obs = Observation("OBS-001", "LN-001", -2.33, 34.83, "Dr. Darwin", "Morning patrol sighting")
83
84print(f"\nAnimal d'exemple : {simba.name} ({simba.species})")
85print(f"Observation d'exemple : {obs.obs_id} à ({obs.latitude}, {obs.longitude})")
86print(f"Dict de config : {json.dumps(config.to_dict(), indent=2)}")
87
88# TODO : Ajoute le chargement de la configuration basé sur l'environnement
89# import os
90# def load_config() -> AppConfig:
91#     env = os.getenv("APP_ENV", "development")
92#     config_data = {
93#         "debug": env == "development",
94#         "log_level": "DEBUG" if env == "development" else "INFO",
95#         "database_url": os.getenv("DATABASE_URL", "sqlite:///safari.db"),
96#         "api_port": int(os.getenv("PORT", "8000")),
97#     }
98#     return AppConfig.from_dict(config_data)
99
100# TODO : Ajoute une fonction de health check
101# def health_check(config: AppConfig) -> dict:
102#     return {
103#         "status": "healthy",
104#         "version": config.version,
105#         "database": "connected",
106#         "uptime_seconds": 0
107#     }
108

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que doit contenir un portfolio GitHub professionnel ?

  2. 2. Quel est le but de la convention Conventional Commits ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Clique sur les éléments pour construire la configuration d'une variable d'environnement :

  • Tri horizontal

    Range la commande git commit avec un message Conventional Commits :

  • Tri vertical

    Range les étapes du workflow Git dans le bon ordre :

  • Tri vertical

    Range les éléments d'un bon README du plus important au moins important :

Articles utiles