det-elt 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 (144) hide show
  1. det_elt-0.1.0/LICENSE +21 -0
  2. det_elt-0.1.0/PKG-INFO +349 -0
  3. det_elt-0.1.0/README.md +292 -0
  4. det_elt-0.1.0/pyproject.toml +200 -0
  5. det_elt-0.1.0/setup.cfg +4 -0
  6. det_elt-0.1.0/src/det/__init__.py +117 -0
  7. det_elt-0.1.0/src/det/cli/__init__.py +21 -0
  8. det_elt-0.1.0/src/det/cli/app.py +38 -0
  9. det_elt-0.1.0/src/det/cli/approvals.py +193 -0
  10. det_elt-0.1.0/src/det/cli/common.py +291 -0
  11. det_elt-0.1.0/src/det/cli/dbt_cmd.py +151 -0
  12. det_elt-0.1.0/src/det/cli/extract_load.py +205 -0
  13. det_elt-0.1.0/src/det/cli/inspect_cmd.py +89 -0
  14. det_elt-0.1.0/src/det/cli/migrate_prune.py +297 -0
  15. det_elt-0.1.0/src/det/cli/ops.py +476 -0
  16. det_elt-0.1.0/src/det/cli/render_runs.py +168 -0
  17. det_elt-0.1.0/src/det/cli/scaffold.py +234 -0
  18. det_elt-0.1.0/src/det/destinations/__init__.py +1 -0
  19. det_elt-0.1.0/src/det/destinations/models.py +200 -0
  20. det_elt-0.1.0/src/det/errors.py +58 -0
  21. det_elt-0.1.0/src/det/ingestion/__init__.py +1 -0
  22. det_elt-0.1.0/src/det/ingestion/base.py +35 -0
  23. det_elt-0.1.0/src/det/ingestion/chunks.py +17 -0
  24. det_elt-0.1.0/src/det/ingestion/det_backend.py +193 -0
  25. det_elt-0.1.0/src/det/ingestion/dlt_backend.py +7 -0
  26. det_elt-0.1.0/src/det/ingestion/duckdb_writer.py +129 -0
  27. det_elt-0.1.0/src/det/ingestion/iceberg_catalog.py +278 -0
  28. det_elt-0.1.0/src/det/ingestion/iceberg_writer.py +563 -0
  29. det_elt-0.1.0/src/det/ingestion/jsonl.py +64 -0
  30. det_elt-0.1.0/src/det/ingestion/postgres_writer.py +136 -0
  31. det_elt-0.1.0/src/det/ingestion/sql_ddl.py +82 -0
  32. det_elt-0.1.0/src/det/ingestion/sql_replace.py +91 -0
  33. det_elt-0.1.0/src/det/ingestion/thin_backend.py +51 -0
  34. det_elt-0.1.0/src/det/logging.py +237 -0
  35. det_elt-0.1.0/src/det/mcp/__init__.py +5 -0
  36. det_elt-0.1.0/src/det/mcp/__main__.py +6 -0
  37. det_elt-0.1.0/src/det/mcp/airflow_inspect.py +471 -0
  38. det_elt-0.1.0/src/det/mcp/catalog.py +178 -0
  39. det_elt-0.1.0/src/det/mcp/context.py +43 -0
  40. det_elt-0.1.0/src/det/mcp/cube_client.py +204 -0
  41. det_elt-0.1.0/src/det/mcp/errors.py +38 -0
  42. det_elt-0.1.0/src/det/mcp/generate.py +413 -0
  43. det_elt-0.1.0/src/det/mcp/inspect/__init__.py +104 -0
  44. det_elt-0.1.0/src/det/mcp/inspect/_common.py +168 -0
  45. det_elt-0.1.0/src/det/mcp/inspect/_diagnose.py +177 -0
  46. det_elt-0.1.0/src/det/mcp/inspect/_partitions.py +405 -0
  47. det_elt-0.1.0/src/det/mcp/inspect/_sample.py +748 -0
  48. det_elt-0.1.0/src/det/mcp/params.py +325 -0
  49. det_elt-0.1.0/src/det/mcp/policy.py +377 -0
  50. det_elt-0.1.0/src/det/mcp/prompts.py +95 -0
  51. det_elt-0.1.0/src/det/mcp/query_sql.py +241 -0
  52. det_elt-0.1.0/src/det/mcp/reload.py +50 -0
  53. det_elt-0.1.0/src/det/mcp/resources.py +67 -0
  54. det_elt-0.1.0/src/det/mcp/server.py +501 -0
  55. det_elt-0.1.0/src/det/mcp/tools.py +1122 -0
  56. det_elt-0.1.0/src/det/optional_deps.py +61 -0
  57. det_elt-0.1.0/src/det/plugins.py +25 -0
  58. det_elt-0.1.0/src/det/py.typed +0 -0
  59. det_elt-0.1.0/src/det/runtime/__init__.py +1 -0
  60. det_elt-0.1.0/src/det/runtime/approval.py +756 -0
  61. det_elt-0.1.0/src/det/runtime/biglake_register.py +380 -0
  62. det_elt-0.1.0/src/det/runtime/bronze_land.py +145 -0
  63. det_elt-0.1.0/src/det/runtime/check.py +578 -0
  64. det_elt-0.1.0/src/det/runtime/coerce.py +182 -0
  65. det_elt-0.1.0/src/det/runtime/config.py +973 -0
  66. det_elt-0.1.0/src/det/runtime/dbt_runner.py +340 -0
  67. det_elt-0.1.0/src/det/runtime/discovery.py +388 -0
  68. det_elt-0.1.0/src/det/runtime/dlt_hygiene.py +185 -0
  69. det_elt-0.1.0/src/det/runtime/ids.py +133 -0
  70. det_elt-0.1.0/src/det/runtime/lake.py +1302 -0
  71. det_elt-0.1.0/src/det/runtime/layout.py +30 -0
  72. det_elt-0.1.0/src/det/runtime/lease/__init__.py +282 -0
  73. det_elt-0.1.0/src/det/runtime/lease/_common.py +175 -0
  74. det_elt-0.1.0/src/det/runtime/lease/dataset_lock.py +317 -0
  75. det_elt-0.1.0/src/det/runtime/lease/dataset_lock_body.py +179 -0
  76. det_elt-0.1.0/src/det/runtime/lease/dataset_lock_lake.py +389 -0
  77. det_elt-0.1.0/src/det/runtime/lease/dataset_lock_postgres.py +638 -0
  78. det_elt-0.1.0/src/det/runtime/lease/dataset_lock_types.py +96 -0
  79. det_elt-0.1.0/src/det/runtime/lease/lake_store.py +238 -0
  80. det_elt-0.1.0/src/det/runtime/lease/postgres_store.py +581 -0
  81. det_elt-0.1.0/src/det/runtime/lease/resolve.py +140 -0
  82. det_elt-0.1.0/src/det/runtime/lease/store.py +104 -0
  83. det_elt-0.1.0/src/det/runtime/load_rows.py +77 -0
  84. det_elt-0.1.0/src/det/runtime/manifest.py +151 -0
  85. det_elt-0.1.0/src/det/runtime/manifest_types.py +41 -0
  86. det_elt-0.1.0/src/det/runtime/mappers.py +13 -0
  87. det_elt-0.1.0/src/det/runtime/meta.py +146 -0
  88. det_elt-0.1.0/src/det/runtime/migrate.py +821 -0
  89. det_elt-0.1.0/src/det/runtime/naming.py +81 -0
  90. det_elt-0.1.0/src/det/runtime/object_store.py +250 -0
  91. det_elt-0.1.0/src/det/runtime/pipelines.py +190 -0
  92. det_elt-0.1.0/src/det/runtime/prune.py +520 -0
  93. det_elt-0.1.0/src/det/runtime/receipt_types.py +34 -0
  94. det_elt-0.1.0/src/det/runtime/receipts.py +590 -0
  95. det_elt-0.1.0/src/det/runtime/receipts_materialize.py +258 -0
  96. det_elt-0.1.0/src/det/runtime/registry.py +199 -0
  97. det_elt-0.1.0/src/det/runtime/runner.py +470 -0
  98. det_elt-0.1.0/src/det/runtime/secrets.py +425 -0
  99. det_elt-0.1.0/src/det/runtime/settings.py +325 -0
  100. det_elt-0.1.0/src/det/runtime/slo.py +237 -0
  101. det_elt-0.1.0/src/det/runtime/sql_types.py +232 -0
  102. det_elt-0.1.0/src/det/scaffold/__init__.py +16 -0
  103. det_elt-0.1.0/src/det/scaffold/adapt_scope.py +215 -0
  104. det_elt-0.1.0/src/det/scaffold/dbt.py +341 -0
  105. det_elt-0.1.0/src/det/scaffold/dbt_sql.py +712 -0
  106. det_elt-0.1.0/src/det/scaffold/dbt_yaml.py +435 -0
  107. det_elt-0.1.0/src/det/scaffold/flatten.py +248 -0
  108. det_elt-0.1.0/src/det/scaffold/init_pipeline.py +200 -0
  109. det_elt-0.1.0/src/det/scaffold/init_source.py +214 -0
  110. det_elt-0.1.0/src/det/scaffold/templates/silver.sql.j2 +55 -0
  111. det_elt-0.1.0/src/det/scaffold/templates/silver_relation.sql.j2 +38 -0
  112. det_elt-0.1.0/src/det/scaffold/templates/sources.yml.j2 +16 -0
  113. det_elt-0.1.0/src/det/scaffold/templates/stg.sql.j2 +14 -0
  114. det_elt-0.1.0/src/det/scaffold/templates/stg_relation.sql.j2 +34 -0
  115. det_elt-0.1.0/src/det/scaffold/view_warn.py +207 -0
  116. det_elt-0.1.0/src/det/sources/__init__.py +1 -0
  117. det_elt-0.1.0/src/det/sources/base.py +82 -0
  118. det_elt-0.1.0/src/det/sources/example_api/__init__.py +1 -0
  119. det_elt-0.1.0/src/det/sources/example_api/events.py +134 -0
  120. det_elt-0.1.0/src/det/sources/example_api/orders.py +275 -0
  121. det_elt-0.1.0/src/det/sources/http.py +500 -0
  122. det_elt-0.1.0/src/det/sources/http_json.py +120 -0
  123. det_elt-0.1.0/src/det/sources/noaa/__init__.py +1 -0
  124. det_elt-0.1.0/src/det/sources/noaa/fatalities.py +26 -0
  125. det_elt-0.1.0/src/det/sources/noaa/locations.py +26 -0
  126. det_elt-0.1.0/src/det/sources/noaa/storm_events.py +315 -0
  127. det_elt-0.1.0/src/det/sources/openlibrary/__init__.py +1 -0
  128. det_elt-0.1.0/src/det/sources/openlibrary/subjects.py +199 -0
  129. det_elt-0.1.0/src/det/testing/__init__.py +27 -0
  130. det_elt-0.1.0/src/det/testing/asserts.py +90 -0
  131. det_elt-0.1.0/src/det/testing/extract.py +113 -0
  132. det_elt-0.1.0/src/det/testing/project.py +169 -0
  133. det_elt-0.1.0/src/det/testing/pytest.py +33 -0
  134. det_elt-0.1.0/src/det/testing/registry.py +67 -0
  135. det_elt-0.1.0/src/det/testing/run.py +36 -0
  136. det_elt-0.1.0/src/det/testing/secrets.py +22 -0
  137. det_elt-0.1.0/src/det/validation/__init__.py +1 -0
  138. det_elt-0.1.0/src/det/validation/jsonschema_validator.py +66 -0
  139. det_elt-0.1.0/src/det_elt.egg-info/PKG-INFO +349 -0
  140. det_elt-0.1.0/src/det_elt.egg-info/SOURCES.txt +142 -0
  141. det_elt-0.1.0/src/det_elt.egg-info/dependency_links.txt +1 -0
  142. det_elt-0.1.0/src/det_elt.egg-info/entry_points.txt +3 -0
  143. det_elt-0.1.0/src/det_elt.egg-info/requires.txt +46 -0
  144. det_elt-0.1.0/src/det_elt.egg-info/top_level.txt +1 -0
