Comment écrire vos propres paquets Python

Vue d'ensemble

Python est un merveilleux langage de programmation et bien plus encore. L'emballage est l'un de ses 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 améliorées au fil des ans, mais elles ne sont toujours pas à la hauteur des nouveaux langages tels que Go and Rust, qui pourraient tirer beaucoup des leçons de la lutte de Python et d’autres langages plus matures.. 

Dans ce tutoriel, vous apprendrez tout ce que vous devez savoir pour créer et partager vos propres packages. Pour des informations générales sur les packages Python, veuillez lire Comment utiliser les packages Python..

Emballer un projet

Le conditionnement d’un projet est le processus par lequel vous prenez un ensemble de modules Python et éventuellement d’autres fichiers cohérent, et les placez dans une structure facile à utiliser. Vous devez prendre en compte divers éléments, tels que les dépendances d'autres packages, la structure interne (sous-packages), la gestion des versions, le public cible et la forme du package (source et / ou binaire)..

Exemple

Commençons par un exemple rapide. Le package conman est un package permettant de gérer la configuration. Il supporte plusieurs formats de fichiers ainsi que la configuration distribuée en utilisant etcd.

Le contenu d'un paquet est généralement stocké dans un seul répertoire (bien qu'il soit courant de scinder des sous-paquets en plusieurs répertoires) et parfois, comme dans ce cas, dans son propre référentiel git.. 

Le répertoire racine contient divers fichiers de configuration (setup.py est le plus important), et le code du paquet lui-même est généralement dans un sous-répertoire dont le nom est le nom du paquet et idéalement un répertoire de tests. Voici à quoi ça ressemble pour "conman":

> arbre. ├── LICENCE ├── MANIFEST.in ├── README.md conman │ __init__.py │ __pycache__ ├── conman_base.py ├── conman_etcd.py conman_file.py ├── Requirements.txt Setup.cfg Setup.py test-Requirements.txt Tests ├── __pycache__ conman_etcd_test.py conman_file_test .py └── └── etcd_test_util.py └── tox.ini

Jetons un coup d'oeil rapide au setup.py fichier. Il importe deux fonctions du paquet setuptools: installer() et find_packages (). Puis il appelle le installer() fonction et utilise find_packages () pour l'un des paramètres.

depuis la configuration d'importation de setuptools, la configuration de find_packages (name = 'conman', version = "0.3", url = "https://github.com/the-gigi/conman", license = "MIT", author = "Gigi Sayfan" , author_email = "[email protected]", description = "Gérer les fichiers de configuration", packages = find_packages (exclude = ['tests']), long_description = open ('README.md'). read (), zip_safe = False, setup_requires = ['nose> = 1.0'], test_suite = "nose.collector") 

C'est plutôt normal. Tandis que le setup.py fichier est un fichier Python normal et vous pouvez y faire ce que vous voulez, son travail principal est d'appeler le installer() fonctionne avec les paramètres appropriés car il sera appelé de manière standard par divers outils lors de l’installation de votre paquet. Je vais passer en revue les détails dans la section suivante.

Les fichiers de configuration

En plus de setup.py, il existe quelques autres fichiers de configuration facultatifs pouvant apparaître ici et servir à diverses fins.

Setup.py

le installer() function utilise un grand nombre d’arguments nommés pour contrôler de nombreux aspects de l’installation du paquetage ainsi que l’exécution de diverses commandes. De nombreux arguments spécifient les métadonnées utilisées pour la recherche et le filtrage lors du téléchargement de votre package dans un référentiel..

  • name: le nom de votre paquet (et comment il sera listé sur PYPI)
  • version: c'est essentiel pour maintenir une gestion correcte des dépendances
  • url: l'URL de votre paquet, généralement GitHub ou peut-être l'URL readthedocs
  • packages: liste des sous-packages à inclure; find_packages () aide ici
  • setup_requires: vous spécifiez ici les dépendances
  • test_suite: quel outil utiliser au moment du test

