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.
Files changed (29) hide show
  1. data_contract_validator-1.1.7/CHANGELOG.md +235 -0
  2. {data_contract_validator-1.1.0/data_contract_validator.egg-info → data_contract_validator-1.1.7}/PKG-INFO +187 -9
  3. data_contract_validator-1.1.7/README.md +464 -0
  4. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/__init__.py +1 -1
  5. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/cli.py +214 -49
  6. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/types.py +63 -2
  7. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/validator.py +28 -8
  8. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/fastapi.py +62 -22
  9. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7/data_contract_validator.egg-info}/PKG-INFO +187 -9
  10. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/pyproject.toml +1 -1
  11. data_contract_validator-1.1.0/CHANGELOG.md +0 -121
  12. data_contract_validator-1.1.0/README.md +0 -286
  13. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/LICENSE +0 -0
  14. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/MANIFEST.in +0 -0
  15. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/__init__.py +0 -0
  16. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/core/models.py +0 -0
  17. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/__init__.py +0 -0
  18. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/base.py +0 -0
  19. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/extractors/dbt.py +0 -0
  20. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/integrations/__init__.py +0 -0
  21. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/py.typed +0 -0
  22. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator/templates/github-actions-template.yml +0 -0
  23. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/SOURCES.txt +0 -0
  24. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/dependency_links.txt +0 -0
  25. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/entry_points.txt +0 -0
  26. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/requires.txt +0 -0
  27. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/data_contract_validator.egg-info/top_level.txt +0 -0
  28. {data_contract_validator-1.1.0 → data_contract_validator-1.1.7}/requirements.txt +0 -0
  29. {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.0
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
- `table=True` SQLModel classes (DB tables, not API contracts) are skipped.
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
- By default, names are matched across `snake_case` / `camelCase` / casing
205
- (`UserAnalytics` → `user_analytics`, `userId` → `user_id`). Reach for `mapping`
206
- only when a model or column is named so differently that the convention can't
207
- bridge it (e.g. Pydantic `user_id` dbt `customer_identifier`).
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.GITHUB_TOKEN }}
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