dbdocs 1.5.0__tar.gz → 1.6.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.
Files changed (54) hide show
  1. {dbdocs-1.5.0 → dbdocs-1.6.0}/PKG-INFO +2 -1
  2. {dbdocs-1.5.0 → dbdocs-1.6.0}/README.md +1 -0
  3. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/core/artifacts.py +12 -0
  4. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/core/config.py +5 -0
  5. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/graph.py +14 -7
  6. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/extractor.py +25 -10
  7. dbdocs-1.6.0/dbdocs/extract/nodes.py +518 -0
  8. dbdocs-1.6.0/dbdocs/extract/tests_index.py +85 -0
  9. dbdocs-1.6.0/dbdocs/site/api_schema.py +371 -0
  10. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/builder.py +158 -13
  11. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/css/icons.css +2 -0
  12. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/css/style.css +157 -4
  13. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/graph/index.css +1 -1
  14. dbdocs-1.6.0/dbdocs/site/bundle/assets/graph/index.js +62 -0
  15. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/js/service.js +186 -2
  16. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/js/ui.js +788 -80
  17. {dbdocs-1.5.0 → dbdocs-1.6.0}/pyproject.toml +1 -0
  18. dbdocs-1.5.0/dbdocs/extract/nodes.py +0 -144
  19. dbdocs-1.5.0/dbdocs/site/bundle/assets/graph/index.js +0 -62
  20. {dbdocs-1.5.0 → dbdocs-1.6.0}/.gitignore +0 -0
  21. {dbdocs-1.5.0 → dbdocs-1.6.0}/LICENSE +0 -0
  22. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/__init__.py +0 -0
  23. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/__main__.py +0 -0
  24. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/cli/__init__.py +0 -0
  25. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/cli/main.py +0 -0
  26. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/core/__init__.py +0 -0
  27. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/core/exceptions.py +0 -0
  28. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/core/log.py +0 -0
  29. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/__init__.py +0 -0
  30. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/_sqlglot_lineage.py +0 -0
  31. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/column_lineage.py +0 -0
  32. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/erd.py +0 -0
  33. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/__init__.py +0 -0
  34. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/dimensions.py +0 -0
  35. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/__init__.py +0 -0
  36. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/base.py +0 -0
  37. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/__init__.py +0 -0
  38. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/documentation.py +0 -0
  39. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/governance.py +0 -0
  40. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/modeling.py +0 -0
  41. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/performance.py +0 -0
  42. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/structure.py +0 -0
  43. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/dimensions/testing.py +0 -0
  44. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/extract/health/rules/registry.py +0 -0
  45. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/main.py +0 -0
  46. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/__init__.py +0 -0
  47. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/favicon.svg +0 -0
  48. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/js/app.js +0 -0
  49. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/js/data.js +0 -0
  50. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/vendor/marked.min.js +0 -0
  51. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/assets/vendor/minisearch.min.js +0 -0
  52. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/bundle/index.html +0 -0
  53. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/deploy.py +0 -0
  54. {dbdocs-1.5.0 → dbdocs-1.6.0}/dbdocs/site/inject.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbdocs
