Quel langage pour la préparation de données sur Azure Databricks ?

Azure Databricks se positionne comme une plateforme unifiée pour le traitement de la donnée et en effet, le service de clusters managés permet d’aborder des problématiques comme le traitement batch, mais aussi le streaming, voire l’exposition de données à un outil de visualisation comme Power BI.

La capacité de mise à l’échelle (scalabilité ici horizontale) d’Azure Databricks  est également un argument de poids pour établir cette solution dans un contexte de forte volumétrie. Par forte volumétrie, nous entendons ici le fait qu’un tableau de données, ou les opérations qui permettraient d’y parvenir,  dépasse la mémoire disponible sur une seule machine. La solution sera donc de distribuer la donnée ou les traitements. Mais attention, il va falloir bien choisir ses armes avant de se lancer dans un premier notebook !

Le choix des armes

A la création d’un notebook sur Azure Databricks, quatre langages sont disponibles.

Sauf à ne vouloir faire que du SQL, je vous déconseille de prendre ce type de notebook puisqu’il sera très simple d’exécuter une commande SQL dans une cellule à l’aide la commande magique %sql.

Si vous venez de la Data Science « traditionnelle » (rien de péjoratif, c’est d’ailleurs mon parcours, comprendre ici l’approche statistique antérieure à l’ère de la Big Data), vous serez attirés par le langage R. Mais vous savez sûrement que R est un langage, de naissance, single-threaded et peu apte à paralléliser les traitements. Microsoft a pourtant racheté la solution RevoScaleR de Revolution Analytics et l’a intégrée en particulier à SQL Server. Dans un contexte Spark, on peut s’orienter vers l’API SparkR mais celle-ci ne semble pas soulever un très gros engouement (contredisez-moi dans les commentaires) dans la communauté.

Python remporte aujourd’hui une plus forte adhésion, en particulier auprès des profils venant du monde du développement. Attention, coder un traitement de données en Python ne vous apportera aucune garantie d’amélioration par rapport au même code R ! Ici, plusieurs choix se présentent à nouveau.

Une première piste sera d’exploiter la librairie Dask qui permet de distribuer les traitements.

Une deuxième possibilité est de remplacer les instructions de la librairie pandas par la librairie développée par Databricks : koalas. Je détaillerai sûrement l’intérêt de cette librairie dans un prochain article.

Le troisième choix possible sera à mon sens le meilleur : l’API pyspark. Cette API va vous permettre :

  • de créer des objets Spark DataFrames
  • d’enregistrer ces DataFrames sous formes de tables (locales ou globales dans Databricks)
  • d’exécuter des requêtes SQL sur ces tables
  • de lire des fichiers au format parquet

Les API Spark permettent d’écrire dans un langage de plus haut niveau et plus accessible pour les développeurs (data scientists ou data engineers) qui sera ensuite traduit pour son exécution dans le langage natif du cluster.

Les différents codes possibles

Je vous propose ci-dessous un comparatif des approches pandas et pyspark pour un même traitement de données. Le scénario est de charger les données de la base open data des accidents corporels, soit deux dossiers de fichiers csv : les usagers et les caractéristiques des accidents. Une fois les fichiers chargés, une jointure sera réalisée entre les deux sources sur la base d’une clé commune.

L’objectif est ici d’établir un parallèle entre les syntaxes et non d’évaluer les performances. La librairie pandas n’est d’ailleurs pas la plus optimale pour charger des fichiers csv et on mettra à profit les méthodes plus classiques de lecture de fichiers.

A l’import, création d’un dataframe

# pandas
df = pd.read_csv(filename)

# pyspark
df = spark.read.format("csv") \
.option("header", "true").option("inferSchema", "true").load("filename")  

Avec pandas, il pourra être nécessaire de caster certaines colonnes dates suite à l’import.

La lecture en Spark autorise l’emploi de caractères génériques ( ? ou *) pour lire et concaténer automatiquement plusieurs fichiers. Les archives .zip contenant par exemple un fichier .csv peuvent être chargées sans décompression préalable, ce qui ne sera pas le cas avec pandas.

Fusion de deux dataframes

# pandas
df = pd.merge(df1, df2, on='key', how='inner')

# pyspark
df = df1.join( df2, 'key', how='inner') 

