dbdocs 1.0.0__tar.gz → 1.0.1__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.0.0 → dbdocs-1.0.1}/PKG-INFO +1 -1
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/config.py +21 -2
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/builder.py +14 -7
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/deploy.py +17 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/inject.py +3 -3
- {dbdocs-1.0.0 → dbdocs-1.0.1}/.gitignore +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/LICENSE +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/README.md +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/__init__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/__main__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/cli/__init__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/cli/main.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/__init__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/artifacts.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/exceptions.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/log.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/__init__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/_sqlglot_lineage.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/column_lineage.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/erd.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/erd_json.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/graph.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/nodes.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/main.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/__init__.py +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/app.js +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/favicon.svg +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/graph/index.css +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/graph/index.js +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/style.css +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/vendor/marked.min.js +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/vendor/minisearch.min.js +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/index.html +0 -0
- {dbdocs-1.0.0 → dbdocs-1.0.1}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbdocs
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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
|
|
@@ -8,6 +8,23 @@ from dbdocs.core.exceptions import DbDocsConfigError
|
|
|
8
8
|
DEFAULT_CONFIG_FILENAME = "dbdocs.yml"
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def _resolve_within_cwd(value: str, field_name: str) -> Path:
|
|
12
|
+
"""Resolve *value* against cwd; reject a relative path that escapes the cwd.
|
|
13
|
+
|
|
14
|
+
An absolute *value* is resolved as-is (the caller's deliberate choice — this
|
|
15
|
+
is documented behaviour and must not be rejected). A relative *value* is
|
|
16
|
+
resolved against ``Path.cwd()`` and then checked: if the result is not
|
|
17
|
+
inside (or equal to) the cwd, ``DbDocsConfigError`` is raised.
|
|
18
|
+
"""
|
|
19
|
+
base = Path.cwd().resolve()
|
|
20
|
+
candidate = Path(value)
|
|
21
|
+
resolved = candidate if candidate.is_absolute() else (base / candidate)
|
|
22
|
+
resolved = resolved.resolve()
|
|
23
|
+
if not candidate.is_absolute() and resolved != base and base not in resolved.parents:
|
|
24
|
+
raise DbDocsConfigError(f"{field_name} {value!r} escapes the project directory ({base}).")
|
|
25
|
+
return resolved
|
|
26
|
+
|
|
27
|
+
|
|
11
28
|
@dataclass
|
|
12
29
|
class DbDocsConfig:
|
|
13
30
|
"""Site configuration for a dbdocs build.
|
|
@@ -103,8 +120,9 @@ class DbDocsConfig:
|
|
|
103
120
|
directory **at access time** — this is intentional and must stay aligned
|
|
104
121
|
with dbterd's ``DbtErd``, which also reads artifacts from ``./target``
|
|
105
122
|
relative to the cwd. An absolute ``target_dir`` is returned unchanged.
|
|
123
|
+
A relative path that would escape the cwd raises ``DbDocsConfigError``.
|
|
106
124
|
"""
|
|
107
|
-
return str(
|
|
125
|
+
return str(_resolve_within_cwd(self.target_dir, "target_dir"))
|
|
108
126
|
|
|
109
127
|
@property
|
|
110
128
|
def output_path(self) -> str:
|
|
@@ -113,5 +131,6 @@ class DbDocsConfig:
|
|
|
113
131
|
Resolved against the cwd at access time, mirroring ``target_path`` — a
|
|
114
132
|
relative ``output_dir`` follows the working directory, an absolute one is
|
|
115
133
|
returned unchanged.
|
|
134
|
+
A relative path that would escape the cwd raises ``DbDocsConfigError``.
|
|
116
135
|
"""
|
|
117
|
-
return str(
|
|
136
|
+
return str(_resolve_within_cwd(self.output_dir, "output_dir"))
|
|
@@ -9,11 +9,12 @@ data → write ``index.html`` + a debug ``dbdocs-data.json``.
|
|
|
9
9
|
import json
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from shutil import copytree
|
|
12
|
+
from shutil import copytree, rmtree
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
from dbdocs.core.artifacts import adapter_type, load_artifacts
|
|
16
|
-
from dbdocs.core.config import DbDocsConfig
|
|
16
|
+
from dbdocs.core.config import DbDocsConfig, _resolve_within_cwd
|
|
17
|
+
from dbdocs.core.exceptions import DbDocsConfigError
|
|
17
18
|
from dbdocs.core.log import logger
|
|
18
19
|
from dbdocs.extract.column_lineage import ColumnLineageExtractor
|
|
19
20
|
from dbdocs.extract.erd import build_erd, build_erd_data
|
|
@@ -66,12 +67,16 @@ class ReportBuilder:
|
|
|
66
67
|
def _read_readme(self) -> str:
|
|
67
68
|
"""The project README markdown (rendered on the overview), or ``""``.
|
|
68
69
|
|
|
69
|
-
``config.readme`` is a path relative to the cwd; a missing file
|
|
70
|
-
empty config value
|
|
70
|
+
``config.readme`` is a path relative to the cwd; a missing file, an
|
|
71
|
+
empty config value, or a relative path that escapes the cwd all yield
|
|
72
|
+
no README section (fail-soft — a bad README must never sink generate).
|
|
71
73
|
"""
|
|
72
74
|
if not self.config.readme:
|
|
73
75
|
return ""
|
|
74
|
-
|
|
76
|
+
try:
|
|
77
|
+
path = _resolve_within_cwd(self.config.readme, "readme")
|
|
78
|
+
except DbDocsConfigError:
|
|
79
|
+
return ""
|
|
75
80
|
try:
|
|
76
81
|
return path.read_text(encoding="utf-8")
|
|
77
82
|
except OSError:
|
|
@@ -109,7 +114,9 @@ class ReportBuilder:
|
|
|
109
114
|
def generate(self, output_dir: "str | None" = None) -> str:
|
|
110
115
|
"""Render the site into ``output_dir`` (or config's). Returns its path."""
|
|
111
116
|
out = Path(output_dir) if output_dir else Path(self.config.output_path)
|
|
112
|
-
out.
|
|
117
|
+
if out.exists():
|
|
118
|
+
rmtree(out)
|
|
119
|
+
out.mkdir(parents=True)
|
|
113
120
|
copytree(src=BUNDLE_DIR, dst=out, dirs_exist_ok=True)
|
|
114
121
|
|
|
115
122
|
data = self.build_data()
|
|
@@ -117,7 +124,7 @@ class ReportBuilder:
|
|
|
117
124
|
index.write_text(inject(index.read_text(encoding="utf-8"), data), encoding="utf-8")
|
|
118
125
|
# Compact, not indented — keeps the debug dump cheap on large projects.
|
|
119
126
|
(out / "dbdocs-data.json").write_text(
|
|
120
|
-
json.dumps(data, separators=(",", ":"), default=self._json_default),
|
|
127
|
+
json.dumps(data, separators=(",", ":"), sort_keys=True, default=self._json_default),
|
|
121
128
|
encoding="utf-8",
|
|
122
129
|
)
|
|
123
130
|
|
|
@@ -14,6 +14,7 @@ the output dir onto a ``gh-pages`` branch and pushes it.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
+
import re
|
|
17
18
|
import subprocess
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from shutil import copytree, rmtree
|
|
@@ -25,6 +26,17 @@ from dbdocs.site.builder import ReportBuilder
|
|
|
25
26
|
|
|
26
27
|
VERSIONS_FILE = "versions.json"
|
|
27
28
|
|
|
29
|
+
#: Allows only alphanumerics, dots, underscores, and hyphens — a safe single path segment.
|
|
30
|
+
_SAFE_SEGMENT = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _validate_segment(value: str, kind: str) -> None:
|
|
34
|
+
"""Raise :class:`DeployError` if *value* is not a safe single path segment."""
|
|
35
|
+
if not _SAFE_SEGMENT.match(value) or value in {".", ".."}:
|
|
36
|
+
raise DeployError(
|
|
37
|
+
f"Invalid {kind} {value!r}: must match ^[A-Za-z0-9._-]+$ and not be '.' or '..'."
|
|
38
|
+
)
|
|
39
|
+
|
|
28
40
|
|
|
29
41
|
def _read_versions(output_root: Path) -> list:
|
|
30
42
|
path = output_root / VERSIONS_FILE
|
|
@@ -57,6 +69,9 @@ def deploy(
|
|
|
57
69
|
title: "str | None" = None,
|
|
58
70
|
) -> str:
|
|
59
71
|
"""Generate ``version`` into the output root and update the version index."""
|
|
72
|
+
_validate_segment(version, "version")
|
|
73
|
+
if alias is not None:
|
|
74
|
+
_validate_segment(alias, "alias")
|
|
60
75
|
output_root = Path(config.output_path)
|
|
61
76
|
version_dir = output_root / version
|
|
62
77
|
if version_dir.exists():
|
|
@@ -85,6 +100,7 @@ def delete(config: DbDocsConfig, version: str, push: bool = False) -> None:
|
|
|
85
100
|
|
|
86
101
|
Raises :class:`DeployError` if the version isn't deployed.
|
|
87
102
|
"""
|
|
103
|
+
_validate_segment(version, "version")
|
|
88
104
|
output_root = Path(config.output_path)
|
|
89
105
|
versions = _read_versions(output_root)
|
|
90
106
|
entry = next((v for v in versions if v.get("version") == version), None)
|
|
@@ -95,6 +111,7 @@ def delete(config: DbDocsConfig, version: str, push: bool = False) -> None:
|
|
|
95
111
|
if version_dir.exists():
|
|
96
112
|
rmtree(version_dir)
|
|
97
113
|
for alias in entry.get("aliases", []):
|
|
114
|
+
_validate_segment(alias, "alias")
|
|
98
115
|
alias_dir = output_root / alias
|
|
99
116
|
if alias_dir.exists():
|
|
100
117
|
rmtree(alias_dir)
|
|
@@ -16,9 +16,9 @@ INJECT_MARKER = "<!-- DBDOCS_DATA -->"
|
|
|
16
16
|
|
|
17
17
|
def data_script(data: dict) -> str:
|
|
18
18
|
"""The ``<script>`` tag that sets ``window.dbdocsData`` from ``data``."""
|
|
19
|
-
payload = base64.b64encode(
|
|
20
|
-
"
|
|
21
|
-
)
|
|
19
|
+
payload = base64.b64encode(
|
|
20
|
+
json.dumps(data, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
|
21
|
+
).decode("ascii")
|
|
22
22
|
return f'<script>window.dbdocsData = JSON.parse(atob("{payload}"));</script>'
|
|
23
23
|
|
|
24
24
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|