Contenu
Vous travaillez régulièrement avec Python, dans un environnement Azure et vous souhaitez faire gagner du temps à votre équipe ? Vous êtes au bon endroit. Nous allons, étape par étape, passer d’un script banal, copié-collé-adapté au grè des projets à une commande unique, résiliente, utilisable par toute votre entreprise.
Volet 1 :
- La modularisation & packaging de code Python
- L’intégration sur Azure
Volet 2 (a et b) :
- L’installation du package depuis Artifacts
- Déploiement manuel d’une function app utilisant le package
- Pipeline CI CD pour une function app utilisant le package
Volet 3 :
- L’intégration continue avec le testing
- La génération de documentation automatisée
Contexte
Vous avez des sujets réguliers de data science à traiter, les données sont sur un Azure DataLake Storage. On va donc industrialiser un code qui permet de se connecter au DataLake, de télécharger le fichier et de le charger dans un DataFrame.
Préparation
Créez 2 fichiers, mon_script.py et requirements.txt, puis initialisez un nouvel environnement.
python3 -m venv .env source .env/bin/activate
On va maintenant munir notre environnement des paquets nécessaires, que l’on va lister dans un fichier « requirements » .
requirements.txt :
pandas azure-storage-file-datalake azure-identity
Puis dans la console :
python3 -m pip install --upgrade pip pip install -r requirements.txt
Modularisation et Packaging
Voici le code que nous allons industrialiser, il permet en pratique de charger in-memory un DataFrame csv en unicode qui est dans le DataLake.
import os, io import pandas as pd from azure.storage.filedatalake import DataLakeServiceClient, FileSystemClient from azure.identity import DefaultAzureCredential def LoadAdlsFileToDataframe(containerName: str, directory: str, fileName: str) -> pd.DataFrame: # Fetching credentials creds = DefaultAzureCredential() # Connecting to the datalake serviceClient = DataLakeServiceClient( account_url="https://{}.dfs.core.windows.net".format(os.environ["datalakeName"]), credential=creds ) # Fetching the file fileSystemClient = serviceClient.get_file_system_client(containerName) fileClient = fileSystemClient.get_file_client(directory + "/" + fileName) downloadedBytes = fileClient.download_file().readall() decodedString = downloadedBytes.decode("utf-8") # Loading as stream csvFile = io.StringIO(decodedString) # Loading the dataframe df = pd.read_csv(csvFile) return df
Il est maintenant temps de modulariser ce code pour ça rien de plus simple, il suffit de renommer notre fichier en « __init__.py» et de le placer dans le dossier qui sera le module à importer.
Il nous est désormais possible d’importer le code depuis notre moduleTuto.
Packageons maintenant ce projet afin de le rendre téléchargeable et installable via pip.
Avant tout partage, il est important d’ajouter quelques éléments à notre projet : un README.md et une licence.
Je ne traiterai pas ici la rédaction de ces 2 fichiers, vous trouverez cependant de l’aide sur la rédaction du fichier LICENSE ici.
Une fois ces conditions remplies, nous allons pouvoir créer un nouveau fichier « setup.py » qui utilise la librairie setuptools. Il s’agit de la librairie préconisée pour le packaging par la Python Packaging Authority. Nous aurons aussi besoin de « wheel » qui est un format permettant l’installation du package.
On commence par mettre à jour la librairie.
pip install --upgrade setuptools pip install --upgrade wheel
Puis on insert dans « setup.py » le code suivant :
import setuptools requirements = [] with open("requirements.txt", "r") as f: requirements = f.read().split("n") setuptools.setup( name="tuto-package", description="Ce package a vocation de tutoriel.", version="0.0.1", author="Charles GOSSELIN", author_email="charles.gosselin@dcube.fr", packages=setuptools.find_packages(), install_requires=requirements, python_requires='>=3.6' )
Il s’agit là d’une version assez light, on pourrait ajouter bien des paramètres à ce setup mais je tiens à le garder (presque) minimal. On couvre le plus important : le nom, la version, les dépendances.
Les deux paramètres « install_requires » et « packages » prennent en entrée des listes de string, il aurait donc été possible de spécifier à la main les différents modules à intégrer dans le package et les dépendances nécessaires à leur fonctionnement. Ces dernières seront installées par pip automatiquement lorsqu’un utilisateur installera votre librairie.
La version : il est important de la changer à chaque mise à jour, selon la nomenclature que vous aurez choisi ou de manière arbitraire peu importe, mais il faut l’incrémenter. C’est une condition nécessaire au remplacement de l’ancienne version de votre package, à la fois sur plateforme où vous l’hébergerez mais également dans les environnements de développement des personnes qui voudraient l’installer.
Notez que l’on vient récupérer la liste des packages listés dans le requirements.txt, ils y ont été inscrits sans version définie, mais vous pouvez tout à fait contraindre votre package à fonctionner dans un environnement plus spécifique en indiquant un intervalle de version, par exemple : « azure-identity >= 1, <1.5 »
On passe maintenant sur la CLI que Setuptools nous met à disposition. On peut commencer par en lister les commandes en exécutant :
python setup.py --help-commands
Celle qui nous intéresse ici est « bdist_wheel » (bdist pour built distribution) qui va nous permettre de créer une distribution au format wheel, qui s’installe avec pip.
python setup.py bdist_wheel
On remarque l’apparition de 2 nouveaux dossiers dans notre projet, « dist » et « tuto_package.egg-info ». Dist contient le wheel de notre package, et tuto_package.egg-info contient des métadonnées.
Pour aller plus loin sur le packaging : le blog de Publicis Sapient
Ca y est ! Votre solution est packagée.
L’intégration sur Azure
Maintenant que notre projet est packagé, il est temps de le distribuer afin que nos collègues, proches et lointains puissent en profiter.
Nous allons créer un nouveau « Feed » dans Azure Artifacts. Pour cela, rendez-vous dans Azure DevOps, dans un projet auquel vous avez accès, puis dans la section Artifacts.
Un feed n’est rien de plus qu’une source, à laquelle on se connecte pour télécharger des packages, avec une gestion des accès et des versions des paquets distribués.
Sélectionnez les paramètres de partage qui conviennent le mieux à votre package. Dans mon cas, les packages, que je vais partager dans ce feed, se voudront très génériques et utiles au-delà d’un seul projet donc je vais le partager à toute l’organisation.
Maintenant que l’on a un feed prêt à recevoir notre paquet pour l’exposer, on peut s’y connecter pour déposer notre fichier.
On clique sur « Connect to feed » et là 2 options en rapport avec Python s’offrent à nous. On va commencer par Twine.
Comme indiqué, on crée un fichier .pypirc à la racine de notre projet et on y colle ce qui nous est donné.
Avant de continuer et afin de pouvoir publier notre travail, il nous manque 2 packages. Donc retour au terminal et là on installe :
- Artifacts-Keyring qui va permettre à notre CLI de s’authentifier auprès d’Azure ;
- Twine qui est un utilitaire de diffusion de packages.
pip install twine artifacts-keyring
Puis on exécute la 2eme commande qui nous est donnée dans le menu « Connect To Feed » (on a déjà produit une distribution du projet), et là … c’est le drame.
Pourtant on a suivi les instructions de MS à la lettre ! Pas de souci, un tour dans la doc de Twine et on y apprend qu’il suffit d’ajouter un paramètre « config-file ». La nouvelle commande à taper est donc :
twine upload -r <nomDuPackage> --config-file .pypirc dist/*
Tout se passe bien, on nous demande maintenant d’ouvrir une page MS et d’y insérer le code.
Ce code expire très vite (90s) donc ne tardez pas à ouvrir la fenêtre.
Une fois l’authentification réussie, l’upload devrait démarrer.
Félicitations, vous venez de mettre votre package dans le feed Artifacts ! Vos collègues peuvent désormais le télécharger en suivant les instructions de la section « pip ».
Super boulot Charles (comme d’hab) !
good job !
Aymeric Inpong