organisation projet python

Ou la difficulté d'importer des modules

a marqué ce sujet comme résolu.

Bonjour tout le monde,

Je viens demander de l’aide, car ça fait quelques heures que je suis sur un problème qui je pense aurait dû être trivial. A noter que j’ai fait pas mal de recherches sur le sujet, sur Stack Overflow, ou sur le forum de ZdS (dans lequel j’ai trouvé un sujet un peu annexe). Je me suis aussi aidé de l’article sur les modules et packages en Python.

Bref, rien n’y fait, je n’arrive pas à faire un import d’un package depuis un dossier test. :(

Voici mon répertoire de projet :

project/
│
├── main.py
├── __init__.py
│
├── algorithms/
│   ├── __init__.py
│   └── algo.py
│
├── test/
│   ├── __init__.py
│   └── test_algo.py

J’aimerais faire en sorte de pouvoir importer algo.py depuis test_algo afin de pouvoir exécuter les tests. Donc j’ai testé plusieurs méthodes :

Ce que j’ai testé

La méthode intuitive (dans test_algo.py) :

from ..algorithms import algo

Ensuite, j’ai cru comprendre qu’on pouvait pas faire de chemin relatif en dehors du package/sous package Donc en modifiant project/algorithms/init.py

import algo

et project/init.py

import test
import algorithms

et dans project/test/test_algo je fais

import algorithms

Bref, rien ne fonctionne.

  • Sauriez-vous ou je me suis trompé ?
  • Pensez-vous qu’une organisation de projet de la sorte est utile ou vaut-il mieux tout mettre dans le même répertoire ? (ça simplifierait effectivement les imports)

Merci beaucoup ^^

Bonjour, j’ai été confronté à ce problème à chaque fois que je faisais des tests dans un projet, c’est assez classique, j’ai l’impression.

La meilleure solution que j’utilise, c’est d’ajouter le path du dossier algorithms à ta variable d’environnement PYTHONPATH.

+0 -1

Salut,

L’organisation de ton project me parait un peu étrange. Si project/ est un module Python lui-même avec main, algorithms et test qui sont des sous-modules, alors from ..algorithms import algo depuis test_algo.py devrait fonctionner. Cela dit, ce serait une organisation peu conventionnelle. En principe, un projet Python s’organise de la façon suivante :

.
├── pyproject.toml
├── src
│   ├── module1
│   │   ├── __init__.py
│   │   ├── submodule
│   │   |   └── ...
│   │   └── some_module.py
│   ├── module2
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   └── other_module.py
│   └── some_tests
│       ├── test_mod1.py
│       ├── test_mod2.py
│       └── test_mod3.py
├── README.md
├── test
│   ├── test_something.py
│   └── test_something_else.py
└── tox.ini

C’est ensuite pyproject.toml qui décrit comment les modules sont organisés (e.g. via tool.setuptools.packages.find). module1 et module2 sont deux modules différents (distribués au sein du même package Python), importer l’un depuis l’autre sera via un import absolu import module1. Utiliser un outil comme tox permet d’exécuter automatiquement la suite de tests dans un environnement virtuel où le package est installé. Dans les tests, les modules à tester sont importés comme s’il s’agissait de paquets externes, par exemple avec import algorithms.


La meilleure solution que j’utilise, c’est d’ajouter le path du dossier algorithms à ta variable d’environnement PYTHONPATH.

Non, c’est la pire solution possible. PYTHONPATH est là pour pouvoir gérer des besoins relativement exotiques de déploiement sur des environnements non-standards (je n’en ai jamais eu le besoin!), c’est pas le truc qu’on utilise en routine et encore moins pour un truc aussi mondain que gérer son environnement de développement.

+2 -0

L’organisation de ton project me parait un peu étrange. Si project/ est un module Python lui-même avec main, algorithms et test qui sont des sous-modules, alors from ..algorithms import algo depuis test_algo.py devrait fonctionner.

adri1

Mais suivant le répertoire où l’on se trouve quand on exécute le programme / les tests et celui dans lequel se trouvent les fichiers, le paquet courant peut être trouvé ou non.

La solution pour que le paquet soit systématiquement trouvé est soit de toujours se placer dans le répertoire parent au projet pour exécuter des commandes et d’utiliser python -m, soit d’installer le projet (pip install -e . exécuté une fois dans l’environnement virtuel, ensuite le paquet est toujours trouvé quand l’env est activé).

Mais oui, tu ne devrais jamais avoir à trifouiller PYTHONPATH pour ça.


Petite illustration avec la hiérarchie suivante :

.
├── pyproject.toml
├── project
│   ├── addition.py
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test_addition.py

dont les fichiers contiennent :

def addition(a, b):
    return a + b
project/addition.py
from project.addition import addition

assert addition(3, 5) == 8

print('passed')
tests/test_addition.py

Les fichiers __init__.py et pyproject.toml sont vides.

Si je me place dans le répertoire parent au projet (répertoire . dans la hiérarchie) et exécute python tests/test_addition.py, j’obtiens une erreur parce que le paquet project n’est pas trouvé.

~ % python tests/test_addition.py
Traceback (most recent call last):
  File "/tmp/test/tests/test_addition.py", line 1, in <module>
    from project.addition import addition
ModuleNotFoundError: No module named 'project'

Si j’utilise python -m suivi du module de tests, le répertoire courant est automatiquement ajouté aux chemins donc ça marche.

~ % python -m tests.test_addition
passed

Si je me place dans le répertoire tests ça ne fonctionne plus.

~ % cd tests
~/tests % python -m test_addition
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/tmp/test/tests/test_addition.py", line 1, in <module>
    from project.addition import addition
ModuleNotFoundError: No module named 'project'

Si j’utilise un environnement virtuel et installe mon projet (le fichier pyproject.toml vide suffit pour rendre le projet installable).

~/tests % cd ..
~ % python -m venv env
~ % source env/bin/activate
(env) ~ % pip install -e .
...

Alors toutes les configurations précédentes fonctionnent correctement.

(env) ~ % python tests/test_addition.py
passed
(env) ~ % python -m tests.test_addition
passed
(env) ~ % cd tests
(env) ~/tests % python test_addition.py
passed
(env) ~/tests % python -m test_addition
passed

Merci pour vos réponses, ça m’a bien aidé et désormais mon problème est résolu !

Alors du coup j’ai refait la structure du programme identique à l’exemple d'@entwanne afin de pouvoir faire différents tests et me rendre compte du fonctionnement déjà depuis la ligne de commande. Effectivement, mon problème était que j’avais tendance à exécuter le script test depuis le test directement (ou depuis Pycharm mais qui n’exécutais que le fichier.

Maintenant, j’ai adapté mon projet de façon à ce qu’il soit semblable à celui d'@adri1. Tout fonctionne.

Il faut noter aussi que j’ai adapté la configuration dans Pycharm en ajoutant un marquage comme quoi le répertoire test est un répertoire test et que le répertoire src est le répertoire root. J’imagine que par ce biais, Pycharm met le répertoire src dans le path de test ? Est-ce d’ailleurs cela que fait tox, l’outil que tu proposes @adri1 ?


Sinon comment vous organisez le pyproject.toml ? Est-il vraiment utile dans le sens ou je ne compte pas faire un package global mais plutôt une application que l’on exécute ?

J’imagine que par ce biais, Pycharm met le répertoire src dans le path de test ? Est-ce d’ailleurs cela que fait tox, l’outil que tu proposes @adri1 ?

Je ne sais pas ce que Pycharm appelle le "path de test". tox est un outil en ligne de commande qui permet de gérer des environnement virtuels et faire tourner les tests (typiquement en appelant pytest). Je ne sais pas du tout ce que fait Pycharm donc je ne sais pas comment ça compare.

Sinon comment vous organisez le pyproject.toml ? Est-il vraiment utile dans le sens ou je ne compte pas faire un package global mais plutôt une application que l’on exécute ?

pyproject.toml est l’endroit canonique pour définir un paquet Python, y compris si le but est de l’utiliser comme un outil en ligne de commande. Tu peux y définir des point d’entrées dans project.scripts (chaque point d’entrée est tout bêtement la fonction exécutée par l’outil en ligne de commande), mais aussi les dépendances de ton paquet, ainsi que des configurations pour des outils de développement (comme mypy, ruff, etc). La documentation de la PyPA est très bien. Voir ici pour un exemple minimal, probablement assez proche de ce que tu veux faire.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte