dbt-fabric appelé depuis un notebook Fabric
Une plateforme intégrée n’offre pas toujours de place où poser un projet dbt. Le point d’entrée en Python de dbt en fournit une, à condition d’accepter ce que cette solution a d’inhabituel.
Sur une plateforme de données intégrée, l’entrepôt expose un point de terminaison SQL et l’adaptateur dbt correspondant existe. Reste une question bête et bloquante : où faire tourner la commande. Il n’y a pas de machine où déposer un projet et lancer une commande à cinq heures du matin ; il y a des notebooks et un ordonnanceur qui sait les déclencher. La réponse tient donc en une bibliothèque plutôt qu’en une infrastructure.
On oublie souvent que dbt s’appelle depuis Python. `dbtRunner` exécute les mêmes commandes que la ligne de commande et rend un objet plutôt qu’un code de sortie : la liste des modèles, leur état, leur durée. Dans un notebook, cela change tout : on n’a plus à analyser une sortie texte pour savoir ce qui a échoué.
from dbt.cli.main import dbtRunner, dbtRunnerResult
# The project is mounted from the lake: it stays versioned elsewhere, and the
# notebook holds no copy of it that could drift.
PROJECT = "/lakehouse/default/Files/dbt/warehouse"
runner = dbtRunner()
result: dbtRunnerResult = runner.invoke([
"build", # run + test, in graph order
"--project-dir", PROJECT,
"--profiles-dir", PROJECT,
"--target", "prod",
])
if not result.success:
# No log to re-read: each node carries its own status and message.
failures = [
f"{r.node.name} — {r.message}"
for r in result.result
if r.status in ("error", "fail")
]
raise RuntimeError("dbt failed:
" + "
".join(failures))Un notebook n’a pas de dossier personnel stable où déposer un fichier de profil. On l’écrit donc au début de l’exécution, à partir de secrets lus dans le coffre de la plateforme : ce qui a l’avantage de garantir qu’aucun identifiant ne dort dans le dépôt, et l’inconvénient de rendre l’exécution muette si le coffre est mal configuré. C’est un compromis acceptable à condition de le dire dans le code plutôt que de le laisser deviner.
warehouse:
target: prod
outputs:
prod:
type: fabric
driver: "ODBC Driver 18 for SQL Server"
server: "<sql-endpoint>.datawarehouse.fabric.microsoft.com"
database: warehouse
schema: marts
# Service principal authentication: the notebook does not borrow the
# identity of whoever started it, otherwise the scheduled run would
# depend on one person.
authentication: ServicePrincipal
tenant_id: "{{ env_var('TENANT_ID') }}"
client_id: "{{ env_var('CLIENT_ID') }}"
client_secret: "{{ env_var('CLIENT_SECRET') }}"
threads: 4L’ordonnanceur de la plateforme déclenche le notebook, et c’est tout ce qu’il sait faire : il voit une tâche là où il y a deux cents modèles. On retrouve exactement le défaut décrit dans un autre article, à ceci près qu’ici il n’y a pas d’alternative : la plateforme n’expose pas de mécanisme permettant de rendre le graphe visible. Ce qu’on peut faire, c’est le rendre lisible après coup : les résultats renvoyés par le point d’entrée s’écrivent dans une table, et un rapport bâti dessus dit quel modèle a échoué et depuis quand.
Je ne compare pas cette solution aux pipelines natifs de la plateforme, qui savent faire une partie de la même chose sans dbt. Le choix se décide sur un critère que la technique ne donne pas : si l’équipe connaît déjà dbt et le pratique ailleurs, le garder vaut mieux qu’apprendre un outil de plus ; si elle ne le connaît pas, l’introduire pour un seul entrepôt est une dette qu’on prend sans le dire. Et je ne dis rien des tests dbt en production, que nous exécutions au même moment que la construction : ce qui est commode et discutable, puisqu’une table fausse est alors publiée avant d’être déclarée fausse.