pbix-lineage
Universal lineage graph for Power BI (.pbix) files: from physical source to the field displayed in a report visual.
source (HTTP, OData, SQL, file...) --> Power Query (M)
--> column / calculated column --> measure (DAX)
--> field displayed in a report visual
The graph is a standard networkx.DiGraph, natively bidirectional: trace a visual back to its source (upstream), or list everything a source feeds (downstream).
Why
pbixray (used here) extracts the data model (tables, DAX, Power Query, schema) but says nothing about where each column or measure ends up displayed — that lives in a separate, undocumented part of the file (Report/Layout). pbix-lineage connects both into one traversable graph.
Install
pip install pbix-lineage
# or
uv add pbix-lineage
Usage
from pbix_lineage import LineageGraphBuilder, upstream, downstream, print_tree, find_nodes
graph = LineageGraphBuilder().build("my_report.pbix")
find_nodes(graph, "customer_name")
# -> ['column::DIM_CUSTOMER::customer_name']
print_tree(graph, "visual_field::my_report.pbix::Page1::16::customer_name", direction="upstream")
print_tree(graph, "source::odata::example.com/odata/", direction="downstream")
Export
from pbix_lineage import export_graphml, export_nodes_csv, export_edges_csv, graph_summary
graph_summary(graph) # {'query': 70, 'column': 183, ...}
export_graphml(graph, "lineage.graphml") # opens in Gephi / yEd
export_nodes_csv(graph, "nodes.csv")
export_edges_csv(graph, "edges.csv")
Source-agnostic
Source detection (pbix_lineage.sources) relies only on native M function names (Web.Contents, OData.Feed, Sql.Database, Folder.Files, SharePoint.Files, Excel.Workbook, AnalysisServices.Database, ...) — never a specific system or domain. Adding a new source type is one config entry in MFunctionSourceDetector.DEFAULT_PATTERNS, no other code touched.
HTTP API / MCP server
The package also exposes a FastAPI app, mounted as an MCP server via FastMCP (FastMCP.from_fastapi): every route becomes an MCP tool automatically.
uv sync --extra api
uv run pbix-lineage # starts on http://127.0.0.1:8080
- REST API:
POST /graphs,/search,/upstream,/downstream,/tree,/export,GET /graphs. - MCP server (streamable HTTP) at
http://127.0.0.1:8080/mcp/: same operations as tools (build_graph,search_nodes,get_upstream,get_downstream,get_lineage_tree,export_graph,list_loaded_graphs), plus alineage_guidanceprompt. - Env vars:
PBIX_LINEAGE_HOST(default0.0.0.0),PBIX_LINEAGE_PORT(default8080).
Each .pbix is parsed once and cached in memory (LineageGraphCache, framework-agnostic).
Publish to the MCP registry
server.json describes this server for registry.modelcontextprotocol.io. After replacing votre-org with your GitHub account everywhere:
uv build && uv publish # publish the package to PyPI first
mcp-publisher login github
mcp-publisher publish --dry-run
mcp-publisher publish
The registry verifies PyPI ownership via the <!-- mcp-name: ... --> marker at the top of this README. The name in server.json, this marker, and your authenticated GitHub namespace must all match.
Architecture
| Module | Responsibility |
|---|---|
models.py |
Node/edge types and shared data structures |
sources.py |
Physical source detection (agnostic, configurable) |
pbix_model.py |
Adapter isolating the rest of the code frompbixray |
dax.py |
DAX reference parsing (Table[Field] / [Field]) |
mquery.py |
Dependencies between Power Query queries (table-level) |
layout.py |
Parsing of the internalReport/Layout format |
graph_builder.py |
Orchestrator: builds thenetworkx.DiGraph |
navigation.py |
Upstream/downstream traversal, search, export |
api/schemas.py |
Pydantic request/response models |
api/service.py |
Graph cache, framework-agnostic |
api/app.py |
FastAPI app + MCP mount (FastMCP) |
Known limitations
- M query dependencies are resolved at table level, not step-by-step inside a single
let ... inquery. - A source reached only through a literal M parameter may be tagged with a generic system (
http) instead of the exact consumer connector (odata, etc.). - Unqualified DAX references (
[MeasureName]) are resolved same-table first, then globally; homonyms across tables resolve to the first match.
Development
uv sync --extra dev
uv run pytest # BDD tests (pytest-bdd) under tests/features/*.feature
uv build # produces dist/*.whl and dist/*.tar.gz
License
MIT
Metadata
Release files for pbix-lineage 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pbix_lineage-0.1.2.tar.gz | 16.2 kB | Without provenance | Details for pbix_lineage-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pbix_lineage-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for pbix_lineage-0.1.2-py3-none-any.whl |
Total release size: 34.9 kB
Release files / pbix_lineage-0.1.2.tar.gz
| Download URL | pbix_lineage-0.1.2.tar.gz |
|---|---|
| Size | 16.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1723dd100a475a1e22f1c4ab867cd7c62a10e4f25bdc4986d5302bd97e67be98
|
|
BLAKE2b-256 checksum How to use checksums |
3faf3a6b24bf422774adaf347e18c6b8bec8668f6e5ba829aab0779da3f041da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pbix_lineage-0.1.2-py3-none-any.whl
| Download URL | pbix_lineage-0.1.2-py3-none-any.whl |
|---|---|
| Size | 18.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8950b22f1f05ad0b739494e884408ee112e9112b883726902e476aac1af64e89
|
|
BLAKE2b-256 checksum How to use checksums |
0e11d34f754f98aae1b178d71a45f3ce71d75f29eab2dd3b12411a9f73cecc5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for pbix-lineage 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.