Versionner un code Python

Françoise CONIL

Présentation

Quelles sont les différentes étapes et quels sont les choix possibles pour versionner son code Python ?

  • Structure d’un identifiant de version Python
  • Logiciel de gestion de version : git
  • Forges (GitHub, GitLab)
  • Librairies de gestion de l’identifiant de version (setuptools_scm, Versioneer, Miniver, …)

Structure d’un identifiant de version Python

Python package version

La structure d’un identifiant de version Python, spécifiée par la PEP 440 1, est la suivante 2 :

Les versions ne sont pas de simples chaînes de caractères, elles sont normalisées afin de pouvoir être comparées à l’aide de la classe Version

>>> from packaging.version import Version, parse
>>> v1 = parse("1.0a5")
>>> v2 = Version("v1.0")
>>> v1
<Version('1.0a5')>
>>> v2
<Version('1.0')>
>>> v1 < v2
True

Description des éléments de l’identifiant

  • un préfixe optionnel “v”, supprimé par la normalisation (v0.1=> 0.1)
  • un segment optionnel “epoch”, utilisé pour ordonner correctement les projets qui passent du Calendar versioning au Semantic versioning
    (1!1.0.0 > 2023.10)
  • un numéro de release obligatoire de type Semantic versioning (2.12.1) ou Calendar versioning (2023.04)
  • un segment optionnel “pre-release” pour désigner les versions “alpha”, “beta” et “release candidate”, normalisé aN, bN et rcN
  • un segment optionnel “post-release”, normalisé .postN
  • un segment optionnel “dev-release”, normalisé .devN
  • un segment “local”, qui a un usage spécifique et ne peut être envoyé sur postPyPI

Normalisation de l’identifiant 1/2

  • les identifiants sont toujours transformés en minuscules
  • il y a pas de limite de taille à un identifiant
>>> from packaging.version import Version, parse
>>> v_v = Version("v1.0.1")          # "v" prefix removed
>>> v_v
<Version('1.0.1')>
>>> v_cal = Version("2023.04")
>>> v_epoch = Version("1!1.0.0")     # "epoch" can be used to act on version ordering
>>> v_epoch > v_cal
True
>>> v_alpha = Version("1.0-alpha5")  # "pre-release" normalization
>>> v_alpha
<Version('1.0a5')>
>>> v_post = Version("1.0.0post0")   # "post-release" normalization
>>> v_post
<Version('1.0.0.post0')>
>>> v_dev = Version("1.0.0dev0")     # "dev" normalization
>>> v_dev
<Version('1.0.0.dev0')>

Normalisation de l’identifiant 2/2

  • zéros implicites : la comparaison de segments met à 0 les segments manquants
  • les entiers étant interprétés par int(), les zéros non significatifs sont supprimés
>>> v_1_0 = Version("1.0")           # zéros implicites
>>> v_1_0
<Version('1.0')>
>>> v_1_0.micro
0
>>> v_1_0.epoch
0
>>> v_1_0 == Version("0!1.0.0")
True
>>> Version("01.001.0000")           # zéros non significatifs
<Version('1.1.0')>
>>> Version("2023.04")
<Version('2023.4')>

SemVer : Semantic Versioning

Le Semantic Versioning / Gestion sémantique de version, très répandue, identifie les versions à l’aide de 3 nombres MAJOR.MINOR.PATCH

Étant donné un numéro de version MAJOR.MINOR.PATCH, il faut incrémenter :

  1. le numéro de version MAJOR quand il y a des changements non rétrocompatibles,
  2. le numéro de version MINOR quand il y a des ajouts de fonctionnalités rétrocompatibles,
  3. le numéro de version de PATCH quand il y a des corrections de bugs rétrocompatibles.

La syntaxe BNF 1 indique que l’on peut ajouter un identifiant de pre-release et un identifiant de build.

CalVer : Calendar Versioning

De façon similaire au Semantic Versioning, l’identifiant en Calendar Versioning peut comporter jusqu’à 4 parties MAJOR.MINOR.MICRO.MODIFIER

Certaines parties correspondent à des portions de dates définies ainsi :

  • YYYY - Full year - 2006, 2016, 2106
  • YY - Short year - 6, 16, 106
  • 0Y - Zero-padded year - 06, 16, 106
  • MM - Short month - 1, 2 … 11, 12
  • 0M - Zero-padded month - 01, 02 … 11, 12
  • WW - Short week (since start of year) - 1, 2, 33, 52
  • 0W - Zero-padded week - 01, 02, 33, 52
  • DD - Short day - 1, 2 … 30, 31
  • 0D - Zero-padded day - 01, 02 … 30, 31

Les logiciels sont libres de formatter leur identifiant à leur convenance.

CalVer : exemples

Applications
Project CalVer Format Examples
Ubuntu YY.0M 4.10 - 20.04
JetBrains PyCharm YYYY.MINOR.MICRO 2017.1.2
ArchLinux YYYY.0M.0D 2018.03.01
Standards
Project CalVer Format Examples
C++ YY 98, 03, 11, 14, 17
ECMAScript (aka JavaScript ) YYYY 2015, 2020
Libraries and Utilities
Project CalVer Format Examples
pytz YYYY.MM 2016.4
pip YY.MINOR.MICRO 19.2.3
youtube_dl YYYY.0M.0D.MICRO 2016.06.19.1

Logiciel de gestion de version : git

Logiciel de gestion de version

Un logiciel de gestion de versions (Version Control System) est un logiciel qui permet de stocker un ensemble de fichiers en conservant la chronologie de toutes les modifications qui y ont été apportées.

git est le gestionnaire de version décentralisé le plus répandu actuellement avec l’utilisation de forges logicielles internes (GitLab CNRS, GitLab INRIA, …) ou externes à nos structures (GitHub, GitLab, Framagit…).

Ces forges permettent de travailler collaborativement sur un projet.

Pour préparer la diffusion une version d’un logiciel, on va commencer par étiqueter (tag) un point de l’historique avec un identifiant de version.

git tag

git tag permet de créer 2 types de tags :

  • les annotated tags sont stockés en tant qu’objets dans la base de données git. Ils ont une somme de contrôle et contiennent le nom de l’annotateur, son mail, la date du tag et peuvent être signés et vérifiés par GPG

  • les lightweight tags qui sont juste un pointeur sur un commit particulier

Il est recommandé de créer des annotated tag pour un identifiant de version, ce qui se fait via l’option -a de la commande “git tag” :
git tag -a <identifiant> -m <message>