le longue description est défini ici pour le contenu de la LISEZMOI.md fichier, qui est une meilleure pratique d'avoir une seule source de vérité.

Setup.cfg

Le fichier setup.py sert également une interface de ligne de commande pour exécuter diverses commandes. Par exemple, pour exécuter les tests unitaires, vous pouvez taper: test setup.py de python

test en cours en cours d'exécution egg_info en train d'écrire conman.egg-info / PKG-INFO en écrivant les noms de premier niveau dans conman.egg-info / top_level.txt en écrivant des liens de dépendance dans conman.egg-info / dependency_links.txt en lisant le fichier manifeste 'conman.egg-info /SOURCES.T.Le manifeste de manifeste ) ... test_initialization ok (conman_etcd_test.ConManEtcdTest) ... test_refresh ok (conman_etcd_test.ConManEtcdTest) ... test_add_config_file_from_env_var ok (conman_file_test.ConmanFileTest) ... test_add_config_file_simple_guess_file_type ok (conman_file_test.ConmanFileTest) ... test_add_config_file_simple_unknown_wrong_file_type ok (conman_file_test.ConmanFileTest) ... test_add_config_file_simple_with_file_type ok (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_w rong_file_type (conman_file_test.ConmanFileTest) ... test_add_config_file_with_base_dir ok (conman_file_test.ConmanFileTest) ... test_dictionary_access ok (conman_file_test.ConmanFileTest) ... test_guess_file_type ok (conman_file_test.ConmanFileTest) ... test_init_no_files ok (conman_file_test.ConmanFileTest) ... test_init_some_bad_files ok (conman_file_test.ConmanFileTest) ... ok test_init_some_good_files ( conman_file_test.ConmanFileTest)… ok ---------------------------------------------------- -------------------------- Ran 16 tests in 0.160s OK 

Setup.cfg est un fichier au format INI pouvant contenir les options par défaut pour les commandes passées. setup.py. Ici, setup.cfg contient quelques options pour nosetests (notre coureur de test):

[nosetests] verbose = 1 nocapture = 1 

MANIFEST.in

Ce fichier contient des fichiers qui ne font pas partie du répertoire du paquet interne, mais que vous souhaitez toujours inclure. Ce sont généralement les readme fichier, le fichier de licence et similaire. Un fichier important est le conditions.txt. Pip utilise ce fichier pour installer les autres packages requis.

Voici conman MANIFEST.in fichier:

include LICENSE include README.md include requirements.txt

Les dépendances

Vous pouvez spécifier des dépendances à la fois dans install_requires section de setup.py et dans un conditions.txt fichier. Pip installera automatiquement les dépendances à partir de install_requires, mais pas du conditions.txt fichier. Pour installer ces exigences, vous devrez le spécifier explicitement lors de l'exécution de pip: pip install -r Requirements.txt.

le install_requires Cette option est conçue pour spécifier des exigences minimales et plus abstraites au niveau de la version principale. Le fichier exigences.txt est destiné à des exigences plus concrètes, souvent avec des versions mineures épinglées.

Voici le fichier de configuration requis de conman. Vous pouvez voir que toutes les versions sont épinglées, ce qui signifie que cela peut avoir un impact négatif si l’un de ces paquetages se met à jour et introduit une modification qui casse conman..

PyYAML == 3.11 python-etcd == 0.4.3 urllib3 == 1.7 pyOpenSSL == 0.15.1 psutil == 4.0.0 six == 1.7.3

Épingler vous donne prévisibilité et tranquillité d'esprit. Ceci est particulièrement important si de nombreuses personnes installent votre paquet à des moments différents. Sans épingler, chaque personne obtiendra un mélange différent de versions de dépendance en fonction de la date d'installation. L’inconvénient de l’épinglage est que, si vous ne suivez pas le développement de vos dépendances, vous risquez de rester bloqué sur une version ancienne, peu performante et même vulnérable de certaines dépendances..

À l'origine, j'ai écrit conman en 2014 et n'y ai pas prêté beaucoup d'attention. Maintenant, pour ce tutoriel, j'ai tout mis à jour et des améliorations majeures ont été apportées dans presque tous les domaines de dépendance..