L’avantage de Databricks sera de pouvoir persister certains dataframes sous forme de vues (« tables locales ») ou de tables (« tables globales).  En particulier, le format delta pourra être mis à profit. Dès lors, il devient très simple de travailler en Spark SQL pur, toujours en débutant la cellule par la commande magique %sql.

Vérification du dataframe

# pandas
df.shape
df.info

# pyspark
df.count()
len(df.columns)
df.printSchema

Pandas se montre ici à son avantage avec des résultats plus simples à obtenir pour connaître les dimensions du dataframe (shape) ou des informations sur le schéma et les valeurs manquantes.

En conclusion

Lorsqu’on pose à certains érudits musicaux la sempiternelle question « Stones ou Beatles », il n’est rare d’entendre la réponse suivante : « ni l’un, ni l’autre, mais les Kinks ! »

A la question non moins sempiternelle « R ou Python », je répondrai donc… Scala ! C’est en effet le langage natif de Spark et donc le plus proche du moteur d’exécution.

Malgré tout, le choix se fera aussi en fonction de critères plus pragmatiques comme la puissance nécessaire au traitement, le coût de travailler sur l’exhaustivité des données (pourquoi par un échantillon représentatif ?) ou la maîtrise des différents langages par les personnes en charge des développements.

Azure Fundamentals, c’est… fondamental !

Soyons un peu plus précis que ce titre tautologique. Toute personne mettant aujourd’hui le doigt dans le cloud de Microsoft va se retrouver confrontée à une quantité de services disponibles mais surtout de problématiques associées à la bonne mise en œuvre au sein de son organisation : choix de la meilleure solution, coûts, sécurité, monitoring, optimisation, gouvernance, etc.

Définitivement, le mouton à 5, 6, 7… pattes n’existe pas et il est impensable pour un seul être humain de maîtriser l’ensemble des pans liés au déploiement de solutions cloud Azure. Pour autant, connaître certaines notions s’avèrent bien utiles pour ne pas rater une étape cruciale lors d’un projet nécessitant une architecture cloud.

Si vous souhaitez prendre le virage du Cloud et surtout de celui de Microsoft, je vous conseille fortement de vous attaquer à la certification AZ-900.

Un contenu de formation est disponible sur la plateforme Microsoft Learn. Vous préfèrerez peut-être le lire en français mais je vous recommande de connaître les termes anglais relatifs aux différentes notions ainsi qu’aux services Azure.

Enfin, bonne nouvelle, l’inscription (gratuite) à l’événement Microsoft Ignite The Tour Paris 2019 vous permettra de vous voir remettre sur place un voucher de passer cette certification !

Pour vous guider dans votre approche de la certification, voici les 12 chapitres du cours Microsoft Learn, pour lesquels je vous propose une sélection de mots-clés indispensables à connaître. Ces thèmes peuvent être vus selon l’arbre ci-dessous.

Thèmes de la formation Les fondamentaux d’Azure

# Concepts du cloud – Principes du cloud computing

  • Cloud computing (public, privé, hybride)
  • Shift & lift
  • Machine virtuelle / conteneur / serverless
  • Scalabilité horizontale / verticale
  • IaaS / PaaS / SaaS

# La base des services cloud – Introduction à Azure

  • Zones géographiques, zones de disponibilité
  • Régions, paires de régions
  • Centre de données
  • Contrat de niveau de service

# Services cloud de base – Architecture Azure et garanties de service

  • Compte
  • Abonnement
  • Locataire (Tenant ?)
  • Azure Active Directory
  • Ressource
  • Plan de support

# Créer un compte Azure

  • Portail Azure
  • Tableau de bord du portail
  • Place de marché Azure
  • Azure PowerShell / Azure CLI
  • Azure Cloud Shell
  • Application mobile Azure

# Services cloud de base – Gérer les services avec le portail Azure

  • Outils d’interaction et de gestion
    • Portail Azure
    • Azure PowerShell
    • Azure CLI
    • Azure Cloud Shell
    • Application mobile Azure
  • Place de marché Azure
  • Tableau de bord

# Services cloud de base – Options de calcul Azure

  • Machines virtuelles
  • Conteneurs
    • ACI (Azure Container Instances)
    • Azure Kubernetes Service (AKS)
  • Azure Batch (AI)
  • Azure App Service
  • Informatique Serverless
    • Azure Function (Application de fonction)
    • Azure Logic Apps

