Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fconil/presentation-versionner-un-code-python
1 result
Show changes
Commits on Source (2)
......@@ -12,7 +12,20 @@ $presentation-font-size-root: 20pt;
margin: 0.75em 0;
}
.reveal pre.shell code {
border: 1px solid #93a1a1;
margin: 0.75em 0;
padding: 0.5em 0.75em;
}
.reveal .slides table {
font-size: 16pt;
font-size: 0.6em;
}
.reveal .slides blockquote {
font-size: 0.9em;
font-style: italic;
color: unset;
background: lightyellow;
border-left-color: green;
}
images/git-tag-cpython.png

160 KiB

images/git-tag-csvkit.png

92.7 KiB

images/xkcd_standards.png

50.6 KiB

......@@ -15,11 +15,19 @@ format:
Quelles sont les différentes étapes et quels sont les choix possibles pour
versionner son code Python ?
::: {.callout-warning title="ATTENTION"}
- Structure d'un identifiant de version Python
- Logiciel de gestion de version : git
- Forges (GitHub, GitLab)
- Librairies de gestion de l'identifiant de version (Versioneer,
setuptools_scm, Miniver, ...)
::: aside
Cette présentation ne décrit pas toutes les étapes nécessaires au packaging
d'une application.
:::
# 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
......@@ -33,7 +41,7 @@ Les versions ne sont pas de simples chaînes de caractères, elles sont
[normalisées](https://peps.python.org/pep-0440/#normalization) afin de pouvoir
être comparées à l'aide de la [classe Version](https://packaging.pypa.io/en/latest/version.html)
```python
```{.python}
>>> from packaging.version import Version, parse
>>> v1 = parse("1.0a5")
>>> v2 = Version("v1.0")
......@@ -47,42 +55,926 @@ True
## Description des éléments de l'identifiant
- un éventuel préfixe
- 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](https://calver.org/) au [Semantic
versioning](https://semver.org/) <br/>(`1!1.0.0` > `2023.10`)
- un **numéro de release obligatoire** de type [Semantic versioning](https://semver.org/)
(`2.12.1`) ou [Calendar versioning](https://calver.org/) (`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](https://pypi.org/)
::: aside
Informations issues de l'article [What is a Python package version?](https://sethmlarson.dev/pep-440#v-prefixes)
sur la [PEP 440](https://peps.python.org/pep-0440/)
:::
## Normalisation de l'identifiant 1/2
- les identifiants sont toujours transformés en minuscules
- il y a pas de limite de taille à un identifiant
```{.python}
>>> 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')>
```
::: aside
Informations issues de l'article [What is a Python package version?](https://sethmlarson.dev/pep-440#v-prefixes)
sur la [PEP 440](https://peps.python.org/pep-0440/)
:::
::: {.notes}
code-line-numbers="2,4"
:::
## 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
```{.python}
>>> 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')>
```
::: aside
Informations issues de l'article [What is a Python package version?](https://sethmlarson.dev/pep-440#v-prefixes)
sur la [PEP 440](https://peps.python.org/pep-0440/)
:::
## SemVer : Semantic Versioning
[SemVer : Semantic Versioning](https://semver.org/)
Le [Semantic Versioning](https://semver.org/) / 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 ^[[Forme de Backus-Naur](https://fr.wikipedia.org/wiki/Forme_de_Backus-Naur)]
indique que l'on peut ajouter un identifiant de pre-release et un identifiant
de build.
::: aside
Je garde la terminologie anglaise pour les éléments de cet identifiant.
:::
## CalVer : Calendar Versioning
[CalVer : Calendar Versioning](https://calver.org/)
De façon similaire au [Semantic Versioning](https://semver.org/), l'identifiant
en [Calendar Versioning](https://calver.org/) 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
| 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 |
: Applications {tbl-colwidths="[33,33,33]"}
| Project | CalVer Format | Examples |
| ---------------------------- | ---------------- | ------------------ |
| C++ | YY | 98, 03, 11, 14, 17 |
| ECMAScript (aka JavaScript ) | YYYY | 2015, 2020 |
: Standards {tbl-colwidths="[33,33,33]"}
| 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 |
: Libraries and Utilities {tbl-colwidths="[33,33,33]"}
::: aside
[Exemples d'utilisation du Calendar Versioning](https://calver.org/users.html)
:::
# Logiciel de gestion de version : git
## Logiciel de gestion de version
Un [logiciel de gestion de versions](https://fr.wikipedia.org/wiki/Logiciel_de_gestion_de_versions)
(ou VCS en anglais, pour version control system) est un logiciel qui permet de
stocker un ensemble de fichiers en conservant la chronologie de toutes les
modifications qui ont été effectuées dessus.
(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](http://git-scm.com/book/) est le gestionnaire de version décentralisé le
plus répandu actuellement avec l'utilisation de forges logicielles internes
^[[GitLab CNRS](https://src.koda.cnrs.fr/), [GitLab INRIA](https://gitlab.inria.fr/), ...]
ou externes à nos structures ^[[GitHub](https://github.com/), [GitLab](https://gitlab.com/),
[Framagit](https://framagit.org/public/projects)...].
([GitLab CNRS](https://src.koda.cnrs.fr/), [GitLab INRIA](https://gitlab.inria.fr/), ...)
ou externes à nos structures ([GitHub](https://github.com/), [GitLab](https://gitlab.com/),
[Framagit](https://framagit.org/public/projects)...).
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.
::: aside
Pour plus d'information sur [git](http://git-scm.com/book/), vous pouvez
consulter les formations de David Parsons données dans le cadre du [réseau ARAMIS](https://aramis.resinfo.org/) :
- [git débutant](https://aramis.resinfo.org/doku.php?id=ateliers:gitintro-112021)
- [git avancé](https://aramis.resinfo.org/doku.php?id=ateliers:git-avance-31032022)
ainsi que les présentations faites lors des [cafés développeur·se·s LIRIS](https://projet.liris.cnrs.fr/edp/cafes-developpeur-liris/)
:::
## git tag
![](images/git-tag-cpython.png){.border .border-thick height="200"}
[git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) 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](https://fr.wikipedia.org/wiki/GNU_Privacy_Guard)
- 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" : <br/>`git tag
-a <identifiant> -m <message>`
```{.bash}
$ 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
```
::: {.notes}
code-line-numbers="1,3,4,5,7,9,13"
:::
---
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 :
```{.bash}
$ 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.
```{.bash}
$ git cat-file -t 0.1.0 # annotated tag
tag
$ git cat-file -t 0.1.1 # lightweight tag
commit
```
::: {.callout-warning title="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](https://git-scm.com/docs/git-describe) détecte le
tag annoté le plus récent accessible depuis le commit courant.
```{.bash code-line-numbers="1,2,10"}
$ 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`
```{.bash}
$ 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) :
```{.bash}
$ touch hello.py
$ git add hello.py
$ git describe
1.2.0-8-g5fe6b80
$ git describe --dirty
1.2.0-8-g5fe6b80-dirty
```
::: {.notes}
![](images/git-tag-csvkit){fig-alt="csvkit" height="120"}
:::
# 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.
- [tags du project Flit](https://github.com/pypa/flit/tags)
## Créer une release sur GitHub
1. Dans [GitHub.com](https://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**
::: aside
Liste des étapes reprise de la documentation officielle :
[Managing releases in a repository](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository?tool=webui#creating-a-release)
:::
---
8. 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 ?)
9. Vous pouvez ajouter des fichiers binaires par drag and drop dans la zone
sous la description
10. Vous pouvez indiquer qu'il s'agit d'une pre-release en cochant **Set as a
pre-release**
11. PAS VU : Optionnellement, cliquez sur **Set as latest release**
12. Optionnellement, créez une discussion pour cette release dans **GitHub
discussion** s'il est activé
13. Publiez la release avec **Publish release** ou enregistrer-la comme draft
avec **Save draft**
::: aside
Liste des étapes reprise de la documentation officielle :
[Managing releases in a repository](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository?tool=webui#creating-a-release)
:::
## 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.
- [tags du project Inkscape](https://gitlab.com/inkscape/inkscape/-/tags)
## 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**
4. Via le menu déroulant **Tag name**
- cliquez sur un **tag** existant
- saississez un **nouveau tag**, et cliquez sur **Create tag <tag saisi>**
5. 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](https://gitlab.com/graphviz/graphviz/-/releases)
- des liens vers d'autres ressources (documentation, binaires, ...)
6. Cliquez sur **Create release**
::: aside
Liste des étapes reprise de la documentation officielle : [Create a release](https://docs.gitlab.com/ee/user/project/releases/#create-a-release)
:::
# Packaging
Quelques informations, très simplifiées, pour aborder le fichier `pyproject.toml`
## Python Packaging Authority
> The [Python Packaging Authority](https://www.pypa.io/) (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](https://packaging.python.org/),
> which is the authoritative resource on how to package, publish, and install
> Python projects using current tools.
Parmis ces documentations, [The Packaging Flow](https://packaging.python.org/en/latest/flow/)
^[Voir aussi le tutoriel [Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/)]
spécifie les principales étapes du packaging.
## Historiquement
Pendant longtemps le packaging a reposé sur le fichier `setup.py`. Cela posait
plusieurs problèmes ^[[L'enfer des paquets Python : la folie des
formats](https://www.stella.coop/blog/00007-l-enfer-des-paquets-python-la-folie-des-formats)]
dont la nécessité d'exécuter un fichier Python pour déterminer comment
installer un package, connaître ses dépendances, etc
L’idée d'utiliser un **format déclaratif** pour la création de package a été
d'abord expérimentée via une solution intégrée à setuptools: `setup.cfg`
^[[Aventuriers du packaging perdu (Présentation du `setup.cfg`)](https://twidi.github.io/python-packaging-talk/fr)]
En 2016, la [PEP 518](https://peps.python.org/pep-0518/) introduit le fichier
`pyproject.toml`. L’idée n’est pas de proposer un nouveau format de métadonnées
mais d’inclure, dans un fichier texte simple, les dépendances nécessaires pour
**construire un package**. Il devient alors possible d'utiliser d'autres outils que
`setuptools` et `distutils`, au moins pour construire des packages. ^[[L’enfer
des paquets Python : des fichiers partout](https://www.stella.coop/blog/00009-l-enfer-des-paquets-python-des-fichiers-partout)]
L'historique réel est bien plus complexe ^[Lire la série d'articles : [L'enfer
des paquets Python](https://www.stella.coop/blog/00003-l-enfer-des-paquets-python-le-sac-de-noeuds)]
et le fichier `setup.py` reste encore nécessaire pour un grand nombre de
projets (compilation de code en C++).
## 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` ^[Voir [PEP 518](https://peps.python.org/pep-0518/)
et [Declaring project metadata](https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#declaring-project-metadata)
qui remplace la [PEP 621](https://peps.python.org/pep-0621/)]
- Exécuter l'**outil de build** pour produire une [source distribution (sdist)](https://packaging.python.org/en/latest/glossary/#term-Source-Distribution-or-sdist)
et une ou plusieurs [built distribution (wheel)](https://packaging.python.org/en/latest/glossary/#term-Built-Distribution)
à partir du fichier de configuration. Souvent, il n'y a qu'un [wheel](https://packaging.python.org/en/latest/glossary/#term-Wheel)
pour un package **pur Python**
- Mettre en ligne les éléments produits sur un service de distribution de
package (généralement [PyPI](https://pypi.org/)).
## pyproject.toml (1/2)
Voici un exemple de `pyproject.toml` qui utilise [Flit](https://flit.pypa.io/en/latest/)
pour générer le package et l'uploader sur `PyPI`.
```{.toml}
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "foobar"
version = "0.1.0"
authors = [{name = "Sir Robin", email = "robin@camelot.uk"}]
description = "Exploring packaging tools"
[project.urls]
Home = "https://github.com/sirrobin/foobar"
```
::: aside
- `requires` : contient la liste des packages nécessaires pour générer le package
- `build-backend` : indique le `backend` que le `fontend` Python `build` doit
utiliser pour générer le package : ici `Flit`
Voir
- `requires`, `build-backend`, `backend`, `fontend` : [The Packaging Flow](https://packaging.python.org/en/latest/flow/),
[Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
:::
## pyproject.toml - les URL du projet (2/2)
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
^[[Core metadata specifications](https://packaging.python.org/en/latest/specifications/core-metadata/)],
diffèrent selon les documentations, ce qui est troublant :
- URL du dépôt du code : `Repository` ^[[Declaring project metadata](https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#declaring-project-metadata)] ou `Source` ^[[Flit : The pyproject.toml config file](https://flit.pypa.io/en/latest/pyproject_toml.html#urls-table)] ?
On trouve aussi `URL`, `GitHub`, `GitLab`, ...
L'article [PyPI Project URLs Cheatsheet](https://daniel.feldroy.com/posts/2023-08-pypi-project-urls-cheatsheet)
évoque des étiquettes trouvées via l'analyse du [code](https://github.com/pypi/warehouse/blob/70eac9796fa1eae24741525688a112586eab9010/warehouse/templates/packaging/detail.html#L20-L62)
de [PyPI](https://pypi.org/).
Pourtant le projet [YAPF](https://pypi.org/project/yapf/0.40.2/#description) a
bien des étiquettes `Docs` et `Issues` mais PyPI n'affiche pas de lien vers les
URL. Problème de simple quote ^[[TOML String type](https://toml.io/en/v1.0.0#string)] ?
`Changelog` est pourtant affiché
::: {.notes}
L'auteur de [PyPI Project URLs Cheatsheet](https://daniel.feldroy.com/posts/2023-08-pypi-project-urls-cheatsheet)
a obtenu les étiquettes suivantes à partir du [code](https://github.com/pypi/warehouse/blob/70eac9796fa1eae24741525688a112586eab9010/warehouse/templates/packaging/detail.html#L20-L62)
de [PyPI](https://pypi.org/)
- `homepage`
- `repository`
- `changelog`
- `docs`, `documentation`
- `bugs`, `issues`, `tracker`
- `dowload`
- `sponsor`, `funding`, `donate`
- `mastodon`, `twitter`, `slack`, `reddit`, `discord`, `gitter`
:::
# PyPI
[PyPI](https://pypi.org/) est le dépôt officiel pour les packages Python
## Documentation
Voici quelques sources d'information sur [PyPI](https://pypi.org/)
- [documentation utilisateur](https://docs.pypi.org/)
- [blog](https://blog.pypi.org/)
- [documentation admin/dev](https://warehouse.pypa.io/)
- [Python Packaging User Guide](https://packaging.python.org/)
## Authentification
Afin de sécuriser le dépôt face aux packages malveillants,
[PyPI](https://pypi.org/) a mis en place l'authentification à 2 facteurs (2FA)
^[[Aide PyPI sur 2FA](https://pypi.org/help/#twofa) : L'authentification à deux
facteurs rend votre compte plus sécurisé en exigeant deux choses pour pouvoir
vous connecter : "quelque chose que vous savez" et "quelque chose que vous possédez"]
et va la rendre obligatoire d'ici fin 2023
^[[Securing PyPI accounts via Two-Factor Authentication](https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/)]
> 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.<br/>
> 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é** ^[[Aide
utfkey](https://pypi.org/help/#utfkey)] ou d'une **application d'authentification**
^[[Aide TOTP](https://pypi.org/help/#totp)]
## Test
`PyPI` dispose d'une infrastructure sur laquelle on peut tester l'upload de ses
packages : [TestPyPI](https://test.pypi.org/]
# Quelques backend pour le build de packages
![](images/xkcd_standards.png){height="350"}
Les backends se sont multipliés au point qu'il est difficile de savoir lequel
utiliser.
## Flit 1/3
> [Flit](https://flit.pypa.io/en/latest/) provides a simple way to create and
> upload pure Python packages and modules to `PyPI`. It focuses on [making the easy things easy](https://flit.pypa.io/en/latest/rationale.html)
> 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 ^[Post [PEP 518](https://peps.python.org/pep-0518/)]
et simple pour générer des packages pur Python : sans compilation de code C,
intégration de JavaScript, ...
`Flit` ne gère pas les dépendances.
`Flit` ne gère pas les environnements virtuels.
`Flit` dépendrait d'un seul développeur ?
## Flit 2/3
Exemple de fichier `pyproject.toml` généré par `Flit` :
```{.toml}
[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"
```
::: aside
Les métadonnées `version` et `description` sont récupérées dynamiquement :
[Flit : The pyproject.toml config file](https://flit.pypa.io/en/latest/pyproject_toml.html?highlight=dynamic#new-style-metadata)
:::
## Flit publish 3/3
Il est intéressant de tester l'upload de son package sur le serveur de test
[test.pypi.org](https://test.pypi.org/)
Flit requiert alors de configurer l'accès à ce serveur dans le fichier
`.pypirc` ^[[Flit issue 122](https://github.com/pypa/flit/issues/122)] ce qu'il
ne fait pas automatiquement contrairement aux upload vers [pypi.org](https://pypi.org/)
- [PyPA : Using TestPyPI](https://packaging.python.org/en/latest/guides/using-testpypi/)
- [Flit : Controlling package uploads](https://flit.pypa.io/en/latest/upload.html)
## Hatch 1/2
> [Hatch](https://hatch.pypa.io/latest/) 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 ^[Post [PEP 518](https://peps.python.org/pep-0518/)]
mis en avant par la [Python Packaging Authority](https://www.pypa.io/) dans sa documentation.
`Hatch` intègre la gestion des environnements virtuels comme
[Poetry](https://python-poetry.org/) et <br/>
[Pipenv](https://pipenv.pypa.io/en/latest/) ^[Par contre, `Pipenv` ne génère pas de packages Python]
Je n'ai pas vu comment `Hatch` gérait les dépendances.
`Hatch` dépendrait d'un seul développeur ?
## Hatch 2/2
Le fichier `pyproject.toml` généré par `hatch new foobar` est assez conséquent
(157 lignes) :
```{.toml}
[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:",
]
```
## Setuptools
> [Setuptools](https://setuptools.pypa.io/en/latest/index.html) 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 `PiPI`.
`setuptools` ne gère pas les environnements virtuels.
::: {.callout-tip}
Les outils que j'ai expérimentés (`Versioneer`, `setuptools_scm`, `Miniver`)
pour intégrer la version git au package semblent nécessiter `setuptools`
comme **backend de build**.
:::
## Poetry
> [Poetry](https://python-poetry.org/) 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é.
Créé avec les [PEP 518](https://peps.python.org/pep-0518/) et
[PEP 621](https://peps.python.org/pep-0621/), il n'était pas en conformité avec
ces nouveaux standards. Et maintenant ?
`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](https://www.pypa.io/).
`Poetry` dépendrait d'un seul développeur ?
# Un mot sur conda
## Présentation
[conda](https://conda.io/) est l'outil de gestion de packages pour les
installations de **Python Anaconda**. Python Anaconda est une distribution
Python de la [société Anaconda](https://www.anaconda.com/) à 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](https://pypi.org/) et ne peut
installer des packages que depuis le dépôt officiel Anaconda, ou [anaconda.org](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](https://docs.conda.io/projects/conda-build/en/latest/user-guide/tutorials/build-pkgs-skeleton.html)
permet de créer des packages conda à partir de package téléchargés depuis `PyPI`
en modifiant leurs métadonnées.
::: {.notes}
Conda does not install packages from PyPI and can install only from the
official Anaconda repositories, or anaconda.org (a place for user-contributed
conda packages), or a local (e.g. intranet) package server. However, note that
pip can be installed into, and work side-by-side with conda for managing
distributions from PyPI. Also, conda skeleton is a tool to make Python packages
installable by conda by first fetching them from PyPI and modifying their
metadata.
:::
::: aside
Traduit de [Project Summaries - Non-PyPA projects](https://packaging.python.org/en/latest/key_projects/#non-pypa-projects)
:::
# 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
L'identifiant de version d'un package Python peut être spécifié statiquement
dans le `pyproject.toml` ou dynamiquement ^[Voir [Declaring project metadata](https://packaging.python.org/en/latest/specifications/declaring-project-metadata/)
qui remplace la [PEP 621](https://peps.python.org/pep-0621/)]
En déclaration statique, l'identifiant de version est une chaîne de caractères
qui doit respecter la [PEP 440](https://peps.python.org/pep-0440/).
```{.toml}
[project]
name = "ntt"
version = "0.1.0"
description = "Library for sport analysis"
```
::: {.callout-tip title="À vérifier"}
Le numéro de version déclaré dans le `pyproject.toml` pourrait-il différer du
tag correspondant au code packagé ?
:::
## Versioneer
[Versioneer](https://pypi.org/project/versioneer/) is a tool for managing a
recorded version number in setuptools-based python projects.
- [Git Book - Tagging](https://git-scm.com/book/en/v2/Git-Basics-Tagging)
- [Git Book - Maintaining a Project](https://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project), Tagging Your Releases
- [git ready - tagging](https://gitready.com/beginner/2009/02/03/tagging.html)
## setuptools_scm
- Annotated Tags
- Lightweight Tags
[setuptools_scm](https://pypi.org/project/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`
## GitHub - Managing releases in a repository
## Miniver
[Managing releases in a repository](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository)
[Miniver](https://pypi.org/project/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.