Chaos de dépendance Python

Connaissez-vous l'histoire de l'emballage Python? Naviguez-vous dans des formats de paquets? Savez-vous que vous devrez démêler l'enchevêtrement des dépendances même s'il semble que ce soit un miracle - zéro dépendance? Je suis sûr qu'ils ne sont pas aussi familiers avec tout cela que l'auteur de la bibliothèque DepHell.



J'ai réussi à parler à Nikita Voronov , mieux connu sous le nom de Gram ou orsinium , et à lui poser des questions sur le sujet du futur rapport, les douleurs des mauvaises décisions de résolution des dépendances, DepHell, pip, le principe du premier match l'emporte, Guido, Pipfile, le développement incrémentiel Python et l'avenir de l'écosystème.

- Chez Moscow Python Conf ++, vous parlerez des dépendances et de tout ce qui est à côté d'eux. Pourquoi avez-vous choisi un tel sujet pour le rapport?

Parce que cette question passe par toute mon expérience avec Python. Quand j'ai fait mon premier paquet, écrit le premier code, j'ai pensé à comment aider d'autres personnes afin qu'elles puissent l'installer, et j'ai fait setup.py. Puis il a travaillé dans une entreprise, dans une autre, dans une troisième, la tâche a été compliquée et développée. Au début, il n'y avait qu'un fichier requirements.txt, puis j'ai réalisé que je devais corriger les dépendances, pip-tools, un fichier de verrouillage est apparu. Plus tard, nous avons eu Pipenv, puis Poetry.

Petit à petit, de plus en plus de problèmes s'ouvraient, je m'enfonçais de plus en plus dans ce chaos. En conséquence, j'ai commencé à implémenter DepHell , un projet de gestion des dépendances qui peut les résoudre et les lire dans un format différent. Alors que je travaillais avec toutes sortes de formats, j'en avais vu assez de l'intérieur et maintenant je sais combien est organisé à l'intérieur, mais chaque jour j'apprends quelque chose de nouveau. Par conséquent, je peux vous dire beaucoup de choses intéressantes sur la douleur et les mauvaises décisions.

- La douleur est toujours intéressante. Selon vous, quels sont les problèmes en ce moment dans cette partie de Python?

JS a un répertoire node_modules , et chaque dépendance a ses propres dépendances empilées à l'intérieur. En Python, ce n'est pas le cas. Par exemple, un package est installé dans le même environnement et tous les packages qui l'utilisent utilisent la même version de ce package. Pour ce faire, vous devez résoudre correctement les dépendances - choisissez la version de ce package qui satisfera généralement tous les packages de cet environnement. La tâche n'est pas anodine: les packages dépendent les uns des autres, tout est entrelacé et la résolution des dépendances est difficile. Il n'y a pratiquement pas de résolveurs en Python. Le résolveur intelligent est uniquement en Poésie et DepHell.

Tout cela est grandement compliqué par le fait que pypi.org ne fournit souvent pas d'informations sur les dépendances des packages, car ces informations doivent être spécifiées par le client, le serveur PyPI ne peut pas le comprendre par lui-même. Par conséquent, lorsque PyPI indique que le package n'a pas de dépendances, vous ne pouvez pas lui faire confiance. Vous devez télécharger l'intégralité de la version, décompresser et analyser les dépendances du package à partir de setup.py. C'est un long processus, donc le résolveur en Python ne peut pas être rapide.

Non seulement il y a peu de résolveurs en Python, mais ils sont également lents de par leur conception.

Dans mon rapport, je veux dire comment fonctionne la résolution DepHell: comment créer un graphique de dépendance, à quoi ressemble ce graphique, pourquoi la plupart des articles scientifiques se trouvent sur la façon de résoudre les dépendances et de travailler avec ce graphique. Bien sûr, il existe des livres blancs sur la façon dont tout cela devrait fonctionner. Les gens intelligents ont écrit des articles avec des algorithmes, mais le plus souvent, ils ne fonctionnent pas pour Python. Par conséquent, je vais décrire comment je travaille avec la résolution des dépendances dans la pratique dans DepHell.

- J'entends souvent des programmeurs qu'ils utilisent pip, et tout fonctionne bien pour eux. Que font-ils mal?

