La structure d'un projet (2) : ça passe sur ma machine, ça casse une fois installé
Par Dorian Chávez · fondateur et PDG de Hábil ·
Tes tests passent et ton paquet casse chez qui l'installe. Le guide PyPA demande src/, mais l'installation éditable lit aussi ton dossier. Avec modèle.
La structure d'un projet · épisode 2 sur 7 · Python
Scénario : les personnages et l'histoire sont inventés. Les données techniques et les citations ont leur source à la fin.
En 1 minute
- Si tu lances les tests depuis la racine du projet, Python peut trouver ton dossier avant le paquet installé. Un fichier qui n'est jamais entré dans le paquet passe tes tests et casse chez qui l'installe [1][3].
- Le guide d'empaquetage de Python (PyPA) règle ça avec un dossier :
src/. Avec lui, le code ne tourne que s'il est installé [1]. - Mais attention : l'installation éditable (
pip install -e .) lit aussi ton dossier. Avec hatchling,src/en entier (mesuré) [5][6]. - C'est pourquoi le modèle de cet épisode teste deux fois : vite contre ton dossier, et pour de vrai contre ce qui est installé. Et il apporte un script qui le démontre en passant au rouge quand il le faut.
- Pour lundi : lance tes tests contre le paquet installé dans un environnement neuf. Si quelque chose casse, tu sais déjà ce que c'est.
Dans cet article
- Que s'est-il passé vendredi à 9:30 ?
- Que demande le guide d'empaquetage ?
- Quelles sont les cinq erreurs les plus courantes ?
- Exercice : peux-tu reproduire l'erreur d'Iván ?
- À retenir
- Et ensuite ?
- Le modèle et le cours
- Que fait Hábil ?
- Références
Que s'est-il passé vendredi à 9:30 ?
Les tests d'Iván passaient tous. Le paquet installé par Sofía n'avait pas le fichier que ces tests utilisaient.
Jeudi, 18:10. Troisième étage.
Après Go, la deuxième mission lui parut facile : « Que le vérificateur Python exporte le rapport en CSV ». Le vérificateur était le même programme que la semaine précédente, mais en Python : il lisait une liste de services, les interrogeait et disait lesquels répondaient.
Le dépôt, hérité une fois de plus, ressemblait à ceci :
revisor/ el paquete
tests/
pyproject.tomlIván écrivit la fonction dans un nouveau fichier, exportar.py. Il le laissa à la racine, à côté de tout le reste, parce que c'est là qu'il le voyait. Dans revisor/cli.py, il ajouta une ligne : import exportar. Il lança les tests comme on le lui avait appris, depuis la racine :
$ python -m pytest -q
. [100%]
1 passed in 0.00sVert. Il poussa le changement et rentra chez lui.
Vendredi, 9:30.
Sofía, de l'exploitation, installa la nouvelle version dans son environnement avec pip install . et lança la commande. Le terminal lui répondit ceci (chemins raccourcis) :
Traceback (most recent call last):
File ".../bin/revisor", line 3, in <module>
from revisor.cli import main
File ".../site-packages/revisor/cli.py", line 1, in <module>
import exportar
ModuleNotFoundError: No module named 'exportar'Iván ouvrit son dossier : exportar.py était là. Il relança les tests. Vert, encore.
— Sur ma machine, ça passe, écrivit-il sur le canal.
Lucía, la responsable technique, répondit par une question :
— Tes tests ont testé ce que Sofía a installé, ou ton dossier ?
Iván resta à fixer l'écran. Il ne lui était jamais venu à l'idée que ce soient deux choses différentes.
— Depuis la racine, Python voit tout ce qu'il y a à la racine, poursuivit Lucía. Sofía n'a pas ta racine. Elle a le paquet. Et dans le paquet, exportar.py n'est jamais entré.
Que demande le guide d'empaquetage ?
Que le code importable vive dans src/, pour qu'il ne tourne qu'une fois installé.
Le guide officiel d'empaquetage de Python, de la PyPA, compare les deux organisations dans src layout vs flat layout [1]. Dans l'organisation à plat, le paquet vit à la racine, à côté de pyproject.toml. Dans l'organisation src/, il descend d'un niveau.
La différence qui compte est sur la première ligne de sa liste : avec src/, il faut installer le projet pour exécuter son code, et avec l'organisation à plat, non [1]. Ça ressemble à un inconvénient. C'est tout l'intérêt.
Le guide explique pourquoi : Python place le dossier courant au début de son chemin de recherche, donc si ce dossier contient un paquet du même nom que celui installé, c'est celui du dossier qui gagne. Et cela, dit-il, peut laisser la configuration de l'empaquetage fausse sans que personne ne le remarque, « which could result in files not being included in a distribution » [1]. C'est ce qui est arrivé à Iván.
Le dossier courant a une nuance : il n'entre pas toujours. python -m pytest l'ajoute ; pytest tout court, non [3][8]. Iván a utilisé la première forme, et avec elle toute sa racine était visible.
Le modèle de cet épisode, estructura-python, utilise l'organisation src/ :
estructura-python/
├── pyproject.toml el proyecto en un solo archivo
├── servicios.toml el ejemplo
├── src/
│ └── revisor/ EL PAQUETE: lo único que viaja al instalar
│ ├── __init__.py · __main__.py
│ ├── cli.py opciones y código de salida
│ ├── servicio.py el vocabulario. Sin red ni archivos
│ ├── config.py lee servicios.toml
│ ├── revisar.py consulta muchos servicios a la vez
│ ├── reporte.py tabla y JSON
│ └── py.typed
├── tests/ las pruebas: no viajan
│ ├── conftest.py
│ └── test_*.py
├── herramientas/
│ ├── _comun.sh
│ ├── probar-instalado.sh
│ └── probar-frontera.sh
├── .github/workflows/verificar.yml
└── README.md · LICENSE · LICENSE-CONTENIDO.mdQu'est-ce qui décide de ce qui part ?
Une ligne de pyproject.toml.
[tool.hatch.build.targets.wheel]
packages = ["src/revisor"]Ce qui s'installe, c'est une roue (wheel), un fichier .whl construit à partir du projet. Cette ligne dit à hatchling, l'outil qui la construit, que la roue emporte src/revisor et rien d'autre [6]. Sans elle, hatchling devine : il cherche d'abord revisor/__init__.py, puis src/revisor/__init__.py [7]. Le modèle l'écrit pour qu'on puisse la lire.
Ainsi, tout ce qui vit dans src/revisor/ part avec le paquet. Ce qui vit en dehors, non.
Alors src/ me protège déjà ?
De ta racine, oui. De l'installation éditable, pas tout à fait.
Pour travailler, le plus pratique est d'installer en mode éditable : pip install -e .. Ainsi tu ne copies rien ; Python lit tes fichiers là où ils sont et chaque changement du code se voit à l'exécution suivante [5]. pip prévient que l'installation éditable et l'installation normale peuvent se comporter différemment, selon l'outil de construction, et que qui utilise ton paquet « will see the behaviour of regular installs » [5].
Et celle de hatchling ? Sa documentation dit que, pour une installation éditable, il décide quels dossiers ajouter au chemin de recherche selon les fichiers sélectionnés [6]. Nous l'avons mesuré : il ajoute src/ en entier. Si quelqu'un laisse un src/suelto.py hors du paquet, l'installation éditable le trouve et la roue ne l'emporte pas.
La même page propose une issue, l'option dev-mode-exact [6]. Mesuré : avec elle, import suelto donne ModuleNotFoundError. Mais elle a un coût, et la page le signale : « The dev-mode-exact mechanism is not supported by static analysis tools & IDEs » [6], donc ton éditeur cesse de compléter automatiquement. Et elle ne voit pas non plus ce que la roue exclut à l'intérieur du paquet (mesuré). C'est pourquoi le modèle préfère tester la roue, qui est ce que reçoit qui installe.
Le guide de la PyPA l'anticipe déjà : on utilise typiquement l'installation éditable pour développer « and a regular installation is used for testing » [1]. Le modèle fait exactement cela :
pytest # rápido: lee tu carpeta src/
bash herramientas/probar-instalado.sh # lo que ve quien instala tu paqueteLa deuxième commande crée un environnement neuf et vide, installe le projet sans -e (c'est-à-dire qu'elle construit la roue et l'installe) et lance les mêmes tests. À la fin, elle compte les résultats en lisant le rapport de pytest, pas son code de sortie. Voici ses trois dernières lignes :
revisor bajo prueba: …/site-packages/revisor (el paquete INSTALADO)
pruebas: 62 · pasaron 62 · fallaron 0 · con error 0 · saltadas 0
✅ lo instalado pasa sus pruebasPourquoi les tests vivent-ils hors du paquet ?
Pour qu'ils ne partent pas avec lui, et pour qu'ils testent le paquet comme n'importe qui le verrait.
tests/ est à côté de src/, pas dedans. pytest tourne en mode importlib, celui que sa documentation recommande pour les nouveaux projets [2] : dans ce mode, il n'ajoute pas de dossiers du projet au chemin de recherche pour importer les tests [4]. Ce qui est testé, c'est ce qui est installé dans l'environnement.
Et comment savoir ce qui est installé ? tests/conftest.py le dit dans l'en-tête de chaque exécution (celui que pytest -q omet) :
revisor bajo prueba: src/revisor (la CARPETA del proyecto)Avec la variable REVISOR_EXIGIR_INSTALADO=1, en plus, il refuse de tourner si revisor vient de ton dossier. Le script du paquet installé l'active, et ainsi il ne peut pas tester ton src/revisor en croyant avoir testé la roue. Attention : le garde ne regarde que d'où vient revisor. Si un autre module de ton dossier se glisse dans les tests, c'est la frontière qui l'arrête, et elle arrive juste après.
Qu'y a-t-il dans pyproject.toml ?
Tout le projet, dans un seul fichier.
Le guide de la PyPA décrit trois tables : [build-system], qui dit avec quoi on construit ; [project], avec les données du projet ; et [tool], pour chaque outil [13]. Le modèle utilise les trois, plus une quatrième : [dependency-groups], où vivent pytest, ruff et mypy avec leur version figée. Ce groupe existe depuis la PEP 735, qui le définit exactement ainsi : des exigences stockées dans pyproject.toml et qui ne sont incluses dans aucune distribution du projet [16]. Qui installe revisor ne reçoit pas pytest.
Un détail mesuré : pip install --group dev exige pip 25.1 ou plus récent [17], et un environnement tout juste créé sur Ubuntu 24.04, la base de Linux Mint 22 [10], arrive avec pip 24.0 [11]. La réponse est no such option: --group. C'est pourquoi le README demande python -m pip install --upgrade pip avant toute chose.
Et où va le CSV d'Iván ?
Dans le paquet, à côté des autres formats du rapport.
Dans le modèle, le rapport a déjà sa place : reporte.py, qui transforme aujourd'hui les résultats en tableau ou en JSON. Une fonction a_csv va là, à côté de a_json, et cli.py ajoute csv aux options de --formato. Aucun fichier à la racine. Aucun nouveau dossier.
Le lundi, Iván déplaça sa fonction dans le paquet et lança les tests contre ce qui était installé. Vert. Cette fois, vraiment vert.
Comment savoir que la frontière existe ?
Parce qu'un script la casse exprès et exige que le paquet installé ne voie pas ce qui est resté dehors.
herramientas/probar-frontera.sh travaille sur une copie temporaire du projet. Il sème src/suelto.py, un module qui vit dans src/ mais hors du paquet, et un test qui l'importe. Puis il lance la suite complète deux fois, dans deux environnements neufs avec le même Python, la même copie et la même version de pytest. Entre les deux, une seule option de pip change, -e :
- Contrôle, avec
pip install -e. Python lit le dossier, donc tous les tests doivent passer, y compris celui semé. S'ils ne passent pas, l'expérience est mal montée et le script s'arrête au rouge. - Violation, avec
pip install. C'est la roue qui s'installe. Un seul test doit échouer, celui semé, et avec le message exact.
Voici la sortie réelle :
1/2 control: pip install -e (la carpeta) · src/suelto.py está fuera del paquete
revisor bajo prueba: src/revisor (la CARPETA del proyecto)
✅ 63 de 63 pasaron: desde la carpeta, suelto.py se ve
2/2 violación: pip install (la rueda) · el mismo archivo, la misma prueba
revisor bajo prueba: …/site-packages/revisor (el paquete INSTALADO)
✗ test_suelto_viaja_con_el_paquete: ModuleNotFoundError: No module named 'suelto'
✅ 62 de 63 pasaron; falló solo la que usa suelto.py, por la razón exacta
✅ frontera: se sostienePourquoi le contrôle ? Parce qu'une expérience qui n'attend qu'un rouge passe aussi au « vert » quand elle échoue pour la mauvaise raison : un test mal écrit, un environnement cassé. Nous l'avons vérifié : avec le test semé cassé exprès, ou avec le contrôle installé sans -e, le script s'arrête sur « l'expérience est mal montée ».
Et si quelqu'un change la ligne packages pour que la roue emporte tout src/, la violation passe et le script répond ❌ PASÓ: lo instalado vio src/suelto.py, que no está en el paquete. La frontera no existe. Nous l'avons vérifié aussi.
La CI de GitHub est écrite pour tout lancer avec Python 3.12 et 3.14, à chaque changement, à la main et chaque lundi : ruff, mypy strict, les tests contre le paquet installé et la frontière. Si le dépôt public reste 60 jours sans activité, GitHub désactive l'exécution du lundi et il faut la réactiver [23].
Quelles sont les cinq erreurs les plus courantes ?
Les trois premières viennent de ne pas savoir quel dossier Python voit ; les deux dernières, de décrire le projet à moitié.
1. Tester le dossier et croire avoir testé le paquet
L'organisation à plat laisse la racine visible, et la racine n'est pas ce qui s'installe.
C'est l'erreur d'Iván. Le guide de la PyPA explique que src/ aide à éviter l'utilisation accidentelle de la copie en développement [1]. pytest le dit franchement : « it is strongly suggested to use a src layout », surtout avec son mode par défaut [2]. Et la documentation de uv dit que uv init préfère mettre le code dans src/<nombre>/ [18] ; nous l'avons vérifié avec uv 0.12.23.
L'issue n'est pas seulement de changer de dossier : c'est de tester aussi ce qui est installé, comme le fait le modèle.
2. pip install dans le Python du système
Le système d'exploitation gère déjà ce Python, et depuis la PEP 668 il te le dit.
La PEP 668 propose que, par défaut, les outils comme pip n'installent ni ne retirent de paquets dans un Python que le système marque comme géré par lui, et qu'ils orientent qui essaie vers un environnement virtuel [9]. Sur Ubuntu 24.04, sans environnement, voici ce qu'il répond (mesuré) :
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.La PEP demande que l'option pour forcer « should carry some connotation that its use is risky » [9]. C'est pourquoi elle s'appelle --break-system-packages. Tout va dans un .venv, comme dans le modèle.
3. Un fichier qui porte le nom d'un module standard
Un random.py qui traîne masque le random de Python.
Le tutoriel officiel prévient : le dossier du script exécuté va au début du chemin de recherche, donc ses fichiers sont chargés à la place des modules de la bibliothèque du même nom, et cela « is an error unless the replacement is intended » [19]. Python 3.13 a ajouté un message qui le signale [12]. Mesuré avec un random.py qui importe random :
Python 3.12:
AttributeError: partially initialized module 'random' has no attribute 'randint' (most likely due to a circular import)
Python 3.14:
AttributeError: module 'random' has no attribute 'randint' (consider renaming '…/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)En 3.12, le message parle d'une importation circulaire et pas de ton fichier ; le 3.12.3 d'Ubuntu 24.04, celui de Mint, dit la même chose (mesuré). Dans un paquet, en l'exécutant comme paquet (python -m revisor.cli ou la commande installée), le conflit n'a pas lieu : revisor/random.py s'appelle revisor.random. Mais si tu exécutes le fichier comme script, python src/revisor/cli.py, il revient (mesuré en 3.12 et 3.14).
4. Deux fichiers de test du même nom
Dans le mode par défaut de pytest, tests/config/test_formato.py et tests/reporte/test_formato.py entrent en conflit.
La documentation de pytest l'explique : si les tests ne sont pas organisés en paquets, chaque fichier a besoin d'un nom unique, sinon pytest signale une erreur [4]. Mesuré avec pytest 9.1.1 :
import file mismatch:
imported module 'test_formato' has this __file__ attribute:
…/tests/config/test_formato.py
which is not the same as the test file we want to collect:
…/tests/reporte/test_formato.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modulesLa solution habituelle était d'ajouter __init__.py à tests/, mais cela fait que pytest met la racine du dépôt dans le chemin de recherche, et dans une organisation à plat ton paquet est de nouveau importé depuis le dossier [2]. Le mode importlib n'a pas ce problème [2] : avec lui, les deux fichiers passent (mesuré).
5. setup.py ou requirements.txt comme seule description du projet
pip ne lit pas un requirements.txt quand il installe ton paquet.
Pour les nouveaux projets, le guide de la PyPA demande la table [project] de pyproject.toml, et de ne garder setup.py que s'il faut configurer quelque chose avec du code, comme compiler des extensions en C [13]. Et un requirements.txt n'est pas analysé à l'installation : il ne sert que quand quelqu'un lance pip install -r [14]. Si tes dépendances ne vivent que là, qui installe ton paquet ne les reçoit pas.
Tu veux que chaque machine installe exactement les mêmes versions ? Pour cela, il existe désormais un format standard de fichier de verrouillage, pylock.toml, de la PEP 751 [15]. C'est un autre fichier, pour un autre problème.
| Erreur | Ce qui la trahit | Source |
|---|---|---|
| 1. Tester le dossier | Vert sur ta machine, ModuleNotFoundError une fois installé | [1][2] |
| 2. pip dans le Python du système | externally-managed-environment | [9] |
3. Un random.py qui traîne | has no attribute sur un module qui l'a bien | [12][19] |
| 4. Des tests du même nom | import file mismatch | [2][4] |
5. requirements.txt comme seule description | Une fois installé, il manque des dépendances | [13][14] |
Exercice : peux-tu reproduire l'erreur d'Iván ?
Tu vas mettre un fichier hors du paquet, voir ton dossier te dire que tout va bien, et voir le paquet installé te dire que non.
Il te faut Python 3.12 ou plus récent (python3 --version), Git et bash, environ 20 minutes. Linux Mint 22 fournit 3.12 ; si python3 -m venv se plaint qu'il manque ensurepip, lance sudo apt install python3.12-venv et répète la commande (la leçon 1 du cours l'explique). Sur macOS, le python3 du système est celui d'Apple, « usually older » [24] (ici, 3.9.6) : installes-en un récent depuis python.org. Sous Windows, utilise WSL.
Étape 1. Clone, prépare l'environnement et vérifie que tout est au vert.
git clone https://github.com/HabilMX/estructura-python
cd estructura-python
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install --group dev -e .
pytest -q
bash herramientas/probar-instalado.shCe que tu dois voir : 62 passed et, à la fin, ✅ lo instalado pasa sus pruebas.
Étape 2. Prédis avant de toucher. Tu vas créer src/exportar.py, hors du paquet, et un test qui l'importe. Note : que dira pytest ? Et le script du paquet installé ?
Étape 3. Reproduis l'erreur.
cat > src/exportar.py <<'EOF'
def a_csv(filas: list[tuple[str, str]]) -> str:
return "\n".join(",".join(fila) for fila in filas)
EOF
cat > tests/test_exportar.py <<'EOF'
from exportar import a_csv
def test_a_csv() -> None:
assert a_csv([("pagos", "ARRIBA")]) == "pagos,ARRIBA"
EOF
pytest -q
bash herramientas/probar-instalado.shpytest dit 63 passed : ton dossier t'assure que tout va bien. Le script dit autre chose (ce qui compte ; dessous suivent la sortie de pytest et un ❌) :
pruebas: 63 · pasaron 62 · fallaron 0 · con error 1 · saltadas 0
✗ tests.test_exportar: ModuleNotFoundError: No module named 'exportar'C'est le vendredi de Sofía, sur ta machine.
Étape 4. Corrige comme Iván : mets le fichier dans le paquet. Copie seulement la ligne sed de ton système : celui de macOS exige un argument vide après -i.
mv src/exportar.py src/revisor/exportar.py
# En Linux:
sed -i 's/^from exportar import/from revisor.exportar import/' tests/test_exportar.py
# En macOS:
sed -i '' 's/^from exportar import/from revisor.exportar import/' tests/test_exportar.py
bash herramientas/probar-instalado.shMaintenant, tu obtiens pruebas: 63 · pasaron 63 et ✅ lo instalado pasa sus pruebas.
Étape 5. Essaie de tromper le garde. Demande à pytest d'exiger le paquet installé, depuis ton environnement éditable :
REVISOR_EXIGIR_INSTALADO=1 pytestERROR: REVISOR_EXIGIR_INSTALADO=1, pero revisor viene de la carpeta src/revisor. Estas pruebas probarían tu carpeta, no lo que se instala. Usa: bash herramientas/probar-instalado.shIl a refusé de te donner un vert. C'est le contrôle qui travaille.
Étape 6. Remets tout en place.
git checkout -- . && git clean -fd
bash herramientas/probar-frontera.shAttention : git clean -fd efface aussi les nouveaux fichiers que tu as créés là, comme tes notes.
Comment savoir que tu as réussi : tu as lancé sept exécutions et vu trois issues. Vert, cinq fois : les deux de l'étape 1, pytest à l'étape 3, l'étape 4 et la frontière de l'étape 6. Rouge, une fois : le paquet installé à l'étape 3, avec No module named 'exportar'. Et le refus du garde à l'étape 5. Si à l'étape 3 le script sort vert, vérifie que exportar.py est bien resté dans src/ et pas dans src/revisor/. Si à l'étape 4 il reste rouge, vérifie que la première ligne de tests/test_exportar.py dit from revisor.exportar import a_csv.
Pour aller plus loin (facultatif) : ajoute a_csv à reporte.py, ajoute csv aux options de --formato dans cli.py et écris son test. Avant de le lancer, prédis ce que comptera probar-instalado.sh.
À retenir
Trois questions à poser à ton dépôt cette semaine.
- D'où tournent tes tests ? Si la réponse est « depuis la racine, avec
python -m pytest», quels fichiers de la racine Python voit-il alors qu'ils ne vont pas dans le paquet ? - As-tu déjà lancé tes tests contre le paquet installé, dans un environnement neuf et sans
-e? Fais-le une fois. Si ça casse, tu as trouvé une erreur avant tes utilisateurs. - Ton projet est-il décrit dans
pyproject.toml, ou faut-il encore lire unsetup.pyet unrequirements.txtpour savoir ce dont il a besoin ?
Et ensuite ?
En Go, la frontière est surveillée par l'outil. En Python, par l'installation. En Rust, par le compilateur, et il est plus strict que tu ne le crois.
Deux semaines plus tard, Iván hérita d'un programme en Rust. Toute la logique vivait dans un main.rs de 900 lignes. Il voulut écrire un test d'intégration, de ceux qui vivent hors du code, dans tests/.
Cargo ne trouva rien à importer. Le programme fonctionnait, mais de l'extérieur personne ne pouvait y toucher.
Comment tester une fonction que personne ne peut importer ? Dans l'épisode 3 : un fichier appelé lib.rs, et pourquoi le compilateur te donne un indice, pas une punition.
Le modèle et le cours
- Modèle : github.com/HabilMX/estructura-python. Il s'utilise comme modèle GitHub ou se clone [22]. Le README explique chaque dossier et quand ajouter chaque chose. Le code est sous MIT ; le texte et les explications, sous CC BY-SA 4.0.
- Versions : il demande Python 3.12 ou plus récent et a été testé jusqu'à la 3.14, sur macOS et sur Linux : avec la 3.14.9, la plus récente de la 3.14 au 22 octobre [21], avec la 3.12.15 et avec la 3.12.3, celle d'Ubuntu 24.04 [11]. Pas encore la 3.15. La 3.14 est maintenue jusqu'en octobre 2030 ; la 3.12 ne reçoit plus que des correctifs de sécurité, jusqu'en octobre 2028 [20]. Il n'utilise que la bibliothèque standard ; pytest 9.1.1, ruff 0.17.1 et mypy 2.4.0 sont figés dans le groupe de développement.
- Cours de Python de Hábil : si
pyproject.toml,python -mou l'installation éditable sont nouveaux pour toi, la leçon 7 les présente depuis zéro : Cours de Python · Leçon 7, Un paquet en ligne de commande.
J'écris à partir de ce que nous voyons tous les jours chez Hábil, où nous construisons des systèmes pour la banque, l'assurance et les entreprises au Mexique. Si, au travail ou à l'école, ton équipe tombe sur un dépôt que personne ne sait par où prendre, parlons-en : habil.mx
Que fait Hábil ?
Nous enseignons la programmation avec ce que nous utilisons pour construire : du code testé, expliqué et ouvert.
Cette série naît du même endroit que nos cours. Le Cours de Python va de zéro à un premier programme complet, et nous le publions en accès libre, sous licence Creative Commons ; sa leçon 7 présente le paquet en ligne de commande, pyproject.toml et l'installation éditable qu'utilise le modèle de cet article. Et le catalogue compte d'autres cours de programmation, gratuits, avec du code testé et expliqué pas à pas.
Découvre le Cours de Python et le catalogue des Cours de programmation.
Si ton équipe hérite d'un système que personne ne sait par où prendre, ou doit en construire un qui puisse continuer à être maintenu, nous le faisons aussi : découvre Construire votre système sur mesure. Demande ton premier échange, sans frais. Nous y examinons s'il convient de configurer ce que ton équipe a déjà, d'utiliser un produit ou de construire, et ce qu'elle recevrait dans chaque cas. Écris-nous sur WhatsApp.
Sources consultées le 22 octobre 2026. Les citations en anglais sont reproduites telles quelles.
Références
- Python Packaging Authority, « src layout vs flat layout », Python Packaging User Guide. https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/
- pytest, « Good Integration Practices », docs.pytest.org (pytest 9.1). https://docs.pytest.org/en/stable/explanation/goodpractices.html
- pytest, « How to invoke pytest » : « Calling pytest through python -m pytest », docs.pytest.org. https://docs.pytest.org/en/stable/how-to/usage.html
- pytest, « pytest import mechanisms and sys.path/PYTHONPATH », docs.pytest.org. https://docs.pytest.org/en/stable/explanation/pythonpath.html
- pip, « Local project installs » : « Editable installs », pip.pypa.io. https://pip.pypa.io/en/stable/topics/local-project-installs/
- Hatch, « Build configuration » : « Packages » et « Dev mode », hatch.pypa.io. https://hatch.pypa.io/latest/config/build/
- Hatch, « Wheel builder » : « Default file selection », hatch.pypa.io. https://hatch.pypa.io/latest/plugins/builder/wheel/
- Python, « Command line and environment » : option
-m, docs.python.org. https://docs.python.org/3/using/cmdline.html - G. Thomas, M. Klose, F. Laíns, D. Stufft, T. Chung, S. Rivera, E. Hashman et P. Gedam, « PEP 668 – Marking Python base environments as “externally managed” », 18 mai 2021. https://peps.python.org/pep-0668/
- Linux Mint, « Linux Mint 22.3 Release Notes » (« Linux Mint 22.x is based on Ubuntu 24.04 »), linuxmint.com. https://www.linuxmint.com/rel_zena.php
- Launchpad (Canonical), paquets publiés dans Ubuntu 24.04 « noble » :
python3-defaults3.12.3 etpython-pip24.0. https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=python3-defaults&exact_match=true · https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=python-pip&exact_match=true - Python, « What's New In Python 3.13 » : « Improved error messages », docs.python.org. https://docs.python.org/3/whatsnew/3.13.html
- Python Packaging Authority, « Writing your pyproject.toml », Python Packaging User Guide. https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
- Python Packaging Authority, « install_requires vs requirements files », Python Packaging User Guide. https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/
- Brett Cannon, « PEP 751 – A file format to record Python dependencies for installation reproducibility », 24 juillet 2024 (acceptée le 31 mars 2025). https://peps.python.org/pep-0751/
- Stephen Rosen, « PEP 735 – Dependency Groups in pyproject.toml », 20 novembre 2023 (acceptée le 10 octobre 2024). https://peps.python.org/pep-0735/
- pip, « Changelog », version 25.1 (26 avril 2025), pip.pypa.io. https://pip.pypa.io/en/stable/news/
- Astral, « Creating projects » (uv), docs.astral.sh. https://docs.astral.sh/uv/concepts/projects/init/
- Python, « The Python Tutorial », 6.1.2 « The Module Search Path », docs.python.org. https://docs.python.org/3/tutorial/modules.html
- Python Developer's Guide, « Status of Python versions ». https://devguide.python.org/versions/
- Python Software Foundation, « Python 3.14.9 », 21 octobre 2026. https://www.python.org/downloads/release/python-3149/
- GitHub Docs, « Creating a template repository ». https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository
- GitHub Docs, « Disabling and enabling a workflow ». https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows
- Python, « Using Python on macOS » (§5.1), docs.python.org. https://docs.python.org/3/using/mac.html · Installateurs : https://www.python.org/downloads/macos/
Votre activité fait face à ces défis ?
Vous préférez l'e-mail ? Écrivez-nous à hola@habil.mx