Les distributions

Vous pouvez créer une distribution source ou une distribution binaire. Je couvrirai les deux.

Distribution à la source

Vous créez une distribution source avec la commande: python setup.py sdist. Voici la sortie pour conman:

> python setup.py sdist lance sdist lance egg_info en écrivant conman.egg-info / PKG-INFO en écrivant les noms de premier niveau dans conman.egg-info / top_level.txt en écrivant des liens de dépendance vers conman.egg-info / dependency_links.txt 'conman.egg-info / SOURCES.txt' modèle de manifeste de lecture 'MANIFEST.in' écrivant le fichier manifeste 'conman.egg-info / SOURCES.txt' avertissement: sdist: fichier standard non trouvé: doit avoir l'un des fichiers README, README. Tout d'abord, README.txt lance la vérification créant conman-0.3 créant conman-0.3 / conman créant conman-0.3 / conman.egg-info faisant des liens durs dans conman-0.3… liant dur LICENCE -> conman-0.3 dur liant MANIFEST.in -> conman-0.3 liens durs README.md -> conman-0.3 liens durs exigences.txt -> conman-0.3 liens durs setup.cfg -> conman-0.3 liens durs setup.py -> conman-0.3 liens durs conman / __ init__.py -> conman-0.3 / conman liant dur conman / conman_base.py -> conman-0.3 / conman dur liant conman / conman_etcd.py -> conman-0.3 / conman dur liant conman / conman_fil e.py -> conman-0.3 / conman liens durs conman.egg-info / PKG-INFO -> conman-0.3 / conman.egg-info liens durs conman.egg-info / SOURCES.txt -> conman-0.3 / conman .egg-info liaison difficile conman.egg-info / dependency_links.txt -> conman-0.3 / conman.egg-info liaison difficile conman.egg-info / not-zip-safe -> conman-0.3 / conman.egg-info lien difficile conman.egg-info / top_level.txt -> conman-0.3 / conman.egg-info copie setup.cfg -> conman-0.3 Ecriture conman-0.3 / setup.cfg creation dist Création de l'archive tar supprimant 'conman-0.3' (et tout dessous) 

Comme vous pouvez le constater, j'ai un avertissement concernant l'absence d'un fichier README avec l'un des préfixes standard, car j'aime Markdown et j'ai donc un fichier "README.md". À part cela, tous les fichiers source du paquet ont été inclus et les fichiers supplémentaires. Ensuite, un tas de métadonnées a été créé dans le conman.egg-info annuaire. Enfin, une archive tar compressée appelée conman-0.3.tar.gz est créé et mis dans un dist sous-répertoire.

L'installation de ce paquet nécessitera une étape de construction (même s'il s'agit de pur Python). Vous pouvez l'installer normalement à l'aide de pip, en passant simplement le chemin d'accès au paquet. Par exemple:

pip install dist / conman-0.3.tar.gz Traitement en cours ./dist/conman-0.3.tar.gz Installation des packages collectés: conman Exécution de setup.py install for conman… done Installation réussie de conman-0.3

Conman a été installé dans les packages de site et peut être importé comme n'importe quel autre package:

import conman conman .__ fichier__ '/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc'

roues

Les roues sont un moyen relativement nouveau de conditionner le code Python et éventuellement les extensions C. Ils remplacent le format d'œuf. Il existe plusieurs types de roues: les roues en python pur, les roues à plateforme et les roues universelles. Les roues python pures sont des paquets comme conman qui n’ont pas de code d’extension C. 

Les roues de la plate-forme ont un code d'extension C. Les roues universelles sont des roues Python pures compatibles avec Python 2 et Python 3 avec le même code de base (elles ne nécessitent même pas 2to3). Si vous avez un paquet Python pur et que vous voulez que votre paquet prenne en charge à la fois Python 2 et Python 3 (cela devient de plus en plus important), vous pouvez créer une construction universelle unique au lieu d'une roue pour Python 2 et d'une roue pour Python 3.. 