Ils ont de la chance, ils ne rencontrent pas de conflit de dépendances. Bien que le problème puisse survenir lorsque vous placez seulement deux packages dans un environnement propre. Récemment, il y a eu une version du package de couverture 5.0, et si vous spécifiez simplement les pip install pytest-cov coveralls , pip ira dans l'ordre et pour le premier package sélectionnera la dernière version de la couverture, à savoir 5.0. Le principe du premier match gagnant fonctionne dans pip, donc même si la version n'est pas compatible avec le deuxième package, elle sera déjà corrigée pour le premier package. Cette approche fonctionne souvent, mais pas toujours.

De plus, il y a une question avec les environnements reproductibles. Puisque pip met toujours la dernière version, les versions dans l'environnement local et en production peuvent différer. Pour résoudre ce problème, il est habituel de corriger les dépendances. Et lorsque les dépendances sont déjà corrigées, les versions spécifiques que pip doit installer sont indiquées, puis pip fonctionne déjà correctement. Pip n'a pas de résolveur, mais il le fait quand quelqu'un d'autre résout des dépendances pour lui, comme DepHell ou Poetry.

- Pourquoi ce sujet gagne-t-il maintenant en pertinence, comme vous le pensez? Pourquoi n’était-il rien arrivé avant, mais maintenant tout est parti, et même dans des directions différentes?

Premièrement, l'écosystème Python se développe. Il y a plus de packages, ils doivent être installés plus, et beaucoup plus de problèmes apparaissent. Deuxièmement, les problèmes de formats de fichiers existent depuis longtemps et sont discutés depuis longtemps.

Setup.py est généralement impossible à analyser, il ne peut être exécuté. Si nous voulons, par exemple, écrire un serveur dans Go pour distribuer rapidement des packages pour Python, nous ne pouvons pas simplement saisir et lire setup.py, car il s'agit d'un fichier exécutable. En conséquence, pour l'exécuter, vous avez besoin de Python et d'un environnement complet, et souvent aussi pour que l'ensemble du projet se trouve à proximité et que certaines dépendances spécifiques soient installées. Outre toutes ces difficultés, l'exécution de setup.py peut être dangereuse, car un autre code sera exécuté sur votre ordinateur. En fait, il est même effrayant d'exécuter du code sous l'utilisateur actuel, car si, par exemple, il reçoit ma clé SSH privée et l'envoie quelque part, ce sera une grande tragédie.

La deuxième option pour définir les dépendances, qui existe depuis longtemps et que tout le monde travaille avec elle, est requirements.txt. Il est également presque impossible d'analyser de la même manière. Pip peut, mais il le fait très, très difficile: les fonctions qui appellent les fonctions, les itérateurs, tout est mélangé. De plus, pip peut lire certaines de ses clés dans requirements.txt, par exemple, un index à télécharger peut être spécifié. Mais cela ne fonctionne pas avec toutes les clés.

Ainsi, pour analyser requirements.txt, vous devez utiliser pip ou une solution tierce. Toutes les solutions tierces sont essentiellement des fourchettes et utilisent une sorte d'hypothèses sur le fichier. Tous les fichiers exigeants que pip peut lire ne pourront pas lire ces fourches.

Pip lui-même n'est pas destiné à être utilisé comme bibliothèque. Il s'agit d'un outil exclusivement CLI qui ne peut être utilisé qu'à partir de la console. Tout le code source de pip est caché derrière _internal , et les développeurs disent directement: "Ne l'utilisez pas!". Et chaque version rompt la compatibilité descendante. Honnêtement, ils ne garantissent pas la compatibilité et peuvent changer quoi que ce soit à tout moment. Et c'est ce qui se passe - chaque fois qu'une nouvelle version est livrée avec pip, je l'apprends à partir d'un CI cassé dans DepHell.

- Et dans d'autres langues? Est-ce tout aussi mauvais là-bas, ou tous ces problèmes sont-ils résolus quelque part?

Guido van Rossum a récemment reçu le prix Dijkstra. J'ai assisté à ses conférences et je lui ai posé des questions sur les dépendances de Python. Guido a déclaré que la dépendance dans toutes les langues est le chaos, il essaie de ne pas entrer et fait confiance à la communauté pour résoudre ce problème.