det_elt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kadeem
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.
det_elt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.4
2
+ Name: det-elt
3
+ Version: 0.1.0
4
+ Summary: DET — Data Extract Tool: config-driven extraction runtime with pluggable ingestion
5
+ Author-email: Kadeem <kletts2000@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/codekid/det
8
+ Project-URL: Repository, https://github.com/codekid/det
9
+ Project-URL: Issues, https://github.com/codekid/det/issues
10
+ Project-URL: Documentation, https://github.com/codekid/det/blob/main/docs/getting-started-library.md
11
+ Keywords: etl,data-extraction,pipeline,iceberg,bronze
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: <3.14,>=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: jsonschema>=4.23.0
22
+ Requires-Dist: pendulum>=3.0.0
23
+ Requires-Dist: pydantic>=2.10.0
24
+ Requires-Dist: pyyaml>=6.0.0
25
+ Requires-Dist: requests>=2.32.0
26
+ Requires-Dist: structlog>=25.1.0
27
+ Requires-Dist: typer>=0.15.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
32
+ Provides-Extra: examples
33
+ Requires-Dist: beautifulsoup4>=4.12.0; extra == "examples"
34
+ Requires-Dist: dlt[filesystem]>=1.24.0; extra == "examples"
35
+ Provides-Extra: duckdb
36
+ Requires-Dist: duckdb>=1.5.1; extra == "duckdb"
37
+ Provides-Extra: scaffold
38
+ Requires-Dist: jinja2>=3.1.0; extra == "scaffold"
39
+ Provides-Extra: dbt
40
+ Requires-Dist: dbt-core>=1.9; extra == "dbt"
41
+ Requires-Dist: dbt-duckdb>=1.9; extra == "dbt"
42
+ Provides-Extra: bigquery
43
+ Requires-Dist: dbt-bigquery>=1.9; extra == "bigquery"
44
+ Requires-Dist: google-cloud-bigquery>=3.0; extra == "bigquery"
45
+ Provides-Extra: postgres
46
+ Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
47
+ Provides-Extra: s3
48
+ Requires-Dist: s3fs>=2024.0.0; extra == "s3"
49
+ Provides-Extra: gcs
50
+ Requires-Dist: gcsfs>=2024.0.0; extra == "gcs"
51
+ Provides-Extra: iceberg
52
+ Requires-Dist: pyiceberg>=0.8; extra == "iceberg"
53
+ Requires-Dist: pyarrow>=15; extra == "iceberg"
54
+ Provides-Extra: mcp
55
+ Requires-Dist: mcp<2,>=1.2; extra == "mcp"
56
+ Dynamic: license-file
57
+
58
+ # DET — Data Extract Tool
59
+
60
+ Extract → **raw** (wire bytes) → **bronze** (typed Iceberg). **dbt** owns silver and gold.
61
+ **dlt never lands bronze** — it may help HTTP; DET owns validation, meta, and writers.
62
+
63
+ ## Choose your path
64
+
65
+ | Audience | Start here |
66
+ | --- | --- |
67
+ | **Operator** (this monorepo: CLI, examples, dbt, Airflow, MCP) | [Try it](#try-it-fixtures-no-noaa-download) below |
68
+ | **Library** (embed extract→raw→bronze in your app) | **[docs/getting-started-library.md](docs/getting-started-library.md)** · [docs/api.md](docs/api.md) |
69
+
70
+ This checkout is the operator product **and** the library source. Embedders install
71
+ the PyPI package **`det-elt`** (import `det`) and keep pipelines under
72
+ *their* `project_root` — they do not need this monorepo’s dbt/Airflow layout.
73
+
74
+ ---
75
+
76
+ ## Try it (fixtures, no NOAA download)
77
+
78
+ Python 3.12+ and [uv](https://github.com/astral-sh/uv):
79
+
80
+ ```bash
81
+ uv venv && make install # recommended extras (includes iceberg + examples)
82
+ export DET_LAKE_PATH="$PWD/data/lake"
83
+
84
+ uv run det run -p noaa.storm_events -s 2026-08-06 \
85
+ --set source.overrides.local_csv_dir=fixtures/storm_events \
86
+ --set source.overrides.filename_substr=details
87
+
88
+ uv run det dbt -p noaa.storm_events
89
+ duckdb data/analytics.duckdb -c "select * from gold.gold_yearly_damage"
90
+ ```
91
+
92
+ You should see:
93
+
94
+ - Raw: `data/lake/raw/noaa/storm_events_v1/…/data/` + `meta/manifest.json`
95
+ - Bronze: Iceberg table at `data/lake/bronze/noaa/storm_events_v1/`
96
+ - Silver/gold in `data/analytics.duckdb`
97
+
98
+ `make run-local` is a **thin JSONL** smoke (`destination.type=filesystem`). It does not
99
+ feed Iceberg `iceberg_scan` in dbt. Use the commands above for the full path, or
100
+ `make all` only if you intend JSONL bronze.
101
+
102
+ Use `uv run det …` (or `make …`). If macOS hides the editable `.pth`, `make unhide` or
103
+ `PYTHONPATH=src .venv/bin/det …`.
104
+
105
+ ---
106
+
107
+ ## Mental model
108
+
109
+ ```mermaid
110
+ flowchart LR
111
+ src[Source plugin] --> extract
112
+ extract --> raw[Raw lake]
113
+ raw --> load
114
+ load --> bronze[Iceberg bronze]
115
+ bronze --> stg[dbt stg]
116
+ stg --> silver --> gold
117
+ ```
118
+
119
+ | Layer | Owner | Rule |
120
+ | --- | --- | --- |
121
+ | Raw | DET | Append extract runs; rebuild source of truth |
122
+ | Bronze | DET | Typed landing; prune bronze only, never raw |
123
+ | Silver / gold | dbt | Dedupe and marts; latest extract wins |
124
+
125
+ **Interval:** `-s` inclusive start, `-e` exclusive end (default start + 1 day).
126
+ `det run` shares one `__extract_run_datetime` across raw, bronze, and rows.
127
+
128
+ ---
129
+
130
+ ## Install extras
131
+
132
+ Base `det` is the runtime + CLI (no Iceberg / DuckDB / example HTTP deps).
133
+ **Recommended first line:** install with the `iceberg` extra.
134
+
135
+ | Extra | For |
136
+ | --- | --- |
137
+ | `iceberg` | **Recommended** — default lake bronze |
138
+ | `examples` | In-tree HTTP sources (`dlt`, BeautifulSoup) |
139
+ | `duckdb` | DuckDB destination / DuckDB-backed prune |
140
+ | `scaffold` | Jinja2 dbt/pipeline scaffolding |
141
+ | `dbt` | `det dbt` |
142
+ | `postgres` | SQL serving destination |
143
+ | `s3` / `gcs` | Object-store lake URI |
144
+ | `mcp` | Cursor inspect / dry-run server |
145
+ | `dev` | pytest, ruff |
146
+
147
+ ```bash
148
+ # App / embed (PyPI):
149
+ uv pip install "det-elt[iceberg]"
150
+ uv pip install "det-elt[iceberg,duckdb,dbt]"
151
+ # This checkout (editable):
152
+ uv pip install -e ".[iceberg]" # recommended
153
+ uv pip install -e ".[iceberg,duckdb,dbt]" # typical app repo
154
+ make install # full extras for operator dev
155
+ make test
156
+ uv run det check
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Pipeline YAML
162
+
163
+ Canonical id is `provider.source` (`-p noaa.storm_events`). Defaults live in the
164
+ plugin; YAML wires schema, destination, and optional dbt knobs. Plugins are
165
+ discovered from `src/det/sources/<provider>/<source>.py` (`name` must equal
166
+ `provider.source`) — they are not listed in `plugins.py`. Out-of-tree packages may
167
+ use entry points `det.sources` / `det.mappers`.
168
+
169
+ ```yaml
170
+ name: noaa.storm_events
171
+ source:
172
+ type: noaa.storm_events
173
+ destination:
174
+ type: iceberg # default lake; filesystem = JSONL; duckdb / postgres = SQL
175
+ partition: extract_run # Iceberg only; omit = extract_run; small tables: none
176
+ wire_version: 1 # lake id is always {name}_vN (including _v1)
177
+ ```
178
+
179
+ Schema defaults to `schemas/<provider>/<source>/<source>.schema.yaml`.
180
+ Greenfield: `det init-pipeline --name example_api.events --source-type example_api.events`.
181
+
182
+ ---
183
+
184
+ ## Destinations
185
+
186
+ Lake root: `DET_LAKE_PATH` / `--lake-path` (default `./data/lake`). Same hive under
187
+ one root (`raw/` + `bronze/` prefixes) — dual buckets are not supported. There is
188
+ no `destination.type: s3`.
189
+
190
+ **`DET_LAKE_MODE`** (policy around the URI; unset → `local`):
191
+
192
+ | Mode | Allowed lake | Typical use |
193
+ | --- | --- | --- |
194
+ | `local` | filesystem path or `memory://` (tests) | laptop, CI default suite, Compose default |
195
+ | `cloud` | `s3://…` or `gs://…` / `gcs://…` | object store; CI MinIO soak covers S3 extract→Iceberg→`iceberg_scan`/`det dbt`; GCS soak covers extract→Iceberg (PyIceberg) |
196
+
197
+ `--lake-path` cannot bypass mode. `det check` errors on mismatch and warns when
198
+ `mode=cloud`. Compose: `DET_LAKE_MODE` + overridable `DET_LAKE_PATH` (see
199
+ `airflow/.env.example`). Local analytics/ops DuckDB stay on the worker filesystem.
200
+ Prod on GCS: BigQuery reads Iceberg bronze via **BigLake** — see
201
+ [docs/gcp-biglake.md](docs/gcp-biglake.md) (architecture C).
202
+
203
+ | `destination.type` | Bronze |
204
+ | --- | --- |
205
+ | **`iceberg`** | Default. Parquet table at `<lake>/bronze/<provider>/<source>_vN/` |
206
+ | `filesystem` | Hive JSONL (thin / fixtures). Cannot share that path with Iceberg |
207
+ | `duckdb` | `bronze_{provider}.{source}_vN` — needs `connection` |
208
+ | `postgres` | Same SQL names — `connection_env: DET_POSTGRES_DSN` (never a DSN in YAML) |
209
+
210
+ There is **no** `destination.type: bigquery`. On `gs://`, bronze stays Iceberg;
211
+ BQ is a reader (BigLake), not a DET lander.
212
+
213
+ Iceberg-only: `destination.partition` is `extract_run` (default — identity on
214
+ `__extract_run_datetime` for load replace / silver watermark) or `none`
215
+ (unpartitioned; use for small tables). Spec applies on **create** only. Live
216
+ mismatch **hard-fails** until `det migrate … --recreate-iceberg` (full table
217
+ purge, then rewrite `-s`/`-e` or `--all-raw`; latest raw per interval unless
218
+ `--all-raw-runs`) or a manual wipe. Not the raw hive layout.
219
+
220
+ `det dbt -p …` sets `DET_BRONZE_SOURCE` from the pipeline (`iceberg` → `iceberg_scan`
221
+ in `sources.yml` for DuckDB). On `s3://` lakes, `det dbt` auto-selects profile
222
+ target `duckdb_s3` (httpfs + S3 secret from the same `AWS_*` as extract/load).
223
+ On `gs://`, it does **not** force DuckDB S3 — set `DET_DBT_TARGET=bigquery` /
224
+ `--target bigquery` for BigLake-backed silver/gold/ops ([docs/gcp-biglake.md](docs/gcp-biglake.md)).
225
+ Register BigLake tables with `det biglake-register` (dry-run → `det approve` → `--apply`).
226
+ See [docs/gcp-biglake.md](docs/gcp-biglake.md) for connection IAM and sandbox teardown.
227
+ Layout contract: [docs/lake-layout.md](docs/lake-layout.md).
228
+
229
+ ---
230
+
231
+ ## Everyday CLI
232
+
233
+ `-p` is a canonical id, `provider/source`, or a YAML path. Project root:
234
+ `--project-root` > `DET_PROJECT_ROOT` > cwd.
235
+
236
+ ```bash
237
+ det run -p noaa.storm_events -s 2026-08-06
238
+ det extract -p noaa.storm_events -s 2026-08-06
239
+ det load -p noaa.storm_events -s 2026-08-06
240
+ det dbt -p noaa.storm_events
241
+ det check # CI runs det check --strict (warnings fail the build)
242
+ det list-pipelines
243
+ det list-sources
244
+ ```
245
+
246
+ | Command | When |
247
+ | --- | --- |
248
+ | `det prune -p … -s … --keep 1 --dry-run` then `--apply` | Drop old bronze extract-run siblings (never raw) |
249
+ | `det migrate -p … --to-bronze … --schema … --mapper identity -s … -e …` | Rebuild bronze from raw after a contract change |
250
+ | `det scaffold-dbt -p …` | Emit stg/silver + `sources.yml` |
251
+ | `det runs` / `det runs-materialize` | Attempt receipts; optional ops Iceberg + `det dbt --select tag:ops` |
252
+ | `det lock-show` / `lock-release --force` | Lake lease on `(pipeline, interval)` if a worker died |
253
+
254
+ Logs: console on a laptop TTY, JSON off TTY (`DET_LOG_FORMAT`). Secrets: names in
255
+ YAML (`auth_env`, `connection_env`); values in env. `det check` fails passwordful DSNs
256
+ in committed config.
257
+
258
+ ---
259
+
260
+ ## dbt
261
+
262
+ ```bash
263
+ det dbt -p noaa.storm_events # stg_<provider>__<source>+ ; sets lake env
264
+ det dbt # full analytics project (excludes tag:ops)
265
+ ```
266
+
267
+ Scaffolded `stg_*` use `det_bronze_from`. Gold is hand-written. Nested flatten /
268
+ relations: `dbt.stg` in pipeline YAML, then `det scaffold-dbt -p …`. Contract
269
+ triangle (schema → sources.yml → dbt.stg): [docs/contract-triangle.md](docs/contract-triangle.md).
270
+ More: [`.cursor/skills/det-dbt/SKILL.md`](.cursor/skills/det-dbt/SKILL.md).
271
+
272
+ ---
273
+
274
+ ## Local Airflow (dev only)
275
+
276
+ ```bash
277
+ make airflow-up # http://localhost:8080 — airflow / airflow
278
+ make airflow-down
279
+ ```
280
+
281
+ Compose is **LocalExecutor**, not production. DAGs: extract → load, silver/gold dbt,
282
+ ops receipts — decoupled. Env: `airflow/.env.example`. Guide:
283
+ [`.cursor/skills/det-airflow/SKILL.md`](.cursor/skills/det-airflow/SKILL.md).
284
+
285
+ ---
286
+
287
+ ## Cube (local semantic layer)
288
+
289
+ Gold and ops metrics for agents. Not Cube Cloud MCP.
290
+
291
+ ```bash
292
+ make cube-up # http://localhost:4000
293
+ make cube-down
294
+ ```
295
+
296
+ Compose reads `data/analytics.duckdb` (`yearly_damage`) and `data/det_ops.duckdb`
297
+ (`run_daily`). Copy `cube/.env.example` → `cube/.env`. MCP: `cube_meta` /
298
+ `cube_load` (`DET_CUBE_BASE_URL`, `DET_CUBE_API_SECRET`). Do not run `det dbt`
299
+ against a DuckDB file Cube has open. Certified metrics go through Cube; silver
300
+ or ops row detail uses MCP `query_analytics`.
301
+
302
+ ---
303
+
304
+ ## MCP (Cursor)
305
+
306
+ Read-only inspect + dry-run (no extract/load/prune-apply/DagRuns).
307
+ `uv pip install -e ".[mcp]"`; [`.cursor/mcp.json`](.cursor/mcp.json) already launches it.
308
+ Agent contract: [AGENTS.md](AGENTS.md). Tools + policy:
309
+ [`.cursor/rules/det-mcp.mdc`](.cursor/rules/det-mcp.mdc).
310
+
311
+ ---
312
+
313
+ ## Repo map
314
+
315
+ ```text
316
+ src/det/ CLI, runtime, example sources, writers, det.testing
317
+ configs/pipelines/ provider.source YAML
318
+ schemas/ bronze JSON Schema
319
+ docs/api.md public Python API (SemVer / __all__)
320
+ docs/getting-started-library.md embedder first hour
321
+ docs/lake-layout.md hive / SQL compatibility
322
+ docs/gcp-biglake.md gs:// Iceberg + BigLake + dbt-BQ (architecture C)
323
+ dbt/ silver + gold + ops
324
+ cube/ local Cube Core (gold + ops metrics)
325
+ dags/ + airflow/ local Compose
326
+ fixtures/ offline NOAA CSVs
327
+ ```
328
+
329
+ Example sources implement `extract_to_raw` / `records_from_raw`. Land near-wire bytes;
330
+ unexpected fields fail JSON Schema. Analytics renames live in `dbt.stg`. True wire
331
+ breaks bump `wire_version`. dlt: `RESTClient` / `@dlt.resource` as iterators only —
332
+ never `dlt.pipeline` for landing.
333
+
334
+ ---
335
+
336
+ ## Troubleshooting
337
+
338
+ | Symptom | Fix |
339
+ | --- | --- |
340
+ | `No module named 'det'` | `make unhide` or `PYTHONPATH=src .venv/bin/det` (macOS hidden `.pth`) |
341
+ | `dbt CLI not found` | `uv run det dbt` so the venv `dbt` is on PATH |
342
+ | `No raw partitions` | Load the **same** `-s`/`-e` you extracted |
343
+ | Iceberg partition YAML ≠ live | `det migrate … --recreate-iceberg` (or wipe the bronze table path); plain load/migrate hard-fails |
344
+ | Iceberg dbt vs JSONL lake | Don’t mix `make run-local` (filesystem) with `det dbt -p` (pipeline is iceberg) |
345
+ | DuckDB lock in Airflow | Absolute `DET_ANALYTICS_DUCKDB`; one dbt process at a time |
346
+ | Cube MCP `cube_unavailable` | `make cube-up`; copy `cube/.env.example` → `cube/.env` |
347
+ | DuckDB lock with Cube | Do not `det dbt` while Cube has that file open |
348
+ | `DET_LAKE_MODE=local forbids…` / `requires an s3://` | Align mode and path: local + filesystem, or cloud + `s3://`/`gs://` |
349
+ | GCS soak / localgcp | Set `STORAGE_EMULATOR_HOST` (`http://127.0.0.1:4443`), `GOOGLE_CLOUD_PROJECT`, `DET_GCS_BUCKET`; `pytest -m gcs`. BigLake needs a real GCP project — see [docs/gcp-biglake.md](docs/gcp-biglake.md) |
@@ -0,0 +1,292 @@
1
+ # DET — Data Extract Tool
2
+
3
+ Extract → **raw** (wire bytes) → **bronze** (typed Iceberg). **dbt** owns silver and gold.
4
+ **dlt never lands bronze** — it may help HTTP; DET owns validation, meta, and writers.
5
+
6
+ ## Choose your path
7
+
8
+ | Audience | Start here |
9
+ | --- | --- |
10
+ | **Operator** (this monorepo: CLI, examples, dbt, Airflow, MCP) | [Try it](#try-it-fixtures-no-noaa-download) below |
11
+ | **Library** (embed extract→raw→bronze in your app) | **[docs/getting-started-library.md](docs/getting-started-library.md)** · [docs/api.md](docs/api.md) |
12
+
13
+ This checkout is the operator product **and** the library source. Embedders install
14
+ the PyPI package **`det-elt`** (import `det`) and keep pipelines under
15
+ *their* `project_root` — they do not need this monorepo’s dbt/Airflow layout.
16
+
17
+ ---
18
+
19
+ ## Try it (fixtures, no NOAA download)
20
+
21
+ Python 3.12+ and [uv](https://github.com/astral-sh/uv):
22
+
23
+ ```bash
24
+ uv venv && make install # recommended extras (includes iceberg + examples)
25
+ export DET_LAKE_PATH="$PWD/data/lake"
26
+
27
+ uv run det run -p noaa.storm_events -s 2026-08-06 \
28
+ --set source.overrides.local_csv_dir=fixtures/storm_events \
29
+ --set source.overrides.filename_substr=details
30
+
31
+ uv run det dbt -p noaa.storm_events
32
+ duckdb data/analytics.duckdb -c "select * from gold.gold_yearly_damage"
33
+ ```
34
+
35
+ You should see:
36
+
37
+ - Raw: `data/lake/raw/noaa/storm_events_v1/…/data/` + `meta/manifest.json`
38
+ - Bronze: Iceberg table at `data/lake/bronze/noaa/storm_events_v1/`
39
+ - Silver/gold in `data/analytics.duckdb`
40
+
41
+ `make run-local` is a **thin JSONL** smoke (`destination.type=filesystem`). It does not
42
+ feed Iceberg `iceberg_scan` in dbt. Use the commands above for the full path, or
43
+ `make all` only if you intend JSONL bronze.
44
+
45
+ Use `uv run det …` (or `make …`). If macOS hides the editable `.pth`, `make unhide` or
46
+ `PYTHONPATH=src .venv/bin/det …`.
47
+
48
+ ---
49
+
50
+ ## Mental model
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ src[Source plugin] --> extract
55
+ extract --> raw[Raw lake]
56
+ raw --> load
57
+ load --> bronze[Iceberg bronze]
58
+ bronze --> stg[dbt stg]
59
+ stg --> silver --> gold
60
+ ```
61
+
62
+ | Layer | Owner | Rule |
63
+ | --- | --- | --- |
64
+ | Raw | DET | Append extract runs; rebuild source of truth |
65
+ | Bronze | DET | Typed landing; prune bronze only, never raw |
66
+ | Silver / gold | dbt | Dedupe and marts; latest extract wins |
67
+
68
+ **Interval:** `-s` inclusive start, `-e` exclusive end (default start + 1 day).
69
+ `det run` shares one `__extract_run_datetime` across raw, bronze, and rows.
70
+
71
+ ---
72
+
73
+ ## Install extras
74
+
75
+ Base `det` is the runtime + CLI (no Iceberg / DuckDB / example HTTP deps).
76
+ **Recommended first line:** install with the `iceberg` extra.
77
+
78
+ | Extra | For |
79
+ | --- | --- |
80
+ | `iceberg` | **Recommended** — default lake bronze |
81
+ | `examples` | In-tree HTTP sources (`dlt`, BeautifulSoup) |
82
+ | `duckdb` | DuckDB destination / DuckDB-backed prune |
83
+ | `scaffold` | Jinja2 dbt/pipeline scaffolding |
84
+ | `dbt` | `det dbt` |
85
+ | `postgres` | SQL serving destination |
86
+ | `s3` / `gcs` | Object-store lake URI |
87
+ | `mcp` | Cursor inspect / dry-run server |
88
+ | `dev` | pytest, ruff |
89
+
90
+ ```bash
91
+ # App / embed (PyPI):
92
+ uv pip install "det-elt[iceberg]"
93
+ uv pip install "det-elt[iceberg,duckdb,dbt]"
94
+ # This checkout (editable):
95
+ uv pip install -e ".[iceberg]" # recommended
96
+ uv pip install -e ".[iceberg,duckdb,dbt]" # typical app repo
97
+ make install # full extras for operator dev
98
+ make test
99
+ uv run det check
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Pipeline YAML
105
+
106
+ Canonical id is `provider.source` (`-p noaa.storm_events`). Defaults live in the
107
+ plugin; YAML wires schema, destination, and optional dbt knobs. Plugins are
108
+ discovered from `src/det/sources/<provider>/<source>.py` (`name` must equal
109
+ `provider.source`) — they are not listed in `plugins.py`. Out-of-tree packages may
110
+ use entry points `det.sources` / `det.mappers`.
111
+
112
+ ```yaml
113
+ name: noaa.storm_events
114
+ source:
115
+ type: noaa.storm_events
116
+ destination:
117
+ type: iceberg # default lake; filesystem = JSONL; duckdb / postgres = SQL
118
+ partition: extract_run # Iceberg only; omit = extract_run; small tables: none
119
+ wire_version: 1 # lake id is always {name}_vN (including _v1)
120
+ ```
121
+
122
+ Schema defaults to `schemas/<provider>/<source>/<source>.schema.yaml`.
123
+ Greenfield: `det init-pipeline --name example_api.events --source-type example_api.events`.
124
+
125
+ ---
126
+
127
+ ## Destinations
128
+
129
+ Lake root: `DET_LAKE_PATH` / `--lake-path` (default `./data/lake`). Same hive under
130
+ one root (`raw/` + `bronze/` prefixes) — dual buckets are not supported. There is
131
+ no `destination.type: s3`.
132
+
133
+ **`DET_LAKE_MODE`** (policy around the URI; unset → `local`):
134
+
135
+ | Mode | Allowed lake | Typical use |
136
+ | --- | --- | --- |
137
+ | `local` | filesystem path or `memory://` (tests) | laptop, CI default suite, Compose default |
138
+ | `cloud` | `s3://…` or `gs://…` / `gcs://…` | object store; CI MinIO soak covers S3 extract→Iceberg→`iceberg_scan`/`det dbt`; GCS soak covers extract→Iceberg (PyIceberg) |
139
+
140
+ `--lake-path` cannot bypass mode. `det check` errors on mismatch and warns when
141
+ `mode=cloud`. Compose: `DET_LAKE_MODE` + overridable `DET_LAKE_PATH` (see
142
+ `airflow/.env.example`). Local analytics/ops DuckDB stay on the worker filesystem.
143
+ Prod on GCS: BigQuery reads Iceberg bronze via **BigLake** — see
144
+ [docs/gcp-biglake.md](docs/gcp-biglake.md) (architecture C).
145
+
146
+ | `destination.type` | Bronze |
147
+ | --- | --- |
148
+ | **`iceberg`** | Default. Parquet table at `<lake>/bronze/<provider>/<source>_vN/` |
149
+ | `filesystem` | Hive JSONL (thin / fixtures). Cannot share that path with Iceberg |
150
+ | `duckdb` | `bronze_{provider}.{source}_vN` — needs `connection` |
151
+ | `postgres` | Same SQL names — `connection_env: DET_POSTGRES_DSN` (never a DSN in YAML) |
152
+
153
+ There is **no** `destination.type: bigquery`. On `gs://`, bronze stays Iceberg;
154
+ BQ is a reader (BigLake), not a DET lander.
155
+
156
+ Iceberg-only: `destination.partition` is `extract_run` (default — identity on
157
+ `__extract_run_datetime` for load replace / silver watermark) or `none`
158
+ (unpartitioned; use for small tables). Spec applies on **create** only. Live
159
+ mismatch **hard-fails** until `det migrate … --recreate-iceberg` (full table
160
+ purge, then rewrite `-s`/`-e` or `--all-raw`; latest raw per interval unless
161
+ `--all-raw-runs`) or a manual wipe. Not the raw hive layout.
162
+
163
+ `det dbt -p …` sets `DET_BRONZE_SOURCE` from the pipeline (`iceberg` → `iceberg_scan`
164
+ in `sources.yml` for DuckDB). On `s3://` lakes, `det dbt` auto-selects profile
165
+ target `duckdb_s3` (httpfs + S3 secret from the same `AWS_*` as extract/load).
166
+ On `gs://`, it does **not** force DuckDB S3 — set `DET_DBT_TARGET=bigquery` /
167
+ `--target bigquery` for BigLake-backed silver/gold/ops ([docs/gcp-biglake.md](docs/gcp-biglake.md)).
168
+ Register BigLake tables with `det biglake-register` (dry-run → `det approve` → `--apply`).
169
+ See [docs/gcp-biglake.md](docs/gcp-biglake.md) for connection IAM and sandbox teardown.
170
+ Layout contract: [docs/lake-layout.md](docs/lake-layout.md).
171
+
172
+ ---
173
+
174
+ ## Everyday CLI
175
+
176
+ `-p` is a canonical id, `provider/source`, or a YAML path. Project root:
177
+ `--project-root` > `DET_PROJECT_ROOT` > cwd.
178
+
179
+ ```bash
180
+ det run -p noaa.storm_events -s 2026-08-06
181
+ det extract -p noaa.storm_events -s 2026-08-06
182
+ det load -p noaa.storm_events -s 2026-08-06
183
+ det dbt -p noaa.storm_events
184
+ det check # CI runs det check --strict (warnings fail the build)
185
+ det list-pipelines
186
+ det list-sources
187
+ ```
188
+
189
+ | Command | When |
190
+ | --- | --- |
191
+ | `det prune -p … -s … --keep 1 --dry-run` then `--apply` | Drop old bronze extract-run siblings (never raw) |
192
+ | `det migrate -p … --to-bronze … --schema … --mapper identity -s … -e …` | Rebuild bronze from raw after a contract change |
193
+ | `det scaffold-dbt -p …` | Emit stg/silver + `sources.yml` |
194
+ | `det runs` / `det runs-materialize` | Attempt receipts; optional ops Iceberg + `det dbt --select tag:ops` |
195
+ | `det lock-show` / `lock-release --force` | Lake lease on `(pipeline, interval)` if a worker died |
196
+
197
+ Logs: console on a laptop TTY, JSON off TTY (`DET_LOG_FORMAT`). Secrets: names in
198
+ YAML (`auth_env`, `connection_env`); values in env. `det check` fails passwordful DSNs
199
+ in committed config.
200
+
201
+ ---
202
+
203
+ ## dbt
204
+
205
+ ```bash
206
+ det dbt -p noaa.storm_events # stg_<provider>__<source>+ ; sets lake env
207
+ det dbt # full analytics project (excludes tag:ops)
208
+ ```
209
+
210
+ Scaffolded `stg_*` use `det_bronze_from`. Gold is hand-written. Nested flatten /
211
+ relations: `dbt.stg` in pipeline YAML, then `det scaffold-dbt -p …`. Contract
212
+ triangle (schema → sources.yml → dbt.stg): [docs/contract-triangle.md](docs/contract-triangle.md).
213
+ More: [`.cursor/skills/det-dbt/SKILL.md`](.cursor/skills/det-dbt/SKILL.md).
214
+
215
+ ---
216
+
217
+ ## Local Airflow (dev only)
218
+
219
+ ```bash
220
+ make airflow-up # http://localhost:8080 — airflow / airflow
221
+ make airflow-down
222
+ ```
223
+
224
+ Compose is **LocalExecutor**, not production. DAGs: extract → load, silver/gold dbt,
225
+ ops receipts — decoupled. Env: `airflow/.env.example`. Guide:
226
+ [`.cursor/skills/det-airflow/SKILL.md`](.cursor/skills/det-airflow/SKILL.md).
227
+
228
+ ---
229
+
230
+ ## Cube (local semantic layer)
231
+
232
+ Gold and ops metrics for agents. Not Cube Cloud MCP.
233
+
234
+ ```bash
235
+ make cube-up # http://localhost:4000
236
+ make cube-down
237
+ ```
238
+
239
+ Compose reads `data/analytics.duckdb` (`yearly_damage`) and `data/det_ops.duckdb`
240
+ (`run_daily`). Copy `cube/.env.example` → `cube/.env`. MCP: `cube_meta` /
241
+ `cube_load` (`DET_CUBE_BASE_URL`, `DET_CUBE_API_SECRET`). Do not run `det dbt`
242
+ against a DuckDB file Cube has open. Certified metrics go through Cube; silver
243
+ or ops row detail uses MCP `query_analytics`.
244
+
245
+ ---
246
+
247
+ ## MCP (Cursor)
248
+
249
+ Read-only inspect + dry-run (no extract/load/prune-apply/DagRuns).
250
+ `uv pip install -e ".[mcp]"`; [`.cursor/mcp.json`](.cursor/mcp.json) already launches it.
251
+ Agent contract: [AGENTS.md](AGENTS.md). Tools + policy:
252
+ [`.cursor/rules/det-mcp.mdc`](.cursor/rules/det-mcp.mdc).
253
+
254
+ ---
255
+
256
+ ## Repo map
257
+
258
+ ```text
259
+ src/det/ CLI, runtime, example sources, writers, det.testing
260
+ configs/pipelines/ provider.source YAML
261
+ schemas/ bronze JSON Schema
262
+ docs/api.md public Python API (SemVer / __all__)
263
+ docs/getting-started-library.md embedder first hour
264
+ docs/lake-layout.md hive / SQL compatibility
265
+ docs/gcp-biglake.md gs:// Iceberg + BigLake + dbt-BQ (architecture C)
266
+ dbt/ silver + gold + ops
267
+ cube/ local Cube Core (gold + ops metrics)
268
+ dags/ + airflow/ local Compose
269
+ fixtures/ offline NOAA CSVs
270
+ ```
271
+
272
+ Example sources implement `extract_to_raw` / `records_from_raw`. Land near-wire bytes;
273
+ unexpected fields fail JSON Schema. Analytics renames live in `dbt.stg`. True wire
274
+ breaks bump `wire_version`. dlt: `RESTClient` / `@dlt.resource` as iterators only —
275
+ never `dlt.pipeline` for landing.
276
+
277
+ ---
278
+
279
+ ## Troubleshooting
280
+
281
+ | Symptom | Fix |
282
+ | --- | --- |
283
+ | `No module named 'det'` | `make unhide` or `PYTHONPATH=src .venv/bin/det` (macOS hidden `.pth`) |
284
+ | `dbt CLI not found` | `uv run det dbt` so the venv `dbt` is on PATH |
285
+ | `No raw partitions` | Load the **same** `-s`/`-e` you extracted |
286
+ | Iceberg partition YAML ≠ live | `det migrate … --recreate-iceberg` (or wipe the bronze table path); plain load/migrate hard-fails |
287
+ | Iceberg dbt vs JSONL lake | Don’t mix `make run-local` (filesystem) with `det dbt -p` (pipeline is iceberg) |
288
+ | DuckDB lock in Airflow | Absolute `DET_ANALYTICS_DUCKDB`; one dbt process at a time |
289
+ | Cube MCP `cube_unavailable` | `make cube-up`; copy `cube/.env.example` → `cube/.env` |
290
+ | DuckDB lock with Cube | Do not `det dbt` while Cube has that file open |
291
+ | `DET_LAKE_MODE=local forbids…` / `requires an s3://` | Align mode and path: local + filesystem, or cloud + `s3://`/`gs://` |
292
+ | GCS soak / localgcp | Set `STORAGE_EMULATOR_HOST` (`http://127.0.0.1:4443`), `GOOGLE_CLOUD_PROJECT`, `DET_GCS_BUCKET`; `pytest -m gcs`. BigLake needs a real GCP project — see [docs/gcp-biglake.md](docs/gcp-biglake.md) |