dbdocs 0.0.1__tar.gz → 1.0.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.0.0/.gitignore +168 -0
- {dbdocs-0.0.1 → dbdocs-1.0.0}/LICENSE +21 -21
- dbdocs-1.0.0/PKG-INFO +78 -0
- dbdocs-1.0.0/README.md +56 -0
- {dbdocs-0.0.1 → dbdocs-1.0.0}/dbdocs/__main__.py +3 -3
- dbdocs-1.0.0/dbdocs/cli/main.py +86 -0
- dbdocs-1.0.0/dbdocs/core/artifacts.py +82 -0
- dbdocs-1.0.0/dbdocs/core/config.py +117 -0
- dbdocs-1.0.0/dbdocs/core/exceptions.py +24 -0
- dbdocs-1.0.0/dbdocs/core/log.py +58 -0
- dbdocs-1.0.0/dbdocs/extract/__init__.py +0 -0
- dbdocs-1.0.0/dbdocs/extract/_sqlglot_lineage.py +287 -0
- dbdocs-1.0.0/dbdocs/extract/column_lineage.py +186 -0
- dbdocs-1.0.0/dbdocs/extract/erd.py +102 -0
- dbdocs-1.0.0/dbdocs/extract/erd_json.py +80 -0
- dbdocs-1.0.0/dbdocs/extract/graph.py +72 -0
- dbdocs-1.0.0/dbdocs/extract/nodes.py +119 -0
- {dbdocs-0.0.1 → dbdocs-1.0.0}/dbdocs/main.py +6 -6
- dbdocs-1.0.0/dbdocs/site/__init__.py +0 -0
- dbdocs-1.0.0/dbdocs/site/builder.py +134 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/app.js +500 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/favicon.svg +12 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/graph/index.css +1 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/graph/index.js +62 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/style.css +289 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/vendor/marked.min.js +6 -0
- dbdocs-1.0.0/dbdocs/site/bundle/assets/vendor/minisearch.min.js +8 -0
- dbdocs-1.0.0/dbdocs/site/bundle/index.html +48 -0
- dbdocs-1.0.0/dbdocs/site/deploy.py +123 -0
- dbdocs-1.0.0/dbdocs/site/inject.py +32 -0
- dbdocs-1.0.0/pyproject.toml +92 -0
- dbdocs-0.0.1/PKG-INFO +0 -46
- dbdocs-0.0.1/README.md +0 -19
- dbdocs-0.0.1/dbdocs/cli/main.py +0 -29
- dbdocs-0.0.1/dbdocs/helpers/log.py +0 -37
- dbdocs-0.0.1/pyproject.toml +0 -97
- {dbdocs-0.0.1 → dbdocs-1.0.0}/dbdocs/__init__.py +0 -0
- {dbdocs-0.0.1 → dbdocs-1.0.0}/dbdocs/cli/__init__.py +0 -0
- {dbdocs-0.0.1/dbdocs/helpers → dbdocs-1.0.0/dbdocs/core}/__init__.py +0 -0
dbdocs-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# custom
|
|
2
|
+
/target
|
|
3
|
+
/samples/local
|
|
4
|
+
CHANGELOG.md
|
|
5
|
+
/dbt_packages
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
# Distribution / packaging
|
|
16
|
+
.Python
|
|
17
|
+
build/
|
|
18
|
+
develop-eggs/
|
|
19
|
+
dist/
|
|
20
|
+
downloads/
|
|
21
|
+
eggs/
|
|
22
|
+
.eggs/
|
|
23
|
+
lib/
|
|
24
|
+
lib64/
|
|
25
|
+
parts/
|
|
26
|
+
sdist/
|
|
27
|
+
var/
|
|
28
|
+
wheels/
|
|
29
|
+
pip-wheel-metadata/
|
|
30
|
+
share/python-wheels/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
.installed.cfg
|
|
33
|
+
*.egg
|
|
34
|
+
MANIFEST
|
|
35
|
+
|
|
36
|
+
# PyInstaller
|
|
37
|
+
# Usually these files are written by a python script from a template
|
|
38
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
39
|
+
*.manifest
|
|
40
|
+
*.spec
|
|
41
|
+
|
|
42
|
+
# Installer logs
|
|
43
|
+
pip-log.txt
|
|
44
|
+
pip-delete-this-directory.txt
|
|
45
|
+
|
|
46
|
+
# Unit test / coverage reports
|
|
47
|
+
htmlcov/
|
|
48
|
+
.tox/
|
|
49
|
+
.nox/
|
|
50
|
+
.coverage
|
|
51
|
+
.coverage.*
|
|
52
|
+
.cache
|
|
53
|
+
nosetests.xml
|
|
54
|
+
coverage.xml
|
|
55
|
+
*.cover
|
|
56
|
+
*.py,cover
|
|
57
|
+
.hypothesis/
|
|
58
|
+
.pytest_cache/
|
|
59
|
+
|
|
60
|
+
# Translations
|
|
61
|
+
*.mo
|
|
62
|
+
*.pot
|
|
63
|
+
|
|
64
|
+
# Django stuff:
|
|
65
|
+
*.log
|
|
66
|
+
local_settings.py
|
|
67
|
+
db.sqlite3
|
|
68
|
+
db.sqlite3-journal
|
|
69
|
+
|
|
70
|
+
# Flask stuff:
|
|
71
|
+
instance/
|
|
72
|
+
.webassets-cache
|
|
73
|
+
|
|
74
|
+
# Scrapy stuff:
|
|
75
|
+
.scrapy
|
|
76
|
+
|
|
77
|
+
# Sphinx documentation
|
|
78
|
+
docs/_build/
|
|
79
|
+
|
|
80
|
+
# PyBuilder
|
|
81
|
+
target/
|
|
82
|
+
|
|
83
|
+
# Jupyter Notebook
|
|
84
|
+
.ipynb_checkpoints
|
|
85
|
+
|
|
86
|
+
# IPython
|
|
87
|
+
profile_default/
|
|
88
|
+
ipython_config.py
|
|
89
|
+
|
|
90
|
+
# pyenv
|
|
91
|
+
.python-version
|
|
92
|
+
|
|
93
|
+
# pipenv
|
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
97
|
+
# install all needed dependencies.
|
|
98
|
+
#Pipfile.lock
|
|
99
|
+
|
|
100
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
101
|
+
__pypackages__/
|
|
102
|
+
|
|
103
|
+
# Celery stuff
|
|
104
|
+
celerybeat-schedule
|
|
105
|
+
celerybeat.pid
|
|
106
|
+
|
|
107
|
+
# SageMath parsed files
|
|
108
|
+
*.sage.py
|
|
109
|
+
|
|
110
|
+
# Environments
|
|
111
|
+
.env
|
|
112
|
+
.venv
|
|
113
|
+
env/
|
|
114
|
+
venv/
|
|
115
|
+
ENV/
|
|
116
|
+
env.bak/
|
|
117
|
+
venv.bak/
|
|
118
|
+
|
|
119
|
+
# Spyder project settings
|
|
120
|
+
.spyderproject
|
|
121
|
+
.spyproject
|
|
122
|
+
|
|
123
|
+
# Rope project settings
|
|
124
|
+
.ropeproject
|
|
125
|
+
|
|
126
|
+
# mkdocs documentation
|
|
127
|
+
/site
|
|
128
|
+
/site-docs
|
|
129
|
+
/demo-site
|
|
130
|
+
# Generated dbdocs demo, bundled into the docs site at /demo/ at build time.
|
|
131
|
+
/docs/demo
|
|
132
|
+
|
|
133
|
+
# mypy
|
|
134
|
+
.mypy_cache/
|
|
135
|
+
.dmypy.json
|
|
136
|
+
dmypy.json
|
|
137
|
+
|
|
138
|
+
# Pyre type checker
|
|
139
|
+
.pyre/
|
|
140
|
+
|
|
141
|
+
# Ruff
|
|
142
|
+
.ruff_cache/
|
|
143
|
+
|
|
144
|
+
# Local Claude settings + personal agent memory
|
|
145
|
+
.claude/settings.local.json
|
|
146
|
+
.claude/agent-memory-local/
|
|
147
|
+
|
|
148
|
+
# Frontend (React Flow graph bundle) — Node.js toolchain artifacts
|
|
149
|
+
node_modules/
|
|
150
|
+
frontend/node_modules/
|
|
151
|
+
frontend/dist/
|
|
152
|
+
frontend/.vite/
|
|
153
|
+
frontend/coverage/
|
|
154
|
+
frontend/*.tsbuildinfo
|
|
155
|
+
.eslintcache
|
|
156
|
+
|
|
157
|
+
# npm / yarn / pnpm logs & debug output
|
|
158
|
+
npm-debug.log*
|
|
159
|
+
yarn-debug.log*
|
|
160
|
+
yarn-error.log*
|
|
161
|
+
pnpm-debug.log*
|
|
162
|
+
|
|
163
|
+
# The BUILT graph bundle IS committed (shipped in the wheel) — do not ignore it.
|
|
164
|
+
!dbdocs/site/bundle/assets/graph/
|
|
165
|
+
!dbdocs/site/bundle/assets/graph/**
|
|
166
|
+
|
|
167
|
+
# dbdocs DEBUG file log
|
|
168
|
+
logs/
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c)
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dat Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dbdocs-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dbdocs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Alternative dbt docs site: Catalog + ERD + column-level lineage
|
|
5
|
+
Project-URL: Homepage, https://github.com/datnguye/dbt-docs
|
|
6
|
+
Project-URL: Repository, https://github.com/datnguye/dbt-docs
|
|
7
|
+
Author-email: Dat Nguyen <datnguyen.it09@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: dbt,dbterd,documentation,erd,lineage,sqlglot
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: dbterd>=1.26
|
|
19
|
+
Requires-Dist: pyyaml>=6.0
|
|
20
|
+
Requires-Dist: sqlglot>=25
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="docs/assets/logo.svg" alt="dbdocs logo" width="220" height="88">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<p align="center"><b>An alternative dbt docs site — catalog + ERD + column-level lineage, baked into one file.</b></p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<a href="https://dbdocs.datnguye.me/latest/demo/"><img src="https://img.shields.io/badge/live-demo-FF694A?style=flat&logo=rocket&logoColor=white" alt="live demo"></a>
|
|
31
|
+
<a href="https://dbdocs.datnguye.me/"><img src="https://img.shields.io/badge/docs-visit%20site-blue?style=flat&logo=gitbook&logoColor=white" alt="docs"></a>
|
|
32
|
+
<a href="https://pypi.org/project/dbdocs/"><img src="https://badge.fury.io/py/dbdocs.svg" alt="PyPI version"></a>
|
|
33
|
+
<img src="https://img.shields.io/badge/CLI-Python-FFCE3E?labelColor=14354C&logo=python&logoColor=white" alt="python-cli">
|
|
34
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
35
|
+
<a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.10|3.11|3.12|3.13-3776AB.svg?style=flat&logo=python&logoColor=white" alt="python"></a>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
Turn your dbt artifacts into a single self-contained `index.html`: a browsable catalog, an interactive lineage DAG and ERD, and **column-level lineage** from your compiled SQL. No server, no database, no build step — just a file you can open or host anywhere.
|
|
39
|
+
|
|
40
|
+
| Catalog | Model page | Lineage DAG |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
|  |  |  |
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install dbdocs --upgrade
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.10+.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
dbt docs generate # writes target/manifest.json + target/catalog.json
|
|
56
|
+
dbdocs generate # builds ./site/index.html with all data baked in
|
|
57
|
+
dbdocs serve # static http server on http://127.0.0.1:8000
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Full walkthrough, configuration, and architecture live in the **[documentation](https://dbdocs.datnguye.me/)**.
|
|
61
|
+
|
|
62
|
+
## Why dbdocs?
|
|
63
|
+
|
|
64
|
+
dbt's own docs are great until you want lineage at the *column* level — that's the gap this fills. Everything is derived from your dbt `manifest.json` / `catalog.json` and baked into one offline-friendly SPA: catalog navigation grouped by database/schema, per-model SQL and columns, interactive React Flow graphs, column-level lineage via sqlglot, client-side search, a dark/light theme, and versioned deploys with a built-in version switcher.
|
|
65
|
+
|
|
66
|
+
See the [docs](https://dbdocs.datnguye.me/) for the deep dives.
|
|
67
|
+
|
|
68
|
+
## Contributing
|
|
69
|
+
|
|
70
|
+
Contributions are welcome — bugs, features, docs, typos. See the **[Contributing Guide](https://dbdocs.datnguye.me/latest/nav/development/contributing-guide.html)**.
|
|
71
|
+
|
|
72
|
+
If dbdocs saves you some clicks, consider [buying me a coffee](https://www.buymeacoffee.com/datnguye).
|
|
73
|
+
|
|
74
|
+
<a href="https://www.buymeacoffee.com/datnguye"><img src="https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?logo=buy-me-a-coffee&logoColor=white&labelColor=ff813f&style=for-the-badge" alt="buy me a coffee"></a>
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[MIT](./LICENSE) © Dat Nguyen
|
dbdocs-1.0.0/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/logo.svg" alt="dbdocs logo" width="220" height="88">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><b>An alternative dbt docs site — catalog + ERD + column-level lineage, baked into one file.</b></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://dbdocs.datnguye.me/latest/demo/"><img src="https://img.shields.io/badge/live-demo-FF694A?style=flat&logo=rocket&logoColor=white" alt="live demo"></a>
|
|
9
|
+
<a href="https://dbdocs.datnguye.me/"><img src="https://img.shields.io/badge/docs-visit%20site-blue?style=flat&logo=gitbook&logoColor=white" alt="docs"></a>
|
|
10
|
+
<a href="https://pypi.org/project/dbdocs/"><img src="https://badge.fury.io/py/dbdocs.svg" alt="PyPI version"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/CLI-Python-FFCE3E?labelColor=14354C&logo=python&logoColor=white" alt="python-cli">
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
13
|
+
<a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.10|3.11|3.12|3.13-3776AB.svg?style=flat&logo=python&logoColor=white" alt="python"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
Turn your dbt artifacts into a single self-contained `index.html`: a browsable catalog, an interactive lineage DAG and ERD, and **column-level lineage** from your compiled SQL. No server, no database, no build step — just a file you can open or host anywhere.
|
|
17
|
+
|
|
18
|
+
| Catalog | Model page | Lineage DAG |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
|  |  |  |
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install dbdocs --upgrade
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.10+.
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dbt docs generate # writes target/manifest.json + target/catalog.json
|
|
34
|
+
dbdocs generate # builds ./site/index.html with all data baked in
|
|
35
|
+
dbdocs serve # static http server on http://127.0.0.1:8000
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Full walkthrough, configuration, and architecture live in the **[documentation](https://dbdocs.datnguye.me/)**.
|
|
39
|
+
|
|
40
|
+
## Why dbdocs?
|
|
41
|
+
|
|
42
|
+
dbt's own docs are great until you want lineage at the *column* level — that's the gap this fills. Everything is derived from your dbt `manifest.json` / `catalog.json` and baked into one offline-friendly SPA: catalog navigation grouped by database/schema, per-model SQL and columns, interactive React Flow graphs, column-level lineage via sqlglot, client-side search, a dark/light theme, and versioned deploys with a built-in version switcher.
|
|
43
|
+
|
|
44
|
+
See the [docs](https://dbdocs.datnguye.me/) for the deep dives.
|
|
45
|
+
|
|
46
|
+
## Contributing
|
|
47
|
+
|
|
48
|
+
Contributions are welcome — bugs, features, docs, typos. See the **[Contributing Guide](https://dbdocs.datnguye.me/latest/nav/development/contributing-guide.html)**.
|
|
49
|
+
|
|
50
|
+
If dbdocs saves you some clicks, consider [buying me a coffee](https://www.buymeacoffee.com/datnguye).
|
|
51
|
+
|
|
52
|
+
<a href="https://www.buymeacoffee.com/datnguye"><img src="https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?logo=buy-me-a-coffee&logoColor=white&labelColor=ff813f&style=for-the-badge" alt="buy me a coffee"></a>
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
[MIT](./LICENSE) © Dat Nguyen
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from dbdocs import main
|
|
2
|
-
|
|
3
|
-
main.main()
|
|
1
|
+
from dbdocs import main
|
|
2
|
+
|
|
3
|
+
main.main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
import socketserver
|
|
4
|
+
from http.server import SimpleHTTPRequestHandler
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from dbdocs.core.config import DbDocsConfig
|
|
9
|
+
from dbdocs.core.exceptions import DbDocsError
|
|
10
|
+
from dbdocs.core.log import logger
|
|
11
|
+
from dbdocs.site import deploy as deploy_module
|
|
12
|
+
from dbdocs.site.builder import ReportBuilder
|
|
13
|
+
|
|
14
|
+
__version__ = importlib.metadata.version("dbdocs")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# dbdocs
|
|
18
|
+
@click.group(
|
|
19
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
20
|
+
invoke_without_command=True,
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
epilog="Specify one of these sub-commands and you can find more help from there.",
|
|
23
|
+
)
|
|
24
|
+
@click.version_option(__version__)
|
|
25
|
+
@click.option("-c", "--config", "config_path", default=None, help="Path to dbdocs.yml.")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def dbdocs(ctx, config_path):
|
|
28
|
+
"""Alternative dbt docs site: dbt docs + ERD + column-level lineage."""
|
|
29
|
+
logger.info("Run with dbdocs==%s", __version__)
|
|
30
|
+
try:
|
|
31
|
+
ctx.obj = DbDocsConfig.load(config_path)
|
|
32
|
+
except DbDocsError as exc:
|
|
33
|
+
raise click.ClickException(str(exc)) from exc
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dbdocs.command(name="generate")
|
|
37
|
+
@click.option("-o", "--output-dir", default=None, help="Where to write the site (default: config).")
|
|
38
|
+
@click.option(
|
|
39
|
+
"--dialect", default=None, help="SQL dialect for column lineage (default: adapter_type)."
|
|
40
|
+
)
|
|
41
|
+
@click.pass_obj
|
|
42
|
+
def generate(config: DbDocsConfig, output_dir, dialect):
|
|
43
|
+
"""Build the self-contained site from dbt artifacts."""
|
|
44
|
+
if dialect is not None:
|
|
45
|
+
config.dialect = dialect
|
|
46
|
+
try:
|
|
47
|
+
out = ReportBuilder(config).generate(output_dir=output_dir)
|
|
48
|
+
except DbDocsError as exc:
|
|
49
|
+
raise click.ClickException(str(exc)) from exc
|
|
50
|
+
click.echo(f"Generated site into {out}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dbdocs.command(name="serve")
|
|
54
|
+
@click.option("-p", "--port", default=8000, show_default=True, help="Port to serve on.")
|
|
55
|
+
@click.pass_obj
|
|
56
|
+
def serve(config: DbDocsConfig, port):
|
|
57
|
+
"""Serve the generated site locally (static http server)."""
|
|
58
|
+
handler = functools.partial(SimpleHTTPRequestHandler, directory=config.output_path)
|
|
59
|
+
click.echo(f"Serving {config.output_path} at http://127.0.0.1:{port} (Ctrl-C to stop)")
|
|
60
|
+
socketserver.ThreadingTCPServer.allow_reuse_address = True
|
|
61
|
+
with socketserver.ThreadingTCPServer(("127.0.0.1", port), handler) as httpd:
|
|
62
|
+
httpd.serve_forever()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dbdocs.command(name="deploy")
|
|
66
|
+
@click.option("--version", "version", required=True, help="Version label to deploy (e.g. 1.2).")
|
|
67
|
+
@click.option("--alias", default=None, help="Moving alias for this version (e.g. latest).")
|
|
68
|
+
@click.option(
|
|
69
|
+
"--title", default=None, help="Display title for this version (default: the version)."
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--delete", "delete", is_flag=True, default=False, help="Delete this version instead."
|
|
73
|
+
)
|
|
74
|
+
@click.option("--push/--no-push", default=False, help="Publish to the gh-pages branch.")
|
|
75
|
+
@click.pass_obj
|
|
76
|
+
def deploy(config: DbDocsConfig, version, alias, title, delete, push):
|
|
77
|
+
"""Generate a versioned build and update the version index (or --delete one)."""
|
|
78
|
+
try:
|
|
79
|
+
if delete:
|
|
80
|
+
deploy_module.delete(config, version=version, push=push)
|
|
81
|
+
click.echo(f"Deleted version {version}")
|
|
82
|
+
return
|
|
83
|
+
out = deploy_module.deploy(config, version=version, alias=alias, push=push, title=title)
|
|
84
|
+
except DbDocsError as exc:
|
|
85
|
+
raise click.ClickException(str(exc)) from exc
|
|
86
|
+
click.echo(f"Deployed version {version} into {out}")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Loading dbt artifacts (manifest/catalog) via the dbterd parser.
|
|
2
|
+
|
|
3
|
+
dbterd parses ``manifest.json`` / ``catalog.json`` into ``dbt_artifacts_parser``
|
|
4
|
+
Pydantic models. Two cross-cutting gotchas live here so the rest of dbdocs never
|
|
5
|
+
has to think about them:
|
|
6
|
+
|
|
7
|
+
* **Schema field aliasing.** ``dbt_artifacts_parser`` aliases the ``schema``
|
|
8
|
+
field to ``schema_`` to avoid clobbering Pydantic's ``BaseModel.schema()`` —
|
|
9
|
+
so ``node.schema`` is a *bound method*, not the value. Always read
|
|
10
|
+
``node.schema_``; :func:`db_schema` centralizes that.
|
|
11
|
+
* **Schema-version relaxation.** Passing the detected schema version to
|
|
12
|
+
``read_manifest``/``read_catalog`` makes dbterd apply its relaxation policies,
|
|
13
|
+
keeping parsing robust across dbt versions (including dbt Core 2.0).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from dbterd.helpers import file
|
|
21
|
+
|
|
22
|
+
#: Bucket label used when a node/source has no database or schema set.
|
|
23
|
+
UNKNOWN = "_unknown"
|
|
24
|
+
|
|
25
|
+
#: unique_id prefixes surfaced as catalog nodes (tests/macros/etc. excluded).
|
|
26
|
+
NODE_PREFIXES = ("model.", "seed.", "snapshot.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def artifact_version(target_path: str, artifact: str) -> "int | None":
|
|
30
|
+
"""Resolve a dbt artifact's schema version int from its ``dbt_schema_version``.
|
|
31
|
+
|
|
32
|
+
Returns ``None`` (auto-detect, strict) if the version can't be determined —
|
|
33
|
+
e.g. the file is missing or not valid JSON.
|
|
34
|
+
"""
|
|
35
|
+
artifact_path = Path(target_path) / f"{artifact}.json"
|
|
36
|
+
try:
|
|
37
|
+
metadata = json.loads(artifact_path.read_text(encoding="utf-8")).get("metadata", {})
|
|
38
|
+
except (OSError, json.JSONDecodeError):
|
|
39
|
+
return None
|
|
40
|
+
extracted = file.extract_artifact_version_from_file(metadata.get("dbt_schema_version", ""))
|
|
41
|
+
return int(extracted) if extracted else None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_artifacts(target_path: str) -> "tuple[Any, Any]":
|
|
45
|
+
"""Return the dbterd-parsed ``(manifest, catalog)`` for a dbt target dir."""
|
|
46
|
+
manifest = file.read_manifest(
|
|
47
|
+
path=target_path, version=artifact_version(target_path, "manifest")
|
|
48
|
+
)
|
|
49
|
+
catalog = file.read_catalog(path=target_path, version=artifact_version(target_path, "catalog"))
|
|
50
|
+
return manifest, catalog
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def adapter_type(target_path: str) -> "str | None":
|
|
54
|
+
"""The warehouse adapter (``snowflake``/``bigquery``/…) from manifest metadata.
|
|
55
|
+
|
|
56
|
+
Read from the raw JSON rather than the parsed model so it works regardless of
|
|
57
|
+
how the parser exposes ``metadata``. Used as the default sqlglot dialect for
|
|
58
|
+
column-level lineage. ``None`` if unreadable.
|
|
59
|
+
"""
|
|
60
|
+
manifest_path = Path(target_path) / "manifest.json"
|
|
61
|
+
try:
|
|
62
|
+
metadata = json.loads(manifest_path.read_text(encoding="utf-8")).get("metadata", {})
|
|
63
|
+
except (OSError, json.JSONDecodeError):
|
|
64
|
+
return None
|
|
65
|
+
return metadata.get("adapter_type")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def db_schema(entity: Any) -> "tuple[str, str]":
|
|
69
|
+
"""The ``(database, schema)`` an entity lands in, with safe fallbacks.
|
|
70
|
+
|
|
71
|
+
Reads ``schema_`` (the Pydantic alias — ``schema`` is a bound method) and
|
|
72
|
+
falls back to :data:`UNKNOWN` when either part is missing, so grouping never
|
|
73
|
+
produces a ``None`` bucket.
|
|
74
|
+
"""
|
|
75
|
+
database = getattr(entity, "database", None) or UNKNOWN
|
|
76
|
+
schema = getattr(entity, "schema_", None) or UNKNOWN
|
|
77
|
+
return str(database), str(schema)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def node_name(unique_id: str) -> str:
|
|
81
|
+
"""The dbt node's short name — the last dotted segment of its unique_id."""
|
|
82
|
+
return unique_id.split(".")[-1]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass, field, fields
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from dbdocs.core.exceptions import DbDocsConfigError
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG_FILENAME = "dbdocs.yml"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DbDocsConfig:
|
|
13
|
+
"""Site configuration for a dbdocs build.
|
|
14
|
+
|
|
15
|
+
Loaded from a ``dbdocs.yml`` in the working directory; every field has a
|
|
16
|
+
default so the file is optional. ``version`` is intentionally absent — it is
|
|
17
|
+
a ``deploy`` CLI argument, not site config.
|
|
18
|
+
|
|
19
|
+
``target_dir`` is where the dbt artifacts are read from; ``output_dir`` is
|
|
20
|
+
where the generated self-contained site is written.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
site_name: str = "dbt docs"
|
|
24
|
+
site_url: str = "https://github.com/datnguye/dbt-docs"
|
|
25
|
+
site_author: str = "Dat Nguyen"
|
|
26
|
+
site_description: str = "Alternative dbt documentation site"
|
|
27
|
+
repo_name: str = "datnguye/dbt-docs"
|
|
28
|
+
repo_url: str = "https://github.com/datnguye/dbt-docs"
|
|
29
|
+
project_name: str = "dbt docs"
|
|
30
|
+
#: The footer's Buy-me-a-coffee badge shows by default; set false to hide it.
|
|
31
|
+
show_buy_me_a_coffee: bool = True
|
|
32
|
+
#: Project README rendered on the overview (relative to the working dir). Set
|
|
33
|
+
#: empty to omit the README section. Missing file ⇒ section simply absent.
|
|
34
|
+
readme: str = "README.md"
|
|
35
|
+
target_dir: str = "target"
|
|
36
|
+
#: Where the generated site is written. Nested under the dbt ``target/`` by
|
|
37
|
+
#: default so docs sit alongside the artifacts they're built from.
|
|
38
|
+
output_dir: str = "target/site"
|
|
39
|
+
#: SQL dialect for column-lineage parsing; ``None`` ⇒ derive from the
|
|
40
|
+
#: artifact's ``adapter_type`` (e.g. snowflake, bigquery, postgres).
|
|
41
|
+
dialect: "str | None" = None
|
|
42
|
+
#: Alias the SPA's version switcher treats as the default landing version.
|
|
43
|
+
default_version: str = "latest"
|
|
44
|
+
#: dbterd ERD options (``algo``, ``entity_name_format``, ``select``,
|
|
45
|
+
#: ``resource_type``, …) passed straight to ``DbtErd``. Configured here so the
|
|
46
|
+
#: ERD shape lives in ``dbdocs.yml`` rather than a separate ``.dbterd.yml``.
|
|
47
|
+
dbterd: dict = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def load(cls, path: "str | Path | None" = None) -> "DbDocsConfig":
|
|
51
|
+
"""Load config from ``path`` (or ``./dbdocs.yml``); all-defaults if absent."""
|
|
52
|
+
config_path = Path(path) if path is not None else Path.cwd() / DEFAULT_CONFIG_FILENAME
|
|
53
|
+
if not config_path.is_file():
|
|
54
|
+
return cls()
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
raw = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
58
|
+
except yaml.YAMLError as exc:
|
|
59
|
+
raise DbDocsConfigError(f"Could not parse {config_path}: {exc}") from exc
|
|
60
|
+
|
|
61
|
+
if raw is None:
|
|
62
|
+
return cls()
|
|
63
|
+
if not isinstance(raw, dict):
|
|
64
|
+
raise DbDocsConfigError(
|
|
65
|
+
f"{config_path} must contain a mapping, got {type(raw).__name__}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
known = {f.name for f in fields(cls)}
|
|
69
|
+
unknown = set(raw) - known
|
|
70
|
+
if unknown:
|
|
71
|
+
raise DbDocsConfigError(
|
|
72
|
+
f"Unknown keys in {config_path}: {', '.join(sorted(unknown))}. "
|
|
73
|
+
f"Allowed keys: {', '.join(sorted(known))}."
|
|
74
|
+
)
|
|
75
|
+
return cls(**raw)
|
|
76
|
+
|
|
77
|
+
#: Build-control fields that are not part of the site's display metadata.
|
|
78
|
+
_NON_METADATA_FIELDS = (
|
|
79
|
+
"target_dir",
|
|
80
|
+
"output_dir",
|
|
81
|
+
"dialect",
|
|
82
|
+
"default_version",
|
|
83
|
+
"dbterd",
|
|
84
|
+
"readme",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def render_context(self) -> dict:
|
|
88
|
+
"""The site display metadata injected into the SPA's ``metadata`` block.
|
|
89
|
+
|
|
90
|
+
Excludes build-control fields (where artifacts are read, where the site
|
|
91
|
+
is written, the lineage dialect override) that aren't site metadata.
|
|
92
|
+
"""
|
|
93
|
+
context = asdict(self)
|
|
94
|
+
for field_name in self._NON_METADATA_FIELDS:
|
|
95
|
+
context.pop(field_name, None)
|
|
96
|
+
return context
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def target_path(self) -> str:
|
|
100
|
+
"""Absolute path to the dbt target/ dir where the artifacts live.
|
|
101
|
+
|
|
102
|
+
A relative ``target_dir`` is resolved against the current working
|
|
103
|
+
directory **at access time** — this is intentional and must stay aligned
|
|
104
|
+
with dbterd's ``DbtErd``, which also reads artifacts from ``./target``
|
|
105
|
+
relative to the cwd. An absolute ``target_dir`` is returned unchanged.
|
|
106
|
+
"""
|
|
107
|
+
return str(Path.cwd() / self.target_dir)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def output_path(self) -> str:
|
|
111
|
+
"""Absolute path to the dir the generated site is written into.
|
|
112
|
+
|
|
113
|
+
Resolved against the cwd at access time, mirroring ``target_path`` — a
|
|
114
|
+
relative ``output_dir`` follows the working directory, an absolute one is
|
|
115
|
+
returned unchanged.
|
|
116
|
+
"""
|
|
117
|
+
return str(Path.cwd() / self.output_dir)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""dbdocs exception types.
|
|
2
|
+
|
|
3
|
+
Multiple exception classes may share one file (per the project's Python style).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DbDocsError(Exception):
|
|
8
|
+
"""Base class for all dbdocs errors."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DbDocsConfigError(DbDocsError):
|
|
12
|
+
"""Raised when dbdocs.yml is malformed or holds invalid values."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LineageError(DbDocsError):
|
|
16
|
+
"""Raised when column-level lineage can't be parsed for a model.
|
|
17
|
+
|
|
18
|
+
Always caught per-model by the extractor so one unparseable model never
|
|
19
|
+
fails the whole ``generate`` — the model is skipped and logged instead.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DeployError(DbDocsError):
|
|
24
|
+
"""Raised when a versioned deploy step (e.g. the git push) fails."""
|