Ainsi, en Python, le travail avec les dépendances est progressivement organisé par la communauté. De nouvelles solutions émergent. Une fois Distutils construit en Python, les gens ont réalisé qu'il y avait beaucoup de problèmes, Setuptools. easy_Install a ensuite été développé pour installer des packages, mais il a également rencontré des problèmes. Pour les résoudre, créé pip. Maintenant, pip a beaucoup de problèmes. Ses sources changent constamment, il n'y a pas d'architecture, il n'y a pas du tout d'interface.

La communauté essaie de trouver quelque chose. Par exemple, il y a eu une longue discussion sur le problème appelé exigences 2.0 sur la façon de rendre les exigences compréhensibles à la fois pour les personnes (voici la version, voici les marqueurs) et par programmation à partir d'autres langues.

Ils ont créé un Pipfile, mais comme pip est très déroutant, ils ne pouvaient pas y ajouter le support Pipfile.

Les développeurs veulent bien sûr le faire. Très probablement, un jour, ils le pourront, mais jusqu'à présent, pip ne peut pas prendre en charge Pipfile. Par conséquent, nous avons fait pipenv pour fonctionner avec Pipfile et l'environnement virtuel, certains autres wrappers avec l'environnement. Mais à Pipenv aussi, tout est mélangé et confus.

Pour les autres langues, j'aime la façon dont la gestion des dépendances est implémentée dans Go. Auparavant, il n'y avait pas de version, il y avait go get , dans lequel vous indiquiez à partir de quel référentiel quel package télécharger. Du point de vue d'un débutant, c'est pratique: il suffit d'écrire go get et le package est déjà dans le système. Et lorsque vous commencez à travailler avec Python, tout un tas de tout s'effondre: certaines versions, PyPI, pip, requirements.txt, setup.py, maintenant aussi Pipfile, Poetry, __pymodules__ , etc.

À mesure que Python évolue progressivement et avec l'aide de la communauté, l'héritage s'accumule dans l'écosystème. Go était juste go get , mais encore une fois, le problème s'est posé que les dépendances devaient être corrigées afin que, en particulier, l'environnement soit reproductible.

Un environnement jouable peut être créé à l'aide d'un conteneur Docker avec toutes les dépendances installées. Mais parfois, vous devez mettre à jour les dépendances individuelles. Par exemple, nous ne sommes peut-être pas prêts à tout mettre à jour, car le projet n'a pas suffisamment de tests pour prouver qu'après la mise à jour, tout fonctionne toujours. Mais une certaine dépendance peut devoir être mise à jour, car, disons, une vulnérabilité y a été trouvée. Pour ce faire, il vaut mieux ne pas avoir d'image docker, mais un fichier qui dit: "Installez une version spécifique d'un package spécifique".

Il n'y avait rien de tel dans Go, et la marchandisation est apparue: toutes les dépendances ont été prises et placées dans un seul répertoire. Il s'agit d'une sale solution, similaire à node_modules , qui dans Go est implémentée depuis un certain temps à l'aide de solutions tierces. En Python, cette approche est également utilisée, par exemple, pip a un répertoire de vendor . Lorsque vous installez pip, les dépendances ne sont pas créées, et vous pourriez penser que tout est très cool et qu'il n'y a aucune dépendance, mais en fait, elles sont toutes à l'intérieur du vendor .

Il y a environ un an, go.mod (modules Go) est apparu dans Go. Il s'agit d'un nouvel outil intégré, mais go get également pris en charge. Le projet contient deux fichiers:

  • l'une décrit les dépendances avec lesquelles le projet travaille directement;
  • l'autre est un fichier de verrouillage, qui décrit absolument toutes les dépendances et leurs versions spécifiques.

Ceci est une solution centralisée cool.

Ce qui est important, ils insistent sur le fait que certaines choses doivent avoir une certaine apparence. Par exemple, dans Go, la version doit être la version sémantique.

Python a également une spécification sur l'apparence de la version. Pour cela, il y a PEP 440. Mais, tout d'abord, la spécification est très compliquée: il y a non seulement trois composants de version (nombres), mais aussi la pré-version, la post-sortie et l'ère (lorsque la façon de versionner change). Deuxièmement, le PEP 440 n'a pas été accepté immédiatement, ils y sont également venus progressivement, par conséquent, la version héritée est prise en charge, ce qui signifie que tout peut être utilisé comme version - n'importe quelle ligne comme «Bonjour tout le monde!».

