Marimo, ou l'Avenir du Notebook

Alerte : sous peu, les Jupyter Notebooks Python pourraient bien disparaître. Merci à eux pour tout ce qu’ils nous ont apporté, nous, Data Engineers, Data Scientists et autres. Mais la relève est là et elle n’amène que du bon : Marimo !

Pas de panique. Marimo, c’est du notebook, et on convertit un notebook Jupyter en notebook Marimo sans difficulté. Mais alors, pourquoi Marimo ?

Pourquoi Marimo ?

On ne présente plus les notebooks Jupyter, environnements de développement interactifs qui allient le code au visuel.

Mais qui n’a jamais été freiné par ses défauts :

  • non reproductible : état du notebook dépendant de l’ordre d'exécution des cellules - qui n’a jamais lancé un notebook qui marchait la veille et s’est retrouvé avec un bon gros message rouge ?
  • difficile à maintenir : bien loin du pur Python, versionable et portable,
  • pas fait pour être déployé : qui n’a jamais voulu utiliser un notebook en application web ou comme script ?

Marimo m'a convaincu car il corrige ces défauts et apporte bien plus. En se basant sur le principe du DAG (Directed Acyclic Graph), il définit les dépendances entre chaque cellule et génère un notebook reproductible. Cette base solide lui permet de rendre ce notebook exécutable et déployable. En fournissant nativement les éléments nécessaires pour construire une interface, il rend le notebook réactif et interactif. Avec Marimo, vous avez :

  • un notebook reproductible, que vous pourrez rouvrir demain sans surprise. Il n’y a pas d’état caché, il marchait, il marche,
  • un fichier source pur Python, formaté par black, facilement lisible, facilement versionnable,
  • un notebook déployable en page web interactive ou script,
  • une interface de développement vraiment aboutie avec, entre autres, panneaux de logs et graphe des dépendances.

Dans cet article, je vous présente les détails du fondement de Marimo, la manière dont il définit le DAG. Je mets ensuite l'outil à l'épreuve sur un cas d'usage et vous livre mes impressions.

La Naissance de Marimo

Marimo a été initié par Akshay Agrawal qui, lors de son PhD orienté Machine Learning à Stanford, s’est senti frustré par son utilisation quotidienne de Jupyter. Après un passage par Google Brain bien formateur, Akshay s’est convaincu qu’il fallait repenser le notebook.

Marimo est jeune, il souffle sa première bougie sous peu, mais coupe déjà le souffle.

L'Esprit du DAG

Le principe de Marimo est de définir un DAG liant chaque cellule de notre notebook. Nous créons et éditons nos cellules depuis l'interface. En arrière plan, Marimo reporte le code de chaque cellule dans une fonction spéciale :

  • les variables lues dans la cellule sont définies en paramètres d'entrée de la fonction,
  • les variables assignées dans la cellule font partie du résultat renvoyé par la fonction.

Le code de ces fonctions est dans un premier temps analysé par Marimo, qui construit le diagramme en reliant les paramètres d'entrée d'une fonction aux fonctions qui renvoient ces paramètres.

Prenons un cas simple pour illustrer ce mécanisme. Trois cellules sont définies. La figure 1 présente la définition des cellules en partie droite et le DAG résultant en partie gauche. La figure 2 montre le code source des fonctions générées pour ces trois cellules :

Figure 1 : Dépendances (DAG) et définitions des cellules depuis l'interface Marimo

Figure 2 : Définitions des fonctions représentant les cellules dans le fichier source

Le respect des contraintes suivantes est contrôlé lorsque l'on édite le notebook, afin de conserver un DAG valide :

  • une variable ne doit être assignée que dans une seule cellule. D'autres cellules peuvent dépendre de cette variable, son assignation doit être unique,
  • les cellules ne doivent pas créer de cycle (cellule 1 : x = y/ cellule 2 : y = x).

Au démarrage, Marimo commence par exécuter les cellules n'ayant pas de dépendances, puis les cellules dont les dépendances ont été assignées par les cellules précédemment exécutées.

Marimo exécute de nouveau une cellule lorsque :

  • une de ses dépendances est de nouveau assignée par une cellule parent,
  • un objet d'interface dont la cellule dépend a muté (par exemple un slider mo.ui.slider(...)d'une cellule parent a changé de valeur).

Attention, hors éléments de l'interface, Marimo ne ré-exécute pas une cellule lorsqu'une dépendance a muté (eg. un append sur une liste) :
→ on utilise donc le paradigme fonctionnel, on assigne mais on ne modifie pas,
→ si l'on a vraiment besoin de gérer des états qui mutent, on creuse. La documentation nous oriente sur le guide reactive state.

Mise à l'épreuve

Figure 3 : Exemples d'utilisation, directement tirés du github de Marimo

Une fois le principe compris, comme les notebooks Jupyter, les possibilités sont multiples. Libre à vous de les utiliser pour tous les usages possibles et imaginables. Les cas d’usage classiques sont l’exploration de données et de la construction d'interfaces interactives. Marimo présente un grand nombre d'exemple sur sa page use cases, dont sont tirées les deux images de la figure 3 ci-dessus.