Si votre paquet a un code d'extension C, vous devez créer une roue de plate-forme pour chaque plate-forme. L’énorme avantage des molettes, en particulier pour les paquets avec des extensions C, est qu’il n’est pas nécessaire d’avoir un compilateur et des bibliothèques de support disponibles sur la machine cible. La roue contient déjà un paquet construit. Vous savez donc que sa construction ne manquera pas et que son installation est beaucoup plus rapide car il s’agit littéralement d’une copie. Les gens qui utilisent des bibliothèques scientifiques comme Numpy et Pandas peuvent vraiment l'apprécier, car l'installation de tels paquets prenait beaucoup de temps et pouvait échouer si une bibliothèque manquait ou si le compilateur n'était pas configuré correctement.

La commande pour construire des roues pures ou de la plate-forme est: python setup.py bdist_wheel.

Setuptools-le moteur qui fournit le installer() fonction détectera automatiquement si une roue pure ou plate-forme est nécessaire.

en cours d'exécution bdist_wheel en cours d'exécution construction en cours d'exécution build_py création construction création / lib création construction / lib / conman copie conman / __ init__.py -> construction / lib / conman copie conman / conman_base.py -> construction / lib / conman copie conman / conman_etcd.py -> build / lib / conman copier conman / conman_file.py -> build / lib / conman installer pour compiler / bdist.macosx-10.9-x86_64 / wheel exécuter l’installation en cours d’exécution install_lib créant build / bdist.macosx-10.9-x86_64 créant build / bdist.macosx-10.9-x86_64 / wheel créant build / bdist.macosx-10.9-x86_64 / wheel / conman copie build / lib / conman / __ init__.py -> build / bdist.macosx-10.9-x86_64 / roue / conman copie construit /lib/conman/conman_base.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman copie de build / lib / conman / conman_etcd.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman copie copiée /lib/conman/conman_file.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman exécutant install_egg_info en exécutant egg_info en créant conman.egg-info en écrivant conman.egg-info / PKG-INFO en écrivant un nom de premier niveau es à conman.egg-info / top_level.txt en écrivant dependency_links en conman.egg-info / dependency_links.txt en écrivant le fichier manifeste 'conman.egg-info / SOURCES.txt' en lisant le fichier manifeste 'conman.egg-info / SOURCES.txt 'lecture du modèle de manifeste' MANIFEST.in 'écriture du fichier de manifeste' conman.egg-info / SOURCES.txt 'Copie de conman.egg-info dans build / bdist.macosx-10.9-x86_64 / wheel / conman-0.3-py2.7. oeuf-info en cours d'exécution install_scripts créant build / bdist.macosx-10.9-x86_64 / wheel / conman-0.3.dist-info / WHEEL

Vérification du dist répertoire, vous pouvez voir qu’une roue pure Python a été créée:

ls -la dist dist / total 32 -rw-r - r-- 1 gigi de personnel 5.5K 29 févr. 07:57 conman-0.3-py2-none-any.whl -rw-r - r-- 1 gigi de personnel 4,4K 28 février 23:33 conman-0.3.tar.gz

Le nom "conman-0.3-py2-none-any.whl" a plusieurs composants: nom du paquet, version du paquet, version Python, version de la plate-forme et enfin l'extension "whl".

Pour construire des packages universels, il vous suffit d'ajouter --universel, un péché python setup.py bdist_wheel --universal.

La roue résultante est appelée "conman-0.3-py2.py3-none-any.whl".

Notez qu'il est de votre responsabilité de vous assurer que votre code fonctionne réellement sous Python 2 et Python 3 si vous créez un package universel..

Conclusion

Écrire vos propres paquets Python nécessite de manipuler de nombreux outils, de spécifier beaucoup de métadonnées et de bien réfléchir à vos dépendances et à votre public cible. Mais la récompense est grande. 

Si vous écrivez du code utile et le mettez en package correctement, les utilisateurs pourront l'installer facilement et en tirer profit.