Créer et déployer un package Python avec Poetry et Pypiserver
Chez Anybox nous sommes orientés majoritairement sur les solutions de développement utilisant le langage Python. Nous allons voir comment créer des paquets Python et les déployer chez nos clients sans avoir à les héberger sur PyPI.
Quel outil pour les packages Python ?
Une des difficultés principales dans la création d'un package Python, c’est la diversité des solutions proposées. Pour l’installeur, on peut choisir entre distribute
, setuptools
, distutils
ou Distutils2
, rien que ça. Ils ont tous été à un moment la façon recommandée de créer son paquet Python.
Avec la PEP-518 la Python Packaging Authority a proposé l'introduction d'un nouveau fichier standardisé nommé pyproject.toml
. Ce dernier permet de remplacer l'ensemble des fichiers setup.py
, requirements.txt
, setup.cfg
, MANIFEST.in
et Pipfile
auparavant nécessaires pour configurer son paquet. Nous allons nous pencher sur le fichier pyproject.toml
.
Poetry est un outil qui va nous permettre de gérer le cycle de vie de notre projet ainsi que nos différentes dépendances. Il va également gérer pour nous la création et la gestion du fichier pyproject.toml
facilement.
Installation de Poetry
Nous allons dans un premier temps installer Poetry
$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
Cela va installer l'outil dans le dossier $HOME/.poetry/bin/poetry
et l'ajouter à notre PATH
.
Création de notre paquet d'exemple
Notre programme d'exemple va aller récupérer les informations concernant les dernières offres d'emploi publiées sur le site de l'AFPY afin d'afficher les titres et les résumés de ces dernières.
Pour créer notre paquet nous allons utiliser la commande poetry new
afin qu'il nous génère le squelette de notre application.
$ poetry new test_lib
$ tree test_lib
├── README.rst
├── pyproject.toml
├── test_lib
│ └── __init__.py
└── tests
├── __init__.py
└── test_test_lib.py
Poetry
a généré l'arborescence de notre application en y ajoutant les tests unitaires et le fichier pyproject.toml
attendu.
[tool.poetry]
name = "test_lib"
version = "0.1.0"
description = ""
authors = ["TROUVERIE Joachim <bin@anybox.fr>"]
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
pytest = "^3.0"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
Nous allons modifier un peu ce fichier pour lui indiquer la licence de notre bibliothèque, la description du projet, une liste de mots-clés et un lien vers notre README. Je vous renvoie à la documentation de poetry pour plus de détails.
[tool.poetry]
name = "test_lib"
version = "0.1.0"
description = "A lib to get the last AFPY job offers"
authors = ["TROUVERIE Joachim <bin@anybox.fr>"]
license = "GPL-3.0+"
keywords = ["afpy", "job offers"]
readme = "README.rst"
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
pytest = "^3.0"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
Ajout des dépendances avec Poetry
Poetry
va nous permettre de gérer l'ensemble de nos dépendances de production et de développement grâce à sa sous-commande add
.
Si vous êtes familier avec les Pipfile
, poetry
utilise une façon analogue de gestion des dépendances.
Dans notre exemple, nous aurons besoin de la bibliothèque BeautilfulSoup afin de naviguer au sein du HTML de la page des offres d'emploi de l'AFPY.
$ poetry add bs4
Poetry
va télécharger puis mettre à jour notre environnement virtuel de développement et les dépendances de notre projet. Une nouvelle ligne a été ajoutée à notre fichier pyproject.toml
dans la section [tool.poetry.dependencies]
: bs4 = "^0.0.1
qui permettra à poetry
de gérer ces dépendances lors de la construction du paquet.
Le code
Nous allons maintenant passer au code de notre bibliothèque. Rien de bien compliqué ici, le code va juste récupérer l'ensemble des offres d'emploi de l'AFPY à l'adresse suivante : https://www.afpy.org/posts/emplois. Puis nous effectuerons un traitement sur la page afin de pouvoir afficher ces informations dans un terminal.
Pour cela nous allons ajouter un fichier jobs.py
dans notre dossier test_lib
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from bs4 import BeautifulSoup, NavigableString
from urllib import request
AFPY_URL = "https://www.afpy.org/posts/emplois"
def get_last_job_offers():
"""Get last job offers from AFPY website and print it in
Terminal
"""
content = request.urlopen(AFPY_URL).read()
html_content = BeautifulSoup(content)
# each job offer is in an article
for article in html_content.find_all("article"):
# job title is in a h2 tag
title = article.find_next("h2").text
# get article inner text
inner_text = [
element.strip() for element in article
if isinstance(element, NavigableString)
and element
]
print(title)
print("-" * len(title))
print("\n".join(inner_text))
La création du paquet
Le code est loin d'être complet, il lui manque notamment les tests unitaires. Il a pour but de vous montrer à quel point il est simple d'ajouter de nouvelles dépendances à votre projet. Une fois tout cela effectué, il ne vous reste qu'à construire votre paquet. Et là encore, poetry
est là pour vous mâcher le travail.
En effet alors qu'auparavant il vous fallait ajouter wheel
à vos dépendances puis lancer un
$ python setup.py sdist
$ python setup.py bdist_wheel
Poetry
se charge de tout ça pour vous avec un simple
$ poetry build
Building test_lib (0.1.0)
- Building sdist
- Built test_lib-0.1.0.tar.gz
- Building wheel
- Built test_lib-0.1.0-py3-none-any.whl
Créer un dépôt à la PyPI
PyPI est le dépôt officiel des packets Python. Il vous permet de rechercher, d'installer et de partager vos paquets Python depuis son interface ou via la commande pip
. Si, comme dans cet exemple vous souhaitez pouvoir déployer vos propres paquets sans avoir à les publier sur le dépôt officiel, il vous est possible de monter votre propre serveur de dépôts.
Pour mettre en place ce dernier nous allons nous appuyer sur le paquet pypiserver. Ce dernier, déployé à l'aide un docker-compose
, créera une arborescence pour rendre disponibles nos paquets Python à l'installation.
Nous nous baserons sur le docker-compose
d'exemple présent dans le dépôt github du projet pour monter notre instance.
version: '3'
services:
pypi:
image: pypiserver/pypiserver:latest
volumes:
- ./data:/data/packages/
- ./auth/:/data/auth/
command: -P /data/auth/.htpasswd -a update,download,list /data/packages
ports:
- 8080:8080
Créons les volumes nécessaires
data
qui contiendra l'ensemble de nos paquets présents sur le serveurauth
qui gérera l'authentification des utilisateurs sur notre dépôt et restreindra à ces derniers le déploiement et l'installation des paquets via un fichierhtpasswd
$ mkdir data auth
$ htpasswd -sc auth/.htpasswd pypi
Après un docker-compose up -d
notre serveur est disponible à l'adresse suivante http://localhost:8080
.
Déployer notre paquet
Il est temps de déployer notre paquet sur notre instance pypiserver. La commande historique python setup.py upload
sera remplacée ici par l'utilitaire poetry publish
qui se chargera pour nous de gérer l'authentification avec notre dépôt. Poetry permet de déployer nos paquets sur PyPI ou sur un dépôt privé en lui spécifiant une clé de configuration pointant vers l'adresse de notre dépôt.
$ poetry config repositories.local http://localhost:8080/
Une fois paramétré il ne nous reste plus qu'à téléverser notre paquet grâce à la commande publish
.
$ poetry publish --repository local \
--username <username_defini_dans_htpasswd> \
--password <password_defini_dans_htpasswd>
- Uploading test-0.1.0-py3-none-any.whl 100%
- Uploading test-0.1.0.tar.gz 100%
En vous rendant à l'adresse suivante http://localhost:8080/packages
, vous allez pouvoir constater que notre bibliothèque a bien été prise en charge sur notre dépôt.
C'est fini, nous pouvons maintenant installer notre paquet grâce à pip
$ pip install --extra-index-url http://localhost:8080/ test_lib
Le mot de la fin
Vous avez pu voir dans cet article comment créer votre projet, de la conception au déploiement via l’utilitaire Poetry
et comment gérer votre propre dépôt via pypiserver
.
Concernant l’utilisation du fichier pyproject.toml
je vous invite à lire avec attention la PEP-518 qui indique le choix de la Pypa de créer un nouveau type de fichier pour gérer nos paquets Python (et ainsi se passer de notre valeureux setup.cfg
). Ce choix n’est pas franchement au goût de tout le monde comme par exemple : https://news.ycombinator.com/item?id=18614654.
Certains auraient en effet préféré une uniformisation ainsi qu'une amélioration du fichier setup.cfg
qui était déjà là depuis tout de même 2016. Il est à noter que vous pouvez tout de même toujours l’utiliser pour construire vos paquets Python : https://docs.python.org/3.7/distutils/configfile.html.