dbt-ml 0.1.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 (166) hide show
  1. dbt_ml-0.1.0/.gitignore +21 -0
  2. dbt_ml-0.1.0/CHANGELOG.md +46 -0
  3. dbt_ml-0.1.0/CONTRIBUTING.md +77 -0
  4. dbt_ml-0.1.0/LICENSE +21 -0
  5. dbt_ml-0.1.0/PKG-INFO +346 -0
  6. dbt_ml-0.1.0/README.md +318 -0
  7. dbt_ml-0.1.0/examples/blog_pipeline/dbt_ml_project.yml +11 -0
  8. dbt_ml-0.1.0/examples/blog_pipeline/models/post_text_stats.yml +20 -0
  9. dbt_ml-0.1.0/examples/blog_pipeline/models/posts_by_author.yml +23 -0
  10. dbt_ml-0.1.0/examples/blog_pipeline/models/raw_posts.yml +27 -0
  11. dbt_ml-0.1.0/examples/blog_pipeline/profiles.yml +8 -0
  12. dbt_ml-0.1.0/examples/blog_pipeline/sources/posts.yml +8 -0
  13. dbt_ml-0.1.0/examples/blog_pipeline/transforms/posts_by_author.py +15 -0
  14. dbt_ml-0.1.0/examples/dbt_consumer/.gitignore +6 -0
  15. dbt_ml-0.1.0/examples/dbt_consumer/.user.yml +1 -0
  16. dbt_ml-0.1.0/examples/dbt_consumer/README.md +43 -0
  17. dbt_ml-0.1.0/examples/dbt_consumer/dbt_project.yml +16 -0
  18. dbt_ml-0.1.0/examples/dbt_consumer/models/marts/invoice_facts.sql +15 -0
  19. dbt_ml-0.1.0/examples/dbt_consumer/models/marts/schema.yml +24 -0
  20. dbt_ml-0.1.0/examples/dbt_consumer/models/marts/vendor_overview.sql +21 -0
  21. dbt_ml-0.1.0/examples/dbt_consumer/models/sources/_dbt_ml_sources.yml +58 -0
  22. dbt_ml-0.1.0/examples/dbt_consumer/profiles.yml +7 -0
  23. dbt_ml-0.1.0/examples/dbt_consumer/pyproject.toml +8 -0
  24. dbt_ml-0.1.0/examples/dbt_consumer/uv.lock +1004 -0
  25. dbt_ml-0.1.0/examples/invoice_pipeline/dbt_ml_project.yml +11 -0
  26. dbt_ml-0.1.0/examples/invoice_pipeline/models/invoice_summary.yml +23 -0
  27. dbt_ml-0.1.0/examples/invoice_pipeline/models/monthly_totals.yml +25 -0
  28. dbt_ml-0.1.0/examples/invoice_pipeline/models/raw_invoices.yml +26 -0
  29. dbt_ml-0.1.0/examples/invoice_pipeline/profiles.yml +8 -0
  30. dbt_ml-0.1.0/examples/invoice_pipeline/sources/invoices.yml +11 -0
  31. dbt_ml-0.1.0/examples/invoice_pipeline/transforms/monthly_totals.py +19 -0
  32. dbt_ml-0.1.0/examples/invoice_pipeline/transforms/summarize.py +15 -0
  33. dbt_ml-0.1.0/examples/llm_invoice_pipeline/dbt_ml_project.yml +10 -0
  34. dbt_ml-0.1.0/examples/llm_invoice_pipeline/models/raw_invoices_llm.yml +41 -0
  35. dbt_ml-0.1.0/examples/llm_invoice_pipeline/profiles.yml +13 -0
  36. dbt_ml-0.1.0/examples/llm_invoice_pipeline/sources/invoices.yml +8 -0
  37. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/README.md +34 -0
  38. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/dbt_ml_project.yml +11 -0
  39. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/models/extracted_invoices.yml +27 -0
  40. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/models/raw_pdf_text.yml +24 -0
  41. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/profiles.yml +13 -0
  42. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/sources/invoices.yml +9 -0
  43. dbt_ml-0.1.0/examples/pdf_invoice_pipeline/transforms/llm_extract.py +44 -0
  44. dbt_ml-0.1.0/examples/support_tickets_pipeline/dbt_ml_project.yml +11 -0
  45. dbt_ml-0.1.0/examples/support_tickets_pipeline/models/agent_workload.yml +27 -0
  46. dbt_ml-0.1.0/examples/support_tickets_pipeline/models/open_tickets.yml +27 -0
  47. dbt_ml-0.1.0/examples/support_tickets_pipeline/models/raw_tickets.yml +44 -0
  48. dbt_ml-0.1.0/examples/support_tickets_pipeline/models/redacted_tickets.yml +23 -0
  49. dbt_ml-0.1.0/examples/support_tickets_pipeline/models/sla_breaches.yml +27 -0
  50. dbt_ml-0.1.0/examples/support_tickets_pipeline/profiles.yml +8 -0
  51. dbt_ml-0.1.0/examples/support_tickets_pipeline/sources/tickets.yml +12 -0
  52. dbt_ml-0.1.0/examples/support_tickets_pipeline/transforms/agent_workload.py +19 -0
  53. dbt_ml-0.1.0/examples/support_tickets_pipeline/transforms/open_tickets.py +32 -0
  54. dbt_ml-0.1.0/examples/support_tickets_pipeline/transforms/sla_breaches.py +50 -0
  55. dbt_ml-0.1.0/pyproject.toml +65 -0
  56. dbt_ml-0.1.0/scripts/benchmark.py +100 -0
  57. dbt_ml-0.1.0/src/dbt_ml/__init__.py +1 -0
  58. dbt_ml-0.1.0/src/dbt_ml/adapters/__init__.py +12 -0
  59. dbt_ml-0.1.0/src/dbt_ml/adapters/base.py +142 -0
  60. dbt_ml-0.1.0/src/dbt_ml/adapters/duckdb.py +202 -0
  61. dbt_ml-0.1.0/src/dbt_ml/adapters/registry.py +34 -0
  62. dbt_ml-0.1.0/src/dbt_ml/backends/__init__.py +19 -0
  63. dbt_ml-0.1.0/src/dbt_ml/backends/base.py +35 -0
  64. dbt_ml-0.1.0/src/dbt_ml/backends/email_backend.py +93 -0
  65. dbt_ml-0.1.0/src/dbt_ml/backends/html_backend.py +80 -0
  66. dbt_ml-0.1.0/src/dbt_ml/backends/json_backend.py +40 -0
  67. dbt_ml-0.1.0/src/dbt_ml/backends/llm_backend.py +238 -0
  68. dbt_ml-0.1.0/src/dbt_ml/backends/markdown_backend.py +78 -0
  69. dbt_ml-0.1.0/src/dbt_ml/backends/pdf_backend.py +63 -0
  70. dbt_ml-0.1.0/src/dbt_ml/backends/registry.py +30 -0
  71. dbt_ml-0.1.0/src/dbt_ml/checks/__init__.py +4 -0
  72. dbt_ml-0.1.0/src/dbt_ml/checks/python.py +63 -0
  73. dbt_ml-0.1.0/src/dbt_ml/checks/runner.py +68 -0
  74. dbt_ml-0.1.0/src/dbt_ml/checks/schema.py +214 -0
  75. dbt_ml-0.1.0/src/dbt_ml/cli.py +671 -0
  76. dbt_ml-0.1.0/src/dbt_ml/config/__init__.py +19 -0
  77. dbt_ml-0.1.0/src/dbt_ml/config/loader.py +54 -0
  78. dbt_ml-0.1.0/src/dbt_ml/config/model.py +41 -0
  79. dbt_ml-0.1.0/src/dbt_ml/config/profile.py +42 -0
  80. dbt_ml-0.1.0/src/dbt_ml/config/project.py +37 -0
  81. dbt_ml-0.1.0/src/dbt_ml/config/source.py +34 -0
  82. dbt_ml-0.1.0/src/dbt_ml/dag.py +181 -0
  83. dbt_ml-0.1.0/src/dbt_ml/dbt_export.py +171 -0
  84. dbt_ml-0.1.0/src/dbt_ml/docs.py +120 -0
  85. dbt_ml-0.1.0/src/dbt_ml/freshness.py +103 -0
  86. dbt_ml-0.1.0/src/dbt_ml/manifest.py +132 -0
  87. dbt_ml-0.1.0/src/dbt_ml/profile.py +179 -0
  88. dbt_ml-0.1.0/src/dbt_ml/runner.py +341 -0
  89. dbt_ml-0.1.0/src/dbt_ml/synth/__init__.py +17 -0
  90. dbt_ml-0.1.0/src/dbt_ml/synth/invoice_pdfs.py +107 -0
  91. dbt_ml-0.1.0/src/dbt_ml/synth/invoice_text.py +64 -0
  92. dbt_ml-0.1.0/src/dbt_ml/synth/invoices.py +50 -0
  93. dbt_ml-0.1.0/src/dbt_ml/synth/posts.py +55 -0
  94. dbt_ml-0.1.0/src/dbt_ml/synth/product_html.py +78 -0
  95. dbt_ml-0.1.0/src/dbt_ml/synth/support_emails.py +50 -0
  96. dbt_ml-0.1.0/src/dbt_ml/synth/support_tickets.py +81 -0
  97. dbt_ml-0.1.0/src/dbt_ml/templates/docs/base.html +52 -0
  98. dbt_ml-0.1.0/src/dbt_ml/templates/docs/index.html +54 -0
  99. dbt_ml-0.1.0/src/dbt_ml/templates/docs/lineage.html +29 -0
  100. dbt_ml-0.1.0/src/dbt_ml/templates/docs/model.html +67 -0
  101. dbt_ml-0.1.0/src/dbt_ml/templates/html/dbt_ml_project.yml +11 -0
  102. dbt_ml-0.1.0/src/dbt_ml/templates/html/models/raw_pages.yml +28 -0
  103. dbt_ml-0.1.0/src/dbt_ml/templates/html/profiles.yml +8 -0
  104. dbt_ml-0.1.0/src/dbt_ml/templates/html/sources/pages.yml +8 -0
  105. dbt_ml-0.1.0/src/dbt_ml/templates/html/transforms/.gitkeep +0 -0
  106. dbt_ml-0.1.0/src/dbt_ml/templates/json/.gitkeep +0 -0
  107. dbt_ml-0.1.0/src/dbt_ml/templates/json/dbt_ml_project.yml +11 -0
  108. dbt_ml-0.1.0/src/dbt_ml/templates/json/models/raw_invoices.yml +25 -0
  109. dbt_ml-0.1.0/src/dbt_ml/templates/json/profiles.yml +8 -0
  110. dbt_ml-0.1.0/src/dbt_ml/templates/json/sources/invoices.yml +8 -0
  111. dbt_ml-0.1.0/src/dbt_ml/templates/json/transforms/.gitkeep +0 -0
  112. dbt_ml-0.1.0/src/dbt_ml/templates/markdown/dbt_ml_project.yml +11 -0
  113. dbt_ml-0.1.0/src/dbt_ml/templates/markdown/models/raw_documents.yml +24 -0
  114. dbt_ml-0.1.0/src/dbt_ml/templates/markdown/profiles.yml +8 -0
  115. dbt_ml-0.1.0/src/dbt_ml/templates/markdown/sources/documents.yml +8 -0
  116. dbt_ml-0.1.0/src/dbt_ml/templates/markdown/transforms/.gitkeep +0 -0
  117. dbt_ml-0.1.0/src/dbt_ml/templates/pdf/dbt_ml_project.yml +11 -0
  118. dbt_ml-0.1.0/src/dbt_ml/templates/pdf/models/raw_pdf_text.yml +24 -0
  119. dbt_ml-0.1.0/src/dbt_ml/templates/pdf/profiles.yml +13 -0
  120. dbt_ml-0.1.0/src/dbt_ml/templates/pdf/sources/documents.yml +11 -0
  121. dbt_ml-0.1.0/src/dbt_ml/templates/pdf/transforms/.gitkeep +0 -0
  122. dbt_ml-0.1.0/src/dbt_ml/text/__init__.py +33 -0
  123. dbt_ml-0.1.0/src/dbt_ml/text/dedup.py +68 -0
  124. dbt_ml-0.1.0/src/dbt_ml/text/encoding.py +16 -0
  125. dbt_ml-0.1.0/src/dbt_ml/text/language.py +21 -0
  126. dbt_ml-0.1.0/src/dbt_ml/text/pii.py +173 -0
  127. dbt_ml-0.1.0/src/dbt_ml/text/stats.py +35 -0
  128. dbt_ml-0.1.0/src/dbt_ml/text/tokens.py +49 -0
  129. dbt_ml-0.1.0/src/dbt_ml/text/transforms/__init__.py +12 -0
  130. dbt_ml-0.1.0/src/dbt_ml/text/transforms/_helpers.py +23 -0
  131. dbt_ml-0.1.0/src/dbt_ml/text/transforms/clean_encoding.py +28 -0
  132. dbt_ml-0.1.0/src/dbt_ml/text/transforms/count_tokens.py +30 -0
  133. dbt_ml-0.1.0/src/dbt_ml/text/transforms/detect_language.py +30 -0
  134. dbt_ml-0.1.0/src/dbt_ml/text/transforms/find_duplicates.py +43 -0
  135. dbt_ml-0.1.0/src/dbt_ml/text/transforms/redact_pii.py +64 -0
  136. dbt_ml-0.1.0/src/dbt_ml/text/transforms/text_stats.py +36 -0
  137. dbt_ml-0.1.0/src/dbt_ml/transforms/__init__.py +3 -0
  138. dbt_ml-0.1.0/src/dbt_ml/transforms/runner.py +69 -0
  139. dbt_ml-0.1.0/src/dbt_ml/versioning.py +46 -0
  140. dbt_ml-0.1.0/tests/__init__.py +0 -0
  141. dbt_ml-0.1.0/tests/conftest.py +10 -0
  142. dbt_ml-0.1.0/tests/test_adapters.py +152 -0
  143. dbt_ml-0.1.0/tests/test_backends.py +70 -0
  144. dbt_ml-0.1.0/tests/test_checks.py +194 -0
  145. dbt_ml-0.1.0/tests/test_cli_init_show.py +125 -0
  146. dbt_ml-0.1.0/tests/test_config.py +56 -0
  147. dbt_ml-0.1.0/tests/test_custom_python_tests.py +122 -0
  148. dbt_ml-0.1.0/tests/test_dag.py +182 -0
  149. dbt_ml-0.1.0/tests/test_dbt_export.py +142 -0
  150. dbt_ml-0.1.0/tests/test_docs.py +62 -0
  151. dbt_ml-0.1.0/tests/test_email_backend.py +81 -0
  152. dbt_ml-0.1.0/tests/test_freshness.py +113 -0
  153. dbt_ml-0.1.0/tests/test_html_backend.py +95 -0
  154. dbt_ml-0.1.0/tests/test_llm_backend.py +187 -0
  155. dbt_ml-0.1.0/tests/test_manifest.py +67 -0
  156. dbt_ml-0.1.0/tests/test_markdown_backend.py +67 -0
  157. dbt_ml-0.1.0/tests/test_pdf_backend.py +51 -0
  158. dbt_ml-0.1.0/tests/test_pdf_pipeline.py +93 -0
  159. dbt_ml-0.1.0/tests/test_pii.py +210 -0
  160. dbt_ml-0.1.0/tests/test_profile.py +215 -0
  161. dbt_ml-0.1.0/tests/test_runner.py +167 -0
  162. dbt_ml-0.1.0/tests/test_synth.py +42 -0
  163. dbt_ml-0.1.0/tests/test_text.py +100 -0
  164. dbt_ml-0.1.0/tests/test_text_transforms.py +157 -0
  165. dbt_ml-0.1.0/tests/test_versioning.py +66 -0
  166. dbt_ml-0.1.0/uv.lock +2281 -0