$ git tag -a 0.1.0 -m "Nice greeting script"
$ git show -s 0.1.0
tag 0.1.0
Tagger: Françoise Conil <francoise.conil@insa-lyon.fr>
Date:   Wed Oct 18 15:51:06 2023 +0200

Nice greeting script

commit 419bfb10b5051259ac9702ac5af244461c11385e (HEAD -> main, tag: 0.1.0)
Author: Françoise Conil <francoise.conil@insa-lyon.fr>
Date:   Wed Oct 18 15:50:44 2023 +0200

    Polite greetings

Lorsque l’on crée un lightweight tag, on place juste une étiquette sur un commit. Ils sont à réserver à un usage temporaire / local.

git tag crée un lightweight tag si les options -a, -m, -s sont absentes :

$ git tag 0.1.1

$ git show -s 0.1.1
commit 6a269e0f488597524b1a86db89f8a688316f8b11 (HEAD -> main, tag: 0.1.1)
Author: Françoise Conil <francoise.conil@insa-lyon.fr>
Date:   Wed Oct 18 15:52:21 2023 +0200

    add a date

Il n’y a alors pas d’objet tag git créé avec les métadonnées de tag.

$ git cat-file -t 0.1.0     # annotated tag
tag

$ git cat-file -t 0.1.1     # lightweight tag
commit

ATTENTION

Il faut explicitement envoyer les annotated tag vers le remote avec l’option --follow-tags :

$ git push origin --follow-tags

en supposant que remote=origin

git describe

La commande git describe détecte le tag annoté le plus récent accessible depuis le commit courant.

$ git log --oneline -n 20
5fe6b80 (HEAD -> master, origin/master, origin/HEAD) docs(changelog): Add entry for previous commit
4f37c30 build: Update classifiers
8e65de4 ci: Test on Python 3.12
002d6a7 docs(csvgrep): Simplify xargs command with $0. Use 22 not 222.
c4e5612 docs(csvgrep): Use variable ($1) instead of replstr to avoid quoting issues
4e0b890 docs(csvgrep): Quote the replstr using single quotes
9d06655 docs(csvgrep): Quote the replstr
66e7086 docs: Document how to get the indices of the columns that contain matching text, #1209
1202688 (tag: 1.2.0) build: Iterate the version number
82233b8 test: Try engine.dispose() to release file handle for SQLite database in Windows tests
034a92c ci: Attempt to expose OSError on Windows

La commande git describe affiche :

  • le tag trouvé : 1.2.0
  • le nombre de commits depuis ce tag : 8
  • l’identifiant du commit courant : 5fe6b80
$ git describe 
1.2.0-8-g5fe6b80

La commande git describe --dirty indique que l’état de la copie de travail est -dirty lorsque des fichiers suivis ont été modifiés ou quand des fichiers ont été indexés (staged) et non enregistrés (commités) :

$ touch hello.py
$ git add hello.py

$ git describe
1.2.0-8-g5fe6b80

$ git describe --dirty
1.2.0-8-g5fe6b80-dirty

Forges

Avec les tags sur GitHub

Si des tag ont été positionnés dans l’historique du code et envoyés sur GitHub, on peut consulter la liste des versions et récupérer un zip ou un tar.gz du projet, pour chaque tag, sans action supplémentaire.

Créer une release sur GitHub 1/2

  1. Dans GitHub.com, accédez à la page principale du dépôt
  2. À droite de la liste des fichiers, cliquez sur Releases
  3. Sur la page affichée, cliquez sur Create a new Release
  4. Sélectionnez un tag via le menu déroulant Choose a tag
    • pour utiliser un tag existant, cliquez sur le tag
    • pour créer un tag, saisissez un identifiant de version et cliquez sur Create new tag
  5. Si vous avez créé un tag, sélectionner la branche qui contient le code que vous voulez “releaser” dans le menu déroulant Target
  6. Optionnellement, sélectionnez le tag de la release précédente dans le menu déroulant Previous tag au dessus du champ “Description”
  7. Saisissez un titre pour la release dans le champ de saisie Release title

Créer une release sur GitHub 2/2

  1. Vous pouvez lister les contributeurs en les déclarant dans la description de la release via @mention (vous permet de sélectionner une personne dans la liste des membres du projet). Alternativement, vous pouvez cliquer sur le bouton Generate release notes pour générer le contenu (diff entre ce tag et le précédent + pull request ?)
  2. Vous pouvez ajouter des fichiers binaires par drag and drop dans la zone sous la description
  3. Vous pouvez indiquer qu’il s’agit d’une pre-release en cochant Set as a pre-release
  4. PAS VU : Optionnellement, cliquez sur Set as latest release
  5. Optionnellement, créez une discussion pour cette release dans GitHub discussion s’il est activé
  6. Publiez la release avec Publish release ou enregistrer-la comme draft avec Save draft

GitHub crée des lightweight tags

ATTENTION

Il semble que les tags créés par GitHub soient des lightweight tags et non des annotated tags.

Cela peut perturber certains outils utilisés pour le packaging comme dans l’issue 521 de setuptools_scm.

Avec les tags sur GitLab

Si des tag ont été positionnés dans l’historique du code et envoyés sur GitLab, on peut consulter la liste des versions et récupérer un zip, un tar.gz, un tar.bz2 ou un tar du projet, pour chaque tag, sans action supplémentaire.

Créer une release sur GitLab

  1. Dans votre forge GitLab, allez à la page principale du dépôt
  2. Dans le menu de gauche, sélectionnez Deploy > Release et cliquez sur Create a new release
  3. Via le menu déroulant Tag name
    • cliquez sur un tag existant
    • saississez un nouveau tag, et cliquez sur Create tag
  4. De façon optionnelle, saisissez
    • un Titre, une Milestone, des Releases notes, l’inclusion du message du tag annoté
    • des “assets” complémentaires, par défaut les zip, un tar.gz, un tar.bz2, tar générés par GitLab, exemples release Graphviz
    • des liens vers d’autres ressources (documentation, binaires, …)
  5. Cliquez sur Create release

Packaging

Quelques informations, très simplifiées, pour aborder le fichier pyproject.toml

Python Packaging Authority

The Python Packaging Authority (PyPA) is a working group that maintains a core set of software projects used in Python packaging.

The PyPA publishes the Python Packaging User Guide, which is the authoritative resource on how to package, publish, and install Python projects using current tools.

Parmis ces documentations, The Packaging Flow 1 spécifie les principales étapes du packaging.

