Comment écrire, empaqueter et distribuer une bibliothèque en Python

Python est un excellent langage de programmation, mais l’emballage en est l’un des points faibles. C'est un fait bien connu dans la communauté. L’installation, l’importation, l’utilisation et la création de paquetages se sont beaucoup améliorées au fil des ans, mais ne sont pas à la hauteur des nouveaux langages tels que Go and Rust qui ont beaucoup appris des luttes de Python et d’autres langages matures.. 

Dans ce tutoriel, vous apprendrez tout ce que vous devez savoir sur l’écriture, le packaging et la distribution de vos propres packages.. 

Comment écrire une bibliothèque Python

Une bibliothèque Python est une collection cohérente de modules Python organisée en tant que paquet Python. En général, cela signifie que tous les modules résident dans le même répertoire et que ce répertoire se trouve sur le chemin de recherche Python.. 

Ecrivons rapidement un petit paquet Python 3 et illustrons tous ces concepts.

Le forfait pathologie

Python 3 possède un excellent objet Path, ce qui représente une amélioration considérable par rapport au module os.path, peu pratique, de Python 2. Mais il manque une possibilité cruciale: trouver le chemin du script actuel. Ceci est très important lorsque vous souhaitez localiser les fichiers d'accès par rapport au script actuel.. 

Dans de nombreux cas, le script peut être installé à n’importe quel emplacement. Vous ne pouvez donc pas utiliser de chemins absolus et le répertoire de travail peut être défini sur n’importe quelle valeur. Vous ne pouvez donc pas utiliser de chemin relatif. Si vous souhaitez accéder à un fichier dans un sous-répertoire ou un répertoire parent, vous devez pouvoir déterminer le répertoire de script actuel.. 

Voici comment vous le faites en Python:

import pathlib script_dir = pathlib.Path (__ fichier __). parent.resolve ()

