Package: hello-chp
BBL - Publier un package en open-source en dix étapes clésQuelles sont les étapes indispensables pour publier un package Python en open-source ? Depuis l’écriture d’un code propre et la rédaction de la documentation, jusqu’aux tests d’intégration continue et au processus de packaging, nous passerons en revue les dix points clés pour une publication d’un package Python en open-source. Pour ce faire, nous prendrons l’exemple d’un toy model que nous publierons sur github et pypi en moins de deux heures.SommaireVoici les 10 bonnes pratiques de développement open-source détaillées ci-après dans ce tutoriel :Mettre en place un dépôt GitHub,soit à partir de zéro, soit en forkant un dépôt existantEncapsuler les fonctions dans un modulefacile à importer et renseignant un numéro de versionDocumenter les fonctions avec une dosctring et un doctest.La docstring sera automatiquement publié en ligne et le doctest automatiquement exécuté pendant l’intégration continue.Ecrire vos fonctions avec déclaration de types.C’est une habitude facile à prendre qui génère automatiquement des tests unitaires statiques avecMyPy.Créer des tests unitaires avec un objectif de couverture de 100%.La paramétrisation des tests avecpytest.xmark.parametrizepermet de générer des tests très rapidement.Implémenter une intégration continue du code.Sur GitHub, le standard est d’utiliser des GitHub Actions. Pensez à toujours tester votre code sur Windows.Générer une documentation semi-automatique avec Sphinx_.L’API de votre package est automatiquement documentée si vous avez écrit les docstrings à l’avance. Il ne reste plus qu’à rédiger les parties importantes et les messages à faire passer aux utilisateurs. Les exemples sont un bon moyen d’accompagner la montée en compétences rapide des utilisateurs.Déployer la documentation de manière continue avec ReadTheDocs_.Le déploiement continu doit se déclencher a minima à chaque pull request.Packager votre module avec le fichier setup.py.Ce fichier est la pierre angulaire de la publication surPyPi. Les numéros de version sont plus facile à gérer avecbump2version.Déployer votre package de manière continue avec les release GitHubet les actions correspondantes. Vous pouvez cacher votre mot de passePyPipar un système de tokens.Pré-requisAvoir un compte GitHubFaire unForkdu dépôt (bouton en haut à droite de GitHub)Avoir une installation locale de condaSi vous n’avez pas de conda installé : téléchargez l’installeurCondaou exécutez les commandes suivantes:$wgethttps://repo.anaconda.com/miniconda/Miniconda3-py39_4.9.2-MacOSX-x86_64.sh-Ominiconda.sh$chmod+xminiconda.sh$bashminiconda.shAttention à bien accepter la demande d’initialisation.Exercice n°1: Mise en place de l’environnementClonez votre dépôt forké.$gitclonehttps://github.com/COMPTE/how-to-opensource.gitInstallez et activez l’EnvCondade développement, environnement qui nous servira à développer le code, la documentation et les tests:cd how-to-opensource
conda env create -f environment.dev.yml
conda activate how_to_opensourceCréer une branche de travail et supprimez la correction :git checkout -b work
chmod +x start.sh
./start.sh
git add .
git commit -m "start exercises"
git push origin workVous pouvez commencer !Exercice n°2: Création d’un module et d’une fonctionNous allons maintenant créer dans leModulehow_to_opensourceune nouvelle fonction calculant la somme de deux vecteurs.
Pour cela créez le fichierhow_to_opensource/core.pyet créez une nouvelle fonctionadd_two_vectorsqui va, comme son
nom l’indique, effectuer une addition de deux vecteurs grâce ànumpy.add.Afin de pouvoir importer la fonction, vous devez définir les redirections d’imports dans le fichierhow_to_opensource/__init__.py.from.coreimportadd_two_vectorsfrom._versionimport__version____all__=["add_two_vectors","__version__"]La première ligne de code vous permet de faire directementfromhow_to_opensourceimportadd_two_vectorsau lieu defromhow_to_opensource.coreimportadd_two_vectorsLa ligne__all__ = ...permet à la fonction d’être importée avec la syntaxefrom how_to_opensource import *.Enfin, nous anticipons d’ores et déjà le packaging en introduisant un numéro de version dans le fichier_version.pyqui contient une seule ligne de code :__version__ = "0.0.0".Il est maintenant possible de tester interactivement la méthode :importnumpyasnpfromhow_to_opensourceimportadd_two_vectorsadd_two_vectors(np.ones(2),np.ones(2))ou la version du package :importhow_to_opensourceprint(how_to_opensource.__version__)Si vous voulez vérifier la syntaxe de votre code, vous pouvez exécuter la commande :$flake8how_to_opensourceCORRECTION :git checkout master how_to_opensource/__init__.py how_to_opensource/core.py how_to_opensource/_version.pyExercice n°3: TypingUne pratique courante pour rendre plus robuste un package consiste à utiliser le typing pour tout ou une partie du code.
Si l’interpréteur python ne vérifie pas ces types à l’exécution, le langage python propose néanmoins le vocabulaire et la grammaire
nécessaire à la définition de ces types par l’intermédiaire du moduleTyping.
Typez maintenant les définitions deadd_two_vectorset de sa fonction de test. Il est aussi possible d’ajouter un test à
l’exécution pour valider que les entrées se conforment au type attendu. Enfin lancez l’analyseur statique de code le second statique utilisantMyPy.$mypyhow_to_opensource--strictCORRECTION :git checkout master how_to_opensource/core.py mypy.iniExercice n°4: Documentation de la fonctionNumpydocpropose une méthode de documentation efficace. Ajoutez une documentation àadd_two_vectorsspécifiant ses paramètres,
sa sortie et en y incluant uneDocTest. Lancez ensuite la procédure de test en incluant cette fois le test de la documentation.$pytest-vs--doctest-modules--cov-branch--cov=how_to_opensource--pyargshow_to_opensourceCORRECTION :git checkout master how_to_opensource/core.pyExercice n°5: Création d’un test unitaireIl convient maintenant de tester cette fonction avecPyTest. Une méthode standard pour élargir rapidement le domaine testé est
d’utiliserParameterizepour paramétriser les fonctions de test.
Danshow_to_opensource/tests/test_core.pyajoutez une fonction de test validant le bon fonctionnement deadd_two_vectorsen testant différentes dimensions de vecteurs. Lancez maintenant le test en générant les métriques validant que vos tests couvrent bien le code:$pytest-vs--doctest-modules--cov-branch--cov=how_to_opensource--pyargshow_to_opensourceCORRECTION :git checkout master how_to_opensource/tests/test_core.pyExercice n°6: Intégration continue du codeAfin d’assurer un niveau de qualité constant, particulièrement dans le cas d’un projet opensource avec de multiples contributeurs, il est
indispensable d’automatiser le processus d’intégration des changements réalisés. C’est à ce point que répond l’intégration continue.
Se basant sur la description d’un pipeline incluant build, test et déploiement, les outils d’integration continue, par exempleGitHubActionsouTravisCIen permettent l’automatisation. Cela apporte les valeurs suivantes:minimiser la charge de travail pour les concepteurssupprimer les erreurs arrivant dans toute action “à la main”réduire le temps nécessaire à la détection et l’analyse de problèmes car chaque changement est validé unitairementréduire le temps de cycle pour la livraison de nouvelles fonctionnalités tout en en améliorant la qualitéNous allons utiliser les GitHub actions, pour cela rendez vous sur l’ongletActionsde la page GiHub de votre projet.
Pour créer notre workflow d’intégration continue nous allons partir du templatePython Package using Anaconda, disponible après avoir
cliqué surSetup this workflow. Créez le fichiertest.ymldans le dossier.github/workflows, copiez le template proposé par GitHub
puis modifiez ensuite les étapes du workflow pour coller aux éléments définis précédemment:déploiement sur Python 3.9 , Python 3.8, Ubuntu et Windowsinstallation de flake8, mypy, numpy, et pytest-covtester le linting, le typing et les tests unitairesUne fois le fichier créé poussé sur le dépôt, vous pouvez suivre l’execution du pipeline depuis l’interface de GitHub.
Un mail vous sera automatiquement envoyé en fin d’execution pour vous informer des résultats.CORRECTION :git checkout master .github/workflows/test.ymlExercice n°7: Génération de la documentationAvoir une documentation à jour est indispensable autant pour les utilisateurs que pour les contributeurs.
Afin de faciliter la création et la maintenance de celle-ci nous allons utiliserSphinx. Le quick start de Sphinx permet l’initialisation rapide des éléments nécessaires.$sphinx-quickstartdocNote: il n’est pas nécessaire de séparer les répertoires sources et build dans notre cas simple.Pour générer la documentation il vous suffit maintenant d’exécuter le script nouvellement créé:$cddoc$makehtmlLa documentation a été générée dans le repertoiredoc/_build, vous pouvez la consulter dans votre navigateur web, elle est belle, mais vide.
En plus de la rédaction que vous ne manquerez pas d’ajouter, il est important de capitaliser sur la documentation écrite à l’exercice n°4.
Pour ce faire, il faut d’abord modifier le fichierdoc/conf.pypour ajouter'sphinx.ext.autodoc','sphinx.ext.napoleon', et'sphinx_autodoc_typehints'à la liste des extensions.
Il faut également définir la version du package:release=0.0.0Enfin, il faut ajouter la documentation automatique du module dansdoc/index.rstqui sera par ailleurs le point d’entrée de toute rédaction additionnelle:.. automodule:: how_to_opensource
   :members:Afin de permettre de trouver le module et d’activer la prise en compte des types, ajoutez les lignes suivantes au fichierdoc/conf.py:importsyssys.path.append('../')napoleon_use_param=TrueUne méthode efficace pour enrichir la documentation consiste à ajouter des exemples que l’on met en valeur à l’aide deSphinxGallery.
