Si non veut diagnostiquer une installation, on peut le faire individuellement (paquetage par paquetage) mais aussi en produisant une liste de paquetages puis en explorant cette liste. Les outils tels que pip, conda, mamba peuvent produire la liste des paquetages qu’ils sont à même de résoudre dans une installation ou un environnement donné. Le module pypkgs.py de buildez.sys fournit des primitives fonctionnant sur ce mode, et qui font appel aux méthodes pip (qui fonctionnera cette fois en mode Json: pip-json) et mamba ou conda (qui fonctionneront cette fois en mode simple). Ces méthodes sont interfacées avec les fonctions de pypkgs qui agissent en mode paquetage (individuel) de manière à compléter la résolution en se basant sur l’utilisation d’importlib. Dans cette note nous allons voir leurs modalités de fonctionnement, avec quelques problématiques à la clé et une possibilité de contournement.
1. Méthode pip
Il s’agit d’utiliser la commande pip list ---format=json, car la sortie en mode liste de pip est plus facile à utiliser avec ce format. Nous utilisons la fonction pypkgs_plist_pip(verb=0) sans autre argument. Celle ci va déclencher un sous-processus pour le lancement de pip, avec la même méthode que pypkgs_pkg_pip. Puis la fonction analyse la chaine de caractères result.stdout correspondant au résultat de subprocess.run d’une manière simple grâce au parseur Json :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
plist = [] # pip execution exec = 'pip' out_opt = "--format=json" try: result = subprocess.run( [sys.executable, "-m", exec, "list", out_opt], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) if result.returncode != 0: print("!pip error :", str(result.stderr)) return plist # process json info packages = json.loads(result.stdout) for p in packages: pdict = pypkgs_init_pdict(p.get('name', '')) pdict['ver'] = p.get('version', '') pdict = pypkgs_scoring(pdict, what='pip') plist.append(pdict) except Exception as e: print("pip exception :", e) return plist |
Ce code nous permet d’obtenir la liste des paquetages correspondant à l’environnement courant, sous la forme d’une liste de dictionnaires (packages) issue du parseur Json. Puis nous déroulons cette liste pour alimenter la notre (plist) dont chaque item correspond à un dictionnaire instancié par la fonction pypkgs_init_pdict. Nous y injectons le nom du paquetage lorsqu’on initialise pdict, puis nous complétons par la version ainsi qu’un premier calcul de score.
Comme nous sommes compatibles avec la gestion individuelle de paquetages nous pouvons compléter pdict par des informations en provenance de la méthode importlib via la fonction pypkgs_importlib, par exemple :
|
1 2 3 4 |
# update with location for i in range (0, len(plist), 1): pname = plist[i]['name'] plist[i] = pypkgs_importlib(pname, plist[i], files=False, meta=True, verb=0) |
A ce stade nous avons donc la liste de tous les paquetages, avec une résolution par importlib et pip.
2. Méthodes mamba et conda
Le principe est similaire, nous produisons une sortie en encapsulant la commande mamba list (ou conda list) via la fonction pypkgs_pkg_conda_cmd qui pour cette opération va être appelée en mode simple par la fonction pypkgs_plist_conda, il n’y pas utilisation d’un flot Json. En effet, ces sorties sont faciles à analyser, avec une ligne par paquetage. Chaque ligne va être traitée de manière à initialiser et renseigner un dictionnaire pdict par paquetage, le mode simple retrouve ici son intérêt.
La fonction pypkgs_plist_conda fonctionne avec mamba et conda (cf. argument exec='mamba' ou exec='conda') et permet de récupérer les informations de nom de paquetage, de build, de canal et renvoie une liste de dictionnaires pdict. Comme dans le cas de la fonction pypkgs_plist_pip cette liste peut être augmentée et rescorée via la fonction pypkgs_importlib. Dans les deux cas, l’argument files est positionné sur False, car l’objectif est d’obtenir une liste de paquetages avec des informations minimales et non d’examiner en détail un paquetage donné.
3. Performances et fiabilité
Les 3 méthodes s’exécutent avec des temps différents, une fois de plus il faut comparer les valeurs plutôt que les interpréter dans l’absolu (d’une machine à l’autre nous avons des architectures performantes et ce type de code est sensible aux caches). Nous constatons que les temps de résolution d’une liste, sont similaires à la résolution individuelle d’un paquetage quelle que soit la méthode, nous avons donc intérêt à procéder avec une approche basée sur la résolution de listes de paquetages.
| Opération | Fonction | Items | Temps |
| pip + importlib (meta) | pypkgs_plist_pip(verb=0) |
125 | 1.4 s |
| mamba + importlib (meta) | pypkgs_plist_conda(exec='mamba', verb=0) |
253 | 4 – 5 s |
| conda + importlib (meta) | pypkgs_plist_conda(exec='conda', verb=0) |
257 | 18 – 19 s |
| Fusion | pypkgs_plist_merge(pip_list, conda_list, mamba_list, verb=0) |
257 | 0 s |
Par contre, nous constatons que le nombre de paquetage résolus est différent selon la méthode. En effet, nous obtenons une liste de 125 paquetages avec pip, 253 paquetages avec mamba, et 257 paquetages avec conda. Nous constatons que mamba est le meilleur compromis temps-résolution, mais que conda arrive à récupérer quelques paquetages de plus. Mais nous ne devons pas en tirer une règle. Pour aller plus loin, le paquetage inclue une fonction pypkgs_plist_compare qui va nous servir à comparer les listes de paquetages (rappel: il s’agit de listes de dictionnaires pdict) pour les trois méthodes.
Nous pouvons donc afficher une table croisée de l’ensemble des paquetages, ce qui nous donne (... pour les lignes non affichées) :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
conda mamba pip ------------------------------------------------------------------------------------------------------------- Brotli (1.1.0) [conda-forge] Brotli (1.1.0) [conda-forge] Brotli (1.1.0) ChemSpiPy (2.0.0) [conda-forge] ChemSpiPy (2.0.0) [conda-forge] ChemSpiPy (2.0.0) - - PyQt5 (5.15.9) PyQt5-sip (12.12.2) [conda-forge] PyQt5-sip (12.12.2) [conda-forge] PyQt5-sip (12.12.2) PySide6 (6.8.1 ) [conda-forge] PySide6 (6.8.1 ) [conda-forge] PySide6 (6.8.1 ) ... SQLAlchemy (2.0.36) [conda-forge] SQLAlchemy (2.0.36) [conda-forge] SQLAlchemy (2.0.36) _openmp_mutex (4.5) [conda-forge] _openmp_mutex (4.5) [conda-forge] - archspec (0.2.3) [conda-forge] archspec (0.2.3) [conda-forge] archspec (0.2.3) attrs (24.3.0) [conda-forge] attrs (24.3.0) [conda-forge] attrs (24.3.0) ... mock (5.1.0) [conda-forge] mock (5.1.0) [conda-forge] mock (5.1.0) - - msgpack (1.1.0) msgpack-python (1.1.0) [conda-forge] msgpack-python (1.1.0) [conda-forge] - munkres (1.1.4) [conda-forge] munkres (1.1.4) [conda-forge] munkres (1.1.4) ... pycosat (0.6.6) [conda-forge] pycosat (0.6.6) [conda-forge] pycosat (0.6.6) pycparser (2.22) [conda-forge] pycparser (2.22) [conda-forge] pycparser (2.22) pydotplus (2.0.2) [pypi] - pydotplus (2.0.2) pyparsing (3.2.1) [conda-forge] pyparsing (3.2.1) [conda-forge] pyparsing (3.2.1) pyqt (5.15.9) [conda-forge] pyqt (5.15.9) [conda-forge] - ... traits (6.4.3) [conda-forge] traits (6.4.3) [conda-forge] traits (6.4.3) truststore (0.10.0) [conda-forge] truststore (0.10.0) [conda-forge] truststore (0.10.0) typing (3.7.4.3) [pypi] - typing (3.7.4.3) tzdata (2024.2) [conda-forge] tzdata (2024.2) [conda-forge] tzdata (2024.2) ... zstandard (0.23.0) [conda-forge] zstandard (0.23.0) [conda-forge] zstandard (0.23.0) zstd (1.5.6) [conda-forge] zstd (1.5.6) [conda-forge] - |
Nous constatons que des paquetages sont absents via la méthode pip-json, il s’agit pour la plupart de paquetages issus du canal [conda-forge], mais d’autres sont absents dans conda ou dans mamba et ce ne sont pas forcément les mêmes, plus précisément pour conda-simple et mamba-simple :
not resolved packages: conda=3, mamba=5, pip=132conda : ['PyQt5', 'msgpack', 'shiboken6']mamba : ['PyQt5', 'msgpack', 'pydotplus', 'shiboken6', 'typing'] |
Nous en concluons que choisir une méthode unique est un risque, même s’il apparaît limité avec conda ou mamba. Je suis presque sur que si vous expliquez ce type de détails à quelqu’un qui sait tout, vous n’allez pas être compris. Pourtant nous le constatons, mais cela ne sera pas un problème en termes de fiabilité car le module pypkgs inclue un agrégateur qui va fusionner ces trois listes de dictionnaires (sans doublons), du coup nous aurons des informations consolidées.
4. Fusion des données
La fonction pypkgs_plist_merge est capable de fusionner les listes de dictionnaires (pip+importlib, mamba+importlib, conda+importlib) générées par les fonctions pypkgs_plist_pip et pypkgs_plist_conda, de manière à générer une liste unique. Le code pour une séquence de génération de listes associé à une fusion pourrait ressembler à ces lignes :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
print() print("+++ get package lists using (importlib) pip, conda, mamba") print() print("-> get package list using pip + importlib\n") plist_pip = pypkgs_plist_pip(verb=1) print() print("-> get package list using mamba + importlib\n") plist_mamba = pypkgs_plist_conda(exec='mamba', verb=1) print() print("-> get package list using conda + importlib\n") plist_conda = pypkgs_plist_conda(exec='conda', verb=1) print() print("-> merge found package lists\n") plist = pypkgs_plist_merge(plist_pip, plist_mamba, plist_conda, verb=1) print(plist) |
Si nous affichons les 4 premiers items de la liste de paquetages, nous avons un contenu des dictionnaires pdict qui peut varier, mais on assure le nom ('name'), la version ('ver'), le canal ('channel'), le build ('build') et le score ('score'), éventuellement la localisation ('loc'). Ce qui est la base minimale pour vérifier les paquetages, on notera aussi la chaîne complexe 'pythonver' pour le paquetage archspec.
|
1 2 3 4 5 |
[{'name': '_openmp_mutex', 'ver': '4.5', 'loc': '', 'files': [], 'err': '', 'installer': '', 'channel': 'conda-forge', 'score': 4, 'summary': '', 'pythonver': '', 'req': [], 'reqby': [], 'build': '2_gnu'}, {'name': 'archspec', 'ver': '0.2.3', 'loc': 'C:\\Python3\\envs\\prod\\Lib\\site-packages\\archspec', 'files': [], 'err': '', 'installer': '', 'channel': 'conda-forge', 'score': 196, 'summary': 'A library to query system architecture', 'pythonver': '>=3.6, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', 'req': [], 'reqby': [], 'build': 'pyhd8ed1ab_0'}, {'name': 'attrs', 'ver': '24.3.0', 'loc': 'C:\\Python3\\envs\\prod\\Lib\\site-packages\\attrs', 'files': [], 'err': '', 'installer': '', 'channel': 'conda-forge', 'score': 196, 'summary': 'Classes Without Boilerplate', 'pythonver': '>=3.8', 'req': [], 'reqby': [], 'build': 'pyh71513ae_0'}, {'name': 'beautifulsoup4', 'ver': '4.12.3', 'loc': '', 'files': [], 'err': '', 'installer': '', 'channel': 'conda-forge', 'score': 68, 'summary': 'Screen-scraping library', 'pythonver': '>=3.6.0', 'req': [], 'reqby': [], 'build': 'pyha770c72_1'}, ... |
Nous pouvons aller voir du coté des paquetages qui n’étaient pas résolus par une des trois méthodes, par exemple brotli-bin (pip), msgpack, PyQt5, pydotplus, shiboken6, typing :
|
1 2 3 4 5 6 |
{'name': 'brotli-bin', 'ver': '1.1.0', 'loc': '', 'files': [], 'err': '', 'installer': '', 'channel': 'conda-forge', 'score': 4, 'summary': '', 'pythonver': '', 'req': [], 'reqby': [], 'build': 'h2466b09_2'}, {'name': 'msgpack', 'ver': '1.1.0', 'loc': 'C:\\Python3\\envs\\prod\\Lib\\site-packages\\msgpack', 'files': [], 'err': '', 'installer': '', 'channel': '', 'score': 208, 'summary': 'MessagePack serializer', 'pythonver': '>=3.8', 'req': [], 'reqby': [], 'build': ''}, {'name': 'PyQt5', 'ver': '5.15.9', 'loc': 'C:\\Python3\\envs\\prod\\Lib\\site-packages\\PyQt5', 'files': [], 'err': '', 'installer': '', 'channel': '', 'score': 208, 'summary': 'Python bindings for the Qt cross platform application toolkit', 'pythonver': '>=3.7', 'req': [], 'reqby': [], 'build': ''}, {'name': 'pydotplus', 'ver': '2.0.2', 'loc': '', 'files': [], 'err': '', 'installer': '', 'channel': 'pypi', 'score': 68, 'summary': "Python interface to Graphviz's Dot language", 'pythonver': None, 'req': [], 'reqby': [], 'build': 'pypi_0'}, {'name': 'shiboken6', 'ver': '6.8.1 ', 'loc': 'C:\\Python3\\envs\\prod\\Lib\\site-packages\\shiboken6', 'files': [], 'err': '', 'installer': '', 'channel': '', 'score': 208, 'summary': None, 'pythonver': None, 'req': [], 'reqby': [], 'build': ''}, {'name': 'typing', 'ver': '3.7.4.3', 'loc': '', 'files': [], 'err': '', 'installer': '', 'channel': 'pypi', 'score': 196, 'summary': 'Type Hints for Python', 'pythonver': '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', 'req': [], 'reqby': [], 'build': 'pypi_0'}, |
C’est beaucoup mieux, même s’il nous manque des informations de localisation (clé 'loc') pour 3 paquetages installés via conda-forge ou pypi. C’est la ou l’on voit que les méthodes de résolution à base de recherche de fichiers sont robustes, si la signature est bonne, le répertoire d’installation ou les fichiers seraient détectés. J’ai ajouté quelques méthodes de ce type via un autre module pypkgs_files, mais elles ne sont exploitées que pour des paquetages ciblés via des macros (module pypkgs_macros).
5. Référentiel
Fonctions du module pypkgs.py pour la génération de listes de paquetages, en mode abrégé (septembre 2025).
| Fonction | Utilisation |
pypkgs_plist_pip |
Génère une liste de paquetages (liste de dictionnaires pdict compatibles avec les autres fonctions de pypkgs) en utilisant pip. |
pypkgs_plist_conda |
Identique à pypkgs_plist_pip mais en utilisant conda (argument exec='conda') ou mamba (exec='mamba'). |
pypkgs_plist_merge |
Fusionne les listes (pip_list, conda_list, mamba_list) générées par les trois méthodes précédentes et renvoie une nouvelle liste agrégée au même format. |
pypkgs_plist_compare |
Fusionne les listes (pip_list, conda_list, mamba_list) et renvoie trois listes (missing_conda, missing_mamba, missing_pip) de noms de paquetages manquants dans chaque méthode. Affiche le nombre de paquetages non résolus (verb=1), affiche les 3 listes missing_ (verb=2), affiche la table croisée de tous les paquetages (verb=3). |
6. Conclusion
Ces fonctions complètent bien le module pypkgs, si nous travaillons en mode liste, si nous n’avons pas besoin de trop de détails sur les paquetages, l’accélération peut être spectaculaire (et d’autant plus que la liste sera longue). Mais ce type d’outils n’est pas à lancer à chaque exécution d’un code, car si on veut être précis il faudrait combiner les 3 méthodes, et conda est lente (sauf si nous avons que mamba et pip sont capables de résoudre sans risques le jeu de paquetages à tester). Donc il faudra compléter pypkgs avec une interface CSVM, l’objectif est de sauver l’installation dans un fichier (par exemple après une installation ou une mise à jour) puis de pouvoir faire la recherche dans cette table.