data-contract-validator 1.1.0__tar.gz → 1.1.7__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.
- data_contract_validator-1.1.7/CHANGELOG.md +235 -0
- {data_contract_validator-1.1.0/data_contract_validator.egg-info → data_contract_validator-1.1.7}/PKG-INFO +187 -9
- data_contract_validator-1.1.7/README.md +464 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/__init__.py +1 -1
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/cli.py +214 -49
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/types.py +63 -2
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/validator.py +28 -8
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/fastapi.py +62 -22
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7/data_contract_validator.egg-info}/PKG-INFO +187 -9
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/pyproject.toml +1 -1
- data_contract_validator-1.1.0/CHANGELOG.md +0 -121
- data_contract_validator-1.1.0/README.md +0 -286
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/LICENSE +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/MANIFEST.in +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/__init__.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/models.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/__init__.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/base.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/dbt.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/integrations/__init__.py +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/py.typed +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/templates/github-actions-template.yml +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/SOURCES.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/dependency_links.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/entry_points.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/requires.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/top_level.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/requirements.txt +0 -0
- {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/setup.cfg +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.1.7] - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **The generated CI workflow now defaults `GITHUB_TOKEN` to
|
|
14
|
+
`secrets.API_REPO_TOKEN`** — a token you create yourself — instead of the
|
|
15
|
+
auto-provided `secrets.GITHUB_TOKEN`, which only has access to the repo
|
|
16
|
+
the workflow runs in. A personal access token works identically for
|
|
17
|
+
public and private targets, so this removes the silent-failure case
|
|
18
|
+
entirely rather than just documenting it (1.1.6's fix). Still skipped
|
|
19
|
+
entirely for a `local` target.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- The generated CI workflow now includes a commented scaffold for
|
|
23
|
+
`dbt deps && dbt docs generate`, unlocking Tier 1 (real warehouse types)
|
|
24
|
+
in CI instead of that only being mentioned in prose docs. Commented out
|
|
25
|
+
by default since it needs the user's warehouse adapter and credentials
|
|
26
|
+
filled in, which can't be inferred.
|
|
27
|
+
|
|
28
|
+
## [1.1.6] - 2026-07-03
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **The generated CI workflow silently assumed the default
|
|
32
|
+
`secrets.GITHUB_TOKEN` could read the target API repo.** That token only
|
|
33
|
+
has access to the repo the workflow itself runs in — if `target.*.repo`
|
|
34
|
+
is a *different*, private repo, validation would fail on every PR with no
|
|
35
|
+
indication why. The generated workflow now documents the fix inline
|
|
36
|
+
(a personal-access-token secret pointed at that specific repo) and skips
|
|
37
|
+
the `GITHUB_TOKEN` env block entirely for a `local` target, which never
|
|
38
|
+
talks to the GitHub API at all.
|
|
39
|
+
|
|
40
|
+
## [1.1.5] - 2026-07-03
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- **Python `int` was mapped to the narrower `INTEGER` canonical rank,
|
|
44
|
+
producing a false "type mismatch" warning against any dbt column typed
|
|
45
|
+
`bigint`** (a very common type for count/id columns). Python's `int` is
|
|
46
|
+
arbitrary-precision, unlike a fixed-width SQL `INTEGER` column, so there's
|
|
47
|
+
no real truncation risk — it's now mapped to the wider `BIGINT` rank.
|
|
48
|
+
A genuinely fractional source (`DECIMAL`/`FLOAT`) is still flagged.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- `init --interactive` now offers to set up a pre-commit hook as part of the
|
|
52
|
+
same wizard, instead of requiring a separate `setup-precommit` invocation.
|
|
53
|
+
(The GitHub Actions CI workflow was already created automatically by
|
|
54
|
+
`init` for both the interactive and non-interactive paths — only the
|
|
55
|
+
pre-commit step needed folding in.)
|
|
56
|
+
|
|
57
|
+
## [1.1.4] - 2026-07-03
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- **`table=True` SQLModel classes are no longer skipped during extraction.**
|
|
61
|
+
Whether a table is meant to come from dbt is business knowledge that can't
|
|
62
|
+
be recovered from the Python source — two structurally identical
|
|
63
|
+
`table=True` classes can need opposite treatment (one is a normal dbt-fed
|
|
64
|
+
table an API also returns directly; another is populated by a separate
|
|
65
|
+
pipeline like Kafka and was never meant to have a dbt model). Blanket-
|
|
66
|
+
skipping every `table=True` class silently exempted the former case from
|
|
67
|
+
validation too, which is likely the more common pattern — defeating the
|
|
68
|
+
tool's purpose for it. `table=True` classes are now validated like any
|
|
69
|
+
other target.
|
|
70
|
+
- Added `mapping.exclude: [<table>, ...]` so the latter case (genuinely no
|
|
71
|
+
source model, e.g. Kafka-populated) can be stated explicitly instead of
|
|
72
|
+
inferred from `table=True`. Excluded tables are skipped entirely and never
|
|
73
|
+
produce a "missing table" issue.
|
|
74
|
+
|
|
75
|
+
## [1.1.3] - 2026-07-03
|
|
76
|
+
|
|
77
|
+
Supersedes 1.1.2, which was only ever published to TestPyPI for verification
|
|
78
|
+
and never released to production PyPI.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- **`table=True` SQLModel classes were incorrectly evaluated as required API
|
|
82
|
+
contracts.** The standard `class Foo(SQLModel, table=True)` syntax puts
|
|
83
|
+
`table=True` on the class definition's own keywords, not nested inside a
|
|
84
|
+
`Call` base — the skip check only looked in the latter, so DB-only tables
|
|
85
|
+
never matched and produced permanent, unfixable "missing table" criticals.
|
|
86
|
+
- **Explicit `__tablename__` is now resolved and used as the target table
|
|
87
|
+
name**, instead of only the class-name-derived guess. A class like
|
|
88
|
+
`VideoViewed` with `__tablename__ = "int_unified_video_viewed"` now matches
|
|
89
|
+
its real source model without needing a manual `mapping.tables` entry.
|
|
90
|
+
- **`init --interactive` no longer guesses local vs. GitHub from the path's
|
|
91
|
+
shape.** A local relative path like `app/models` (the wizard's own
|
|
92
|
+
suggested default) is syntactically identical to a GitHub `org/repo`
|
|
93
|
+
string, and was always guessed as a repo, producing a nonsensical
|
|
94
|
+
`app/models/app/models` GitHub target. The wizard now asks explicitly
|
|
95
|
+
("local project or a different GitHub repo?") before asking for the
|
|
96
|
+
path, and asks for the repo and the path within it as separate prompts.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- `init --interactive` and `contract-validator test` now verify a configured
|
|
100
|
+
GitHub target path actually exists via the GitHub API, instead of silently
|
|
101
|
+
accepting a stale or typo'd path.
|
|
102
|
+
- GitHub API error messages hint at setting `GITHUB_TOKEN` when an
|
|
103
|
+
unauthenticated 404 is ambiguous with a private repo.
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
- **`contract-validator init` no longer silently overwrites an existing
|
|
107
|
+
`.retl-validator.yml` or generated workflow file.** Re-running `init` (e.g.
|
|
108
|
+
after upgrading to pick up a newer version's config defaults) now refuses
|
|
109
|
+
and exits if either file already exists — pass `--force` to regenerate
|
|
110
|
+
them from scratch. Previously this was an unconditional overwrite with no
|
|
111
|
+
confirmation, which could silently destroy hand-added `mapping` entries.
|
|
112
|
+
|
|
113
|
+
## [1.1.1] - 2026-06-30
|
|
114
|
+
|
|
115
|
+
### Added
|
|
116
|
+
- **Automatic plural/singular table & column matching.** dbt models are
|
|
117
|
+
conventionally plural (`users`) while Pydantic classes are singular
|
|
118
|
+
(`User` → `user`); these now match automatically with no `mapping` needed.
|
|
119
|
+
Candidate forms are only matched against names that actually exist on the
|
|
120
|
+
other side, so it never over-strips (`address` is never mistaken for
|
|
121
|
+
`addres`). Explicit `mapping` still takes precedence.
|
|
122
|
+
|
|
123
|
+
## [1.1.0] - 2026-06-30
|
|
124
|
+
|
|
125
|
+
This release is focused on **accuracy** — making a red check always mean a real
|
|
126
|
+
problem and a green check genuinely safe, so the tool can be trusted to gate a
|
|
127
|
+
deploy.
|
|
128
|
+
|
|
129
|
+
### Added
|
|
130
|
+
- **Canonical type system** (`core/types.py`): every extractor now normalizes
|
|
131
|
+
its native types (warehouse SQL types, Python hints) into a shared, neutral
|
|
132
|
+
vocabulary (`CanonicalType`). The validator compares canonical types instead
|
|
133
|
+
of raw strings, eliminating the bulk of false "type mismatch" warnings
|
|
134
|
+
(e.g. dbt `varchar` vs Pydantic `str` are now correctly equal).
|
|
135
|
+
- Dialect-aware normalization: Snowflake `NUMBER(38,0)`→bigint, BigQuery
|
|
136
|
+
`INT64`/`FLOAT64`, Redshift `SUPER`, Postgres `jsonb`, and more.
|
|
137
|
+
- **Tiered dbt extraction** with graceful degradation:
|
|
138
|
+
1. `catalog.json` — real warehouse types (high confidence).
|
|
139
|
+
2. `sqlglot` — a proper SQL parser. Handles CTEs, `||`, window functions, and
|
|
140
|
+
quoted identifiers that the old regex parser mangled. Detects `SELECT *`
|
|
141
|
+
and flags the schema as incomplete.
|
|
142
|
+
3. regex — last-resort best effort (low confidence, never hard-fails).
|
|
143
|
+
- **Confidence-aware validation**: when source columns can't be fully resolved
|
|
144
|
+
(e.g. `SELECT *`), a missing column is reported as a **warning, not a
|
|
145
|
+
build-blocking critical**. Type warnings are suppressed for low-confidence
|
|
146
|
+
(regex-tier) sources. This is the core false-positive guard.
|
|
147
|
+
- **Explicit mapping config** (`mapping:` in `.retl-validator.yml`) for when
|
|
148
|
+
name heuristics aren't enough — map a target table/column to a differently
|
|
149
|
+
named source model/column:
|
|
150
|
+
```yaml
|
|
151
|
+
mapping:
|
|
152
|
+
tables:
|
|
153
|
+
user_analytics: user_analytics_summary
|
|
154
|
+
columns:
|
|
155
|
+
user_analytics:
|
|
156
|
+
userId: user_id
|
|
157
|
+
```
|
|
158
|
+
- **Name normalization**: tables/columns now match across snake_case, camelCase
|
|
159
|
+
and casing differences (`userId` == `user_id` == `USER_ID`).
|
|
160
|
+
|
|
161
|
+
### Changed
|
|
162
|
+
- `Schema` now carries `confidence` and `is_complete` (via `metadata`).
|
|
163
|
+
- `BaseExtractor` no longer contains Python-specific type mapping; type
|
|
164
|
+
normalization lives in the canonical type system. Added `_make_column` helper.
|
|
165
|
+
- Added `sqlglot` as a dependency (imported optionally; falls back to regex if
|
|
166
|
+
absent).
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
- Hardened GitHub API rate-limit handling against non-dict response headers
|
|
170
|
+
(previously could raise when headers weren't a mapping).
|
|
171
|
+
|
|
172
|
+
## [1.0.5] - 2025-01-24
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
- **CRITICAL**: Fixed missing return statement in `DBTExtractor.extract_schemas()` that could return `None` instead of dictionary
|
|
176
|
+
- Added fallback to SQL file parsing when manifest.json is unavailable
|
|
177
|
+
- Now works reliably with or without DBT CLI installed
|
|
178
|
+
- **HIGH**: Fixed function signature mismatch in `_test_configuration()` causing TypeError on `--dry-run` command
|
|
179
|
+
- Added missing `disable_manifest` parameter
|
|
180
|
+
- Enhanced to display manifest parsing status
|
|
181
|
+
- **MEDIUM**: Replaced bare exception handler in `_try_compile_dbt()` with specific exception types
|
|
182
|
+
- Now properly handles TimeoutExpired, FileNotFoundError
|
|
183
|
+
- Provides helpful error messages instead of silent failures
|
|
184
|
+
- Respects keyboard interrupts
|
|
185
|
+
- **MEDIUM**: Removed unused `fastapi_directory` parameter from CLI
|
|
186
|
+
- Simplified API - use `--fastapi-local` for both files and directories
|
|
187
|
+
- **MEDIUM**: Added comprehensive YAML error handling with user-friendly messages
|
|
188
|
+
- Catches malformed YAML files with helpful suggestions
|
|
189
|
+
- Validates required configuration sections
|
|
190
|
+
- Provides clear error messages instead of Python tracebacks
|
|
191
|
+
- **LOW**: Added GitHub API rate limiting detection and handling
|
|
192
|
+
- Monitors rate limit headers and warns when limits are low
|
|
193
|
+
- Provides helpful guidance to use GITHUB_TOKEN for higher limits
|
|
194
|
+
- Better error messages for 403 and 404 responses
|
|
195
|
+
|
|
196
|
+
### Improved
|
|
197
|
+
- Enhanced error messages throughout the application
|
|
198
|
+
- Better support for different use-cases:
|
|
199
|
+
- DBT projects with or without manifest.json
|
|
200
|
+
- Local files and directories for FastAPI models
|
|
201
|
+
- GitHub repositories with rate limit awareness
|
|
202
|
+
- Configuration validation with clear error reporting
|
|
203
|
+
|
|
204
|
+
## [1.0.0] - 2025-01-XX
|
|
205
|
+
|
|
206
|
+
### Added
|
|
207
|
+
- Initial release of Data Contract Validator
|
|
208
|
+
- DBT schema extraction from SQL files and manifest.json
|
|
209
|
+
- FastAPI/Pydantic model extraction from local files and GitHub repos
|
|
210
|
+
- Command-line interface with multiple output formats
|
|
211
|
+
- GitHub Actions integration
|
|
212
|
+
- Contract validation with critical/warning/info severity levels
|
|
213
|
+
- Support for multiple repositories and complex validation scenarios
|
|
214
|
+
|
|
215
|
+
### Features
|
|
216
|
+
- ✅ DBT model schema extraction
|
|
217
|
+
- ✅ FastAPI/Pydantic schema extraction
|
|
218
|
+
- ✅ Cross-repository validation
|
|
219
|
+
- ✅ GitHub Actions workflows
|
|
220
|
+
- ✅ Multiple output formats (terminal, JSON, GitHub Actions)
|
|
221
|
+
- ✅ Comprehensive error reporting with suggested fixes
|
|
222
|
+
- ✅ Type compatibility checking
|
|
223
|
+
- ✅ Missing table/column detection
|
|
224
|
+
|
|
225
|
+
### Known Limitations
|
|
226
|
+
- Only supports DBT and FastAPI currently
|
|
227
|
+
- Requires manual installation of DBT CLI
|
|
228
|
+
- Limited type inference from SQL
|
|
229
|
+
- No support for complex nested types
|
|
230
|
+
|
|
231
|
+
[Unreleased]: https://github.com/OGsiji/data-contract-validator/compare/v1.1.1...HEAD
|
|
232
|
+
[1.1.1]: https://github.com/OGsiji/data-contract-validator/releases/tag/v1.1.1
|
|
233
|
+
[1.1.0]: https://github.com/OGsiji/data-contract-validator/releases/tag/v1.1.0
|
|
234
|
+
[1.0.5]: https://github.com/OGsiji/data-contract-validator/releases/tag/v1.0.5
|
|
235
|
+
[1.0.0]: https://github.com/OGsiji/data-contract-validator/releases/tag/v1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-contract-validator
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.7
|
|
4
4
|
Summary: Validate data contracts between dbt models and FastAPI/Pydantic APIs with accurate, low-false-positive schema checks
|
|
5
5
|
Author-email: Ogunniran Siji <ogunniransiji@gmail.com>
|
|
6
6
|
Maintainer-email: Ogunniran Siji <ogunniransiji@gmail.com>
|
|
@@ -101,6 +101,80 @@ contract-validator test
|
|
|
101
101
|
contract-validator validate
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## 🚀 Getting started, step by step
|
|
105
|
+
|
|
106
|
+
If you're setting this up on a project for the first time, the order below
|
|
107
|
+
avoids the sharp edges:
|
|
108
|
+
|
|
109
|
+
1. **Install into the same environment dbt runs in** (not a separate venv) —
|
|
110
|
+
the tool needs to see your dbt project:
|
|
111
|
+
```bash
|
|
112
|
+
pip install data-contract-validator
|
|
113
|
+
```
|
|
114
|
+
Already have `.retl-validator.yml` committed by a teammate? Skip to step 5.
|
|
115
|
+
|
|
116
|
+
2. **Generate the config + CI workflow** (one-time):
|
|
117
|
+
```bash
|
|
118
|
+
contract-validator init --interactive
|
|
119
|
+
```
|
|
120
|
+
You'll be asked: where your dbt project is, which API framework you use,
|
|
121
|
+
whether your models live in this local project or a different GitHub
|
|
122
|
+
repo, and then the local path (or the `org/repo` + path within it). It's
|
|
123
|
+
asked explicitly rather than guessed from the path's shape — a local path
|
|
124
|
+
like `app/models` is syntactically identical to a GitHub `org/repo`
|
|
125
|
+
string, so there's no reliable way to infer which one you mean. If you
|
|
126
|
+
pick GitHub, it checks the path actually exists before writing the
|
|
127
|
+
config — so a typo surfaces here instead of at `validate` time.
|
|
128
|
+
|
|
129
|
+
`init` refuses to touch an existing `.retl-validator.yml` or workflow
|
|
130
|
+
file — it won't clobber hand-added `mapping` entries just because you
|
|
131
|
+
upgraded the package and re-ran `init`. Pass `--force` if you really want
|
|
132
|
+
to regenerate them from the new version's defaults.
|
|
133
|
+
|
|
134
|
+
3. **Pre-commit hook**: `init --interactive` asks whether you want one set
|
|
135
|
+
up right after creating the config and CI workflow — say yes there and
|
|
136
|
+
it's done. To add one later (or if you used non-interactive `init`,
|
|
137
|
+
which doesn't prompt), run it standalone:
|
|
138
|
+
```bash
|
|
139
|
+
contract-validator setup-precommit --install-hooks
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
4. **If the target repo is private, set a token** before running anything
|
|
143
|
+
that talks to GitHub locally:
|
|
144
|
+
```bash
|
|
145
|
+
export GITHUB_TOKEN=$(gh auth token) # or a PAT with repo read access
|
|
146
|
+
```
|
|
147
|
+
See [Private GitHub repos need `GITHUB_TOKEN`](#private-github-repos-need-github_token) below for why this is easy to miss.
|
|
148
|
+
|
|
149
|
+
5. **Sanity-check the setup**:
|
|
150
|
+
```bash
|
|
151
|
+
contract-validator test
|
|
152
|
+
```
|
|
153
|
+
Confirms the config parses, the dbt project is found, and the target
|
|
154
|
+
(local path or GitHub path) is reachable. If this fails, `validate` will
|
|
155
|
+
fail the same way — fix it here first.
|
|
156
|
+
|
|
157
|
+
6. **Run it**:
|
|
158
|
+
```bash
|
|
159
|
+
contract-validator validate
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
7. **When it reports a critical issue, diagnose before assuming your dbt
|
|
163
|
+
model is wrong**:
|
|
164
|
+
- Real missing column/table → fix the dbt model.
|
|
165
|
+
- Target name doesn't match the dbt model by convention (renamed/prefixed)
|
|
166
|
+
→ add an entry under `mapping.tables` in `.retl-validator.yml` (see
|
|
167
|
+
[When do I need `mapping`?](#when-do-i-need-mapping)).
|
|
168
|
+
- A table that's genuinely populated by something other than dbt (e.g. a
|
|
169
|
+
separate streaming pipeline) and has no source model on purpose → add
|
|
170
|
+
it to `mapping.exclude`. `table=True` alone is **not** used to infer
|
|
171
|
+
this automatically — see [FastAPI side](#fastapi-side) for why.
|
|
172
|
+
|
|
173
|
+
8. **For accurate type-checking** (not just column-presence checks), run
|
|
174
|
+
`dbt docs generate` before `validate` so it picks up `catalog.json` (Tier 1,
|
|
175
|
+
real warehouse types) instead of inferring from SQL text — see
|
|
176
|
+
[How extraction works](#-how-extraction-works-and-why-its-accurate) below.
|
|
177
|
+
|
|
104
178
|
### One-off validation (no config file)
|
|
105
179
|
|
|
106
180
|
```bash
|
|
@@ -132,13 +206,25 @@ CI job.
|
|
|
132
206
|
|
|
133
207
|
> 💡 **Tip:** run `dbt docs generate` in CI before validating to unlock Tier 1
|
|
134
208
|
> (real types). Without it, you still get accurate column-presence checks from
|
|
135
|
-
> Tier 2.
|
|
209
|
+
> Tier 2. The workflow `init` generates includes this step already, commented
|
|
210
|
+
> out — it needs your warehouse adapter and credentials filled in, which
|
|
211
|
+
> can't be guessed, so it isn't active by default.
|
|
136
212
|
|
|
137
213
|
### FastAPI side
|
|
138
214
|
|
|
139
215
|
Pydantic / SQLModel classes are parsed from source with Python's `ast` (no
|
|
140
|
-
imports executed). `Optional[...]` controls whether a field is required
|
|
141
|
-
`
|
|
216
|
+
imports executed). `Optional[...]` controls whether a field is required.
|
|
217
|
+
An explicit `__tablename__` is used as the table name when present;
|
|
218
|
+
otherwise the class name is converted to `snake_case`.
|
|
219
|
+
|
|
220
|
+
`table=True` SQLModel classes are validated the same as any other class —
|
|
221
|
+
they are **not** skipped. Whether a table is meant to come from dbt is
|
|
222
|
+
business knowledge that isn't recoverable from the Python source: two
|
|
223
|
+
structurally identical `table=True` classes can need opposite treatment (one
|
|
224
|
+
is a normal dbt-fed table your API also returns directly; another is
|
|
225
|
+
populated by a Kafka stream and was never meant to have a dbt model). Use
|
|
226
|
+
`mapping.exclude` to state the latter case explicitly rather than relying on
|
|
227
|
+
`table=True` to imply it.
|
|
142
228
|
|
|
143
229
|
## 🚦 What gets flagged
|
|
144
230
|
|
|
@@ -193,18 +279,103 @@ mapping:
|
|
|
193
279
|
user_analytics:
|
|
194
280
|
# target column : source column
|
|
195
281
|
userId: user_id
|
|
282
|
+
# Target tables with no source model on purpose (e.g. Kafka-populated,
|
|
283
|
+
# not dbt) -- see "When do I need mapping?" below.
|
|
284
|
+
exclude:
|
|
285
|
+
- feed_interaction
|
|
196
286
|
|
|
197
287
|
validation:
|
|
198
288
|
fail_on: ["missing_tables", "missing_required_columns"]
|
|
199
289
|
warn_on: ["type_mismatches", "missing_optional_columns"]
|
|
200
290
|
```
|
|
201
291
|
|
|
292
|
+
### Private GitHub repos need `GITHUB_TOKEN`
|
|
293
|
+
|
|
294
|
+
If `target.*.repo` points at a private repository, `contract-validator`
|
|
295
|
+
needs a token with read access to it. Where that token comes from is
|
|
296
|
+
different locally vs. in CI — and the CI case has a sharp edge worth
|
|
297
|
+
understanding before it silently fails on a PR.
|
|
298
|
+
|
|
299
|
+
**Locally**, set the `GITHUB_TOKEN` environment variable before running the
|
|
300
|
+
CLI. On bash/zsh that's `export` (there's nothing to install — `export` just
|
|
301
|
+
makes the variable visible to the `contract-validator` process you run
|
|
302
|
+
next):
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
export GITHUB_TOKEN=$(gh auth token) # or a PAT with repo read access
|
|
306
|
+
contract-validator validate
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
GitHub's API 404s (not 403s) an unauthenticated request to a private path,
|
|
310
|
+
so without a token this looks identical to a plain typo in `path` —
|
|
311
|
+
`contract-validator init --interactive` and `contract-validator test` both
|
|
312
|
+
check `target.*.path` actually exists and will point you at this if the
|
|
313
|
+
lookup 404s with no token set.
|
|
314
|
+
|
|
315
|
+
**In CI**, the workflow `init` generates for a GitHub target wires up
|
|
316
|
+
`GITHUB_TOKEN: ${{ secrets.API_REPO_TOKEN }}` — a token **you** create,
|
|
317
|
+
*not* the auto-provided `secrets.GITHUB_TOKEN`. That auto-provided token
|
|
318
|
+
only has access to **the repository the workflow is running in**, so if
|
|
319
|
+
your dbt repo and your API repo are different repos, it silently can't read
|
|
320
|
+
the target the first time that target is private — and a PAT works
|
|
321
|
+
identically for a public target too, so there's no reason to default to the
|
|
322
|
+
token that only sometimes works. To finish the setup the generated workflow
|
|
323
|
+
expects:
|
|
324
|
+
|
|
325
|
+
1. Create a token with read access to the *target* repo — a
|
|
326
|
+
[fine-grained PAT](https://github.com/settings/personal-access-tokens/new)
|
|
327
|
+
scoped to just that repo's Contents (read-only) is the least-privilege
|
|
328
|
+
option; a classic PAT with the `repo` scope also works.
|
|
329
|
+
2. In the repo running the workflow (your dbt repo): **Settings → Secrets
|
|
330
|
+
and variables → Actions → New repository secret**. Name it
|
|
331
|
+
**`API_REPO_TOKEN`** exactly (that's the name the generated workflow
|
|
332
|
+
already references) and paste the token as the value.
|
|
333
|
+
|
|
334
|
+
> ⚠️ **GitHub rejects any secret name starting with `GITHUB_`** — it's a
|
|
335
|
+
> reserved prefix. You cannot create a secret literally called
|
|
336
|
+
> `GITHUB_TOKEN`; that's not a naming suggestion, the UI will refuse it.
|
|
337
|
+
> That's exactly why the workflow's secret is named `API_REPO_TOKEN`
|
|
338
|
+
> instead, even though the environment variable it feeds is `GITHUB_TOKEN`
|
|
339
|
+
> — two different things with confusingly similar names:
|
|
340
|
+
> ```yaml
|
|
341
|
+
> env:
|
|
342
|
+
> GITHUB_TOKEN: ${{ secrets.API_REPO_TOKEN }}
|
|
343
|
+
> # ^^^^^^^^^^^ local variable name, can be anything -- the CLI
|
|
344
|
+
> # just needs it called GITHUB_TOKEN to find it
|
|
345
|
+
> # ^^^^^^^^^^^^^^ the *secret's* name --
|
|
346
|
+
> # this is what GitHub restricts
|
|
347
|
+
> ```
|
|
348
|
+
|
|
349
|
+
Skip all of this for a `local` target — `init` omits the whole `env:` block
|
|
350
|
+
since a local target never talks to the GitHub API at all.
|
|
351
|
+
|
|
202
352
|
### When do I need `mapping`?
|
|
203
353
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
354
|
+
Most of the time you don't. Names are matched automatically across:
|
|
355
|
+
- `snake_case` / `camelCase` / casing — `UserAnalytics` → `user_analytics`, `userId` → `user_id`
|
|
356
|
+
- **plural ↔ singular** — dbt's plural `users` matches Pydantic's `User` (→ `user`)
|
|
357
|
+
with no config (and it won't over-match — `address` is never confused with `addres`).
|
|
358
|
+
|
|
359
|
+
Reach for `mapping.tables` / `mapping.columns` only when a model or column is
|
|
360
|
+
named so differently that convention can't bridge it (e.g. Pydantic
|
|
361
|
+
`user_id` ↔ dbt `customer_identifier`).
|
|
362
|
+
|
|
363
|
+
`mapping.exclude` is different — it's not about renamed models, it's for a
|
|
364
|
+
target table that has **no source model on purpose**, because it's
|
|
365
|
+
populated by something other than dbt (a Kafka stream, a cron job, etc.).
|
|
366
|
+
This can't be inferred from the code (a `table=True` SQLModel class looks
|
|
367
|
+
identical whether or not dbt is supposed to feed it), so it has to be a
|
|
368
|
+
deliberate, human-stated exception:
|
|
369
|
+
|
|
370
|
+
```yaml
|
|
371
|
+
mapping:
|
|
372
|
+
exclude:
|
|
373
|
+
- feed_interaction
|
|
374
|
+
- affiliate_reward
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Anything not listed is validated normally — including `table=True` classes,
|
|
378
|
+
which are treated the same as any other target and are not silently skipped.
|
|
208
379
|
|
|
209
380
|
## 🐍 Python API
|
|
210
381
|
|
|
@@ -248,9 +419,16 @@ jobs:
|
|
|
248
419
|
# Optional: `dbt docs generate` here for real warehouse types (Tier 1)
|
|
249
420
|
- run: contract-validator validate --output github
|
|
250
421
|
env:
|
|
251
|
-
GITHUB_TOKEN: ${{ secrets.
|
|
422
|
+
GITHUB_TOKEN: ${{ secrets.API_REPO_TOKEN }}
|
|
252
423
|
```
|
|
253
424
|
|
|
425
|
+
`GITHUB_TOKEN` here is only needed if `target` is a `github` repo (`init`
|
|
426
|
+
omits the whole `env:` block for a `local` target). `secrets.API_REPO_TOKEN`
|
|
427
|
+
is a token you create yourself, not GitHub's auto-provided
|
|
428
|
+
`secrets.GITHUB_TOKEN` — see
|
|
429
|
+
[Private GitHub repos need `GITHUB_TOKEN`](#private-github-repos-need-github_token)
|
|
430
|
+
above for why, and how to set it up.
|
|
431
|
+
|
|
254
432
|
### Pre-commit
|
|
255
433
|
|
|
256
434
|
```bash
|