Skip to content

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:

  1. Check name validation: Misspelled check names in your config (e.g. check_modle_access instead of check_model_access) now produce an error instead of being silently ignored.
  2. 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 listing
  • dbt-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:

- dbt-bouncer --create-pr-comment-file false
+ dbt-bouncer --no-create-pr-comment-file

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:

  1. No class inheritance — use the @check decorator on a function
  2. No execute() method — the function body is the check logic
  3. No name field — inferred from the function name
  4. No iterate_over ClassVar — inferred from the first parameter name (e.g. model → iterates over models)
  5. fail() instead of raising exceptions — call fail("message") to report failures
  6. 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 from BaseCheck. Calling self._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 Any for 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. Declaring model: Any (as the @check decorator 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 tests
  • check_model_hard_coded_references — detect hard-coded references in model SQL
  • check_seed_column_types — validate seed column types
  • check_seed_has_unit_tests — verify seeds have unit tests
  • check_snapshot_description_populated — require snapshot descriptions
  • check_test_has_meta_keys — require specific meta keys on tests

New features#

  • JSON Schema for config files — enables IDE autocomplete and validation
  • dbt-bouncer.toml config format support
  • --dry-run flag — preview checks without running them
  • list --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:

  1. Check that your config file has no misspelled check names
  2. Update custom check imports per the table above
  3. Open an issue at github.com/godatadriven/dbt-bouncer/issues