Dansdoc/conf.py, ajoutez l’extension'sphinx_gallery.gen_gallery', puis définissez la configuration de la galerie:sphinx_gallery_conf={'examples_dirs':'../examples',# path to your example scripts'gallery_dirs':'auto_examples',# path to where to save gallery generated output}Enfin il est nécessaire d’inclure cette galerie à la racine de la documentation, dansdoc/index.rstajoutez son inclusion:.. toctree::
  :maxdepth: 2

  auto_examples/indexPour créer un exemple qui s’affichera dans la doc, vous devez simplement créer un script python dans le répertoireexamples. Par exemple :"""
===========
Toy Example
===========
L'exemple le plus simple que l'on puisse imaginer.
"""fromhow_to_opensourceimportadd_two_vectorsadd_two_vectors([12.5,26.1],[7.5,3.9])Le dossierexamplestout juste créé doit s’accompagner d’un fichierREADME.rstavec un titre comme:Exemples avancés
================Vous pouvez alors reconstruire la doc avecmake htmlet vérifier que votre documentation est belle !open doc/_build/html/index.htmlCORRECTION :git checkout master doc examplesExercice n°8: Déploiement continu de la documentationPour diffuser cette documentation il est nécessaire de la publier sur un site publique, par exemple en utilisantReadTheDocs.
Ce dernier réalisera les tâches définies dans le fichier.readthedocs.yml, ajoutez donc ce fichier au dépôt avec le contenu suivant:version: 2

build:
  image: latest

conda:
  environment: environment.dev.yml

sphinx:
  builder: html
  configuration: doc/conf.py
  fail_on_warning: falseEnsuite, créez un compte gratuit surReadTheDocsen utilisant votre login GitHub.Une fois inscrit et connecté, importez votre projet GitHub (attention à ajouter votre trigramme à l’url du projet par souci d’unicité).Après avoir soigneusement choisi la branche et la version, lancez la compilation. Suivez son bon déroulement et vérifiez que la documentation produite est conforme à vos attentes.Pour automatiser la compilation de la doc à chaque pull request, allez ensuite dans Admin > Paramètres avancés et cochez la case “Build pull requests for this project”.
Il faut également connecter vos comptes GitHub et ReadTheDocs par un webhook comme suit :sur votre compte ReadTheDocs, allez dans Admin > Integrations > Add integration > GitHub incoming webhooksur votre repo GitHub, allez dans Settings > Webhooks > Add webhook > copier l’URL “payload URL” de readthedocs.Et voilà ! Votre documentation se reconstruit automatiquement à chaque pull request !CORRECTION :git checkout master .readthedocs.ymlExercice n°9: PackagingDe façon à offrir une API claire à l’ensemble des modules de notre projet (certes il n’y en a qu’un en l’état mais cela est voué à changer),
il est utile de créer unpackagequi permet d’avoir un espace de nommage encapsulant les modules et variables, et diffusable directement surPyPi.
Pour cela, il est nécessaire d’ajouter un fichiersetup.pyà notre projet, et de le définir, vous pouvez pour cela partir de cetutoriel.Voici un exemple de fichiersetup.py, ce sont essentiellement des descripteurs qui s’afficheront tels quels surPyPi.IMPORTANT :chaque package doit avoir un nom unique surPyPi, qui est déduit du paramètrename. Pensez-bien à ajouter votre trigramme dans lenamepour que chacun puisse publier son package sans conflit de noms.importosfromsetuptoolsimportsetupdefread(fname):returnopen(os.path.join(os.path.dirname(__file__),fname)).read()setup(name="QM How to Opensource by TRIGRAMME",version="0.0.1",author="Grégoire Martignon, Vianney Taquet, Damien Hervault",author_email="gmartignon@quantmetry.com",description="A Quantmetry tutorial on how to publish an opensource python package.",license="BSD",keywords="example opensource tutorial",url="http://packages.python.org/how_to_opensource",packages=['how_to_opensource'],install_requires=["numpy>=1.20"],extras_require={"tests":["flake8","mypy","pytest-cov"],"docs":["sphinx","sphinx-gallery","sphinx_rtd_theme","numpydoc"]},long_description=read('README.rst'),classifiers=["License :: OSI Approved :: BSD License","Programming Language :: Python :: 3.9"],)Il ne vous reste plus qu’à construire votre package$pythonsetup.pysdistbdist_wheelCela crée trois répertoires :dist,buildetQM_How_to_Opensource.egg-info.Leegg-infoest une simple collection de fichiers texte purement informatifs, et ledistest le contenu de ce qui sera hébergé surPyPi.Si vous voulez vérifier que votreREADME.rstest sans erreur, vous pouvez exécuter la commande$twinecheckdist/*N.B.Cette commande vérifie le contenu du répertoiredist. En conséquence, si vous modifiez leREADME.rst, il faut exécuter à nouveau la commandepython setup.py sdistpour faire un nouveau check.Dernier élément d’un package open-source: la license. Elles sont toutes disponibles surOpenSourceInitiative, il suffit de la copier-coller dans le fichierLICENSEet de remplacer les noms des auteurs et la date !Pour un projet open-source entièrement libre, la license new BSD-3 est courante en machine learning..Notre package est maintenant en place, prêt à être publié et ouvert à sa communauté d’utilisateurs et de contributeurs. Il est nécessaire de donner à ses deux populations les outils dont ils ont besoin.
Une accessibilité simple et maîtrisée pour les premiers, de clarté sur les règles de leur engagement pour les seconds.Dans la mesure où ce nom de version va se retrouver à plusieurs endroits (setup.py,doc/conf.py, …), et pour ne pas risquer d’erreurs dans le maintien en cohérence de cette information à plusieurs endroits, il est possible d’utiliserbump2version. Pour cela créez un fichier.bumpversion.cfgà la racine du projet, ce dernier va définir dans quel fichier remplacer automatiquement le numéro de version. Ajoutez-y le contenu ci-dessous et assurez vous que tous les fichiers contiennent initialement les mêmes numéros de version, par la suite ils seront mis à jour automatiquement :[bumpversion]
current_version = 0.0.0
commit = True
tag = True

[bumpversion:file:setup.py]
search = version="{current_version}"
replace = version="{new_version}"

[bumpversion:file:how_to_opensource/_version.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

[bumpversion:file:doc/conf.py]
search = release = "{current_version}"
replace = release = "{new_version}"Vous pouvez désormais incrémenter le numéro de version avecbumpversion.
Trois choix sont possibles pour l’incrémentation du numéro de version: patch, minor, et major. Nous choisissons ici d’incrémenter le “patch”:$bumpversionpatch$gitpush--tagsVotre publication surPyPise fait simplement avec la commande :$twineuploaddist/*Attention, cette commande nécessite un identifiant et un mot de passe, il faut donc vous créer un compte au préalable surPyPi.CORRECTION :git checkout master setup.py LICENSE .bumpversion.cfgExercice n°10: déploiement continuMaintenant nous allons mettre en place la publication automatique surPyPiaprès chaque release officielle de votre package.
Le but est de déclencher automatiquement, à la publication d’une nouvelle release depuis GitHub, la publication de la nouvelle version du package vers PyPi.
Cela signifie donc que le workflow GitHub devra se connecter à votre comptePyPi.
Pour ne pas avoir à mettre en clair les éléments nécessaires à cette authentification dans votre dépôt, il existe un mécanisme permettant de se connecter à
PyPi sur base d’un token, et de stocker ce token en tant qu’élément secret dans le dépôt GitHub.
Pour cela, une fois connecté sur PyPi:Rendez-vous sur la pageAccount Settingset descendez jusqu’à la sectionAPI Tokens.Cliquez surAdd Token, donnez lui un nom, par exemplehow-to-opensourceet donnez lui accès au scope complet.Copiez le token généré et gardez cette page ouverte au cas où.Dans une autre fenêtre, rendez vous sur votre dépôt GitHub à la pageSettings, sectionSecrets.Appelez le PYPI_API_TOKEN et collez dans le champValuele token copié depuisPyPi.Nous pouvons maintenant mettre en place le workflow de publication automatique, pour cela:Rendez vous dans l’ongletActionsdu projet GitHub et cliquez surNew workflow.Choisissez le templatePublish Python Package, renommez le fichierpublish.yml, spécifiez la version 3.9 de python et confirmez l’ajout du workflow.Pour déclencher le workflow, allez sur la page principale du dépôt GitHub, à droite, cliquez sur Releases. Vous devriez voir tous les tags poussés jusqu’à présent. Choisissez le dernier et cliquez sur “Edit tag”. Pensez à bien pointer sur la branchework. Cliquez ensuite sur “Publish release”. L’action de publication s’est normalement déclenchée dans l’onglet GitHub Actions. Une fois terminée, vous pouvez vérifier que la mise à jour surPyPis’est bien déroulée.Enfin il convient d’ajouter de documenter les règles de contribution et d’usage du package. Pour cela rendez vous dans la pageInsights/Communityde GitHub. Cette dernière fournit un moyen simple d’initier les documents nécessaires.Vous pouvez également naviguer dans l’onglet Insights > Community de github et remplir votre projet avec des template d’issue, pull request ou codes de conduite.IMPORTANT :Vous avez déjà publié une version de votre package à l’étape précédente. Pour republier une nouvelle version, vous être obligé de “bumper” la version à nouveau :$bumpversionpatch$gitpush--tagsCORRECTION :git checkout master .github/workflows/publish.ymlRécapitulatifVoici les 10 bonnes pratiques de développement open-source:Mettre en place un dépôt GitHub,soit à partir de zéro, soit en forkant un dépôt existantEncapsuler les fonctions dans un modulefacile à importer et renseignant un numéro de versionDocumenter les fonctions avec une dosctring et un doctest.La docstring sera automatiquement publié en ligne et le doctest automatiquement exécuté pendant l’intégration continue.Ecrire vos fonctions avec déclaration de types.C’est une habitude facile à prendre qui génère automatiquement des tests unitaires statiques avecMyPy.Créer des tests unitaires avec un objectif de couverture de 100%.La paramétrisation des tests avecpytest.xmark.parametrizepermet de générer des tests très rapidement.Implémenter une intégration continue du code.Sur GitHub, le standard est d’utiliser des GitHub Actions. Pensez à toujours tester votre code sur Windows.Générer une documentation semi-automatique avec Sphinx_.L’API de votre package est automatiquement documentée si vous avez écrit les docstrings à l’avance. Plus qu’à rédiger les parties importantes et les messages à faire passer aux utilisateurs. Les exemples sont un bon moyen d’accompagner la montée en compétences rapide des utilisateurs.Déployer la documentation de manière continue avec ReadTheDocs_.Le déploiement continu doit se déclencher a minima à chaque pull request.Packager votre module avec le fichier setup.py.Ce fichier est la pierre angulaire de la publication surPyPi. Les numéros de version sont plus facile à gérer avecbump2version.Déployer votre package de manière continue avec les release GitHubet les actions correspondantes. Vous pouvez cacher votre mot de passePyPipar un système de tokens.BONUS: Gestion du dépôt sur le long termeQuelques bonnes pratiques de gestion du dépôt sur le long terme :Tout problème ou amélioration du code doit faire l’objet d’une issue avant une pull request. Les pull request doivent être reliées aux issues qu’elles résolvent.Tout incrément de code doit passer par des pull request revue par une personne tierceL’onglet GitHub Projects vous permets d’organiser les issues sous formes de cartes simili-Trello, et rend publique votre feuille de route de développement.Il est recommandé d’ajouter deux fichiers de documentation à votre repo : unCONTRIBUTING.mdqui renseigne les contributeurs éventuels sur l’art et la manière de faire des pull request pour ce projet, et unRELEASE_CHECKLIST.mdrécapitulant toutes les étapes de vérification avant publication surPyPi. Vous trouverez un exemple surMAPIE.Bonus: BadgesNotre intégration continue est maintenant en place. Afin de donner une vue de synthèse de son execution et de donner confiance aux utilisateurs potentiels quand à la qualité du package, il est possible d’ajouter des badges qui donneront un status à jour de l’execution de l’intégration continue.
Il faut pour cela, ajoutez dans le README situé à la racine du dépôt les liens suivants:|GitHubActionTestBadge|_ |ReadTheDocsBadge|_ |GitHubActionPublishBadge|_ |PyPiBadge|_

.. |GitHubActionTestBadge| image:: https://github.com/simai-ml/how-to-opensource/actions/workflows/test.yml/badge.svg
.. _GitHubActionTestBadge: https://github.com/simai-ml/how-to-opensource/actions

.. |ReadTheDocsBadge| image:: https://readthedocs.org/projects/how-to-opensource/badge
.. _ReadTheDocsBadge: https://how-to-opensource.readthedocs.io/en/latest

.. |GitHubActionPublishBadge| image:: https://github.com/simai-ml/how-to-opensource/actions/workflows/publish.yml/badge.svg
.. _GitHubActionPublishBadge: https://github.com/simai-ml/how-to-opensource/actions

.. |PyPiBadge| image:: https://img.shields.io/pypi/v/QM-How-to-Opensource
.. _PyPiBadge: https://pypi.org/project/QM-How-to-Opensource/