Principales étapes du packaging

  • Avoir le code source du package, typiquement le checkout d’un tag du code issu d’un gestionnaire de version
  • Créer un fichier de configuration pour décrire les métadonnées du package (nom, version, etc) et pour spécifier la manière de générer le package. Il s’agit généralemement du fichier pyproject.toml 1
  • Exécuter l’outil de build pour produire une source distribution (sdist) et une ou plusieurs built distribution (wheel) à partir du fichier de configuration. Souvent, il n’y a qu’un wheel pour un package pur Python
  • Mettre en ligne les éléments produits sur un service de distribution de package (généralement PyPI).

Historiquement

Le packaging a longtemps a reposé sur setuptools et un script setup.py. Cela posait plusieurs problèmes dont la nécessité d’exécuter ce script Python pour déterminer comment installer un package, connaître ses dépendances, etc 1

L’idée d’utiliser un format déclaratif pour la création des métadonnées de package a été d’abord expérimentée via une solution intégrée à setuptools : setup.cfg 2.

Cependant, il était également nécessaire de décorréler l’outil d’installation pip de l’exécution de setuptools avec setup.py.

Les PEP 517 et PEP 518 ont défini les notions de build frontend (pip, build) et de build backend (setuptools, Flit, Hatch, …) qui doivent être spécifiées dans le fichier pyproject.toml.

Ce découplage frontend / backend a permis de faire évoluer pip et setuptools et de faire apparaître de nouveaux outils de build.

Build frontend

L’idée n’est pas de proposer un nouveau format de métadonnées mais de définir, dans ce fichier texte pyproject.toml, les informations nécessaires pour que le build frontend puisse créer un environnement de génération du package et invoquer le build backend spécifié.

[build-system]
requires = [
  "cffi; implementation_name == 'pypy'",
  "cython>=3.0.0; implementation_name == 'cpython'",
  "packaging",
  "setuptools>=61",
  "setuptools_scm[toml]",
]
build-backend = "setuptools.build_meta"

[project]
name = "pyzmq"

Via la section [build-system], le build frontend (pip, build) sait qu’il a besoin d’un “environnement” Python avec cffi, packaging, … et une version minimale du build backend setuptool supérieure à 61.

Il sait aussi qu’il doit appeler setuptool comme build backend pour générer le package pyzmq.

Build backends

Il devient alors possible d’utiliser d’autres build backend que setuptools pour construire des packages. 1

L’extrait du pyproject.toml du projet structlog ci-dessous indique que le build backend est hatchling pour générer le package.

Il indique également que les packages hatch-vcs et hatch-fancy-pypi-readme (en version supérieure à 22.8.0) sont nécessaires pour le build du package structlog.

[build-system]
requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme>=22.8.0"]
build-backend = "hatchling.build"

[project]
dynamic = ["readme", "version"]
name = "structlog"

Génération du package par le frontend build

Le frontend build crée un environnement “isolé” où il installe les requires spécifiés dans build-system pour générer les packages source sdist et binaire wheel du projet structlog.

$ git clone https://github.com/hynek/structlog.git
$ cd structlog
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install --upgrade pip build
$ python -m build 
* Creating venv isolated environment...
* Installing packages in isolated environment... (hatch-fancy-pypi-readme>=22.8.0, hatch-vcs, hatchling)
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating venv isolated environment...
* Installing packages in isolated environment... (hatch-fancy-pypi-readme>=22.8.0, hatch-vcs, hatchling)
* Getting build dependencies for wheel...
* Building wheel...
/tmp/build-env-br9rr4sl/lib/python3.10/site-packages/setuptools_scm/git.py:308: UserWarning: git archive did not support describe output
  warnings.warn("git archive did not support describe output")
/tmp/build-env-br9rr4sl/lib/python3.10/site-packages/setuptools_scm/git.py:327: UserWarning: unprocessed git archival found (no export subst applied)
  warnings.warn("unprocessed git archival found (no export subst applied)")
Successfully built structlog-23.2.1.dev34.tar.gz and structlog-23.2.1.dev34-py3-none-any.whl
$ ls dist/
structlog-23.2.1.dev34-py3-none-any.whl  structlog-23.2.1.dev34.tar.gz

Génération du package par le frontend pip

$ git clone https://github.com/hynek/structlog.git
$ cd structlog
$ git describe
23.2.0-34-g8d3eeb1
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install --upgrade pip
$ python -m pip wheel --wheel-dir=dist .
Processing /home/fconil/LogicielsSrc/structlog
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: structlog
  Building wheel for structlog (pyproject.toml) ... done
  Created wheel for structlog: filename=structlog-23.2.1.dev34-py3-none-any.whl size=63292 sha256=f0afd834471686ece125bc56332b47b77eeda85377ca79cc378dd36f19159d4f
  Stored in directory: /tmp/pip-ephem-wheel-cache-iru73n78/wheels/d4/a0/d1/88d4397a5f4751562af152ee044e264ac0fb9f7d6be1c3002d
Successfully built structlog
$ ls dist
structlog-23.2.1.dev34-py3-none-any.whl

Installation

$ python -m pip install dist/structlog-23.2.1.dev34-py3-none-any.whl
Processing ./dist/structlog-23.2.1.dev34-py3-none-any.whl
Installing collected packages: structlog
Successfully installed structlog-23.2.1.dev34
$ pip list
Package    Version
---------- ------------
pip        23.3.1
setuptools 59.6.0
structlog  23.2.1.dev34

setuptools fait toujours partie des backends de build !

L’article Why you shouldn’t invoke setup.py directly précise que setuptools est toujours maintenu et fait partie des backend utilisables (19 oct 2021).

C’est l’utilisation de python setup.py <commande> qui fait l’objet de DeprecationWarning 1 2

$ python setup.py install
running install
/tmp/test-setuptools/.venv/lib/python3.10/site-packages/setuptools/_distutils/cmd.py:66: SetuptoolsDeprecationWarning: setup.py install is deprecated.
!!

        ********************************************************************************
        Please avoid running ``setup.py`` directly.
        Instead, use pypa/build, pypa/installer or other
        standards-based tools.

        See https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html for details.
        ********************************************************************************

pyproject.toml - exemple minimal 1/3

Le fichier pyproject.toml est un fichier texte, au format TOML, dont voici une version minimale :

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "example"
version = "0.1.0"

pour configurer le packaging du projet basique :

├── pyproject.toml
└── src
    └── example
        ├── __init__.py
        └── hello.py

pyproject.toml - Spécifications 2/3

Consulter

  • PEP 518 Specifying Minimum Build System Requirements for Python Projects

  • Declaring project metadata (PyPA) qui regroupe et remplace :

    • PEP 621 Storing project metadata in pyproject.toml
    • PEP 631 Dependency specification in pyproject.toml based on PEP 508 qui a été intégrée à la PEP 621
  • PEP 508 Dependency specification for Python Software Packages