# Services cloud de base – Options de stockage de données Azure

  • Données structurées / semi-structurées / non structurées
  • Azure Blob Storage
  • Azure Files
  • Azure Data Lake Storage (gen2)
  • Azure Queue (file d’attente)
  • Niveau de stockage, chiffrement, réplication
  • Azure Storage Service Encryption

# Services cloud de base – Options de réseau Azure

  • Architecture multiniveau
  • Réseau virtuelle
  • Sous-réseau
  • Adresse IP publique / privée
  • Passerelle VPN
  • Groupe de sécurité réseau (trafic entrant)
  • Disponibilité, haute disponibilité
  • Résilience
  • Equilibreur de charge, pool
    • Azure Load Balancer
    • Azure Application Gateway
  • Réseau de distribution continu
  • DNS
  • Latence réseau / bande passante
    • Trafic Manager

# Sécurité, responsabilité et approbation dans Azure

  • Sécurité en couches
    • Azure Security Center
  • Authentification (AuthN) unique – multifacteur, autorisation (AuthZ)
    • Azure Active Directory
  • Distribution d’identités aux services
    • Service principal
    • Identité managée
  • Contrôle d’accès en fonction du rôle (RBAC ?)
    • Azure Resource Manager
    • Azure AD Privileged Identity Management (PIM)
  • Chiffrement
    • symétrique / asymétrique
    • au repos / en transit
  • Azure Storage Service Encryption
  • Transparent Data Encryption
  • Azure Disk Encryption
  • Azure Key Vault
  • Azure DDoS Protection
  • Groupe de sécurité réseau
  • Réseau privé virtuel (VPN)
    • Azure ExpressRoute
  • Microsoft Azure Information Protection (MSIP / AIP)
  • Azure Advanced Threat Protection (ATP)

# Appliquer et superviser les standards d’infrastructure avec Azure Policy

  • Azure Policy
  • Groupe de gouvernance
  • Azure Blueprint
  • Déclaration de confidentialité Microsoft
  • Centre de gestion de la confidentialité Microsoft
  • Portail d’approbation de services
  • Gestionnaire de conformité
  • Azure Monitor
  • Azure Health Service

# Contrôler et organiser les ressources Azure avec Azure Resource Manager

  • Groupe de ressources
  • RBAC
  • Etiquettes
  • Stratégies
  • Verrous

# Prévoir les coûts et optimiser les dépenses pour Azure

  • Zone de facturation
  • Azure Advisor
  • Azure Cost Management
  • Azure Hybrid Benefit

Ne laissez pas vos access keys dans vos notebooks !

Dans une architecture complète Cloud tout en services managés, Azure Databricks est extrêmement efficace pour se connecter aux données des comptes de stockage Azure : Blob Storage, Data Lake Store gen1 ou gen2.

Pour autant, il n’est pas raisonnable d’utiliser la solution de facilité et de se connecter au travers des commandes suivantes (exemple en Python) :

storage_account = "<nom de votre ressource Azure Storage>"
container = "<nom du container souhaité>"
storage_account_access_key = <"iciuneclévisiblecequilnefautjamaisfaire!">
dbutils.fs.mount(
  source = "wasbs://"+container+"@"+storage_account+".blob.core.windows.net",
  mount_point = "/mnt/"+container,
  extra_configs = { "fs.azure.account.key."+storage_account+".blob.core.windows.net":  storage_account_access_key })

La procédure permettant de cacher les informations de sécurité est relativement simple, la voici en détails.

Databricks CLI

Le principe est de créer des secret scopes, au moyen de l’interface de lignes de commandes (CLI) de Databricks. Celle-ci s’obtient au travers de l’installation d’un package python.

pip install databricks-cli
Installation du package databricks-cli (ici dans un virtualenv dédié)

Pour une première utilisation, il est nécessaire d’associer l’espace de travail Azure Databricks avec le poste où sera exécuté le CLI. Une fois le package installé, saisir la commande suivante :

databricks configure --token

Deux informations sont alors attendues : l’URL du service managé puis un jeton d’identification préalablement créé depuis l’espace de travail Databricks, à partir du menu Users settings > Access tokens.

Générer un token d’accès pour Databricks CLI

Les commandes du package sont maintenant en interaction avec la ressource Databricks, en voici quelques exemples :

