Static column-to-column SQL lineage extractor producing a dbt-manifest-like JSON graph.
Project description
sql-lineage-extractor
Extraction statique de lineage SQL colonne-à-colonne, inspirée du
manifest.json de dbt — mais sans moteur d'exécution ni orchestration.
L'outil n'exécute aucune requête de transformation, ne charge aucune donnée : il
analyse du texte SQL et produit un graphe structuré (JSON versionné et
diffable).
Le SQL analysé peut provenir indifféremment de :
- fichiers
.sql; - notebooks Fabric / Jupyter (
.ipynb) ; - une connexion live à un endpoint SQL (Fabric SQL endpoint, SQL Server, Synapse) via une connexion DB-API générique injectée de l'extérieur.
Aucune dépendance à un SDK Fabric propriétaire. Le parsing repose sur
sqlglot.
Installation
pip install -e .
# mode live (connexion DB-API, ex. pyodbc) :
pip install -e ".[live]"
# outillage de dev (tests) :
pip install -e ".[dev]"
Python ≥ 3.10.
Architecture — pipeline en 4 couches
Sources SQL (fichiers / notebooks / live)
│ -> SQLUnit (id, dialecte, sql_text, origine)
▼
Parsing + lineage colonne-à-colonne (sqlglot) -> ObjectNode | ParseError
▼
Construction du graphe multi-objets (depends_on_nodes)
▼
Sérialisation en manifest JSON (schématisé, déterministe)
Chaque couche est testable indépendamment (tests/).
Utilisation (CLI)
# Fichiers .sql
sql-lineage extract --source files --path ./vues --dialect tsql --out manifest.json
# Notebooks .ipynb
sql-lineage extract --source notebooks --path ./notebooks --dialect spark --out manifest.json
# Endpoint SQL live (nécessite l'extra [live])
sql-lineage extract --source live --conn-string "<DSN ou chaîne ODBC>" --dialect tsql --out manifest.json
# Vérifier qu'un manifest est conforme au schéma publié
sql-lineage validate manifest.json
Options utiles : --glob pour restreindre le motif de fichiers
(--glob "silver/**/*.sql").
Utilisation programmatique
from sql_lineage.sources import FileSQLSource
from sql_lineage.pipeline import run_extraction
from sql_lineage.manifest import write_manifest
nodes, errors = run_extraction(FileSQLSource("./vues", dialect="tsql"))
write_manifest("manifest.json", nodes, errors)
Pour le mode live, la connexion est fournie par l'appelant (jamais construite dans la librairie) :
import pyodbc
from sql_lineage.sources import LiveSQLEndpointSource
conn = pyodbc.connect("Driver=...;Server=...;Authentication=ActiveDirectoryInteractive;...")
source = LiveSQLEndpointSource(conn, dialect="tsql")
Schéma du manifest
Documenté et versionné dans
src/sql_lineage/manifest/schema.json
(schema_version en tête de fichier, actuellement 1.0). La politique
d'évolution du contrat (MAJOR.MINOR, dépréciation, golden tests) est décrite
dans docs/MANIFEST_SCHEMA.md. Extrait :
{
"schema_version": "1.0",
"generated_at": "2026-07-20T10:00:00Z",
"nodes": {
"silver_shipping_escale": {
"origine": "vues-shipping/silver/escale.sql",
"dialecte": "tsql",
"columns": {
"date_traitement": {
"expression_sql": "e.date_arrivee AS date_traitement",
"depends_on": [{"table": "bronze_navis.escale", "column": "date_arrivee"}]
}
},
"depends_on_nodes": ["bronze_navis_escale"],
"parametres_neutralises": [],
"avertissements": []
}
},
"errors": [
{"id": "silver_finance_xyz", "origine": "...", "message": "..."}
]
}
Déterminisme : même entrée → même sortie. Clés triées, listes ordonnées,
formatage stable — pour un diff propre en CI. Le seul champ non reproductible,
generated_at, est injectable (build_manifest(..., generated_at=...)).
Export vers la BI (Power BI, etc.)
Le manifest JSON reste canonique (contrat versionné, diffable en CI). Pour la BI on en dérive une couche de service tabulaire — schéma en étoile — que Power BI consomme directement, sans avoir à « expand » du JSON imbriqué :
manifest.json (canonique)
│ sql-lineage export (dérivation pure, rejouable, append par run)
▼
dim_run · dim_object · dim_column · fact_column_edge · object_edge · closure · errors
| Table | Grain | Rôle |
|---|---|---|
dim_run |
1 run | dimension temps (historisation) |
dim_object |
1 objet | dimension (slice par layer bronze/silver/gold) |
dim_column |
1 colonne | dimension colonne |
fact_column_edge |
1 arête colonne→colonne | fait central |
object_edge |
1 dépendance objet directe | graphe 1-saut |
closure |
1 couple ancêtre→descendant | multi-saut (impact / provenance) |
errors |
1 erreur | suivi qualité |
Toutes les tables portent un run_id : chaque run est ajouté (jamais
écrasé), ce qui ouvre la dérive dans le temps, l'« as-of date » et l'audit. La
table closure (fermeture transitive précalculée en Python) débloque les
rapports impact (« si cette source change, quoi en aval ? ») et provenance
(« d'où vient cette colonne à la source ? ») sur plusieurs niveaux — impossible
en DAX récursif sur un DAG.
Backends
# CSV (un fichier par table)
sql-lineage export manifest.json --format csv --out ./exports
# SQLite (fichier unique, lisible par Power BI via ODBC)
sql-lineage export manifest.json --format sqlite --out lineage.db
# SQL Server / Fabric Warehouse (connexion injectée)
sql-lineage export manifest.json --format sqlserver --conn-string "<chaîne ODBC>"
Le backend sqlserver utilise l'extra [live] (pyodbc). Les backends base de
données sont idempotents par run (les lignes d'un run_id existant sont
remplacées) ; le CSV est append-only.
Fabric : guide d'usage complet (exécution hors Spark, connexion Warehouse via Service Principal, script d'orchestration prêt à coller) dans docs/FABRIC_USAGE.md et examples/fabric_orchestration.py.
Modélisation Power BI (indicatif)
- Relations :
dim_object[node_id] 1—* fact_column_edge[target_node];dim_run[run_id] 1—*sur chaque table (filtre de run). - Impact/provenance : filtrer
closure[ancestor_node](aval) ouclosure[descendant_node](amont),depthdonne la distance. - Volume faible → mode Import suffit (pas besoin de DirectQuery/DirectLake).
Conventions
Identifiant d'un objet dans un notebook
Priorité de résolution de l'id d'une cellule / d'un appel spark.sql :
- Commentaire de convention en tête du SQL :
-- lineage:id=silver_shipping_escale(valeur par défaut, ajustable si l'équipe data préfère une autre convention). - Nom de la variable cible en Python (
df_silver_finance = spark.sql(...)→silver_finance; le préfixedf_est retiré). - Repli
nom_notebook#numero_cellule— ce cas émet un avertissement (champavertissements), il n'est jamais silencieux.
Neutralisation des f-strings
Une cellule Python spark.sql(f"... WHERE d >= '{date_execution}'") est parsée
avec le module ast (jamais par regex). Chaque interpolation {...} en
position valeur (>= {x}, '{x}', IN ({x})) est remplacée par le jeton
neutre __PARAM__ avant transmission à sqlglot, pour ne pas casser le parsing.
Une interpolation qui occupe une ligne à elle seule est traitée comme un
fragment de clause SQL (motif courant en Fabric :
{'' if terminal_names is None else 'AND t.col IN ' + terminal_names}) et
supprimée plutôt que tokenisée — un __PARAM__ orphelin casserait le parsing,
et un prédicat WHERE/HAVING ne contribue jamais au lineage colonne.
Dans les deux cas, les fragments remplacés/supprimés sont conservés dans le champ
parametres_neutralises du nœud, pour transparence.
Personnaliser les conventions (LineageConfig)
Aucune convention n'est codée en dur : le commentaire d'id, le préfixe de
variable, le jeton de neutralisation et le dialecte par défaut proviennent tous
d'un objet LineageConfig injectable. Les défauts reproduisent exactement le
comportement décrit ci-dessus (rétro-compatible).
from sql_lineage import LineageConfig
from sql_lineage.sources import NotebookSQLSource
config = LineageConfig(
id_comment_pattern=r"--\s*lin:id\s*=\s*(\w+)", # 1 groupe de capture = l'id
var_prefixes=("df_", "result_"), # préfixes retirés de l'id
param_token="@@PARAM@@",
default_dialect="spark",
)
source = NotebookSQLSource("./notebooks", config=config)
La même chose en CLI (les flags surchargent un éventuel --config) :
sql-lineage extract --source notebooks --path ./notebooks \
--config conventions.toml \
--var-prefix df_ --var-prefix result_ \
--id-comment-pattern '--\s*lin:id\s*=\s*(\w+)' \
--param-token '@@PARAM@@' \
--out manifest.json
Fichier de conventions (.toml ou .json) — les clés peuvent aussi vivre sous
[tool.sql_lineage] d'un pyproject.toml partagé :
[sql_lineage]
var_prefixes = ["df_", "result_"]
param_token = "@@PARAM@@"
default_dialect = "spark"
La convention -- lineage:id= s'applique aussi aux fichiers .sql : un
commentaire présent dans le fichier l'emporte sur l'id dérivé du nom de fichier.
Cas d'erreur (attendus)
Une erreur de parsing sur un objet n'interrompt jamais le traitement des
autres : elle est capturée et reportée dans errors. Cas rejetés :
SELECT *(ett.*) sur une table physique : colonnes inconnues en analyse statique. En revanche, unSELECT *au-dessus d'un CTE ou d'une sous-requête énumérable est expansé automatiquement (y compris les cas imbriquésT.*), donc supporté.- Colonne calculée sans alias (
e.a + e.b) : pas de nom de sortie stable. - SQL non analysable par sqlglot pour le dialecte donné.
Limites connues
- SQL procédural complexe (batchs T-SQL multi-instructions, variables,
MERGE, procédures stockées) : seul le premierSELECTpertinent est analysé. - Dialectes : testés sur
tsqletspark. Les autres dialectes sqlglot fonctionnent probablement mais ne sont pas couverts par les tests — à étendre selon les besoins. - Résolution du graphe : le rapprochement
table source ↔ objetnormalise les points en underscores (bronze_navis.escale↔bronze_navis_escale) et autorise un match sur le dernier segment. Des collisions de noms courts sont théoriquement possibles. - Non-objectifs : pas d'exécution de requêtes, pas de génération de site de documentation (le manifest est destiné à être consommé par un outil de rendu séparé), pas de tests de qualité de données.
Tests
pytest
Aucun test ne nécessite de connexion réseau ni d'accès à un environnement Fabric réel — le mode live est couvert par une connexion mockée.
Structure du dépôt
src/sql_lineage/
models.py # SQLUnit, ColumnLineage, ObjectNode, ParseError, ColumnSource
sources/ # file / notebook / live (interface commune SQLSource)
parsing/ # wrapper sqlglot + extraction de lineage
graph/ # construction des arêtes depends_on_nodes
manifest/ # schema.json + writer (sérialisation déterministe)
pipeline.py # orchestration source -> parse -> graph
cli.py # commandes `extract` / `validate` (click)
tests/ # fixtures .sql / .ipynb + tests par couche
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sql_lineage_extractor-0.1.1.tar.gz.
File metadata
- Download URL: sql_lineage_extractor-0.1.1.tar.gz
- Upload date:
- Size: 89.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1f12e310d3515367fb1b79db08a6fc9d53207d508d0a9554c58944e16333232
|
|
| MD5 |
89e1acea0fd495a07cf7e9b3aa854c8b
|
|
| BLAKE2b-256 |
0f4c93e4f63cd4b98ce440b348bdc25069bf0a168787ff7b575f2850e046534d
|
Provenance
The following attestation bundles were made for sql_lineage_extractor-0.1.1.tar.gz:
Publisher:
release.yml on EasyTalents/sql-lineage-extractor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sql_lineage_extractor-0.1.1.tar.gz -
Subject digest:
f1f12e310d3515367fb1b79db08a6fc9d53207d508d0a9554c58944e16333232 - Sigstore transparency entry: 2257380356
- Sigstore integration time:
-
Permalink:
EasyTalents/sql-lineage-extractor@50465abe594de6b428475039d7c1897d38948694 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/EasyTalents
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@50465abe594de6b428475039d7c1897d38948694 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sql_lineage_extractor-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sql_lineage_extractor-0.1.1-py3-none-any.whl
- Upload date:
- Size: 38.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25e87e9b850ac4dd7e9e4a1d46ed51a027048adc510544f29fe08b4dff355f07
|
|
| MD5 |
5dcc6b51fc63ad39939f2fc1375788bc
|
|
| BLAKE2b-256 |
cbdddfe4854bf6d39bfb29f721edd3795c6ccae4f2141837a8edb91b67d271b8
|
Provenance
The following attestation bundles were made for sql_lineage_extractor-0.1.1-py3-none-any.whl:
Publisher:
release.yml on EasyTalents/sql-lineage-extractor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sql_lineage_extractor-0.1.1-py3-none-any.whl -
Subject digest:
25e87e9b850ac4dd7e9e4a1d46ed51a027048adc510544f29fe08b4dff355f07 - Sigstore transparency entry: 2257380369
- Sigstore integration time:
-
Permalink:
EasyTalents/sql-lineage-extractor@50465abe594de6b428475039d7c1897d38948694 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/EasyTalents
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@50465abe594de6b428475039d7c1897d38948694 -
Trigger Event:
push
-
Statement type: