dbt-testpilot 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.
- dbt_testpilot-0.1.0/.env.example +10 -0
- dbt_testpilot-0.1.0/.gitignore +39 -0
- dbt_testpilot-0.1.0/.python-version +1 -0
- dbt_testpilot-0.1.0/LICENSE +21 -0
- dbt_testpilot-0.1.0/PKG-INFO +385 -0
- dbt_testpilot-0.1.0/README.md +356 -0
- dbt_testpilot-0.1.0/check_llm.py +93 -0
- dbt_testpilot-0.1.0/dbt-testpilot_Roadmap.md +182 -0
- dbt_testpilot-0.1.0/dbt_testpilot/__init__.py +3 -0
- dbt_testpilot-0.1.0/dbt_testpilot/__main__.py +4 -0
- dbt_testpilot-0.1.0/dbt_testpilot/apply.py +164 -0
- dbt_testpilot-0.1.0/dbt_testpilot/artifacts.py +81 -0
- dbt_testpilot-0.1.0/dbt_testpilot/cli.py +130 -0
- dbt_testpilot-0.1.0/dbt_testpilot/llm.py +135 -0
- dbt_testpilot-0.1.0/dbt_testpilot/macrogen.py +106 -0
- dbt_testpilot-0.1.0/dbt_testpilot/profiler.py +124 -0
- dbt_testpilot-0.1.0/dbt_testpilot/proposals.py +193 -0
- dbt_testpilot-0.1.0/dbt_testpilot/report.py +66 -0
- dbt_testpilot-0.1.0/dbt_testpilot/yaml_writer.py +148 -0
- dbt_testpilot-0.1.0/docs/context-freeze.md +159 -0
- dbt_testpilot-0.1.0/docs/progress-report.md +295 -0
- dbt_testpilot-0.1.0/main.py +6 -0
- dbt_testpilot-0.1.0/pyproject.toml +50 -0
- dbt_testpilot-0.1.0/requirements.txt +275 -0
- dbt_testpilot-0.1.0/test-llms.py +75 -0
- dbt_testpilot-0.1.0/uv.lock +747 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copy this file to .env and fill in your keys. NEVER commit .env.
|
|
2
|
+
|
|
3
|
+
# Which LLM lane to use: gemini | groq | ollama
|
|
4
|
+
LLM_PROVIDER=gemini
|
|
5
|
+
|
|
6
|
+
# Google AI Studio (Gemini) — https://aistudio.google.com (primary)
|
|
7
|
+
GEMINI_API_KEY=
|
|
8
|
+
|
|
9
|
+
# Groq — https://console.groq.com (backup lane)
|
|
10
|
+
GROQ_API_KEY=
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Secrets — never commit
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
# Python
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*.egg-info/
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
|
|
14
|
+
# dbt build output
|
|
15
|
+
target/
|
|
16
|
+
dbt_packages/
|
|
17
|
+
logs/
|
|
18
|
+
|
|
19
|
+
# DuckDB
|
|
20
|
+
*.duckdb
|
|
21
|
+
*.duckdb.wal
|
|
22
|
+
|
|
23
|
+
# Tooling
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
|
|
28
|
+
# macOS
|
|
29
|
+
.DS_Store
|
|
30
|
+
|
|
31
|
+
# Local sandbox — external sample project, not part of dbt-testpilot
|
|
32
|
+
jaffle_shop_duckdb/
|
|
33
|
+
|
|
34
|
+
# Stray build wheels
|
|
35
|
+
*.whl
|
|
36
|
+
|
|
37
|
+
# Generated profiler / proposer output
|
|
38
|
+
profiles.json
|
|
39
|
+
proposals.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kushal Mishra
|
|
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.
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dbt-testpilot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scan a dbt project, profile the real data, and propose (and write) the dbt tests you're missing — with a human approve step.
|
|
5
|
+
Project-URL: Homepage, https://github.com/orgkushal/dbt-testpilot
|
|
6
|
+
Project-URL: Repository, https://github.com/orgkushal/dbt-testpilot
|
|
7
|
+
Project-URL: Issues, https://github.com/orgkushal/dbt-testpilot/issues
|
|
8
|
+
Author: Kushal Mishra
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: analytics-engineering,data-engineering,data-quality,dbt,duckdb,testing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: duckdb>=1.5
|
|
24
|
+
Requires-Dist: ruamel-yaml>=0.18
|
|
25
|
+
Provides-Extra: llm
|
|
26
|
+
Requires-Dist: google-genai>=1.7; extra == 'llm'
|
|
27
|
+
Requires-Dist: groq>=1.5; extra == 'llm'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# dbt-testpilot
|
|
31
|
+
|
|
32
|
+
> Scans a dbt project, profiles the **real data**, and proposes the dbt tests you're missing — `not_null`, `unique`, `accepted_values`, `relationships`, and custom — with a human approve step.
|
|
33
|
+
|
|
34
|
+
**Status:** Part 0 (setup), Week 1 (profiler), Week 2 (proposals), and Week 3 (write + run) are complete. Not yet published to PyPI.
|
|
35
|
+
Part of a small **dbt reliability toolkit** (alongside a SQL→dbt converter).
|
|
36
|
+
|
|
37
|
+
Analysts chronically under-test their data. dbt-testpilot reads your dbt project's metadata, profiles the actual tables in DuckDB, and suggests the tests you're missing — you stay in control and approve what gets written. The design is **heuristics-first, LLM-augmented**: obvious tests come from deterministic rules; the LLM adds rationale, cross-table relationships, and custom tests.
|
|
38
|
+
|
|
39
|
+
## Contents
|
|
40
|
+
|
|
41
|
+
- [What it does](#what-it-does)
|
|
42
|
+
- [How it works](#how-it-works)
|
|
43
|
+
- [Requirements](#requirements)
|
|
44
|
+
- [Week 0 — environment setup](#week-0--environment-setup)
|
|
45
|
+
- [Week 1 — profiling your data](#week-1--profiling-your-data)
|
|
46
|
+
- [Week 2 — propose the missing tests](#week-2--propose-the-missing-tests)
|
|
47
|
+
- [Week 3 — write tests + run](#week-3--write-tests--run)
|
|
48
|
+
- [Project layout](#project-layout)
|
|
49
|
+
- [Roadmap](#roadmap)
|
|
50
|
+
- [Troubleshooting](#troubleshooting)
|
|
51
|
+
- [License](#license)
|
|
52
|
+
|
|
53
|
+
## What it does
|
|
54
|
+
|
|
55
|
+
| Stage | Capability | Status |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Week 1 | Profile every model's data — nulls, cardinality, ranges, value lists, existing tests | **Done** |
|
|
58
|
+
| Week 2 | Propose the *missing* tests — heuristics + optional LLM, as validated JSON | **Done** |
|
|
59
|
+
| Week 3 | Write approved tests to `schema.yml` (human approve) and run `dbt test` | **Done** |
|
|
60
|
+
| Week 4 | `pip install dbt-testpilot` | Planned |
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
1. **Read** the dbt project's `manifest.json` / `catalog.json` (from `target/`). *(done)*
|
|
65
|
+
2. **Profile** each model in DuckDB — row counts, null %, cardinality, ranges, value patterns. *(done)*
|
|
66
|
+
3. **Propose** tests: deterministic heuristics first, LLM-augmented for rationale, relationships, and custom tests (strict JSON). *(done)*
|
|
67
|
+
4. **Approve** — you review; approved tests are written into the model's `schema.yml`. *(done)*
|
|
68
|
+
5. **Run** — `dbt test` executes them. *(done)*
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
- macOS (Apple Silicon) or Linux
|
|
73
|
+
- **Python 3.12** (dbt Core supports 3.10–3.13; 3.12 is the safe middle)
|
|
74
|
+
- **DuckDB** (Python package `duckdb`); **`ruamel.yaml`** for round-trip `schema.yml` edits (Week 3)
|
|
75
|
+
- A dbt project to point at — this repo uses **jaffle_shop_duckdb** as its sandbox
|
|
76
|
+
- A **Gemini** and/or **Groq** API key (only needed from Week 2)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Week 0 — environment setup
|
|
81
|
+
|
|
82
|
+
One-time setup on your machine. No account here needs a credit card.
|
|
83
|
+
|
|
84
|
+
### 1. Create accounts
|
|
85
|
+
|
|
86
|
+
- **GitHub** — enable two-factor auth.
|
|
87
|
+
- **Google AI Studio** (https://aistudio.google.com) → *Get API key* → save it (primary LLM: Gemini).
|
|
88
|
+
- **Groq** (https://console.groq.com) → create an API key (fast backup lane).
|
|
89
|
+
- *(PyPI / TestPyPI come later, for Week 4.)*
|
|
90
|
+
|
|
91
|
+
### 2. Install tools (macOS / Apple Silicon)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Homebrew (skip if installed) — then follow its PATH note for /opt/homebrew
|
|
95
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
96
|
+
|
|
97
|
+
brew install git uv duckdb
|
|
98
|
+
git config --global user.name "Your Name"
|
|
99
|
+
git config --global user.email "you@example.com" # match GitHub
|
|
100
|
+
uv python install 3.12
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Create the project and a virtual environment
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv init dbt-testpilot && cd dbt-testpilot # or: git clone <this repo> && cd dbt-testpilot
|
|
107
|
+
uv venv --python 3.12 && source .venv/bin/activate
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 4. Install dbt — read this (common gotcha)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -m pip install --upgrade pip wheel setuptools
|
|
114
|
+
pip install dbt-core dbt-duckdb # do NOT run `pip install dbt`
|
|
115
|
+
dbt --version # expect a 1.x core + the duckdb adapter
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> Plain `pip install dbt` installs the newer Rust-based **Fusion / platform CLI**, which shadows `dbt-core` on your PATH. For a stable, artifact-friendly setup, install the Python **dbt Core v1** line plus `dbt-duckdb`, inside the venv.
|
|
119
|
+
|
|
120
|
+
### 5. Clone the sandbox and build it
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git clone https://github.com/dbt-labs/jaffle_shop_duckdb.git
|
|
124
|
+
cd jaffle_shop_duckdb
|
|
125
|
+
# follow that repo's README to set up, then:
|
|
126
|
+
dbt build # creates target/manifest.json, target/catalog.json, jaffle_shop.duckdb
|
|
127
|
+
cd ..
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 6. Configure secrets
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cp .env.example .env # then edit .env and paste your keys
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`.env` holds `LLM_PROVIDER` (`gemini` | `groq` | `ollama`), `GEMINI_API_KEY`, and `GROQ_API_KEY`. It is gitignored — **never commit it**.
|
|
137
|
+
|
|
138
|
+
### 7. Verify your LLM keys
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install google-genai groq
|
|
142
|
+
python check_llm.py # uses LLM_PROVIDER from .env; `python check_llm.py groq` forces a lane
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Expect `[ok] gemini responded: '...key OK'`.
|
|
146
|
+
|
|
147
|
+
**Week 0 is done when:** `dbt build` succeeds on jaffle_shop and `check_llm.py` prints `[ok]`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Week 1 — profiling your data
|
|
152
|
+
|
|
153
|
+
The profiler is the `dbt_testpilot` Python package in this repo.
|
|
154
|
+
|
|
155
|
+
### Run it
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
source .venv/bin/activate
|
|
159
|
+
pip install duckdb
|
|
160
|
+
python -m dbt_testpilot profile --project-dir jaffle_shop_duckdb --out profiles.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Flag | Meaning |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `--project-dir` | dbt project directory (must contain `target/` and the `.duckdb` file). Default `.` |
|
|
166
|
+
| `--db` | Explicit path to the DuckDB file (auto-detected from `--project-dir` if omitted) |
|
|
167
|
+
| `--out` | Write per-model profiles to this JSON file |
|
|
168
|
+
| `--max-values` | Max distinct values captured for low-cardinality columns (default 25) |
|
|
169
|
+
|
|
170
|
+
### What you get
|
|
171
|
+
|
|
172
|
+
**Console** — per model, each column with type, null %, cardinality, `UNIQUE` / `NOT NULL` flags, min/max range, low-cardinality value lists, and any tests it already has.
|
|
173
|
+
|
|
174
|
+
**`profiles.json`** — the machine-readable input for Week 2. Each column carries:
|
|
175
|
+
|
|
176
|
+
`name`, `data_type`, `null_count`, `null_pct`, `distinct_count`, `distinct_pct`, `is_unique`, `min`, `max`, `top_values` (for low-cardinality columns), and `existing_tests`.
|
|
177
|
+
|
|
178
|
+
### Example output
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
== main.customers == (rows: 100)
|
|
182
|
+
customer_id INTEGER nulls 0.0% distinct 100 (100%) [UNIQUE, NOT NULL] range[1..100] tests:unique,not_null
|
|
183
|
+
first_order DATE nulls 38.0% distinct 46 (46%) range[2018-01-01..2018-04-07]
|
|
184
|
+
|
|
185
|
+
== main.orders == (rows: 99)
|
|
186
|
+
status VARCHAR nulls 0.0% distinct 5 (5%) [NOT NULL] tests:accepted_values
|
|
187
|
+
values: completed(67), placed(13), shipped(13), returned(4), return_pending(2)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### How it works (architecture)
|
|
191
|
+
|
|
192
|
+
| Module | Responsibility |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `artifacts.py` | Parse `manifest.json` (model list + already-existing tests) and `catalog.json` (declared column types) |
|
|
195
|
+
| `profiler.py` | Read the **actual** columns/types from DuckDB's `information_schema`, then one aggregate pass per table for row count, null %, cardinality, and min/max; grab value lists for low-cardinality columns |
|
|
196
|
+
| `report.py` | Render each profile as a JSON-ready dict and a readable console view |
|
|
197
|
+
| `cli.py` / `__main__.py` | The `profile` command |
|
|
198
|
+
|
|
199
|
+
Two design choices worth knowing: columns are read from the **database itself** (source of truth, not stale metadata), and **existing tests are captured** so later steps propose only the tests you're *missing*.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Week 2 — propose the missing tests
|
|
204
|
+
|
|
205
|
+
`propose` reads the `profiles.json` from Week 1 and suggests the dbt tests each model is missing. It runs in two layers: a deterministic **heuristic** base (no API key, works offline) and an optional **LLM** layer (`--llm`) that adds rationale, relationships, and custom tests. Tests a column already has are always skipped.
|
|
206
|
+
|
|
207
|
+
### Run it
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
source .venv/bin/activate
|
|
211
|
+
|
|
212
|
+
# heuristics only — no key needed
|
|
213
|
+
python -m dbt_testpilot propose --profiles profiles.json --out proposals.json
|
|
214
|
+
|
|
215
|
+
# heuristics + LLM augmentation (reads LLM_PROVIDER + key from .env)
|
|
216
|
+
python -m dbt_testpilot propose --profiles profiles.json --out proposals.json --llm
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Flag | Meaning |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `--profiles` | Path to the `profiles.json` produced by `profile` (default `profiles.json`) |
|
|
222
|
+
| `--out` | Write the proposals to this JSON file |
|
|
223
|
+
| `--llm` | Also query the LLM and merge its proposals |
|
|
224
|
+
| `--provider` | `gemini` or `groq` (default: `LLM_PROVIDER` from `.env`) |
|
|
225
|
+
| `--env` | Path to the `.env` holding your API keys (default `.env`) |
|
|
226
|
+
|
|
227
|
+
### The heuristic rules (deterministic, no key)
|
|
228
|
+
|
|
229
|
+
| Test | Proposed when |
|
|
230
|
+
|---|---|
|
|
231
|
+
| `not_null` | the column has **zero nulls** across the table |
|
|
232
|
+
| `unique` | distinct count **equals the non-null count** (a real key) |
|
|
233
|
+
| `accepted_values` | a **categorical** column (text/boolean) with ≤ 15 distinct values — the observed values become the allowed set |
|
|
234
|
+
| `relationships` | a `*_id` column that is **not** this table's own key but **is** unique in another model (a foreign-key guess) |
|
|
235
|
+
|
|
236
|
+
Confidence is `high` for clear cases and `medium` for softer guesses (relationships, nullable uniques).
|
|
237
|
+
|
|
238
|
+
### The LLM layer (`--llm`)
|
|
239
|
+
|
|
240
|
+
Each model's profile is sent to Gemini/Groq **one model at a time** (small prompts, friendly to free-tier token limits) with an instruction to return **only** the missing tests as strict JSON. Every returned item is validated against the proposal schema — the column must actually exist, `accepted_values` must include `values`, `relationships` must include `to`/`field` — and anything malformed or hallucinated is dropped. If a call fails, that model degrades to heuristics-only instead of erroring. The LLM can also suggest **custom** tests (a named check plus a rationale).
|
|
241
|
+
|
|
242
|
+
### What you get
|
|
243
|
+
|
|
244
|
+
**Console** — per model, each proposed test with its column, source (`H`euristic / `L`LM), confidence, arguments, and a one-line rationale.
|
|
245
|
+
|
|
246
|
+
**`proposals.json`** — grouped by model; each proposal carries `model`, `column`, `test`, `arguments`, `rationale`, `source` (`heuristic` | `llm`), and `confidence`.
|
|
247
|
+
|
|
248
|
+
### Example output (heuristics on jaffle_shop)
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
== main.orders == (2 proposed)
|
|
252
|
+
+ not_null order_date [H/high] 0 nulls across 99 rows
|
|
253
|
+
+ not_null status [H/high] 0 nulls across 99 rows
|
|
254
|
+
|
|
255
|
+
== main.stg_payments == (4 proposed)
|
|
256
|
+
+ not_null order_id [H/high] 0 nulls across 113 rows
|
|
257
|
+
+ relationships order_id [H/medium] -> ref('orders').order_id
|
|
258
|
+
+ not_null payment_method [H/high]
|
|
259
|
+
+ not_null amount [H/high]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
On jaffle_shop the heuristics alone produce **14 proposals** (12 `not_null`, 2 `relationships`) and correctly skip every column that already has a `unique` / `accepted_values` / `relationships` test.
|
|
263
|
+
|
|
264
|
+
### How it works (architecture)
|
|
265
|
+
|
|
266
|
+
| Module | Responsibility |
|
|
267
|
+
|---|---|
|
|
268
|
+
| `proposals.py` | `Proposal` schema, the deterministic heuristic rules, LLM-output validation, and merge/dedupe (drops anything already tested) |
|
|
269
|
+
| `llm.py` | Per-model prompt, Gemini/Groq call in JSON mode, parse + validate, graceful per-model fallback |
|
|
270
|
+
|
|
271
|
+
The approved proposals feed **Week 3**, which writes them into each model's `schema.yml` and runs `dbt test`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Week 3 — write tests + run
|
|
276
|
+
|
|
277
|
+
`apply` closes the loop: review the proposals, write the ones you approve into the models' `schema.yml`, and run `dbt test`. **Nothing is written without your OK** — approval is interactive by default.
|
|
278
|
+
|
|
279
|
+
### Run it
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
source .venv/bin/activate
|
|
283
|
+
pip install ruamel.yaml
|
|
284
|
+
|
|
285
|
+
# review each proposal interactively, then write the approved ones
|
|
286
|
+
python -m dbt_testpilot apply --proposals proposals.json --project-dir jaffle_shop_duckdb
|
|
287
|
+
|
|
288
|
+
# preview only (writes nothing)
|
|
289
|
+
python -m dbt_testpilot apply --proposals proposals.json --project-dir jaffle_shop_duckdb --dry-run
|
|
290
|
+
|
|
291
|
+
# accept all and run dbt test in one go
|
|
292
|
+
python -m dbt_testpilot apply --proposals proposals.json --project-dir jaffle_shop_duckdb --yes --run
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Flag | Meaning |
|
|
296
|
+
|---|---|
|
|
297
|
+
| `--proposals` | Proposals JSON from `propose` (default `proposals.json`) |
|
|
298
|
+
| `--project-dir` | dbt project whose `schema.yml` files get updated |
|
|
299
|
+
| `--yes` | Approve all (non-interactive) |
|
|
300
|
+
| `--min-confidence` | Only consider proposals at/above `low` / `medium` / `high` |
|
|
301
|
+
| `--dry-run` | Show what would be written; change nothing |
|
|
302
|
+
| `--include-custom` | Generate vetted macros for supported custom tests (value ≥ 0, not-future) and write them |
|
|
303
|
+
| `--run` | Run `dbt test` after writing |
|
|
304
|
+
|
|
305
|
+
### The approve/reject flow
|
|
306
|
+
|
|
307
|
+
For each proposal you see the model, column, test, arguments, and rationale, then choose **[y]es / [n]o / [a]ll remaining / [q]uit**. Approved tests are merged into the model's `schema.yml` — located via the manifest's `patch_path` — using **round-trip YAML**, so your existing descriptions, comments, ordering, and formatting are preserved. It matches the file's existing key (`tests:` or `data_tests:`) and uses dbt's `arguments:` wrapper for tests that take arguments. Missing columns are created; duplicate tests are skipped.
|
|
308
|
+
|
|
309
|
+
### Built-in vs custom tests
|
|
310
|
+
|
|
311
|
+
Only dbt's built-in generic tests (`not_null`, `unique`, `accepted_values`, `relationships`) are written by default, so `dbt test` runs green immediately.
|
|
312
|
+
|
|
313
|
+
The LLM also proposes **custom** tests (e.g. `positive_amount`, `date_in_past_or_present`). dbt runs a test named `foo` via a macro `test_foo`, which won't exist for these — so by default they're skipped with a note. With `--include-custom`, dbt-testpilot **generates vetted macros** into `macros/dbt_testpilot/` for the patterns it recognises (value ≥ 0; date not in the future) and writes those tests. Genuinely bespoke ones (multi-column or regex logic) are still skipped for you to implement — auto-writing SQL we can't guarantee would risk false confidence. On jaffle_shop this turns 9 of the 13 LLM suggestions into passing tests (`dbt test` → 45/45).
|
|
314
|
+
|
|
315
|
+
### End-to-end result (jaffle_shop)
|
|
316
|
+
|
|
317
|
+
Approving the built-in proposals added 16 tests, and `dbt test` passed clean:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
Done. PASS=36 WARN=0 ERROR=0 SKIP=0 TOTAL=36
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
That's 20 pre-existing + 16 generated — including `not_null` on `orders.order_date`, `relationships` from `stg_orders.customer_id` → `customers`, and more. With `--include-custom`, the 9 supported custom tests get generated macros too, taking the suite to **45/45** (bespoke tests skipped).
|
|
324
|
+
|
|
325
|
+
### How it works (architecture)
|
|
326
|
+
|
|
327
|
+
| Module | Responsibility |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `yaml_writer.py` | Locate each model's `schema.yml` (manifest `patch_path`) and merge approved tests into the right column via round-trip YAML — no clobbering |
|
|
330
|
+
| `apply.py` | Interactive approve/reject, built-in vs custom filtering, then optionally shell out to `dbt test` |
|
|
331
|
+
| `macrogen.py` | Generate vetted generic-test macros for supported custom patterns (value ≥ 0, not-future) |
|
|
332
|
+
|
|
333
|
+
That completes the loop — **scan → review → approve → tests running**.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Project layout
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
dbt-testpilot/
|
|
341
|
+
├─ dbt_testpilot/ # the tool (Python package)
|
|
342
|
+
│ ├─ __init__.py
|
|
343
|
+
│ ├─ __main__.py # python -m dbt_testpilot
|
|
344
|
+
│ ├─ artifacts.py # parse manifest.json + catalog.json
|
|
345
|
+
│ ├─ profiler.py # DuckDB profiling (Week 1)
|
|
346
|
+
│ ├─ proposals.py # heuristic proposals + schema/validation (Week 2)
|
|
347
|
+
│ ├─ llm.py # LLM proposals, strict JSON (Week 2)
|
|
348
|
+
│ ├─ yaml_writer.py # merge approved tests into schema.yml (Week 3)
|
|
349
|
+
│ ├─ apply.py # approve/reject flow + run dbt test (Week 3)
|
|
350
|
+
│ ├─ macrogen.py # generate macros for supported custom tests (Week 3.5)
|
|
351
|
+
│ ├─ report.py # JSON + console rendering
|
|
352
|
+
│ └─ cli.py # profile + propose + apply commands
|
|
353
|
+
├─ jaffle_shop_duckdb/ # sample dbt project (sandbox) — gitignored
|
|
354
|
+
├─ check_llm.py # Week 0: one-shot LLM key verifier
|
|
355
|
+
├─ dbt-testpilot_Roadmap.md # the 4-week plan
|
|
356
|
+
├─ docs/progress-report.md # detailed build log
|
|
357
|
+
├─ README.md
|
|
358
|
+
├─ pyproject.toml, uv.lock # uv project scaffolding
|
|
359
|
+
├─ .env / .env.example / .gitignore
|
|
360
|
+
└─ profiles.json # generated profiler output — gitignored
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Roadmap
|
|
364
|
+
|
|
365
|
+
- [x] **Part 0** — accounts, installs, sandbox, secrets, repo hygiene
|
|
366
|
+
- [x] **Week 1** — data profiler (`manifest`/`catalog` + DuckDB stats → `profiles.json`)
|
|
367
|
+
- [x] **Week 2** — propose missing tests (heuristics + optional LLM, validated JSON)
|
|
368
|
+
- [x] **Week 3** — write approved tests to `schema.yml` + `dbt test`
|
|
369
|
+
- [ ] **Week 4** — package and publish (`pip install dbt-testpilot`)
|
|
370
|
+
|
|
371
|
+
Full details in [`docs/progress-report.md`](docs/progress-report.md).
|
|
372
|
+
|
|
373
|
+
## Troubleshooting
|
|
374
|
+
|
|
375
|
+
- **`no manifest.json under .../target`** — run `dbt build` (and `dbt docs generate` for `catalog.json`) in the dbt project first.
|
|
376
|
+
- **`catalog.json` missing** — `dbt build` alone doesn't create it; run `dbt docs generate`.
|
|
377
|
+
- **`SDK not installed` from `check_llm.py`** — `pip install google-genai groq`.
|
|
378
|
+
- **`No module named duckdb`** — `pip install duckdb` inside the active venv.
|
|
379
|
+
- **`No module named 'ruamel'`** — `pip install ruamel.yaml` (needed by `apply`).
|
|
380
|
+
- **`apply` skipped my LLM tests** — custom tests are skipped by default. Add `--include-custom` to auto-generate vetted macros for supported patterns (value ≥ 0, not-future) and write those; genuinely bespoke ones still need a hand-written macro.
|
|
381
|
+
- **Free-tier limits change** — provider is read from `.env`, so switching Gemini ⇄ Groq ⇄ Ollama is a one-line edit.
|
|
382
|
+
|
|
383
|
+
## License
|
|
384
|
+
|
|
385
|
+
MIT (planned).
|