pyproject.toml - métadonnées dynamiques 3/3

Les métadonnées dynamiques sont les métadonnées qu’un backend de build va renseigner à l’exécution.

La liste des métadonnées qui peuvent être dynamiques dépend du backend ainsi que la façon de récupérer dynamiquement ces métadonnées.

L’identifiant de version du projet est souvent récupéré dynamiquement depuis le gestionnaire de version, généralement avec un plugin du backend.

setuptools permet de récupérer la liste des dépendances depuis un fichier conforme à un sous-ensemble du requirement format 1

ATTENTION

Le nom du projet ne peut pas être une métadonnée dynamique. 2

PyPI

PyPI : Présentation

PyPI est le standard de fait pour le dépôt de packages Python.

En 2022, il y a eu 35,7 milliards de téléchargements et près de 450 000 projets hébergés sur PyPI 1, représentant une augmentation annuelle de 57% du nombre de téléchargements et de la bande passante.

En 2016, l’article de Donald Stufft indiquait que PyPI n’avait que 3 mainteneurs / administrateurs et que l’essentiel des soutiens se faisaient via la mise à disposition de services à titre gratuit par quelques entreprises.

En 2021, Dustin Ingram a publié un nouvel article intéressant sur la charge de PyPI, sa gestion et ses soutiens 2.

LIMITATIONS

On comprend alors les nécessaires limitations sur la taille des fichiers uploadés (100 MiB) et sur la taille totale des projets sur PyPI (10 GiB) 3.

PyPI : Documentation et tests

Voici quelques sources d’information sur PyPI

Tests

PyPI dispose d’une infrastructure sur laquelle on peut tester l’upload de ses packages : TestPyPI

PyPI : Authentification

Afin de sécuriser le dépôt face aux packages malveillants, PyPI a mis en place l’authentification à 2 facteurs (2FA) 1 et va la rendre obligatoire d’ici fin 2023 2

Si vous avez choisi de configurer l’authentification à deux facteurs, vous devrez fournir votre deuxième méthode de vérification d’identité pendant le processus de connexion. Cela affecte uniquement la connexion via un navigateur Web, et pas (encore) la publication des paquets.
Nous recommandons à tous les personnes utilisant PyPI de définir au moins deux méthodes d’authentification à deux facteurs et de générer des codes de récupération.

Voir la documentation sur l’utilisation d’un périphérique de sécurité 3 ou d’une application d’authentification 4

PyPI : Activation authentification 2FA

PyPI : Jeton d’API

Il est fortement recommandé de s’authentifier avec un Jeton d’API 1 pour la publication d’un package sur PyPI.

On peut définir un jeton global pour tous les projets de son compte PyPI (Paramètres du compte) ou des jetons dont la portée est limitée à un projet (Paramètres du projet).

On peut alors configurer l’utilisation d’un jeton dans le fichier .pypirc utilisé par plusieurs outils 2 (voir la partie backend).

[distutils]
index-servers = 
   pypi
   testpypi
   test_ntt

[test_ntt]
repository = https://test.pypi.org/legacy/
username = __token__

PyPI : Trusted Publisher

La notion de Trusted Publisher repose sur OpenID Connect pour permettre de connecter un “environnement automatisé” de type CI 1 à PyPI.

La documentation et l’interface semblent essentiellement orientées GitHub Action 2 3

Warning

Je n’ai pas testé cette fonctionnalité. Lire attentivement :

car pousser automatiquement des versions pose un certain nombre de questions. 4

PyPI : Personnes en charge du projet

Pour définir les personnes en charge du projet, il faut se connecter à PyPI et aller dans la gestion du projet concerné.

Cliquer alors sur le menu Personnes et définir les gestionnaires du projet en saisissant l’identifiant de leur compte PyPI et leur rôle : owner (tous les droits) ou maintainer.

Note

À ce jour, il ne semble pas y avoir de lien entre les métadonnées authors et maintainers du package et les comptes gestionnaires du projet sur PyPI.

PyPI : Organizations

Les organisations PyPI sont une notion récente 1, elles ont pour but :

  • de faciliter l’identification de la provenance d’un package 2
  • de permettre aux grosses communautés et sociétés de gérer de multiples équipes, membres, projet avec différentes permissions

Une organisation est payante pour une société et gratuite pour une communauté.

Une organisation ne permet pas de détenir des packages “privés” 3.

Un mot sur conda

Présentation

conda est l’outil de gestion de packages pour les installations de Python Anaconda. Python Anaconda est une distribution Python de la société Anaconda à destination de la communauté scientifique, en particulier sous Windows où l’installation d’extensions binaires est souvent difficile.

conda est un outil complètement séparé des outils pip, virtualenv et wheel mais il fournit plusieurs de leurs fonctionnalités en terme de gestion de packages, de gestion d’environnement virtuel et de déploiement d’extension binaires.

conda n’installe pas de package depuis PyPI et ne peut installer des packages que depuis le dépôt officiel Anaconda, ou anaconda.org ou un serveur de packages local.

Cependant, pip peut être installé et fonctionner en parallèle de conda pour installer des packages depuis PyPI. conda skeleton permet de créer des packages conda à partir de package téléchargés depuis PyPI en modifiant leurs métadonnées.

Quelques backend pour le build de packages

Les backends se sont multipliés au point qu’il est difficile de savoir lequel utiliser.

backend : Flit 1/3

Flit provides a simple way to create and upload pure Python packages and modules to PyPI. It focuses on making the easy things easy for packaging. Flit can generate a configuration file to quickly set up a simple project, build source distributions and wheels, and upload them to PyPI.

Flit est un outil récent 1 et simple pour générer des packages pur Python : sans compilation de code C, sans intégration de JavaScript, …

Flit ne gère pas les dépendances.

Flit ne gère pas les environnements virtuels.

Flit ne veut pas gérer d’identifiant de version dynamique à partir des tags git 2

Flit a un contributeur majeur (cf “Statistiques sur le code”)

backend : Flit 2/3

Exemple de fichier pyproject.toml généré par la commande flit init :

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "foobar"
authors = [{name = "Sir Robin", email = "robin@camelot.uk"}]
license = {file = "LICENSE"}
classifiers = ["License :: OSI Approved :: MIT License"]
dynamic = ["version", "description"]

[project.urls]
Home = "https://github.com/sirrobin/foobar"

backend : Flit publish 3/3

On peut tester l’upload de son package sur le serveur : test.pypi.org 1