databricks workspace ls
databricks clusters spark-versions
databricks fs ls dbfs:/delta/
Quelques commandes du CLI Databricks

Créer un secret scope

Passons maintenant aux commandes qui définiront le secret scope (un scope peut contenir plusieurs informations secrète).

databricks secrets create-scope --scope scopeAzStorage
databricks secrets put --scope  scopeAzStorage  --key accessKeyAzStorage

La seconde commande ouvre alors un éditeur de texte, type VI, dans lequel on copiera par exemple l’access key d’un Azure Blob Storage.

Créer (proprement) un point de montage

Voici maintenant le code Python que l’on exécutera dans un notebook pour définir un point de montage, c’est-à-dire un accès simplifié aux fichiers contenu sur un compte de stockage Azure, ici en reprenant le nom du container dans le chemin d’accès.

dbutils.fs.mount(
  source = "wasbs://"+container+"@"+storage_account+".blob.core.windows.net/",
  mountPoint = "/mnt/"+container,
  extra_configs = { "fs.azure.account.key."+storage_account+".blob.core.windows.net" :dbutils.secrets.get(scope = "scopeAzStorage" , key = "accessKeyAzStorage" )}) 

A vous maintenant les commandes magiques… et ceci, en toute sécurité !

%fs ls /mnt/

(Cet article détaille par écrit la vidéo disponible ici.)

Versionning des notebooks sous Azure Databricks

A l’aide de GitHub

Comme pour tout développement, les notebooks méritent d’être archivés et versionnés. Tout notebook sera ainsi automatiquement sauvegardé et versionné dans l’espace de travail Azure Databricks (voir la documentation officielle).

Menu Revision history du notebok

Tant que le menu latéral Revision history est visible, il n’est pas possible de modifier le contenu du notebook.

Azure Databricks permet également d’utiliser un gestionnaire de versions externe parmi les trois solutions suivantes :

  • GitHub
  • Bitbucked Cloud
  • Azure DevOps Service

Dans un même espace de travail, il ne sera possible d’associer qu’un seul des trois gestionnaires (mais il serait sans doute étrange de versionner à différents endroits…). Notons que GitLab ne fait pas partie de cette liste, à ce jour, je n’ai pas réussi à le lier à Azure Databricks. Ce n’est pas le cas non plus de la version Enterprise de GitHub.

Rappel des notions et principes de base de Git

repository : c’est le répertoire de dépôt d’un projet de développement
master : version initiale et de référence du code
branch : lors de la suite des développements, il est important créer une nouvelle branche pour ne pas dégrager le master
commit : envoi de la liste des modifications effectuées
pull request : demande de prise en compte de modifications réalisées par un autre développeur
merge : appliquer les modifications à une autre branche, souvent le master

Nous allons découvrir maintenant comment se fait le lien entre l’espace de travail Azure Databricks et GitHub. Il faut tout d’abord se rendre sur la page dédiée aux paramètres de l’utilisateur (User Settings).

Paramétrage de l’intégration Git

Depuis le site GitHub, une fois identifié, il faut créer un jeton d’accès personnel, en suivant les écrans ci-dessous. Celui-ci devra disposer des droits complets sur le repo.

Génération d’un jeton d’accès personnel dans GitHub
Accorder le contrôle complet des repositories
Association réalisée avec succès

Nous pouvons maintenant quitter la page des paramètres de l’utilisateur pour nous rendre dans le notebook de notre choix. Le menu Revision history laisse apparaître le lien Git: Synced.

Association du notebook avec un repo GitHub
Enregistrement d’une première version
Première synchronisation réussie

Le fichier est maintenant bien créé sur notre compte GitHub dans le repo associé. Chaque nouvelle révision pourra être enregistrée et commitée, en associant un commentaire.

Enregistrement (et commit) d’un révision

Par défaut un notebook python est enregistré au format .py. Les commandes magiques ne sont pas perdues et seront correctement réinterprétées à l’import du fichier sur un autre espace de travail. Afin de converser les propriétés d’affichage du notebook dans GitHub, il suffit de forcer l’extention à .ipynb lors de la première synchronisation.

Ainsi, chaque nouvelle sauvegarde se fait donc sur la branche principale (master) mais il est bien sûr possible de créer de nouvelles branches du développement, en cliquant à nouveau sut Git: synced.

création et sélection d’une nouvelle branche