- Vous avez dit que la communauté développe la langue progressivement, donc il y a un grand nombre de solutions. Mais pourquoi ne pas se débarrasser de toutes ces ordures? Pourquoi ne pas jeter les Distutils, abandonner les anciens et inutiles que personne n'utilise, et plutôt introduire activement de nouvelles pratiques et de nouveaux outils?

Maintenir tout cela a du sens, afin que vous puissiez toujours installer les anciens packages. Il est impossible d'insister sur la nécessité de le faire, et non autrement, car la décision est prise par la communauté. Aucun des développeurs de Core Python ne vient dire: «Ça y est, nous faisons tout maintenant, et pas de clous.»

Go a tout ce dont vous avez besoin pour travailler avec les dépendances tout de suite. En Python, vous devez tout réinstaller de l'extérieur et vous devez toujours comprendre quoi exactement. Le plus souvent, pip suffit, mais maintenant d'autres options apparaissent.

Sur le site avec les recommandations de package officielles de la Python Packaging Authority, le groupe qui fabrique pip, pipenv, PyPI, il est écrit pour utiliser pipenv. Avec pipenv, c'est une autre histoire. Premièrement, sa résolution est médiocre. Deuxièmement, il n'y a pas eu de sortie depuis très longtemps et la communauté attend déjà que les créateurs admettent honnêtement que ce projet est mort. Le troisième problème avec pipenv est qu'il ne convient qu'aux projets, mais pas aux packages: vous pouvez spécifier les dépendances du projet dans pipenv, mais vous ne pouvez pas spécifier son nom, sa version et, par conséquent, le placer dans un package à télécharger sur PyPI. Il s'avère que suivre les recommandations de Python Packaging Authority et utiliser pipenv n'est toujours pas suffisant pour le comprendre.

La poésie essaie d'être révolutionnaire. Il ne génère essentiellement pas le fichier setup.py, ce qui serait utile pour la compatibilité descendante, car Poetry veut être le nouveau et le seul format pour tout. Il sait comment collecter les packages, et il a un fichier de verrouillage, qui est nécessaire pour les projets. Néanmoins, la poésie a beaucoup de choses étranges, de nombreuses fonctionnalités familières ne sont pas prises en charge.

- Selon vous, quel est l'avenir de l'écosystème en termes de travail avec les dépendances? Votre prédiction.

Tout va plus ou moins mieux. Par exemple, j'ai vu un poste vacant à pip, et un développeur qui le met en ordre se voit promettre beaucoup d'argent. Peut-être que pip deviendra une solution plus universelle. Mais vous avez besoin de quelqu'un pour le prendre au sérieux: venez dire que nous le faisons de cette façon, maintenant nous suivons un PEP plus strict et insisterons dessus (parce que le PEP n'est que des recommandations que personne en fait pas obligé de suivre).

Par exemple, nous avions une telle histoire: une certaine version de PyYAML était verrouillée dans le fichier de verrouillage. Un jour les tests sur CI passent, nous déployons en production, et tout y tombe, car la version PyYAML est introuvable. Le fait est que la version verrouillée a été supprimée de pypi.org. Tout le monde était indigné, a mis à jour le fichier de verrouillage, a survécu, mais les sédiments sont restés.

Il n'y a pas si longtemps, le PEP 592 est apparu; il a déjà été adopté et est maintenu dans le pip, dans lequel des rejets en yank sont apparus. Yank signifie que la version n'a pas encore été complètement supprimée de pypi.org - elle est cachée. En d'autres termes, si vous spécifiez que vous avez besoin, par exemple, d'une version PyYAML supérieure à 3.0, pip ignorera les versions copiées et installera la dernière version disponible. Mais si une version spécifique est indiquée dans le fichier de verrouillage, et que cette version est yank, pip l'installera quand même. Ainsi, verrouiller les fichiers et déployer ne se cassera pas, mais, si possible, l'ancienne version ne sera pas utilisée.

La deuxième chose intéressante est le PEP pour __pymodules__ . Ce sont des environnements virtuels légers: vous ouvrez le répertoire du projet, écrivez pip install PyYAML, et PyYAML n'est pas installé globalement, mais dans le répertoire __pymodules__ . Lorsque Python démarre dans ce répertoire, il importe PyYAML non pas globalement, mais à partir de ce répertoire.