@@ -0,0 +1,21 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+
8
+ # generated synthetic data + DuckDB output
9
+ target/
10
+ examples/**/data/
11
+ examples/**/target/
12
+
13
+ # never commit database files anywhere in the tree
14
+ *.duckdb
15
+ *.duckdb.wal
16
+ *.duckdb.tmp
17
+ *.db
18
+ *.db-journal
19
+ *.sqlite
20
+ *.sqlite3
21
+ dist/
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ ## v0.1.0 (unreleased)
4
+
5
+ Initial public preview.
6
+
7
+ ### Backends
8
+ - `json` — project keys from JSON objects (deterministic, no API)
9
+ - `markdown` — frontmatter + body + word count
10
+ - `pdf` — text extraction via pypdf, with empty-text warnings for scanned PDFs
11
+ - `html` — body text, CSS selectors, OpenGraph, meta tags via BeautifulSoup
12
+ - `llm` — Claude-backed structured extraction with response caching
13
+
14
+ ### Pipeline mechanics
15
+ - Declarative YAML: project, sources, extraction models, transform models
16
+ - DAG via `graphlib`, `ref()` syntax, cycle detection
17
+ - Incremental materialization keyed on content + code version
18
+ - `full` / `incremental` materialization
19
+ - `target/manifest.json` and `target/run_results.json` artifacts on every run
20
+
21
+ ### CLI
22
+ - `init` (with `--template {json,pdf,markdown,html}`)
23
+ - `seed`, `compile`, `graph`, `run` (with `--full-refresh`), `test`, `show`, `clean`
24
+ - `source freshness` — mtime-vs-threshold check
25
+ - `emit-dbt-sources` — write dbt-compatible `sources.yml`
26
+
27
+ ### Selection + filtering
28
+ - `--select` / `--exclude` with dbt-shaped syntax: name, `name+`, `+name`, `+name+`
29
+ - `tag:` prefix for tag-based selection
30
+ - `tags:` on models and sources
31
+
32
+ ### Testing
33
+ - Built-in: `not_null`, `unique`, `min_rows`, `not_empty`
34
+ - Severity: `severity: warn` downgrades fail → warn (exit 0)
35
+ - Custom Python tests: drop `tests/<module>.py` with `run(con, table_ref) -> str | None`
36
+
37
+ ### Profiles
38
+ - dbt-shaped `profiles.yml` with per-target warehouse + llm config
39
+ - Lookup: `--profiles-dir` → `$DBT_ML_PROFILES_DIR` → `<project>/profiles.yml` → `~/.dbt_ml/profiles.yml`
40
+ - `--target` flag selects within active profile
41
+ - LLM cache and model id come from profile, with per-model overrides
42
+
43
+ ### Composition
44
+ - `dbt-ml emit-dbt-sources` writes dbt-compatible `sources.yml` so a
45
+ `dbt-duckdb` project can `{{ source(...) }}` dbt-ml-materialized tables in the same DuckDB file
46
+ - Worked example in `examples/dbt_consumer/` (verified end-to-end with `dbt build`)
@@ -0,0 +1,77 @@
1
+ # Contributing to dbt-ml
2
+
3
+ dbt-ml is a small, focused Python project. Contributions welcome.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/<your-org>/dbt-ml
9
+ cd dbt-ml
10
+ uv sync
11
+ uv run pytest -q
12
+ ```
13
+
14
+ ## Local checks (required before opening a PR)
15
+
16
+ ```bash
17
+ uv run ruff check # lint
18
+ uv run pytest -q # tests
19
+ ```
20
+
21
+ CI runs both on every push and PR (see `.github/workflows/ci.yml`).
22
+
23
+ ## Adding a new backend
24
+
25
+ 1. New file in `src/dbt_ml/backends/` inheriting from `BaseBackend`.
26
+ 2. Decorate the class with `@register`.
27
+ 3. Implement `name()`, `supported_formats()`, `extract(path, options)`.
28
+ 4. Register the import in `src/dbt_ml/backends/__init__.py` (the side-effect
29
+ import is what triggers `@register`).
30
+ 5. Add a synth generator under `src/dbt_ml/synth/` if you want to support
31
+ `dbt-ml seed --type <name>`.
32
+ 6. Add tests under `tests/test_<backend>_backend.py`.
33
+ 7. Add an init template under `src/dbt_ml/templates/<backend>/` if a fresh-
34
+ project starter makes sense.
35
+
36
+ ## Adding a new schema test
37
+
38
+ 1. Add the name to `SUPPORTED_TESTS` in `src/dbt_ml/checks/schema.py`.
39
+ 2. Implement a helper function returning `TestResult`(s).
40
+ 3. Wire into `_run_named_test`.
41
+ 4. Add tests under `tests/test_checks.py`.
42
+
43
+ ## Adding a new CLI command
44
+
45
+ 1. Add the click subcommand in `src/dbt_ml/cli.py`.
46
+ 2. Wire through `ctx.obj["project_dir"]` / `profiles_dir` / `target` like the
47
+ existing commands do.
48
+ 3. Raise `click.ClickException` from any `*Error` exception you catch.
49
+ 4. Update README's CLI section.
50
+
51
+ ## Scope discipline
52
+
53
+ dbt-ml v1 is intentionally limited:
54
+
55
+ - DuckDB-only. No Snowflake/BigQuery adapters in v1.
56
+ - Pure Python. No Rust until the model is proven.
57
+ - LLM provider is Anthropic-only. Bedrock/Vertex/OpenAI are v2.
58
+ - Schema tests are the four listed in README, plus custom Python. No dbt-style
59
+ generic test machinery in v1.
60
+
61
+ If a change pulls in any of the above, push back or open a discussion first.
62
+
63
+ ## Commit style
64
+
65
+ Conventional commits not required, but tight subject lines please:
66
+
67
+ ```
68
+ add html backend
69
+ fix: pdf backend silent failure on scanned PDFs
70
+ test: cover profile lookup with $DBT_ML_PROFILES_DIR
71
+ docs: rewrite README quickstart for end users
72
+ ```
73
+
74
+ ## Releasing
75
+
76
+ Bump `version` in `pyproject.toml`, update `CHANGELOG.md`, tag the commit
77
+ `v0.X.Y`. PyPI publishing is manual today (`uv build && uv publish`).
dbt_ml-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Noonan
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.
dbt_ml-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,346 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbt-ml
3
+ Version: 0.1.0
4
+ Summary: dbt for unstructured data — declarative pipelines for documents, text, and ML preprocessing
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: anthropic>=0.40
9
+ Requires-Dist: beautifulsoup4>=4.12
10
+ Requires-Dist: click>=8.1
11
+ Requires-Dist: datasketch>=1.6
12
+ Requires-Dist: duckdb>=1.0
13
+ Requires-Dist: faker>=25
14
+ Requires-Dist: fpdf2>=2.7
15
+ Requires-Dist: ftfy>=6.2
16
+ Requires-Dist: jinja2>=3.1
17
+ Requires-Dist: langdetect>=1.0
18
+ Requires-Dist: polars>=1.0
19
+ Requires-Dist: presidio-analyzer>=2.2
20
+ Requires-Dist: presidio-anonymizer>=2.2
21
+ Requires-Dist: pyarrow>=16
22
+ Requires-Dist: pydantic>=2.7
23
+ Requires-Dist: pypdf>=4.0
24
+ Requires-Dist: pyyaml>=6
25
+ Requires-Dist: tiktoken>=0.7
26
+ Requires-Dist: watchfiles>=0.21
27
+ Description-Content-Type: text/markdown
28
+
29
+ # dbt-ml
30
+
31
+ **dbt for unstructured data.** Declarative YAML pipelines that turn folders of
32
+ documents — PDFs, markdown, HTML, JSON, email, free-form text — into warehouse
33
+ tables. Incremental processing, schema tests, dbt-style selectors, profiles,
34
+ and a manifest artifact you can wire into other tools.
35
+
36
+ This is the v0.1 PoC: pure Python, DuckDB warehouse. **v0.2 is in scope** —
37
+ adding RAG support (chunking, embeddings, vector storage via LanceDB) and a
38
+ warehouse adapter pattern aimed at the dbt-core set (Postgres, Snowflake,
39
+ BigQuery, Databricks, …). The full Rust+Python design lives in
40
+ `docbt-core-implementation-plan.md` and is deferred to a later v2.
41
+
42
+ ## Where dbt-ml fits
43
+
44
+ The 2026 landscape for unstructured document pipelines has two stable poles:
45
+
46
+ - **Managed RAG-as-a-Service** (Vectara, Bedrock Knowledge Bases, Vertex AI
47
+ Search, Snowflake Cortex Search, Glean) — best when time-to-value matters
48
+ and the team can't dedicate ML engineers.
49
+ - **Compose best-of-breed Python components** (LlamaParse → contextual
50
+ chunking → Voyage embeddings → Qdrant → Cohere Rerank → Ragas) — best when
51
+ retrieval quality, multi-tenant isolation, or unusual document types
52
+ matter and you have ≥2 ML engineers.
53
+
54
+ dbt-ml is the **opinionated, declarative path through the second lane**.
55
+ Where LlamaIndex is imperative Python, dbt-ml is YAML + a manifest + tests +
56
+ lineage. Where Snowflake Cortex Search hides everything, dbt-ml makes every
57
+ stage inspectable and reproducible. It's *dbt-shaped*: the same DAG +
58
+ selectors + tests + artifacts pattern, applied to unstructured data.
59
+
60
+ ---
61
+
62
+ ## You have a folder of files. Get them into your warehouse.
63
+
64
+ ```bash
65
+ # Install (once it's published; today: clone and `uv sync`)
66
+ uv add git+https://github.com/<your-org>/dbt-ml # or local: uv pip install -e .
67
+
68
+ # 1. Scaffold a project for whatever shape your data is
69
+ uv run dbt-ml init my_project --template pdf # or json, markdown, html
70
+
71
+ # 2. Drop your files into ./my_project/data/pdfs/ (or wherever the source points)
72
+
73
+ # 3. Run it
74
+ cd my_project
75
+ uv run dbt-ml run
76
+
77
+ # 4. Query the result
78
+ duckdb target/dbt_ml.duckdb -c "SELECT * FROM my_project.raw_pdf_text LIMIT 5"
79
+ ```
80
+
81
+ That's the whole loop. Everything else (selectors, profiles, tests, LLM
82
+ extraction, dbt handoff) is opt-in on top.
83
+
84
+ ## What dbt-ml actually does
85
+
86
+ | Concept | What it means |
87
+ |--------------------|--------------------------------------------------------------------------------|
88
+ | **Source** | A glob over a folder. `*.pdf`, `*.json`, `*.html`, `*.md` — your choice. |
89
+ | **Extraction model** | One row per source file, produced by a backend (pdf, json, markdown, html, llm). |
90
+ | **Transform model** | A Python module returning a Polars DataFrame, depends on other models via `ref()`. |
91
+ | **Materialization** | `full` (always replace) or `incremental` (skip unchanged input on re-runs). |
92
+ | **Tests** | `not_null`, `unique`, `min_rows`, custom Python — with `severity: warn` if you want.|
93
+ | **Profile** | Warehouse + LLM config, swappable per `--target dev|prod`. No credentials in models. |
94
+ | **Artifacts** | `target/manifest.json`, `target/run_results.json`, `target/sources.yml` (for dbt). |
95
+
96
+ ## Backends
97
+
98
+ | Backend | Reads | Notes |
99
+ |------------|-------------------|-------------------------------------------------------------------------------------------|
100
+ | `json` | `*.json` | Projects keys per `options.fields`. Deterministic, no API. |
101
+ | `markdown` | `*.md` | YAML frontmatter + `body` + optional `word_count`. Deterministic, no API. |
102
+ | `pdf` | `*.pdf` | Per-page text via pypdf. Warns on empty extracts (likely scanned). Deterministic, no API. |
103
+ | `html` | `*.html`/`*.htm` | Body text + CSS selectors + OpenGraph/meta via BeautifulSoup. Deterministic, no API. |
104
+ | `email` | `*.eml` | from/to/subject/date/body via stdlib `email`. Deterministic, no API. |
105
+ | `llm` | `*.txt`/`*.md` | Claude tool-use → structured fields. Responses cached. Requires `ANTHROPIC_API_KEY`. |
106
+
107
+ Add a new backend = drop a file under `src/dbt_ml/backends/`, inherit from
108
+ `BaseBackend`, decorate with `@register`. No plugin system needed for v1.
109
+
110
+ ## The CLI
111
+
112
+ ```
113
+ dbt-ml init <name> [--template {json,pdf,markdown,html}] # scaffold a fresh project
114
+ dbt-ml seed [--count N] [--type {invoices,posts,...,tickets,emails}]
115
+ dbt-ml compile # parse YAML, validate DAG, write manifest.json
116
+ dbt-ml graph # Mermaid DAG to stdout
117
+ dbt-ml run [--select EXPR] [--exclude EXPR] [--full-refresh] [--threads N] [--watch]
118
+ dbt-ml test [--select EXPR] [--exclude EXPR]
119
+ dbt-ml show <model> [--limit N] # peek at a materialized table
120
+ dbt-ml source freshness # mtime vs warn_after/error_after
121
+ dbt-ml docs generate [--output DIR] # static HTML site from manifest.json
122
+ dbt-ml docs serve [--port N] # local http.server over target/docs/
123
+ dbt-ml emit-dbt-sources [--output PATH] # write dbt-compatible sources.yml
124
+ dbt-ml clean # delete the project's DuckDB
125
+
126
+ # Global flags (work on every command):
127
+ dbt-ml --project-dir <dir> --profiles-dir <dir> --target <name> <command>
128
+ ```
129
+
130
+ ### Useful flags
131
+
132
+ - `--watch` on `run` listens to source paths and re-runs on file changes
133
+ (debounced 500ms). Ctrl-C to stop.
134
+ - `--threads N` parallelizes per-document extraction within an extraction
135
+ model. Most useful for PDF / LLM / HTML (I/O- or API-bound). The LLM cache
136
+ is lock-serialized so threading is safe.
137
+
138
+ ## Selectors
139
+
140
+ dbt-shaped. Whitespace-separated tokens, optional `+` modifiers, `tag:` prefix.
141
+
142
+ ```bash
143
+ dbt-ml run --select raw_pdf_text # one model
144
+ dbt-ml run --select 'raw_pdf_text+' # plus all downstream
145
+ dbt-ml run --select '+invoice_summary' # plus all upstream
146
+ dbt-ml run --select 'tag:raw+' # all models tagged "raw" + their downstream
147
+ dbt-ml run --exclude tag:expensive
148
+ ```
149
+
150
+ ## Profiles
151
+
152
+ Warehouse and LLM config live in `profiles.yml`, *not* in `dbt_ml_project.yml`.
153
+ Project YAML says `profile: my_project`; profile says where to write and which
154
+ LLM to call. Swap `--target prod` to switch environments.
155
+
156
+ ```yaml
157
+ # profiles.yml — sits next to dbt_ml_project.yml, or in ~/.dbt_ml/profiles.yml
158
+ my_project:
159
+ target: dev
160
+ outputs:
161
+ dev:
162
+ warehouse:
163
+ type: duckdb
164
+ path: ./target/dbt_ml.duckdb
165
+ schema: my_project
166
+ llm:
167
+ provider: anthropic
168
+ model: claude-haiku-4-5
169
+ api_key_env: ANTHROPIC_API_KEY
170
+ cache_path: ./target/llm_cache.duckdb
171
+ prod:
172
+ warehouse:
173
+ type: duckdb
174
+ path: /data/prod/dbt_ml.duckdb
175
+ schema: my_project_prod
176
+ llm:
177
+ model: claude-sonnet-4-6
178
+ cache_path: /data/prod/llm_cache.duckdb
179
+ ```
180
+
181
+ Lookup order: `--profiles-dir` flag → `$DBT_ML_PROFILES_DIR` →
182
+ `<project>/profiles.yml` → `~/.dbt_ml/profiles.yml`.
183
+
184
+ ## Built-in text preprocessing
185
+
186
+ Reference any of these as a Python transform module — no project-local code
187
+ needed. Users can override by writing their own `transforms/<name>.py`
188
+ (project-local files win over installed packages).
189
+
190
+ ```yaml
191
+ - name: post_text_stats
192
+ depends_on: [ref('raw_posts')]
193
+ transform:
194
+ type: python
195
+ module: dbt_ml.text.transforms.text_stats # built-in, ships with dbt-ml
196
+ options:
197
+ text_field: body
198
+ emit: [word_count, sentence_count]
199
+ ```
200
+
201
+ | Module | What it does |
202
+ |-------------------------------------------|--------------------------------------------------------------------------------|
203
+ | `dbt_ml.text.transforms.text_stats` | Adds `word_count` / `char_count` / `sentence_count` / `paragraph_count` |
204
+ | `dbt_ml.text.transforms.clean_encoding` | Fixes mojibake (UTF-8-as-Latin-1 confusion) via ftfy |
205
+ | `dbt_ml.text.transforms.detect_language` | Adds a 2-letter ISO language code per row via langdetect |
206
+ | `dbt_ml.text.transforms.count_tokens` | Adds `token_count` for an OpenAI / Claude-style tokenizer (tiktoken) |
207
+ | `dbt_ml.text.transforms.find_duplicates` | Flags near-duplicate rows via MinHash + LSH (Jaccard threshold configurable) |
208
+ | `dbt_ml.text.transforms.redact_pii` | Detects + redacts PII via Microsoft Presidio (requires `en_core_web_sm` spaCy model) |
209
+
210
+ All are pure functions importable via `from dbt_ml.text import …` if you'd
211
+ rather wire them into your own transforms.
212
+
213
+ **PII setup** — `redact_pii` uses spaCy under the hood. First-time install:
214
+
215
+ ```bash
216
+ python -m spacy download en_core_web_sm
217
+ ```
218
+
219
+ Without the model, calls into `redact_pii` raise a clear `PIIError` pointing
220
+ at this command.
221
+
222
+ ## Tests
223
+
224
+ ```yaml
225
+ tests:
226
+ - not_null: [vendor, total] # column-level, fails the run
227
+ - unique: invoice_id # single-column
228
+ - unique: [a, b] # composite (compiled to dbt_utils on emit)
229
+ - min_rows: 100
230
+ - not_empty # bare-string form of min_rows: 1
231
+ - not_null: total, severity: warn # warn doesn't fail the run
232
+ - python: tests.my_check # custom: tests/my_check.py defines run(con, table_ref) -> str | None
233
+ ```
234
+
235
+ ## Examples in this repo
236
+
237
+ | Path | What it shows |
238
+ |-------------------------------------|------------------------------------------------------------------------|
239
+ | `examples/invoice_pipeline/` | JSON extraction → per-vendor + monthly aggregations |
240
+ | `examples/blog_pipeline/` | Markdown frontmatter → per-author word counts |
241
+ | `examples/pdf_invoice_pipeline/` | PDFs → text via pypdf → LLM-extracted structured fields |
242
+ | `examples/llm_invoice_pipeline/` | Free-form invoice text → LLM extraction (no PDF stage) |
243
+ | `examples/support_tickets_pipeline/`| JSON tickets → open queue + SLA breaches + per-team workload (no LLM) |
244
+ | `examples/dbt_consumer/` | dbt-duckdb project consuming dbt-ml-materialized tables |
245
+
246
+ Each example is runnable end-to-end with `uv run dbt-ml --project-dir examples/<name> ...`.
247
+
248
+ ## Composing with dbt (dbt-duckdb)
249
+
250
+ dbt-ml and dbt can share a DuckDB file: dbt-ml does the unstructured→structured
251
+ "E", dbt does the SQL "T". The bridge:
252
+
253
+ ```bash
254
+ uv run dbt-ml --project-dir examples/invoice_pipeline run
255
+ uv run dbt-ml --project-dir examples/invoice_pipeline emit-dbt-sources \
256
+ --output examples/dbt_consumer/models/sources/_dbt_ml_sources.yml
257
+
258
+ cd examples/dbt_consumer && uv sync && uv run dbt build --profiles-dir .
259
+ ```
260
+
261
+ `emit-dbt-sources` translates dbt-ml tables into a dbt-compatible `sources.yml`.
262
+ Column tests carry over (`not_null`, single-column `unique`); composite unique
263
+ becomes a `dbt_utils.unique_combination_of_columns` macro test.
264
+
265
+ ## Artifacts
266
+
267
+ Every `dbt-ml compile` / `dbt-ml run` writes to `target/`:
268
+
269
+ - **`manifest.json`** — project, sources, models, refs, tags, `code_version` per
270
+ model, DAG nodes+edges+execution order. Re-generated each run.
271
+ - **`run_results.json`** — per-model documents processed/skipped, rows written,
272
+ duration, errors.
273
+ - **`sources.yml`** — only when you call `emit-dbt-sources`. dbt-shaped.
274
+ - **`docs/`** — static HTML site (`dbt-ml docs generate`) with project overview,
275
+ Mermaid DAG, per-model pages. Serve locally with `dbt-ml docs serve`.
276
+
277
+ External tools (lineage viewers, CI dashboards, the dbt-consumer above)
278
+ consume these.
279
+
280
+ ## Benchmarks
281
+
282
+ ```bash
283
+ uv run python scripts/benchmark.py --count 5000
284
+ ```
285
+
286
+ 5000-doc benchmark on the JSON backend:
287
+
288
+ ```
289
+ seed 5000 invoices 0.8s → 6.3k docs/sec
290
+ first run (cold) 4.8s → 1.0k docs/sec
291
+ second run (all skipped) 0.3s → 19.9k docs/sec
292
+ third run (1 changed) 0.3s → 18.2k docs/sec
293
+ full-refresh 4.3s → 1.2k docs/sec
294
+ ```
295
+
296
+ Linear through 5k. Bottleneck is single-threaded extraction; parallelism is a v2 item.
297
+
298
+ ## Layout
299
+
300
+ ```
301
+ src/dbt_ml/
302
+ ├── cli.py # click: init/seed/compile/graph/run/test/show/clean/source freshness/emit-dbt-sources
303
+ ├── config/ # pydantic models for project/source/model/profile + loader
304
+ ├── profile.py # profile discovery + resolution (warehouse + llm)
305
+ ├── dag.py # graphlib-based DAG, selectors (+ name +, tag:foo), Mermaid render
306
+ ├── state.py # DuckDB-backed incremental state
307
+ ├── runner.py # extract → materialize orchestration
308
+ ├── manifest.py # target/manifest.json + run_results.json
309
+ ├── dbt_export.py # target/sources.yml (dbt-shaped)
310
+ ├── freshness.py # source mtime check
311
+ ├── backends/ # json, markdown, pdf, html, llm
312
+ ├── transforms/runner.py # loads user Python transform modules + TransformContext
313
+ ├── checks/ # schema tests + custom Python tests + severity
314
+ ├── synth/ # synthetic data generators per shape
315
+ └── templates/ # init scaffolds for {json,pdf,markdown,html}
316
+ ```
317
+
318
+ ## Roadmap
319
+
320
+ **v0.2 — RAG + warehouse adapter pattern.** Tracked in GitHub issues
321
+ tagged `roadmap`. The four headline pieces:
322
+
323
+ 1. **Warehouse adapter pattern** matching dbt-core's set. v0.2 starts with
324
+ DuckDB (current) + LanceDB (lakehouse-style vector store); subsequent
325
+ versions add Postgres, then Snowflake / BigQuery / Databricks / Redshift.
326
+ 2. **Chunking primitives** as a first-class model kind: recursive (default),
327
+ token-aware, layout-aware, optional Anthropic Contextual Retrieval
328
+ (49–67% retrieval failure reduction per published numbers).
329
+ 3. **Embedding primitives** as a first-class model kind: Voyage, Cohere,
330
+ OpenAI, and local sentence-transformers providers. Same cache mechanic
331
+ as today's LLM backend so re-runs are free.
332
+ 4. **Layout-aware OSS parsers** as additional backends: Docling (privacy +
333
+ table quality), Marker (best OSS layout fidelity).
334
+
335
+ **Deferred beyond v0.2:**
336
+
337
+ - Rust CLI + PyO3 bridge.
338
+ - Metaxy integration (replace `state.py` with `MetadataStore`).
339
+ - Field-level lineage (`version_from: [ref('x').field_a]`).
340
+ - Parallel *model* execution (today's `--threads` parallelizes within a model).
341
+ - Managed parser backends (Reducto, Mistral OCR 3, LlamaParse) — generic
342
+ remote-parser adapter pattern when there's a real ask.
343
+ - Reranker hooks (Cohere Rerank, Voyage Rerank).
344
+ - Multi-LLM-provider adapters (Bedrock, Vertex, OpenAI structured output).
345
+ - PII detection / redaction (Microsoft Presidio).
346
+ - Ragas integration (`dbt-ml eval`).