Pour accéder à un fichier appelé 'fichier.txt' dans un sous-répertoire 'données' du répertoire du script actuel, vous pouvez utiliser le code suivant: print (open (str (rép_script / / data / fichier.txt '). read ())

Avec le paquet de pathologie, vous avez un intégré script_dir méthode, et vous l'utilisez comme ceci:

depuis pathology.Path import script_dir print (open (str (script_dir () / 'data / fichier.txt').) read ()) 

Oui, c'est une bouchée. Le paquet pathologie est très simple. Il dérive sa propre classe Path à partir de Pathlib et ajoute une valeur statique. script_dir () qui retourne toujours le chemin du script d'appel. 

Voici l'implémentation:

import pathlib import inspect class Path (type (pathlib.Path ())): @staticmethod def script_dir (): print (inspect.stack () [1] .filename) p = chemin_lib.Path (inspect.stack () [1 ] .filename) retourne p.parent.resolve () 

En raison de la mise en œuvre multi-plateforme de pathlib.Path, vous pouvez en dériver directement et doit provenir d'une sous-classe spécifique (PosixPath ou WindowsPath). La résolution du script dir utilise le module inspect pour trouver l'appelant, puis son attribut filename.

Test du package de pathologie

Chaque fois que vous écrivez quelque chose qui est plus qu'un script jetable, vous devriez le tester. Le module de pathologie ne fait pas exception. Voici les tests utilisant le framework de tests unitaires standard: 

import os importez shutil depuis unittest import TestCase from pathology.path import Path class PathTest (TestCase): def test_script_script (auto): attendu = os.path.abspath (os.path.dirname (__ fichier__)) actual = str (Path.script_dir ()) self.assertEqual (attendu, réel) def test_accès_fichier (self): rép_script = os.path.abspath (os.path.dirname (__ file__)) subdir = os.path.join (rép_script, 'test_data') si Path (subdir) .is_dir (): shutil.rmtree (subdir) os.makedirs (subdir) chemin_fichier = str (chemin (sousdir) / 'fichier.txt') content = '123' ouvert (chemin_fichier, 'w'). (content) test_path = Path.script_dir () / subdir / 'fichier.txt' actuel = ouvert (str (chemin_essai)). read () self.assertEqual (contenu, actuel) 

Le chemin de python

Les packages Python doivent être installés quelque part sur le chemin de recherche Python pour pouvoir être importés par les modules Python. Le chemin de recherche Python est une liste de répertoires et est toujours disponible dans sys.path. Voici mon sys.path actuel:

>>> print ('\ n'.join (sys.path)) /Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip /Users/gigi.sayfan/miniconda3/envs/py3/lib/ python3.6 /Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload /Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages / Users gigi.sayfan / miniconda3 / envs / py3 / lib / python3.6 / site-packages / setuptools-27.2.0-py3.6.egg 

Notez que la première ligne vide de la sortie représente le répertoire actuel. Vous pouvez donc importer des modules à partir du répertoire de travail actuel, quel qu’il soit. Vous pouvez directement ajouter ou supprimer des répertoires à / de sys.path. 

Vous pouvez également définir un PYTHONPATH variable d’environnement, et il existe quelques autres moyens de la contrôler. Le standard forfaits de sites est inclus par défaut, et c’est là que les paquets que vous installez en utilisant via pip go. 

Comment empaqueter une bibliothèque Python

Maintenant que nous avons notre code et nos tests, regroupons le tout dans une bibliothèque appropriée. Python fournit un moyen facile via le module de configuration. Vous créez un fichier nommé setup.py dans le répertoire racine de votre package. Ensuite, pour créer une distribution source, vous exécutez: python setup.py sdist

Pour créer une distribution binaire appelée roue, vous exécutez: python setup.py bdist_wheel

Voici le fichier setup.py du package pathology:

depuis la configuration d'importation de setuptools, la configuration de find_packages (name = 'pathology', version = "0.1", url = "https://github.com/the-gigi/pathology", license = "MIT", author = "Gigi Sayfan" , author_email = "[email protected]", description = "Ajoutez la méthode script_dir () statique à Path", packages = find_packages (exclude = ['tests']), long_description = open ('README.md'). read (), zip_safe = False)

Il inclut beaucoup de métadonnées en plus de l'élément 'packages' qui utilise le find_packages () fonction importée de setuptools trouver des sous-paquets.

Construisons une distribution source:

$ python setup.py sdist lance sdist lance egg_info créant pathology.egg-info écrit pathology.egg-info / PKG-INFO écrit des liens de dépendance dans pathology.egg-info / dependency_links.txt écrit des noms de premier niveau dans pathology.egg-info / top_level.txt écriture du fichier manifeste 'pathology.egg-info / SOURCES.txt' lecture du fichier manifeste 'pathology.egg-info / SOURCES.txt' écriture du fichier manifeste 'pathology.egg-info / SOURCES.txt' avertissement: sdist: standard fichier non trouvé: doit contenir l'un des fichiers README, README.rst, README.txt en cours d'exécution, vérifier créer pathologie-0.1 créer pathologie-0.1 / pathologie créer pathologie-0.1 / pathology.egg-info copier des fichiers dans pathology-0.1… -> pathologie-copie 0.1 pathologie / __ init__.py -> pathologie-0.1 / pathologie copie pathology / path.py -> pathologie-0.1 / copie pathologique pathology.egg-info / PKG-INFO -> pathologie-0.1 / pathology.egg -info copie de pathology.egg-info / SOURCES.txt -> pathology-0.1 / pathology.egg-info copie de pathology.egg-info / dependency_links.txt -> pathologie -0.1 / pathology.egg-info copier pathology.egg-info / not-zip-safe -> pathology-0.1 / pathology.egg-info copier pathology.egg-info / top_level.txt -> pathology-0.1 / pathology.egg -info Writing pathology-0.1 / setup.cfg creation dist Création d'un archive tar supprimant 'pathology-0.1' (et tout ce qu'il contient)

L'avertissement vient du fait que j'ai utilisé un fichier README.md non standard. Il est prudent d'ignorer. Le résultat est un fichier tar-gzip sous le répertoire dist:

$ ls -la dist total 8 drwxr-xr-x 3 gigi.sayfan gigi.sayfan 102 18 avril 21:20. drwxr-xr-x 12 gigi.sayfan gigi.sayfan 408 18 avril 21:20… -rw-r - r-- 1 gigi.sayfan gigi.sayfan 1223 18 avril 21:20 pathologie-0.1.tar.gz

Et voici une distribution binaire:

$ python setup.py bdist_wheel exécutant bdist_wheel exécutant build exécutant build_py création build construction / lib création build / lib / pathologie copie de pathologie / __ init__.py -> build / lib / pathologie copie de pathology / path.py -> build / lib / pathology installation pour construire / bdist.macosx-10.7-x86_64 / wheel en cours d’exécution install_lib créant build / bdist.macosx-10.7-x86_64 créant build / bdist.macosx-10.7-x86_64 / wheel créant build / bdist.macosx-10.7-x86_64 / copie de roue / pathologie build / lib / pathologie / __ init__.py -> build / bdist.macosx-10.7-x86_64 / roue / copie de pathologie build / lib / pathology / path.py -> build / bdist.macosx-10.7-x86_64 / wheel / pathology exécutant install_egg_info exécutant egg_info en écrivant pathology.egg-info / PKG-INFO en écrivant des liens de dépendance vers pathology.egg-info / dependency_links.txt en écrivant des noms de premier niveau dans pathology.egg-info / top_level.txt en lisant le fichier manifeste 'pathology. egg-info / SOURCES.txt 'écriture du fichier manifeste' pathology.egg-info / SOURCES.txt 'Copie pathology.egg-info vers bui ld / bdist.macosx-10.7-x86_64 / wheel / pathology-0.1-py3.6.egg-info en cours d'exécution install_scripts créant build / bdist.macosx-10.7-x86_64 / wheel / pathology-0.1.dist-info / WHEEL

Le paquet pathologie ne contient que des modules Python purs, ce qui permet de construire un paquet universel. Si votre paquet comprend des extensions C, vous devrez créer une roue distincte pour chaque plate-forme:

$ ls -la dist total 16 drwxr-xr-x 4 gigi.sayfan gigi.sayfan 136 avril 18 21:24. drwxr-xr-x 13 gigi.sayfan gigi.sayfan 442 18 avril 21:24… -rw-r - r-- 1 gigi.sayfan gigi.sayfan 2695 18 avril 21:24 pathologie-0.1-py3-aucune .whl -rw-r - r-- 1 gigi.sayfan gigi.sayfan 1223 18 avril 21:20 pathologie-0.1.tar.gz 

Pour approfondir le sujet de l’emballage des bibliothèques Python, consultez Comment écrire vos propres paquets Python.

Comment distribuer un paquet Python

Python dispose d'un référentiel de packages central appelé PyPI (Python Packages Index). Lorsque vous installez un paquet Python à l'aide de pip, le paquet sera téléchargé à partir de PyPI (à moins que vous ne spécifiiez un référentiel différent). Pour distribuer notre paquet de pathologie, nous devons le télécharger sur PyPI et fournir quelques métadonnées supplémentaires requises par PyPI. Les étapes sont les suivantes:

  • Créer un compte sur PyPI (une seule fois).
  • Enregistrez votre colis.
  • Téléchargez votre colis.

Créer un compte

Vous pouvez créer un compte sur le site Web PyPI. Puis créer un .pypirc déposer dans votre répertoire personnel:

[distutils] index-servers = pypi [pypi] référentiel = https://pypi.python.org/pypi nom_utilisateur = the_gigi 

À des fins de test, vous pouvez ajouter un serveur d’index "pypitest" à votre .pypirc fichier:

[distutils] index-servers = pypi pypitest [pypitest] référentiel = https://testpypi.python.org/pypi nom_utilisateur = the_gigi [pypi] référentiel = https://pypi.python.org/pypi nom_utilisateur = le_gigi

Enregistrez votre paquet

S'il s'agit de la première version de votre paquet, vous devez l'enregistrer auprès de PyPI. Utilisez la commande register de setup.py. Il vous demandera votre mot de passe. Notez que je le pointe vers le référentiel de test ici:

$ python setup.py register -r pypitest exécutant register en cours egg_info en écrivant pathology.egg-info / PKG-INFO en écrivant des liens de dépendance en pathology.egg-info / dependency_links.txt en écrivant les noms de niveau supérieur en pathology.egg-info / top_level.txt lecture du fichier manifeste 'pathology.egg-info / SOURCES.txt' écriture du fichier manifeste 'pathology.egg-info / SOURCES.txt' en cours de vérification Contrôle ): D'ACCORD

Téléchargez votre paquet

Maintenant que le paquet est enregistré, nous pouvons le télécharger. Je recommande d'utiliser de la ficelle, qui est plus sécurisée. Installez-le comme d'habitude en utilisant pip installer ficelle. Ensuite, téléchargez votre colis en utilisant une ficelle et indiquez votre mot de passe (rédigé ci-dessous):

$ twine upload -r pypitest -p  dist / * Téléchargement de distributions sur https://testpypi.python.org/pypi Téléchargement de pathology-0.1-py3-none-any.whl [=================== ===========] 5679/5679 - 00:00:02 Téléchargement de pathology-0.1.tar.gz [=================== =============] 4185/4185 - 00:00:01 

Pour approfondir la question de la distribution de vos packages, consultez Comment partager vos packages Python..

Conclusion

Dans ce didacticiel, nous avons suivi le processus complet d’écriture, de mise en forme et de distribution d’une bibliothèque Python, via PyPI. À ce stade, vous devriez avoir tous les outils pour écrire et partager vos bibliothèques avec le monde entier..

De plus, n'hésitez pas à voir ce que nous avons disponible à la vente et à étudier sur le marché, et s'il vous plaît posez vos questions et fournissez vos précieux commentaires en utilisant le flux ci-dessous.