La création d’une nouvelle branche fait apparaître un hyperlien vers la pull request sur le compte GitHub.


Lien vers la pull request

La comparaison des modifications et l’éventuel merge des versions se fait ensuite sur la page GitHub.

Comparaison des modifications sous GitHub

Rappelons enfin qu’il est possible d’importer un fichier par son URL, et donc par l’URL obtenue depuis GitHub. Cette fonctionnalité, couplée à l’utilisation des paramètres dans un notebook, permet de recopier le notebook d’un environnement de développement à un environnement de production.


Import d’un fichier dans l’espace de travail
Import par URL

Dans un prochain article, nous explorerons les interactions entre Azure Databricks et Azure DevOps.

Piloter l’exécution des notebooks Databricks

Azure Databricks est un service managé de cluster Spark, permettant d’exécuter du code Scala, Python, R ou SQL sur des volumes importants de données, grâce à son approche distribuée et en mémoire.

Si le meilleur scénario de mise en production d’un traitement reste de créer un fichier Jar à partir de code Scala (voir un prochain billet de ce blog), il peut être très utile d’ordonnancer le lancement de notebooks Python car il n’existe pas aujourd’hui d’approche similaire à celle du fichier Jar.

Deux éléments seront importants dans une approche de mise en production :

  • Pouvoir modifier facilement un paramètre d’environnement (dev / qualif / prod par exemple)
  • Réaliser un enchainement conditionnel des traitements, avec un minimum de logs de suivi

Nous allons voir ici plusieurs solutions qui sont disponibles dans un environnement Azure. Nous considérons que nous disposons déjà des ressources suivantes :

  • Un compte de stockage Azure avec deux containers (« citidev » et « citiprod »)
  • Une ressource Azure Databricks avec un cluster déjà créé (« myDBcluster »)
  • Une ressource Azure Data Factory déployée
  • Un notebook Python contenant les transformations à effectuer (chargement des données et création d’une table temporaire)

Créer une tâche planifiée (job)

Le premier réflexe sera d’utiliser l’interface Azure Databricks de création de job.

Job scheduling in Azure Databricks

Nous précisions les éléments suivants :

  • Le notebook voulu
  • Le jour et l’heure d’exécution (syntaxe CRON de type 0 0 * * * ?)
  • Le cluster d’exécution ou bien la configuration d’un nouveau cluster qui sera créé au lancement
  • D’éventuelles dépendances de librairies
  • D’éventuels paramètres pour l’exécution

Nous allons nous servir de cette notion de paramètre pour passer à notre notebook le nom du container qui désigne l’environnement.

Afin de récupérer la valeur de ce paramètre dans le notebook, nous définissons une première cellule à l’aide du code ci-dessous.

Nous exploitons ici la notion de widget qui permet de récupérer une valeur dans un élément visuel comme une zone de texte, une liste déroulante, une combo box… La documentation officielle donne l’ensemble des possibilités.

Lancer un notebook depuis un autre

Nous nous appuyons de nouveau sur cette astuce pour piloter le lancement de notebooks à partir d’un notebook « master ».

La commande magique %run est suivie du chemin du notebook puis de la valeur attendue pour le paramètre d’environnement.

Nous constatons ici que le notebook lancé a renvoyé un message lorsqu’il s’est terminé. Cela est réalisé dans la dernière cellule du notebook par la commande ci-dessous.

Créer un pipeline Azure Data Factory

Azure Data Factory v2 est le parfait ordonnanceur de tâches de copie ou de transformation de données au sein de l’écosystème Azure et en interactions avec des sources extérieures au cloud de Microsoft. Charles-Henri Sauget a publié un ouvrage référence sur le sujet.

Nous devons ici définir un service lié de type Azure Databricks en précisant :

  • La souscription Azure utilisée
  • L’espace de travail Databricks
  • Un jeton d’accès (access token) que l’on obtient depuis l’interface Databricks

Attention à bien noter ce token lors de sa création, il ne sera plus possible de l’afficher par la suite !

Nous créons ensuite un pipeline contenant au moins une activité de type Databricks notebook.

Cette activité est associée au service lié défini préalablement.

Enfin, nous définissons le paramètre d’accès à l’environnement voulu.

Une astuce permet d’obtenir facilement le chemin vers le notebook : il suffit de cliquer dans l’explorateur sur la flèche à droite du notebook, puis cliquer sur “Copy File Path”.