J'appelle cela des environnements virtuels au minimum, car il y a moins d'isolement. Par exemple, il n'y a pas d'accès aux fichiers binaires. Lorsqu'un environnement virtuel avec pytest installé est activé, il peut être utilisé à partir de la console: il suffit d'écrire pytest et de faire quelque chose. Avec __pymodules__ seront disponibles pour l'importation, mais pas les binaires, car ils ne seront pas réellement installés.

Ce PEP est conçu pour le rendre facile pour les débutants. Pour qu'ils n'aient pas à gérer toutes les subtilités des environnements virtuels, mais installez simplement tout ce dont vous avez besoin dans __pymodules__ via pip install.

- Eh bien, l'avenir de vos prévisions est plus brillant que maintenant.

Oui, mais comme je l'ai dit, si personne ne vient et dit que nous refaisons et essayons de jeter l'héritage, alors les problèmes resteront. Maintenant, nous accumulons et accumulons des outils, et il sera impossible de s'en débarrasser complètement dans un avenir proche.

- Que pensez-vous, pourquoi aucun des développeurs ne peut mettre à jour les dépendances Presque partout - ni dans les entreprises ni dans l'open source - le processus de travail avec les versions de sécurité, en principe, avec de nouvelles versions mineures ou majeures, a été construit. Où voyez-vous les problèmes ici?

Au minimum, lorsque vous souhaitez mettre à jour des dépendances, il est effrayant de mettre à jour toutes les dépendances, car ce n'est pas un fait que même si les tests réussissent, tout fonctionnera. Par exemple, cette situation se produit souvent avec le céleri, car le céleri ne peut pas être entièrement testé dans les tests. Vous pouvez verrouiller quelque chose, simplifier quelque chose, mais le fait que les travailleurs s'exécutent ne peut pas être vérifié.

Go work with tests est bien implémenté, même dans les tutoriels Go Modules il est écrit comment mettre à jour les dépendances: vous mettez à jour certaines dépendances et exécutez des tests. De plus, les tests s'exécutent non seulement sur le vôtre, mais aussi sur cette dépendance.

Un aspect intéressant mérite encore d'être mentionné: les tests devraient-ils être dans des packages en Python? Lorsque vous téléchargez un package sur pypi.org, doit-il y avoir des tests? En théorie, ils devraient, et même avoir un mécanisme pour les exécuter: dans setup.py, vous pouvez spécifier comment exécuter les tests, quelles dépendances ils ont.

Mais, premièrement, de nombreuses personnes ne savent pas comment les exécuter et n'exécutent pas de tests dépendants. Par conséquent, ils ne sont souvent pas nécessaires. Deuxièmement, souvent ces tests ont des montages très difficiles, et donc inclure des tests dans un package signifie rendre le package 6 à 10 fois plus grand.

Ce serait formidable de pouvoir télécharger un package avec des tests et sans tests. Mais maintenant, il n'y a pas une telle possibilité, donc les tests ne s'additionnent souvent pas dans les packages. Il y a le chaos, et je ne sais même pas s'il est possible d'exécuter des tests de ces dépendances lors de la mise à jour des dépendances.

Cet aspect semble être largement ignoré. Mais dans certaines autres langues, en particulier, Go est considéré comme une bonne pratique, en mettant à jour un package dans l'environnement, exécutez immédiatement des tests pour s'assurer que ce package fonctionne correctement dans cet environnement.

- Pourquoi, à votre avis, en Python, les outils de versioning sémantique automatique ne sont pas populaires?

Je pense que l'un des problèmes est que la version peut être décrite à de nombreux endroits. Le plus souvent, il en existe trois: le format de description des métadonnées du projet (pypi.org, poety, setup.py, etc.), à l'intérieur du projet lui-même et dans la documentation. La mise à niveau d'une version à trois endroits n'est pas très difficile, mais facile à oublier.

DepHell a une équipe pour les mises à niveau de version. DepHell , , . semantic version, compatible version .. , , .

Flit. Flit — , . : init , build , publish install . , , PyPI — . Flit , . docstring . , .

DepHell Flit . description, , , .

, .

DepHell import , , , , . , , .

, Moscow Python Conf++ 27 . DepHell backend, web, , AI/ML, , DevOps, , IoT, infosec . , , Moscow Python Conf++.

Source: https://habr.com/ru/post/fr483920/


All Articles