Data Engineer au quotidien, donc moins orienté Data Science et Visualisation, j’ai éprouvé l'outil au travers d'un cas que j’avais en tête : une interface de dépôt de fichiers.

Chez certains clients, nous avons parfois besoin d’ingérer des canards boiteux, fichiers produits manuellement ou récupérés directement par les métiers, donc sujets à problèmes et qui méritent amplement d’être contrôlés avant l'ingestion. Une petite interface web accessible aux métiers s’y prête bien :

  • l’utilisateur dépose un fichier à ingérer,
  • une batterie de contrôles de conformité est jouée,
  • si le fichier est valide, il est déposé sur AWS S3,
  • un rapport est affiché à l’utilisateur.

Au programme :

  • de l’UI pas chère, merci Marimo,
  • pour les contrôles, un module en arrière-plan qui utilise notamment polars, qui peut être utile pour des contrôles plus poussés,
  • Poetry pour gérer le projet,
  • un déploiement Docker.

⇒ Marimo m’a permis de faire une petite interface utilisateur à peu de frais.

Le code est disponible sur ce repository github.

Avec le fichier pas du tout valide qu’a voulu me déposer le métier :

Figure 4 : Cas d'usage Data Entry : avec échec des contrôles

Heureusement, mon utilisateur voit le résultat des contrôles, corrige, recharge un fichier valide (et doux espoir : je n’en entends pas parler) :

Figure 5 : Cas d'usage Data Entry : avec réussite des contrôles

Prise en Main

Je ne ferai pas mieux que Marimo qui met à disposition tout ce qu’il faut pour apprendre vite et bien, ci-dessous de quoi vous orienter pour vos débuts :

  • on peut commencer doucement mais surement avec le guide Coming from Jupyter Notebook qui pose bien les bases,
  • un playground est disponible sur marimo.app, avec une dizaine de tutoriels et des exemples,
  • pour les convaincus, on passe au getting started de la documentation pour installer Marimo en local,
  • les guides sont d'une grande utilité, présentant entre autres les bonnes pratiques, les interactions avec les graphiques et tableaux de données (dataframes) ainsi que les déploiements,
  • les recettes, multiples de petits cas d’usage sont très utiles, notamment la partie control flow.

Pros and Cons

Pros

Vous l’aurez compris mais je refais une courte liste : notebook reproductible, compatible git, déployable en application web, une belle interface de développement.

Je rajouterai un point : Marimo peut charger automatiquement les modules en cas de changement. Ceci couplé à un projet géré par poetry qui installe nos modules en mode éditable, c’est tellement pratique : on développe des modules dans l’IDE pour ne pas charger le notebook, du côté Marimo la mise à jour est faite de manière transparente.

Neutral

  • Un temps de montée en compétence sur les bonnes pratiques (mais le retour sur investissement est bon),
  • La documentation est vraiment bien faite mais il faudra fouiller un peu : Marimo est jeune et pour l’heure, il faut oublier Google, Stackoverflow ou autres, on tombe vite sur des images d’étranges organismes aquatiques.

Cons (mais ça peut changer vite)

  • Le développement se fait dans l’interface web, un keymap Vim est disponible mais rien d’autre. Il y a une extension VS Code, mais elle se limite pour l'instant à lancer l’interface dans un onglet de l'IDE,
    ⇒ on est parfois frustré de ne pas pouvoir faire du multi curseur, des nouveaux raccourcis clavier ou de devoir basculer entre Marimo et notre IDE pour développer les modules utilisés en arrière-plan.
  • Jupyter est un écosystème qui va bien au-delà de Python et qui a une longue vie devant lui. On ne fera sûrement jamais tourner un noyau Scala/Spark sur Marimo (mais qui le voudrait ?),
  • Le logo est rond, alors que Marimo fait des DAGs. Et ça c’est dur. 😡

Aller Plus Loin

Pour les convaincus, je conseille de prendre le temps de lire le post d’Akshay Agrawal, à l’origine de Marimo : Lessons learned reinventing the Python notebook. La genèse du projet est détaillée ainsi que les différents choix d'architecture qui ce sont présentés et les choix qui ont été faits. La lecture est instructive.

Il reste à noter différentes fonctionnalités qui m'ont paru intéressantes mais que je n'ai pas testé :

  • les intégrations déjà existantes, dont Google (sheets / Google Cloud BigQuery / Google Cloud Storage),
  • l’intégration dans l’interface de développement de l’IA Copilot,
  • l'intégration de Marimo dans une application plus large, l'exemple d'une intégration dans une application FastAPI étant présenté dans ce guide.

C’est maintenant à votre tour de prendre en main Marimo, si ce n’est pas déjà fait. Bons développements !

Author image
Data Engineer chez IPPON Technologies depuis 2022, toujours heureux d'apprendre et de partager dans ce monde de la Data qui évolue si vite !
Lyon LinkedIn