dbdocs 1.3.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dbdocs-1.3.0 → dbdocs-1.4.0}/PKG-INFO +2 -2
- dbdocs-1.4.0/dbdocs/extract/erd.py +150 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/graph/index.js +7 -7
- {dbdocs-1.3.0 → dbdocs-1.4.0}/pyproject.toml +3 -2
- dbdocs-1.3.0/dbdocs/extract/erd.py +0 -111
- dbdocs-1.3.0/dbdocs/extract/erd_json.py +0 -80
- {dbdocs-1.3.0 → dbdocs-1.4.0}/.gitignore +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/LICENSE +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/README.md +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/__main__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/cli/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/cli/main.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/core/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/core/artifacts.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/core/config.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/core/exceptions.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/core/log.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/_sqlglot_lineage.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/column_lineage.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/graph.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/dimensions.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/extractor.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/base.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/documentation.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/governance.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/modeling.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/performance.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/structure.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/dimensions/testing.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/health/rules/registry.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/extract/nodes.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/main.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/__init__.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/builder.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/css/icons.css +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/css/style.css +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/favicon.svg +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/graph/index.css +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/js/app.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/js/data.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/js/service.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/js/ui.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/vendor/marked.min.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/assets/vendor/minisearch.min.js +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/bundle/index.html +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/deploy.py +0 -0
- {dbdocs-1.3.0 → dbdocs-1.4.0}/dbdocs/site/inject.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbdocs
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Alternative dbt docs site: Catalog + ERD + column-level lineage
|
|
5
5
|
Project-URL: Homepage, https://github.com/datnguye/dbt-docs
|
|
6
6
|
Project-URL: Repository, https://github.com/datnguye/dbt-docs
|
|
@@ -16,7 +16,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Requires-Dist: artifact-parser[dbt]>=1.0.0
|
|
18
18
|
Requires-Dist: click>=8.1
|
|
19
|
-
Requires-Dist: dbterd>=1.
|
|
19
|
+
Requires-Dist: dbterd>=1.28
|
|
20
20
|
Requires-Dist: pydantic>=2.13.4
|
|
21
21
|
Requires-Dist: pyyaml>=6.0
|
|
22
22
|
Requires-Dist: sqlglot>=25
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Structured ERD data via dbterd's official ``json`` target.
|
|
2
|
+
|
|
3
|
+
dbterd 1.28.0 ships a built-in, schema-validated ``json`` target that emits
|
|
4
|
+
``{nodes, edges, metadata}``. Each node carries ``id`` (the dbt unique_id),
|
|
5
|
+
``name``, ``schema_name``, ``database``, ``resource_type``, and ``columns``
|
|
6
|
+
(with ``data_type``, ``is_primary_key``, ``is_foreign_key``). Each edge carries
|
|
7
|
+
``id``, ``from_id`` (FK/child side), ``to_id`` (referenced/parent side),
|
|
8
|
+
``from_columns``, ``to_columns``, ``label``, and ``cardinality``.
|
|
9
|
+
|
|
10
|
+
``build_erd_data`` maps that shape into the SPA's ``{nodes, edges}`` — the
|
|
11
|
+
React Flow bundle reads ``nodes`` (entities + column flags) and ``edges``
|
|
12
|
+
(FK relationships, ``source``/``target`` keyed by dbt unique_id).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
from dbterd.api import DbtErd, default
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def erd_algo(dbterd_options: "dict | None" = None) -> str:
|
|
21
|
+
"""The dbterd algorithm that detected the ERD relationships.
|
|
22
|
+
|
|
23
|
+
Reads the configured ``algo`` from the ``dbterd:`` block, falling back to
|
|
24
|
+
dbterd's own default. Surfaced into the SPA so it can explain an empty ERD.
|
|
25
|
+
"""
|
|
26
|
+
return (dbterd_options or {}).get("algo") or default.default_algo()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_erd(dbterd_options: "dict | None" = None, artifacts_dir: "str | None" = None) -> DbtErd:
|
|
30
|
+
"""Build the ERD generator (json target) from dbdocs' ``dbterd`` options.
|
|
31
|
+
|
|
32
|
+
``dbterd_options`` is the ``dbterd:`` block of ``dbdocs.yml`` (``algo``,
|
|
33
|
+
``entity_name_format``, ``resource_type``, ``select``, …) passed straight to
|
|
34
|
+
``DbtErd``. We force ``target="json"`` — the SPA needs structured data — but
|
|
35
|
+
let everything else come from the project's config so the ERD matches what
|
|
36
|
+
the team configured. (Config lives in ``dbdocs.yml``, not a separate
|
|
37
|
+
``.dbterd.yml``.)
|
|
38
|
+
|
|
39
|
+
``artifacts_dir`` is the dbt target dir (``config.target_dir``). dbterd reads
|
|
40
|
+
the manifest/catalog directly from this dir; without it dbterd would default
|
|
41
|
+
to ``./target`` and ignore the configured ``target_dir``. An explicit
|
|
42
|
+
``artifacts_dir`` in ``dbterd_options`` still wins.
|
|
43
|
+
"""
|
|
44
|
+
dbterd_kwargs = {k: v for k, v in (dbterd_options or {}).items() if k != "target"}
|
|
45
|
+
if artifacts_dir is not None:
|
|
46
|
+
dbterd_kwargs.setdefault("artifacts_dir", artifacts_dir)
|
|
47
|
+
return DbtErd(target="json", **dbterd_kwargs)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_erd_data(erd: DbtErd) -> dict:
|
|
51
|
+
"""Parse dbterd's official ``{nodes, edges}`` payload into the SPA shape.
|
|
52
|
+
|
|
53
|
+
dbterd's ``json`` target emits nodes keyed by dbt unique_id (``id`` field).
|
|
54
|
+
When ``entity_name_format`` is configured, dbterd emits edge ``from_id`` /
|
|
55
|
+
``to_id`` as the formatted entity name (e.g. ``orders``) rather than the
|
|
56
|
+
full unique_id (e.g. ``model.jaffle_shop.orders``). A ``name_to_id`` map
|
|
57
|
+
resolves those short names back to the node id before building edges, so the
|
|
58
|
+
SPA's ``source``/``target`` always reference a valid node ``id``.
|
|
59
|
+
|
|
60
|
+
Some dbterd algos (e.g. ``model_contract``) do not set ``is_foreign_key``
|
|
61
|
+
on node columns even when those columns appear in FK edges. After building
|
|
62
|
+
edges, any column named in an edge's ``from_columns`` (the FK/child side)
|
|
63
|
+
has its ``is_foreign_key`` flag back-filled to ``True`` on the target node.
|
|
64
|
+
|
|
65
|
+
SPA edge direction: ``source`` is the referenced (parent) side, ``target``
|
|
66
|
+
is the FK (child) side — matching dbterd's ``to_id`` and ``from_id``
|
|
67
|
+
respectively.
|
|
68
|
+
"""
|
|
69
|
+
payload = json.loads(erd.get_erd())
|
|
70
|
+
raw_nodes = payload.get("nodes", [])
|
|
71
|
+
raw_edges = payload.get("edges", [])
|
|
72
|
+
nodes = [_build_node(n) for n in raw_nodes]
|
|
73
|
+
node_ids = {n["id"] for n in nodes}
|
|
74
|
+
# Count occurrences first; ambiguous names (more than one node) are excluded
|
|
75
|
+
# so a collision can't silently resolve to the wrong node.
|
|
76
|
+
name_counts: dict[str, int] = {}
|
|
77
|
+
for n in raw_nodes:
|
|
78
|
+
nm = n.get("name")
|
|
79
|
+
if nm:
|
|
80
|
+
name_counts[nm] = name_counts.get(nm, 0) + 1
|
|
81
|
+
name_to_id = {
|
|
82
|
+
n.get("name"): n["id"]
|
|
83
|
+
for n in raw_nodes
|
|
84
|
+
if n.get("name") and name_counts[n.get("name")] == 1
|
|
85
|
+
}
|
|
86
|
+
edges = [_build_edge(e, i, node_ids, name_to_id) for i, e in enumerate(raw_edges)]
|
|
87
|
+
_backfill_fk_flags(nodes, edges)
|
|
88
|
+
return {"nodes": nodes, "edges": edges}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _backfill_fk_flags(nodes: "list[dict]", edges: "list[dict]") -> None:
|
|
92
|
+
"""Set is_foreign_key=True on columns named in each edge's from_columns.
|
|
93
|
+
|
|
94
|
+
Keyed by node id so the lookup is O(1) per column per edge.
|
|
95
|
+
"""
|
|
96
|
+
nodes_by_id = {n["id"]: n for n in nodes}
|
|
97
|
+
for edge in edges:
|
|
98
|
+
target_node = nodes_by_id.get(edge["target"])
|
|
99
|
+
if target_node is None:
|
|
100
|
+
continue
|
|
101
|
+
fk_cols = {c.lower() for c in edge.get("from_columns", [])}
|
|
102
|
+
if not fk_cols:
|
|
103
|
+
continue
|
|
104
|
+
for col in target_node["columns"]:
|
|
105
|
+
if col["name"].lower() in fk_cols:
|
|
106
|
+
col["is_foreign_key"] = True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _build_node(node: dict) -> dict:
|
|
110
|
+
return {
|
|
111
|
+
"id": node["id"],
|
|
112
|
+
"label": node.get("name") or "",
|
|
113
|
+
"database": node.get("database") or "",
|
|
114
|
+
"schema": node.get("schema_name") or "",
|
|
115
|
+
"resource_type": node.get("resource_type") or "model",
|
|
116
|
+
"columns": [
|
|
117
|
+
{
|
|
118
|
+
"name": c["name"],
|
|
119
|
+
"type": c.get("data_type") or "",
|
|
120
|
+
"description": c.get("description") or "",
|
|
121
|
+
"is_primary_key": bool(c.get("is_primary_key")),
|
|
122
|
+
"is_foreign_key": bool(c.get("is_foreign_key")),
|
|
123
|
+
}
|
|
124
|
+
for c in node.get("columns", [])
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _resolve_edge_id(raw: str, node_ids: "set[str]", name_to_id: "dict[str, str]") -> str:
|
|
130
|
+
# If raw is already a valid node id, keep it (the no-entity_name_format case).
|
|
131
|
+
# Otherwise resolve through the name→id map built from node labels.
|
|
132
|
+
if raw in node_ids:
|
|
133
|
+
return raw
|
|
134
|
+
return name_to_id.get(raw, raw)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _build_edge(edge: dict, index: int, node_ids: "set[str]", name_to_id: "dict[str, str]") -> dict:
|
|
138
|
+
# from_id is the FK/child side; to_id is the referenced/parent side.
|
|
139
|
+
# SPA convention: source = parent (to_id), target = child (from_id).
|
|
140
|
+
raw_from = edge.get("from_id") or ""
|
|
141
|
+
raw_to = edge.get("to_id") or ""
|
|
142
|
+
return {
|
|
143
|
+
"id": edge.get("id") or f"e{index}",
|
|
144
|
+
"source": _resolve_edge_id(raw_to, node_ids, name_to_id),
|
|
145
|
+
"target": _resolve_edge_id(raw_from, node_ids, name_to_id),
|
|
146
|
+
"from_columns": edge.get("from_columns") or [],
|
|
147
|
+
"to_columns": edge.get("to_columns") or [],
|
|
148
|
+
"label": edge.get("label") or "",
|
|
149
|
+
"type": edge.get("cardinality") or "",
|
|
150
|
+
}
|