Il est nécessaire de configurer, au préalable, l’accès au serveur test.pypi.org dans le fichier .pypirc 2 ce que ne requiert pas un upload vers pypi.org

Publication avec un token d'API spécifique au projet, configuré dans le fichier .pypirc :

[distutils]
index-servers = 
   pypi
   testpypi
   test_ntt

[test_ntt]
repository = https://test.pypi.org/legacy/
username = __token__
$ flit publish --repository test_ntt
Found 144 files tracked in git                                          I-flit.sdist
Built sdist: dist/ntt-0.1.1.tar.gz                                 I-flit_core.sdist
Copying package file(s) from /tmp/tmpdgzv3ozw/ntt-0.1.1/src/ntt    I-flit_core.wheel
Writing metadata files                                             I-flit_core.wheel
Writing the record of files                                        I-flit_core.wheel
Built wheel: dist/ntt-0.1.1-py3-none-any.whl                       I-flit_core.wheel
Using repository at https://test.pypi.org/legacy/                      I-flit.upload
Install keyring to store passwords securely                            W-flit.upload
Server  : https://test.pypi.org/legacy/
Username: __token__
Password:
Uploading dist/ntt-0.1.1-py3-none-any.whl...                           I-flit.upload
Package is at https://test.pypi.org/project/ntt/                       I-flit.upload
...
Uploading dist/ntt-0.1.1.tar.gz...                                     I-flit.upload
...

backend : Hatch 1/2

Hatch is a unified command-line tool meant to conveniently manage dependencies and environment isolation for Python developers. Python package developers use Hatch and its build backend Hatchling to configure, version, specify dependencies for, and publish packages to PyPI. Its plugin system allows for easily extending functionality.

Hatch est un outil récent 1 mis en avant par la Python Packaging Authority dans sa documentation.

Hatch intègre la gestion des environnements virtuels comme Poetry et
Pipenv 2

Hatch ne gère pas les dépendances.

Hatch a un contributeur majeur (cf “Statistiques sur le code”)

backend : Hatch 2/2

Le fichier pyproject.toml généré par hatch new foobar est assez conséquent (157 lignes) :

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "foobar"
dynamic = ["version"]
description = ''
readme = "README.md"
requires-python = ">=3.7"
license = "MIT"
keywords = []
authors = [
  { name = "Sir Robin", email = "robin@camelot.uk" },
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3.7",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = []

[project.urls]
Documentation = "https://github.com/unknown/foobar#readme"
Issues = "https://github.com/unknown/foobar/issues"
Source = "https://github.com/unknown/foobar"

[tool.hatch.version]
path = "src/foobar/__about__.py"

[tool.hatch.envs.default]
dependencies = [
  "coverage[toml]>=6.5",
  "pytest",
]
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
test-cov = "coverage run -m pytest {args:tests}"
cov-report = [
  "- coverage combine",
  "coverage report",
]
cov = [
  "test-cov",
  "cov-report",
]

[[tool.hatch.envs.all.matrix]]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]

[tool.hatch.envs.lint]
detached = true
dependencies = [
  "black>=23.1.0",
  "mypy>=1.0.0",
  "ruff>=0.0.243",
]
[tool.hatch.envs.lint.scripts]
typing = "mypy --install-types --non-interactive {args:src/foobar tests}"
style = [
  "ruff {args:.}",
  "black --check --diff {args:.}",
]
fmt = [
  "black {args:.}",
  "ruff --fix {args:.}",
  "style",
]
all = [
  "style",
  "typing",
]

[tool.black]
target-version = ["py37"]
line-length = 120
skip-string-normalization = true

[tool.ruff]
target-version = "py37"
line-length = 120
select = [
  "A",
  "ARG",
  "B",
  "C",
  "DTZ",
  "E",
  "EM",
  "F",
  "FBT",
  "I",
  "ICN",
  "ISC",
  "N",
  "PLC",
  "PLE",
  "PLR",
  "PLW",
  "Q",
  "RUF",
  "S",
  "T",
  "TID",
  "UP",
  "W",
  "YTT",
]
ignore = [
  # Allow non-abstract empty methods in abstract base classes
  "B027",
  # Allow boolean positional values in function calls, like `dict.get(... True)`
  "FBT003",
  # Ignore checks for possible passwords
  "S105", "S106", "S107",
  # Ignore complexity
  "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
]
unfixable = [
  # Don't touch unused imports
  "F401",
]

[tool.ruff.isort]
known-first-party = ["foobar"]

