Microsoft Fabric : arrêter le copier-coller entre notebooks avec %run

Centralisons les constantes et fonctions partagées dans un notebook Fabric commun puis réutilisons-les avec %run sans copier-coller.

Partager
Microsoft Fabric : arrêter le copier-coller entre notebooks avec %run
Photo by ilgmyzin / Unsplash
Série

Organiser des notebooks Microsoft Fabric

  1. Partager du code avec %run Vous êtes ici
  2. Télécharger un fichier dans un Lakehouse avec run()
  3. Charger plusieurs fichiers en parallèle avec runMultiple()
  4. %run, run() ou runMultiple() : quel outil choisir ?

À force de travailler dans des notebooks Fabric, on finit presque toujours par recréer la même fonction de log, le même chemin vers une source ou la même constante de projet. Le copier-coller fonctionne très bien… jusqu'au premier correctif oublié dans l'un des notebooks.

Pour quelques helpers et constantes, je n'ai pas forcément envie de construire une librairie Python classique qu'il faudrait ensuite maintenir comme telle. Microsoft Fabric propose une solution beaucoup plus directe : la commande magique %run.

Nous allons l'utiliser sur un petit dataset de jeux vidéo rétro publié par Nice Data. Il contient peu de données et, soyons honnêtes, c'est très peu pour Spark. Mais c'est parfait pour comprendre les mécanismes sans devoir chercher une erreur dans plusieurs millions de lignes.

Petit projet d'ingestion de données

Notre projet consiste à récupérer des fichiers CSV depuis un repository GitHub public, puis à les déposer dans Fabric.

L'ingestion sera faite dans le prochain article. Pour l'instant, nous allons seulement préparer nos fonction et variables communes : le nom du projet, l'emplacement du dataset et quelques chemins de destination.

Créer le Lakehouse

Comme nous allons travailler avec des fichiers à déposer, le choix du stockage est simple : nous utiliserons un Lakehouse.

Dans le workspace Fabric, créons un nouveau Lakehouse :

  1. New item puis Lakehouse ;
  2. nommez-le lh_retro_video_games ;
Création du lakehouse

Nous avons maintenant besoin d'écrire notre notebook de définition communes et pour ce faire il nous faut,... un nouveau notebook ! Vu que l'on vient de créer le lakehouse, on peux directement le créer depuis celui-ci :

Création d'un nouveau notebook depuis un lakehouse

Sinon le traditionel chemin de création d'un nouvel objet :

  1. New item puis Notebook
  2. nommez-le : nb_common ;
  3. ajoutez lh_retro_video_games dans l'explorateur Lakehouse du notebook ;

Le notebook partagé : nb_common

Son rôle est très simple : regrouper les informations et les fonctions communes au projet.

La première cellule contient les constantes. Nous y définissons le nom du projet, les URLs GitHub et les chemins qui seront utilisés plus tard lorsque nous téléchargerons les fichiers.

project_name = "Retro Video Games"
repository_url = "https://github.com/nicedatafr/datasets"
raw_data_base_url = "https://raw.githubusercontent.com/nicedatafr/datasets/retro-video-games-v1.0.0/retro-video-games/data/v1/small/relational/csv"
lakehouse_root_path = "Files/bronze/retro-video-games"

datasets = {
    "games": {
        "source_url": f"{raw_data_base_url}/games.csv",
        "target_path": f"{lakehouse_root_path}/games",
    },
    "platforms": {
        "source_url": f"{raw_data_base_url}/platforms.csv",
        "target_path": f"{lakehouse_root_path}/platforms",
    },
}

Le dictionnaire datasets contient le strict minimum pour chaque fichier, l'URL source et le chemin cible dans le Lakehouse. Cela suffit largement à ce stade. Nottons tout de même que l'on réutilise déjà nos constantes dans le dictionnaire de déifinition des datasets. Si l'url du repo change, il n'y a qu'un seul endroit ou on doit le changer !

Ajoutons ensuite trois petites fonctions : deux fonctions de log pour rendre les sorties plus faciles à lire, puis une fonction qui retrouve la configuration d'un dataset à partir de son nom.

def log_info(message):
    print(f"\033[94m[INFO] {message}\033[0m")


def log_error(message):
    print(f"\033[91m[ERROR] {message}\033[0m")


def get_dataset(dataset_name):
    if dataset_name not in datasets:
        raise ValueError(f"Unknown dataset: {dataset_name}")

    return datasets[dataset_name]

Et c'est tout. nb_common ne télécharge rien, ne transforme rien et n'écrit aucune donnée dans le Lakehouse. C'est simplement notre petit endroit commun pour les constantes et les fonctions dont plusieurs notebooks auront besoin.

Utiliser %run dans nb_main_prun

Créons maintenant un autre notebook nb_main_prun. La première cellule de code contient uniquement l'appel à %run 

%run nb_common

Cette cellule doit rester seule. %run est une commande magique Fabric : les commentaires et le reste du code doivent vivre dans les cellules voisines.

Lorsque Fabric exécute cette ligne, il exécute nb_common dans le contexte de nb_main_prun. Les variables, le dictionnaire et les fonctions définis dans nb_common sont maintenant utilisables ici, sans import ni préfixe.

Ajoutons une seconde cellule pour le vérifier :

log_info(f"Project: {project_name}")
log_info(f"Repository: {repository_url}")

for dataset_name, dataset_config in datasets.items():
    log_info(
        f"Dataset: {dataset_name} | "
        f"Source: {dataset_config['source_url']} | "
        f"Target: {dataset_config['target_path']}"
    )

games_config = get_dataset("games")
log_info(f"Games target path confirmed: {games_config['target_path']}")

On peu maintenant executer notre notebook nb_main_prun et voir cette injection en action !

Résultat d'execution du notebook principal

Ce que %run fait et ne fait pas

%run est très pratique lorsqu'on veut partager quelques fonctions ou constantes entre plusieurs notebooks. Il injecte le code du notebook référencé dans la session courante, à l'endroit même où la commande est exécutée.

Il ne renvoie pas une valeur comme le ferait une fonction Python. Il ne lance pas non plus un traitement isolé. Après l'appel, nous avons simplement accès aux éléments définis dans nb_common, comme s'ils avaient été écrits directement dans nb_main_prun.

Deux règles suffisent pour commencer sereinement :

  • les deux notebooks doivent se trouver dans le même workspace ;
  • la cellule contenant %run doit contenir uniquement cette commande.

Nous verrons dans le prochain article un autre mécanisme, run(), qui servira cette fois à déclencher un notebook de traitement avec des paramètres et à récupérer un résultat.

En résumé

Le premier notebook centralise nos constantes, les chemins du projet et quelques helpers très simples. Le second les récupère avec %run, sans copier-coller et sans construire de librairie Python.

Dans cet exemple précis, seul nb_main_prun appelle nb_common. Nous ne profitons donc pas encore vraiment de la réutilisation : on aurait presque pu copier directement la configuration dans une cellule.

Mais %run montre déjà un autre intérêt : la séparation des responsabilités. D'un côté, nous avons la configuration et les helpers communs ; de l'autre, le code qui les utilise. Ce sera lorsque d'autres notebooks viendront s'ajouter au projet qu'ils pourront eux aussi s'appuyer sur ce même nb_common et que nous profiterons réellement de cette réutilisation.