Azure Data Factory met ensuite à disposition son système propre de planification ou de déclenchement sur événement (trigger).

En résumé

Selon la complexité de votre scénario, voici les trois possibilités qui s’offrent à vous :

  1. Planification d’un notebook unique
  2. Utiliser le job scheduler de l’espace de travail Databricks
  3. Enchainement de plusieurs notebooks
  4. Créer un notebook « master » et utiliser la commande magique %run
  5. Enchainement du notebook avec des traitement extérieurs à Databricks
  6. Créer un pipeline Azure Data Factory

Et pour aller encore plus loin dans l’accès aux bons environnements, n’oubliez pas d’utiliser les secret scopes que je présente dans cette vidéo.

Survol du service Azure Machine Learning

L’essor actuel du Machine Learning (apprentissage automatique, branche de l’intelligence artificielle) est en partie dû à la puissance de calcul obtenue au travers du Cloud Computing, représenté en particulier par Microsoft Azure.

De plus, l’approche des « Platforms as a Service » permet d’aller à l’essentiel, sans perdre de temps dans la configuration fine de machines virtuelles.

Au travers du service Azure Machine Learning, Microsoft propose un panel d’outils permettant d’accompagner les data scientists et les data engineers du développement de modèles prédictifs jusqu’à leur mise en production.

Afin de construire un premier modèle prédictif, trois solutions sont accessibles depuis le portail et correspondent à différents scénarios d’utilisation ainsi qu’à différents profils d’utilisateurs. Nous détaillerons ces trois services ci-dessous.

Azure Machine Learning Service – Visual Interface

Anciennement connu sous le nom de Azure Machine Learning Studio, ce produit est une interface graphique permettant de construire un « pipeline » de Machine Learning, depuis le chargement des données jusqu’à l’entrainement et l’évaluation du modèle, en passant par des étapes classiques de préparation des données.

Azure Machine Learning Service – Automated ML

Il existe des dizaines de modèles de Machine Learning et tous méritent d’être essayés sur les données, car, sauf grande expérience (et bonne intuition !), il est difficile de choisir a priori le modèle le plus adapté. L’approche de l’Automated Machine Learning consiste à tester une batterie de modèles et à trouver leur meilleur paramétrage, sur la base de métriques de performance, le tout de manière automatique.

Azure Machine Learning Service – Notebooks VMs

Les notebooks Jupyter se sont imposés comme un environnement de développement chez les Data Scientists qui affectionnent particulièrement son aspect interactif et sa capacité à devenir rapidement un support de présentation.

JupyterHub et JupyterLab sont deux moyens de lancer des notebooks pour lesquels on choisira un « kernel » parmi ceux disponibles (Python, R, Scala…). La puissance de calcul est ici définie par une taille de machine virtuelle associée au serveur de notebooks.

Les machines peuvent être démarrées ou arrêtées selon les besoins, lorsqu’elles sont arrêtées, le service n’est bien sûr pas facturé.

Dès que la machine est démarrée, il est possible d’accéder à JupyterHub ou JupyterLab.

D’autres moyens existent pour utiliser les notebooks Jupyter comme par exemple le site https://notebooks.azure.com/ qui met à disposition une capacité de calcul gratuite (free compute) mais sur lequel il est également possible de relier une ou plusieurs machines virtuelles créées dans Azure (direct compute).

Que propose Microsoft pour la Data Science ?

En mars 2019, je publiais sur un blog ami cet état des lieux de l’offre Microsoft pour la Data Science : https://www.stat4decision.com/fr/microsoft-pour-la-data-science/

Depuis, la version Gen2 d’Azure Data Lake Storage est sortie (le logo a évolué). Azure Machine Learning Service est devenu un portail à part entière : ml.azure.com et depuis

Voici un aperçu des fonctionnalités en vidéo de ce qui s’appelle pour l’instant Azure Machine Learning Web Experience.

Bonjour tout le monde !

Bienvenue sur ce site où je regroupe mes différentes publications traitant de la data.

Le contenu publié autour des technologies Microsoft est fait dans le cadre de mon titre de MVP Artificial Intelligence depuis 2018.

Plusieurs démonstrations sont disponibles en vidéo sur ma chaîne YouTube : https://www.youtube.com/channel/UCO8P4F7DH_OfCg_YAlYaG3g