[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

[tool.coverage.run]
source_pkgs = ["foobar", "tests"]
branch = true
parallel = true
omit = [
  "src/foobar/__about__.py",
]

[tool.coverage.paths]
foobar = ["src/foobar", "*/foobar/src/foobar"]
tests = ["tests", "*/foobar/tests"]

[tool.coverage.report]
exclude_lines = [
  "no cov",
  "if __name__ == .__main__.:",
  "if TYPE_CHECKING:",
]

backend : Setuptools

Setuptools is a collection of enhancements to the Python distutils that allow you to more easily build and distribute Python distributions, especially ones that have dependencies on other packages.

setuptools est l’outil historique pour générer des packages Python.

setuptools ne gère pas les dépendances.

setuptools n’upload pas les distributions générées sur PyPI, il est conseillé d’utiliser twine.

setuptools ne gère pas les environnements virtuels.

Tip

Les outils que j’ai expérimentés (setuptools_scm, Versioneer, Miniver) pour intégrer la version git au package semblent nécessiter setuptools comme backend de build.

backend : Poetry

Poetry is a command-line tool to handle dependency installation and isolation as well as building and packaging of Python packages. It uses pyproject.toml and, instead of depending on the resolver functionality within pip, provides its own dependency resolver. It attempts to speed users’ experience of installation and dependency resolution by locally caching metadata about dependencies.

Poetry est un outil apprécié par la communauté.

Poetry intègre la gestion des environnements virtuels.

Poetry gère les dépendances.

Poetry n’est pas un projet de la Python Packaging Authority.

Conformité PEP 517

Créé avant les PEP 518 et PEP 621, Poetry n’était pas en conformité avec ces nouveaux standards. Les statistiques PyPI présentées plus loin montrent un usage très important de poetry.core.masonry.api 1

backend : PDM

PDM est mis en avant 1 par pyOpenSci une organisation 2 pour le peer review de package scientifique en Open Science.

En fait, PDM est à la fois un frontend et un backend ce qui était le cas de setuptools et pourrait prêter à confusion 3.

Le projet semble très actif 2500 commits avec beaucoup de contributeurs. Cependant 1789 commits / 2500 sont faits par un unique contributeur.

PEP 582

PDM est le seul outil qui implémente la PEP 582. Cette PEP proposait une alternative aux environnements virtuels avec l’utilisation d’un dossier __pypackages__, permettant un fonctionnement proche de npm. 4

Est-il raisonnable d’adopter un usage sur une spécification rejetée ?

frontend / backend : une vraie séparation ?

Un post sur le forum packaging souligne que PDM est à la fois un backend et un frontend 1.

C’est également le cas pour Flit, créé avant l’élaboration des spécifications PEP 517, PEP 621, etc

Ce qui ennuyeux c’est que flit build ne produise pas les mêmes packages que python -m build 2

Hatch peut également se comporter en frontend puisqu’il propose également une commande build.

Comparaison des backends

Le Scientific Python Library Development Guide, donne un descriptif rapide de nombreux backend sur sa page de template de projets

D’autres tableaux comparatifs ont été réalisés 1 :

Python Developers Survey 2022 Results

Le Python Developers Survey 2022 Results de JetBrains donne aussi des informations intéressantes sur les pratiques de la communauté

Statistiques sur le code 1/2

Statistiques de commits 

| backend    | Premier commit | dernier commit | nb commits | nb commit      | nb authors |
|            |                |                |            | per active day |            |
| ---------- | -------------- | -------------- | ---------- | -------------- | ---------- |
| Flit       |     2015-03-13 |     2023-12-09 |       1182 |            3.0 |         75 |
| Hatch      |     2021-12-29 |     2023-12-12 |        732 |            2.8 |         49 |
| setuptools |     1998-12-18 |     2023-12-06 |      14301 |            4.5 |        606 |
| PDM        |     2019-12-27 |     2023-12-11 |       2545 |            3.4 |        158 |
| Poetry     |     2018-02-20 |     2023-12-11 |       3025 |            3.0 |        515 |

Principaux contributeurs 

| Flit             | Hatch                  | setuptools        | PDM                  | Poetry            |
| ---------------- | ---------------------- | ----------------- | -------------------- | ----------------- |
| 1 : 881 (74.53%) | 1       : 641 (87.57%) | 1 : 6138 (42.92%) | 1    : 1949 (76.58%) | 1 : 1049 (34.68%) |
| 2 :  46  (3.89%) | 2 (bot) :  14  (1.91%) | 2 : 1480 (10.35%) | idem :  175  (6.88%) | 2 :  346 (11.44%) |
| 3 :  26  (2.20%) | 3       :  12  (1.64%) | 3 :  632  (4.42%) | 3    :   53  (2.08%) | 3 :  162  (5.36%) |

Date de leur dernier commit

| Flit           | Hatch                | setuptools     | PDM               | Poetry         |
| -------------- | -------------------- | -------------- | ----------------- | -------------- |
| 1 : 2023-12-09 | 1       : 2023-12-12 | 1 : 2023-12-06 | 1    : 2023-12-11 | 1 : 2022-09-18 |
| 2 : 2023-11-10 | 2 (bot) : 2023-12-01 | 2 : 2023-11-28 | idem : 2020-09-04 | 2 : 2022-06-10 |
| 3 : 2021-03-01 | 3       : 2023-04-02 | 3 : 2001-08-23 | 3    : 2021-05-05 | 3 : 2023-11-19 |

Statistiques sur le code 2/2

Flit

Hatch

setuptools

PDM

Poetry

Statistiques PyPI 1/2

Statistiques PyPI - log scale 2/2

Librairies de gestion de l’identifiant de version

Il existe des librairies qui récupèrent le tag pour le définir dynamiquement comme numéro de version du package.

Intégrer l’identifiant de version au package Python 1/2

L’identifiant de version d’un package Python peut être spécifié statiquement dans le pyproject.toml ou dynamiquement 1

Statiquement

En déclaration statique, l’identifiant de version est une chaîne de caractères dans les données globales du pyproject.toml.

[project]
name = "ntt"
version = "0.1.0"
description = "Library for sport analysis"

Dynamiquement

Déclaration des paramètres dynamiques dans le pyproject.toml.

[project]
name = "ntt"
dynamic = ["version", "description"]

Intégrer l’identifiant de version au package Python 2/2

Les modalités de récupération dépendent du backend.

Flit

Flit récupère l’identifiant de version de la propriété __version__ du module et la description de la docstring du module.

$ tree
.
├── pyproject.toml
├── README.md
├── src
│   └── ntt
│       ├── __init__.py
│       ├── ...

Ces deux métadonnées sont récupérées du fichier src/ntt/__init__.py.

"""Library for sport analysis"""

__version__ = "0.1.1"

Librairies possibles

setuptools_scm

setuptools_scm extracts Python package versions from git or hg metadata instead of declaring them as the version argument or in an SCM managed file.

Le seul des trois qui semble compatible pyproject.toml

Versioneer

Versioneer is a tool for managing a recorded version number in setuptools-based python projects.

Warning

Le projet qui m’a fait découvrir Versioneer est passé à setuptools_scm.

Miniver

Miniver is a minimal versioning tool that serves the same purpose as Versioneer, except that it only works with Git and multiplatform support is still experimental.

À vérifier

Warning

Veillez à respecter la PEP 440 pour l’identifiant de version

À vérifier

Que se passe-t-il si le numéro de version déclaré dans le pyproject.toml est différent du tag positionné dans le gestionnaire de version ?

Guides - documentations

Scientific Python Library Development Guide

Le Scientific Python Library Development Guide publié en juillet 2023, semble être une excellente ressource à ce jour

Accessoirement

pyproject.toml - la déclaration des URL

La section [project.urls] liste les liens à afficher pour le projet et les étiquettes à utiliser pour ces liens. Ces étiquettes, qui ne sont pas définies 1, diffèrent selon les documentations, ce qui est troublant :

  • URL du dépôt du code : Repository 2 ou Source 3 ? On trouve aussi URL, GitHub, GitLab, …

L’article PyPI Project URLs Cheatsheet évoque des étiquettes trouvées via l’analyse du code de PyPI.

Pourtant le projet YAPF a bien des étiquettes Docs et Issues mais PyPI n’affiche pas de lien vers les URL. Problème de simple quote 4 ? Changelog est pourtant affiché

Gestion des dépendances avec pip-tools 1/2

pip-tools est un outil qui calcule récursivement les dépendances spécifiées dans un pyproject.toml, un setup.py/setup.cfg ou fichier texte avec une liste de packages (par convention souvent nommée requirements.in).

pip-compile crée un environnement isolé pour le calcul des dépendances lorsqu’on lui fournit un pyproject.toml :

$ pip-compile -v pyproject.toml
Using pip-tools configuration defaults found in 'pyproject.toml'.
Creating venv isolated environment...
Installing packages in isolated environment... (flit_core >=3.2,<4)
Getting build dependencies for wheel...
Getting metadata for wheel...
Using indexes:
  https://pypi.org/simple

Gestion des dépendances avec pip-tools 2/2

Cette étape ne semble pas présente lorsqu’on lui fournit un requirements.in

$ pip-compile -v requirements.in
Using indexes:
  https://pypi.org/simple

pip-compile génère un fichier requirements.txt avec les dépendances calculées que l’on installe par pip-sync.

$ pip-sync requirements.txt

ATTENTION

pip-sync installe les packages spécifiés dans le fichier requirements.txt. Si des packages ont été manuellement installés dans l’environnement et qu’ils ne sont pas présents dans le fichier requirements.txt, ils seront désinstallés.

Gestion des dépendances : committer requirements.txt ?

La question est posée dans une issue pip-tools 1838

Il est certains que si les utilisateurs d’un projet ont des environnements différents, un seul requirements.txt (ou xxx.lock) ne peut pas être appliqué pour tous.

Workflow de tag 1/2

Workflow de tag 2/2

Template de projet 1/4

Le Scientific Python Library Development Guide, propose des templates de projet très riches à utiliser au choix avec copier, cookiecutter ou cruft. Exemple avec cookiecutter :

$ cookiecutter gh:scientific-python/cookie
  [1/9] The name of your project (package): my_scientific_package
  [2/9] The name of your (GitHub?) org (org): fconil
  [3/9] The url to your GitHub or GitLab repository (https://github.com/fconil/my_scientific_package):
  [4/9] Your name (My Name): Françoise CONIL
  [5/9] Your email (me@email.com): fcodvpt@gmail.com
  [6/9] A short description of your project (A great package.): Testing the scientific templates for different backends
  [7/9] Select a license
    1 - BSD
    2 - Apache
    3 - MIT
    Choose from [1/2/3] (1):
  [8/9] Choose a build backend
    1 - Hatchling                      - Pure Python (recommended)
    2 - Flit-core                      - Pure Python (minimal)
    3 - PDM-backend                    - Pure Python
    4 - Whey                           - Pure Python
    5 - Poetry                         - Pure Python
    6 - Setuptools with pyproject.toml - Pure Python
    7 - Setuptools with setup.py       - Pure Python
    8 - Setuptools and pybind11        - Compiled C++
    9 - Scikit-build-core              - Compiled C++ (recommended)
    10 - Meson-python                  - Compiled C++ (also good)
    11 - Maturin                       - Compiled Rust (recommended)
    Choose from [1/2/3/4/5/6/7/8/9/10/11] (1): 6
  [9/9] Use version control for versioning [y/n] (y):

Template de projet 2/4

Contenu du projet généré 1 :

└── my_scientific_package
    ├── docs
    │   ├── conf.py
    │   └── index.md
    ├── .git_archival.txt
    ├── .gitattributes
    ├── .github
    │   ├── CONTRIBUTING.md
    │   ├── dependabot.yml
    │   ├── matchers
    │   │   └── pylint.json
    │   └── workflows
    │       ├── cd.yml
    │       └── ci.yml
    ├── .gitignore
    ├── LICENSE
    ├── noxfile.py
    ├── .pre-commit-config.yaml
    ├── pyproject.toml
    ├── README.md
    ├── .readthedocs.yml
    ├── src
    │   └── my_scientific_package
    │       ├── __init__.py
    │       ├── py.typed
    │       └── _version.pyi
    └── tests
        └── test_package.py

Template de projet 3/4

Ayant demandé d’utiliser les informations du gestionnaire de version pour l’identifiant de version. Le template utilise setuptools_scm. Le pyproject.toml référence un fichier _version.py

[tool.setuptools_scm]
write_to = "src/example/_version.py"

ignoré par le .gitignore du template

# setuptools_scm
src/*/_version.py

Le projet contient un fichier src/example/_version.pyi

from __future__ import annotations

version: str
version_tuple: tuple[int, int, int] | tuple[int, int, int, str, str]

Template de projet 4/4

setuptools_scm génère src/example/_version.py au build.

# file generated by setuptools_scm
# don't change, don't track in version control
TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple, Union
    VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
    VERSION_TUPLE = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE

__version__ = version = '0.0.1'
__version_tuple__ = version_tuple = (0, 0, 1)

les packages ont bien l’identifiant de version correspondant au tag

$ git tag -l -n
0.0.1           Initial release
$ git status
Sur la branche main
Fichiers non suivis:
  (utilisez "git add <fichier>..." pour inclure dans ce qui sera validé)
    .pre-commit-config.yaml
$ ls dist/
example-0.0.1-py3-none-any.whl  example-0.0.1.tar.gz

Différences de génération de packages entre backends : wheel

Flit

├── my_scientific_package
│   ├── __init__.py
│   ├── py.typed
│   ├── _version.py
│   └── _version.pyi
├── my_scientific_package-0.0.2.dist-info
│   ├── LICENSE
│   ├── METADATA (*)
│   ├── RECORD   (*)
│   └── WHEEL    (*)

setuptools

├── my_scientific_package
│   ├── __init__.py
│   ├── py.typed
│   ├── _version.py
│   └── _version.pyi
├── my_scientific_package-0.0.2.dist-info
│   ├── LICENSE
│   ├── METADATA (*)
│   ├── RECORD   (*)
│   ├── top_level.txt
│   └── WHEEL    (*)

Hatch

├── my_scientific_package
│   ├── __init__.py
│   ├── py.typed
│   ├── _version.py
│   └── _version.pyi
├── my_scientific_package-0.0.2.dist-info
│   ├── licenses
│   │   └── LICENSE
│   ├── METADATA (*)
│   ├── RECORD   (*)
│   └── WHEEL    (*)

(*)

Différences dues :

  • à des hash (RECORD),
  • à la présence du nom backend utilisé (WHEEL)
  • à l’ordre des lignes (METADATA) à vérifier cependant. Hath et setuptools incluent le texte de la licence.

Différences de génération de packages entre backends : sdist 1/2

Packages générés par python -m build 1.

Flit

├── my_scientific_package-0.0.2
│   ├── LICENSE
│   ├── PKG-INFO
│   ├── pyproject.toml
│   ├── README.md
│   └── src
│       └── my_scientific_package
│           ├── __init__.py
│           ├── py.typed
│           ├── _version.py
│           └── _version.pyi

Hatch

├── my_scientific_package-0.0.2
│   ├── docs
│   │   ├── conf.py
│   │   └── index.md
│   ├── .git_archival.txt
│   ├── .gitattributes
│   ├── .github
│   │   ├── CONTRIBUTING.md
│   │   ├── dependabot.yml
│   │   ├── matchers
│   │   │   └── pylint.json
│   │   └── workflows
│   │       ├── cd.yml
│   │       └── ci.yml
│   ├── .gitignore
│   ├── LICENSE
│   ├── noxfile.py
│   ├── PKG-INFO
│   ├── .pre-commit-config.yaml
│   ├── pyproject.toml
│   ├── README.md
│   ├── .readthedocs.yml
│   ├── src
│   │   └── my_scientific_package
│   │       ├── __init__.py
│   │       ├── py.typed
│   │       ├── _version.py
│   │       └── _version.pyi
│   └── tests
│       └── test_package.py

Différences de génération de packages entre backends : sdist 2/2

Packages générés par python -m build.

setuptools

├── my_scientific_package-0.0.2
│   ├── docs
│   │   ├── conf.py
│   │   └── index.md
│   ├── .git_archival.txt
│   ├── .gitattributes
│   ├── .github
│   │   ├── CONTRIBUTING.md
│   │   ├── dependabot.yml
│   │   ├── matchers
│   │   │   └── pylint.json
│   │   └── workflows
│   │       ├── cd.yml
│   │       └── ci.yml
│   ├── .gitignore
│   ├── LICENSE
│   ├── noxfile.py
│   ├── PKG-INFO
│   ├── .pre-commit-config.yaml
│   ├── pyproject.toml
│   ├── README.md
│   ├── .readthedocs.yml
│   ├── setup.cfg
│   ├── src
│   │   ├── my_scientific_package
│   │   │   ├── __init__.py
│   │   │   ├── py.typed
│   │   │   ├── _version.py
│   │   │   └── _version.pyi
│   │   └── my_scientific_package.egg-info
│   │       ├── dependency_links.txt
│   │       ├── PKG-INFO
│   │       ├── requires.txt
│   │       ├── SOURCES.txt
│   │       └── top_level.txt
│   └── tests
│       └── test_package.py

Hatch

├── my_scientific_package-0.0.2
│   ├── docs
│   │   ├── conf.py
│   │   └── index.md
│   ├── .git_archival.txt
│   ├── .gitattributes
│   ├── .github
│   │   ├── CONTRIBUTING.md
│   │   ├── dependabot.yml
│   │   ├── matchers
│   │   │   └── pylint.json
│   │   └── workflows
│   │       ├── cd.yml
│   │       └── ci.yml
│   ├── .gitignore
│   ├── LICENSE
│   ├── noxfile.py
│   ├── PKG-INFO
│   ├── .pre-commit-config.yaml
│   ├── pyproject.toml
│   ├── README.md
│   ├── .readthedocs.yml
│   ├── src
│   │   └── my_scientific_package
│   │       ├── __init__.py
│   │       ├── py.typed
│   │       ├── _version.py
│   │       └── _version.pyi
│   └── tests
│       └── test_package.py

Différences de génération entre flit frontend et build: sdist

sdist généré par python -m build.

├── my_project-0.0.1
│   ├── LICENSE
│   ├── PKG-INFO
│   ├── pyproject.toml
│   ├── README.md
│   └── src
│       └── my_project
│           └── __init__.py

sdist généré par flit build.

├── my_project-0.0.1
│   ├── docs
│   │   ├── conf.py
│   │   └── index.rst
│   ├── .gitignore
│   ├── LICENSE
│   ├── PKG-INFO
│   ├── pyproject.toml
│   ├── README.md
│   ├── src
│   │   └── my_project
│   │       └── __init__.py
│   └── tests
│       └── test_package.py
$ python -m build
* Creating venv isolated environment...
* Installing packages in isolated environment... (flit_core >=3.2,<4)
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating venv isolated environment...
* Installing packages in isolated environment... (flit_core >=3.2,<4)
* Getting build dependencies for wheel...
* Building wheel...
Successfully built my_project-0.0.1.tar.gz and my_project-0.0.1-py3-none-any.whl
$ flit build
Found 8 files tracked in git                                                          I-flit.sdist
Built sdist: dist/my_project-0.0.1.tar.gz                                        I-flit_core.sdist
Copying package file(s) from /tmp/tmpwbdpows6/my_project-0.0.1/src/my_project    I-flit_core.wheel
Writing metadata files                                                           I-flit_core.wheel
Writing the record of files                                                      I-flit_core.wheel
Built wheel: dist/my_project-0.0.1-py3-none-any.whl                              I-flit_core.wheel

Génération sur ce squelette de projet.

Différences de génération entre hatch frontend et build 1/2

Aucune différence entre le sdist généré par python -m build et le sdist généré par hatch build.

├── my_project-0.0.1
│   ├── docs
│   │   ├── conf.py
│   │   └── index.rst
│   ├── .gitignore
│   ├── LICENSE
│   ├── PKG-INFO
│   ├── pyproject.toml
│   ├── README.md
│   ├── src
│   │   └── my_project
│   │       └── __init__.py
│   └── tests
│       └── test_package.py
├── my_project-0.0.1
│   ├── docs
│   │   ├── conf.py
│   │   └── index.rst
│   ├── .gitignore
│   ├── LICENSE
│   ├── PKG-INFO
│   ├── pyproject.toml
│   ├── README.md
│   ├── src
│   │   └── my_project
│   │       └── __init__.py
│   └── tests
│       └── test_package.py
$ python -m build
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (hatchling)
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (hatchling)
* Getting build dependencies for wheel...
* Building wheel...
Successfully built my_project-0.0.1.tar.gz and my_project-0.0.1-py3-none-any.whl
$ hatch build
[sdist]
dist/my_project-0.0.1.tar.gz

[wheel]
dist/my_project-0.0.1-py3-none-any.whl

Différences de génération entre hatch frontend et build 2/2

hatch build affiche message transitoirement sur une ligne ... building environment.

Si on déplace les packages qui ont été générés dans dist, et que l’on exécute hatch build à nouveau cette ligne n’est plus affichée comme si l’environnement était encore actif quelque part et avait une trace du type “rien n’a changé” (les fichiers sont regénérés à l’identique).

On ne sait pas où cette information et ces données sont stockées.

changelogs : towncrier

towncrier semble servir à créer des changelogs.

À ÉTUDIER

Used by Twisted, pytest, pip, BuildBot, and attrs, among others.

Dépôt GitHub de fichiers .gitignore

.gitignore Python