Migrating to dbt-bouncer v3.0.0#
dbt-bouncer v3.0.0 is a major release with significant internal improvements. This guide covers what changed and what you need to do to upgrade.
Upgrading past v3? Several of the APIs described below changed again in v4. This guide documents the v2 → v3 migration only; see Migrating to v4.0.0 for what v4 renames, removes and rejects.
Who needs to do what#
| You are... | Action required |
|---|---|
Using dbt-bouncer.yml config only |
Minimal — review config changes |
Writing custom checks (subclassing BaseCheck) |
Must migrate — see custom check migration |
Importing dbt-bouncer as a Python library |
Review — see API changes |
| Using dbt-bouncer GitHub Action | No changes required (action updated internally) |
Configuration changes#
Your config files still work#
Existing dbt-bouncer.yml and pyproject.toml [tool.dbt-bouncer] configurations are fully compatible. Check names and parameters are unchanged.
Config validation is now strict#
Two changes that may surface pre-existing issues in your config:
- Check name validation: Misspelled check names in your config (e.g.
check_modle_accessinstead ofcheck_model_access) now produce an error instead of being silently ignored. - Config parse errors: Previously, some config file parsing errors were silently swallowed. They now produce clear error messages with line numbers.
If your config was working correctly before, no action is needed.
New: dbt-bouncer.toml support#
You can now use dbt-bouncer.toml as an alternative config format alongside YAML.
CLI changes#
New run subcommand#
The default behaviour (running checks) is now also available as an explicit run subcommand. Both work:
# These are equivalent:
dbt-bouncer --config-file dbt-bouncer.yml
dbt-bouncer run --config-file dbt-bouncer.yml
New CLI features#
dbt-bouncer list --output-format json— machine-readable check listingdbt-bouncer run --dry-run— preview which checks would run without executing them- Rich-formatted table output for artifact parsing and results
Click → Typer#
The CLI framework changed from Click to Typer. If you were relying on Click-specific behaviour in wrapper scripts, review the Typer docs. The command-line interface is otherwise the same.
One notable difference: boolean flags now use --flag / --no-flag syntax instead of --flag true/false:
The GitHub Action handles this automatically — no changes needed if you use the action.
Dependency changes#
Removed dependencies#
The following are no longer installed with dbt-bouncer. If you relied on them transitively, add them to your own requirements.txt:
| Removed | Replacement |
|---|---|
click |
typer (included) |
dbt-artifacts-parser |
Internal parser (no action needed) |
progress |
rich (included) |
requests |
Not needed |
tabulate |
rich (included) |
New dependencies#
| Package | Version |
|---|---|
typer |
>=0.12, <1 |
rich |
>=13, <15 |
orjson |
>=3, <4 |
Python API changes#
Import path changes#
# v2.x
from dbt_bouncer.resource_type import ResourceType
# v3.0.0
from dbt_bouncer import ResourceType
# or
from dbt_bouncer.enums import ResourceType
The dbt_bouncer.resource_type module no longer exists.
New exports#
from dbt_bouncer import (
CheckOutcome, # FAILED, SUCCESS
CheckSeverity, # ERROR, WARN
ConfigFileName, # DBT_BOUNCER_TOML, DBT_BOUNCER_YML, PYPROJECT_TOML
Materialization, # EPHEMERAL, INCREMENTAL, TABLE, VIEW
ResourceType,
run_bouncer,
)
Module structure changes#
| v2.x | v3.0.0 |
|---|---|
dbt_bouncer.resource_type |
Removed → dbt_bouncer.enums |
checks/manifest/check_models.py |
Split into checks/manifest/models/{access,code,columns,...}.py |
checks/manifest/check_sources.py |
Split into checks/manifest/sources/ subpackage |
checks/manifest/check_columns.py |
Split into checks/manifest/columns/ subpackage |
| — | dbt_bouncer.check_framework.decorator (new) |
| — | dbt_bouncer.testing (new) |
| — | dbt_bouncer.enums (new) |
Migrating custom checks#
v2.x: Class-based checks#
from dbt_bouncer.checks.common import BaseCheck
class CheckModelAccess(BaseCheck):
access: Literal["private", "protected", "public"]
model: DbtBouncerModelBase | None = Field(default=None)
name: Literal["check_model_access"]
def execute(self) -> None:
assert self.model is not None
if self.model.access.value != self.access:
raise DbtBouncerFailedCheckError(
f"Model `{self.model.name}` has access `{self.model.access.value}`, expected `{self.access}`."
)
v3.0.0: Decorator-based checks#
The decorator API is the recommended way to write custom checks in v3. Class-based checks (subclassing BaseCheck) still work as a legacy escape hatch for cases that need complex Pydantic validation, but they are not the encouraged path — see class-based gotchas below.
from dbt_bouncer.check_framework.decorator import check, fail
@check
def check_model_access(model, *, access: str):
"""Each model should have the specified access level."""
if model.access and model.access.value != access:
fail(
f"Model `{model.name}` has access `{model.access.value}`, expected `{access}`."
)
Key differences:
- No class inheritance — use the
@checkdecorator on a function - No
execute()method — the function body is the check logic - No
namefield — inferred from the function name - No
iterate_overClassVar — inferred from the first parameter name (e.g.model→ iterates over models) fail()instead of raising exceptions — callfail("message")to report failures- Parameters are keyword-only — use
*separator after the resource parameter
Plugin registration#
Custom check packages must now register via entry points:
# pyproject.toml of your custom checks package
[project.entry-points."dbt_bouncer.checks"]
my_checks = "my_package.checks"
Testing custom checks#
The check_passes / check_fails helpers take the check name as a string (its config name), followed by resource overrides and parameters as keyword arguments:
from dbt_bouncer.testing import check_fails, check_passes
def test_check_model_access_pass():
check_passes(
"check_model_access",
model={"name": "my_model", "access": {"value": "private"}},
access="private",
)
def test_check_model_access_fail():
check_fails(
"check_model_access",
model={"name": "my_model", "access": {"value": "public"}},
access="private",
)
If your custom check lives in a custom_checks_dir rather than an installed plugin package, pass that directory so the helpers can find it — otherwise the check name will not resolve and you would have to monkeypatch the registry:
def test_my_custom_check_pass():
check_passes(
"check_my_custom_check",
custom_checks_dir="path/to/custom_checks",
model={"name": "my_model"},
)
Class-based checks (legacy)#
Class-based checks remain supported in v3 but are a legacy escape hatch — prefer the decorator API above. If you do subclass BaseCheck, two things commonly trip people up:
- Re-declare the resource field on your subclass. The runner infers which resource a class-based check iterates over from the subclass's own annotations (
__annotations__), which Python does not inherit fromBaseCheck. Callingself._require_model()is not enough — you must also declare the field, e.g.model: Any = Field(default=None). Without it the check never iterates over any resource. - Use
Anyfor the resource field when testing with the helpers. The test helpers build resources as lightweight dicts; declaring the field as the strict artifact type (e.g.ModelNode) makes Pydantic reject those dicts during instantiation. Declaringmodel: Any(as the@checkdecorator does internally) avoids this.
New checks in v3.0.0#
The following checks are new in this release:
check_model_columns_have_relationship_tests— verify columns have relationship testscheck_model_hard_coded_references— detect hard-coded references in model SQLcheck_seed_column_types— validate seed column typescheck_seed_has_unit_tests— verify seeds have unit testscheck_snapshot_description_populated— require snapshot descriptionscheck_test_has_meta_keys— require specific meta keys on tests
New features#
- JSON Schema for config files — enables IDE autocomplete and validation
dbt-bouncer.tomlconfig format support--dry-runflag — preview checks without running themlist --output-format json— machine-readable output- Entry-points plugin system — third-party check packages
- Rich table output — improved terminal formatting
Getting help#
If you run into issues upgrading:
- Check that your config file has no misspelled check names
- Update custom check imports per the table above
- Open an issue at github.com/godatadriven/dbt-bouncer/issues