3
- Version: 1.5.0
3
+ Version: 1.6.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
@@ -56,6 +56,7 @@ dbt's built-in docs stop short of telling you *which upstream column fed this do
56
56
  - **Project Health Check** across the six [dbt-project-evaluator](https://dbt-labs.github.io/dbt-project-evaluator/) dimensions.
57
57
  - **Versioned deploys** with a built-in version switcher, no plugins.
58
58
  - **Full-text search** across names, columns, descriptions, tags, and SQL at the client-side, no backend.
59
+ - **Static REST API** (`api/v1/`) — addressable JSON for every node, lineage, and health, for headless / agent consumption.
59
60
  - **Dark / light theme.**
60
61
 
61
62
  ## Install
@@ -32,6 +32,7 @@ dbt's built-in docs stop short of telling you *which upstream column fed this do
32
32
  - **Project Health Check** across the six [dbt-project-evaluator](https://dbt-labs.github.io/dbt-project-evaluator/) dimensions.
33
33
  - **Versioned deploys** with a built-in version switcher, no plugins.
34
34
  - **Full-text search** across names, columns, descriptions, tags, and SQL at the client-side, no backend.
35
+ - **Static REST API** (`api/v1/`) — addressable JSON for every node, lineage, and health, for headless / agent consumption.
35
36
  - **Dark / light theme.**
36
37
 
37
38
  ## Install
@@ -25,6 +25,18 @@ UNKNOWN = "_unknown"
25
25
  #: unique_id prefixes surfaced as catalog nodes (tests/macros/etc. excluded).
26
26
  NODE_PREFIXES = ("model.", "seed.", "snapshot.")
27
27
 
28
+ #: unique_id prefixes for manifest.nodes entries that carry only code (no columns).
29
+ CODE_ONLY_PREFIXES = ("analysis.", "operation.")
30
+
31
+ #: Manifest collections outside manifest.nodes, mapping id_prefix → attribute name.
32
+ COLLECTION_ATTRS: "dict[str, str]" = {
33
+ "metric.": "metrics",
34
+ "semantic_model.": "semantic_models",
35
+ "saved_query.": "saved_queries",
36
+ "unit_test.": "unit_tests",
37
+ "exposure.": "exposures",
38
+ }
39
+
28
40
 
29
41
  def artifact_version(target_path: str, artifact: str) -> "int | None":
30
42
  """Resolve a dbt artifact's schema version int from its ``dbt_schema_version``.
@@ -81,6 +81,11 @@ class DbDocsConfig:
81
81
  #: dotted import path whose ``register_rule`` calls add custom rules). Entry
82
82
  #: points under ``dbdocs.health_rules`` are also discovered automatically.
83
83
  health: dict = field(default_factory=dict)
84
+ #: The pinned About link shows by default; set false to hide it.
85
+ show_about: bool = True
86
+ #: Links rendered on the About page as external CTA buttons. Each entry is a
87
+ #: ``{label, href}`` dict; empty by default — no extra links.
88
+ about_links: list = field(default_factory=list)
84
89
 
85
90
  @classmethod
86
91
  def load(cls, path: "str | Path | None" = None) -> "DbDocsConfig":
@@ -2,14 +2,18 @@
2
2
 
3
3
  ``LineageGraph`` turns the manifest's ``parent_map`` (falling back to per-node
4
4
  ``depends_on.nodes``) into directed parent→child edges plus adjacency maps,
5
- restricted to the nodes the SPA actually surfaces (models/seeds/snapshots/
6
- sources) so test/macro dependencies don't dangle. The result feeds the
7
- interactive DAG view.
5
+ covering all surfaced resource types (models/seeds/snapshots/sources/analyses/
6
+ operations/metrics/semantic_models/saved_queries/unit_tests/exposures) so that
7
+ ``dependsOn``/``referencedBy`` on new pages resolve correctly.
8
+
9
+ The visual DAG view filters by ``data-rtype`` in the graph bundle, so including
10
+ non-physical types in the lineage graph does not pollute the DAG display. Macros
11
+ and test nodes are still excluded — they are never surfaced as navigable pages.
8
12
  """
9
13
 
10
14
  from typing import Any
11
15
 
12
- from dbdocs.core.artifacts import NODE_PREFIXES
16
+ from dbdocs.core.artifacts import CODE_ONLY_PREFIXES, COLLECTION_ATTRS, NODE_PREFIXES
13
17
 
14
18
 
15
19
  class LineageGraph:
@@ -17,17 +21,17 @@ class LineageGraph:
17
21
 
18
22
  def __init__(self, manifest: Any, node_ids: "set | None" = None) -> None:
19
23
  self.manifest = manifest
20
- #: Restrict edges to these ids. Defaults to models/seeds/snapshots +
21
- #: sources derived from the manifest, matching ``nodes.build_nodes``.
22
24
  self.node_ids = node_ids if node_ids is not None else self._default_node_ids()
23
25
 
24
26
  def _default_node_ids(self) -> set:
25
27
  ids = {
26
28
  uid
27
29
  for uid in (getattr(self.manifest, "nodes", {}) or {})
28
- if str(uid).startswith(NODE_PREFIXES)
30
+ if str(uid).startswith(NODE_PREFIXES + CODE_ONLY_PREFIXES)
29
31
  }
30
32
  ids.update(getattr(self.manifest, "sources", {}) or {})
33
+ for attr in COLLECTION_ATTRS.values():
34
+ ids.update(getattr(self.manifest, attr, None) or {})
31
35
  return ids
32
36
 
33
37
  def build(self) -> dict:
@@ -69,4 +73,7 @@ class LineageGraph:
69
73
  def _lookup(self, unique_id: str) -> Any:
70
74
  if str(unique_id).startswith("source."):
71
75
  return (getattr(self.manifest, "sources", {}) or {}).get(unique_id)
76
+ for prefix, attr in COLLECTION_ATTRS.items():
77
+ if str(unique_id).startswith(prefix):
78
+ return (getattr(self.manifest, attr, None) or {}).get(unique_id)
72
79
  return (getattr(self.manifest, "nodes", {}) or {}).get(unique_id)
@@ -34,6 +34,7 @@ from pydantic import ValidationError
34
34
 
35
35
  from dbdocs.core.log import logger
36
36
  from dbdocs.extract.health.dimensions import DimensionAnalyzer
37
+ from dbdocs.extract.tests_index import manifest_test_node_details, manifest_test_node_metadata
37
38
 
38
39
  # ---------------------------------------------------------------------------
39
40
  # Test type → category mapping
@@ -108,13 +109,21 @@ def _is_unit_test(unique_id: str) -> bool:
108
109
  return unique_id.startswith(_UNIT_TEST_PREFIX)
109
110
 
110
111
 
112
+ #: Adapter-emitted aliases for a successful test result, normalized to ``"pass"``
113
+ #: so the status pills / per-test badges tally correctly. Snowflake and a few
114
+ #: other adapters emit ``"success"`` for an asserting test that returned 0 rows.
115
+ _PASS_ALIASES = {"success", "ok"}
116
+
117
+
111
118
  def _status_value(status: Any) -> str:
112
- """The plain status string for a result.
119
+ """The plain status string for a result, with adapter aliases normalized.
113
120
 
114
121
  artifact-parser models ``status`` as a versioned enum (e.g. ``Status1.pass_``
115
122
  with value ``"pass"``); fall back to ``str`` for anything that isn't enum-like.
123
+ Adapter aliases for success (``"success"``/``"ok"``) collapse to ``"pass"``.
116
124
  """
117
- return str(getattr(status, "value", status) or "unknown")
125
+ raw = str(getattr(status, "value", status) or "unknown").lower()
126
+ return "pass" if raw in _PASS_ALIASES else raw
118
127
 
119
128
 
120
129
  def _short_name(unique_id: str) -> str:
@@ -204,9 +213,12 @@ class HealthCheckExtractor:
204
213
  is_unit = _is_unit_test(uid)
205
214
  if is_unit:
206
215
  test_type, model, column, category = "unit_test", self._unit_test_model(uid), "", "unit"
216
+ description, kwargs = "", {}
207
217
  else:
208
- test_type, model, column = self._resolve_metadata(uid)
218
+ node = self._nodes.get(uid)
219
+ test_type, model, column = self._resolve_metadata(node, uid)
209
220
  category = TEST_CATEGORIES.get(test_type, _FALLBACK_CATEGORY)
221
+ description, kwargs = self._resolve_details(node)
210
222
  return {
211
223
  "rule": _short_name(uid),
212
224
  "kind": "unit" if is_unit else "data",
@@ -216,24 +228,27 @@ class HealthCheckExtractor:
216
228
  "status": _status_value(getattr(result, "status", "unknown")),
217
229
  "failures": int(getattr(result, "failures", 0) or 0),
218
230
  "message": str(getattr(result, "message", "") or ""),
231
+ "description": description,
232
+ "kwargs": kwargs,
219
233
  "category": category,
220
234
  "docs_url": _UNIT_TESTS_DOCS if is_unit else _TESTS_DOCS,
221
235
  }
222
236
 
223
- def _resolve_metadata(self, unique_id: str) -> "tuple[str, str, str]":
237
+ @staticmethod
238
+ def _resolve_details(node: Any) -> "tuple[str, dict]":
239
+ """Manifest description + kwargs for a data-test node, or ``("", {})``."""
240
+ return manifest_test_node_details(node) if node is not None else ("", {})
241
+
242
+ def _resolve_metadata(self, node: Any, unique_id: str) -> "tuple[str, str, str]":
224
243
  """``(test_type, model, column)`` for a *data* test, manifest-first.
225
244
 
226
245
  Reads the authoritative test type / tested model / column from the
227
246
  manifest node when available; falls back to inferring the type from the
228
247
  ``unique_id`` (model/column then unknown) when it isn't.
229
248
  """
230
- node = self._nodes.get(unique_id)
231
249
  if node is not None:
232
- metadata = getattr(node, "test_metadata", None)
233
- test_type = str(getattr(metadata, "name", "") or "") if metadata else ""
234
- model = _short_name(str(getattr(node, "attached_node", "") or ""))
235
- column = str(getattr(node, "column_name", "") or "")
236
- return test_type, model, column
250
+ test_type, attached, column = manifest_test_node_metadata(node)
251
+ return test_type, _short_name(attached), column
237
252
  return _infer_test_type(unique_id), "", ""
238
253
 
239
254
  def _unit_test_model(self, unique_id: str) -> str: