Skip to content

Versioning#

Note

The below checks require manifest.json to be present.

Checks related to model versioning.

Functions:

Name Description
check_model_latest_version_specified

Check that the latest_version attribute of the model is set.

check_model_version_allowed

Check that the version of the model matches the supplied regex pattern.

check_model_version_pinned_in_ref

Check that the version of the model is always specified in downstream nodes.

check_model_latest_version_specified #

Check that the latest_version attribute of the model is set.

Rationale

The latest_version attribute tells dbt which version of a model downstream consumers should default to when using ref('model_name') without specifying a version. Without it, dbt cannot resolve unversioned references, and consumers may inadvertently pin to an older version. Enforcing this attribute ensures the model versioning contract is fully specified.

Receives at execution time:

Name Type Description
model ModelNode

The ModelNode object to check.

Other Parameters (passed via config file):

Name Type Description
description str | None

Description of what the check does and why it is implemented.

exclude str | list[str] | None

Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.

include str | list[str] | None

Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.

materialization Literal[ephemeral, incremental, table, view] | None

Limit check to models with the specified materialization.

severity Literal[error, warn] | None

Severity level of the check. Default: error.

Example(s):

manifest_checks:
    - name: check_model_latest_version_specified
      include: ^models/marts

Source code in src/dbt_bouncer/checks/manifest/models/versioning.py
@check(code="MO045")
def check_model_latest_version_specified(model):
    """Check that the `latest_version` attribute of the model is set.

    !!! info "Rationale"

        The `latest_version` attribute tells dbt which version of a model downstream consumers should default to when using `ref('model_name')` without specifying a version. Without it, dbt cannot resolve unversioned references, and consumers may inadvertently pin to an older version. Enforcing this attribute ensures the model versioning contract is fully specified.

    Receives:
        model (ModelNode): The ModelNode object to check.

    Other Parameters:
        description (str | None): Description of what the check does and why it is implemented.
        exclude (str | list[str] | None): Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.
        include (str | list[str] | None): Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.
        materialization (Literal["ephemeral", "incremental", "table", "view"] | None): Limit check to models with the specified materialization.
        severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_latest_version_specified
              include: ^models/marts
        ```

    """
    if model.latest_version is None:
        fail(f"`{model.name}` does not have a specified `latest_version`.")

check_model_version_allowed #

Check that the version of the model matches the supplied regex pattern.

Rationale

Teams that use model versioning often enforce a convention for version identifiers — for example, numeric-only versions or semantic version strings. This check validates that version values conform to the team's chosen scheme, preventing arbitrary or malformed version strings from being introduced.

Only versioned models are checked; a model without a version has nothing to validate.

Parameters:

Name Type Description Default
version_pattern str

Regexp the version must match.

required

Receives at execution time:

Name Type Description
model ModelNode

The ModelNode object to check.

Other Parameters (passed via config file):

Name Type Description
description str | None

Description of what the check does and why it is implemented.

exclude str | list[str] | None

Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.

include str | list[str] | None

Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.

materialization Literal[ephemeral, incremental, table, view] | None

Limit check to models with the specified materialization.

severity Literal[error, warn] | None

Severity level of the check. Default: error.

Example(s):

manifest_checks:
    - name: check_model_version_allowed
      include: ^models/marts
      version_pattern: >- # Versions must be numeric
        [0-9]\d*
    - name: check_model_version_allowed
      include: ^models/marts
      version_pattern: ^(stable|latest)$ # Version can be "stable" or "latest", nothing else is permitted

Source code in src/dbt_bouncer/checks/manifest/models/versioning.py
@check(code="MO046")
def check_model_version_allowed(model, *, version_pattern: str):
    r"""Check that the version of the model matches the supplied regex pattern.

    !!! info "Rationale"

        Teams that use model versioning often enforce a convention for version identifiers — for example, numeric-only versions or semantic version strings. This check validates that version values conform to the team's chosen scheme, preventing arbitrary or malformed version strings from being introduced.

    Only versioned models are checked; a model without a `version` has nothing to validate.

    Parameters:
        version_pattern (str): Regexp the version must match.

    Receives:
        model (ModelNode): The ModelNode object to check.

    Other Parameters:
        description (str | None): Description of what the check does and why it is implemented.
        exclude (str | list[str] | None): Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.
        include (str | list[str] | None): Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.
        materialization (Literal["ephemeral", "incremental", "table", "view"] | None): Limit check to models with the specified materialization.
        severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_version_allowed
              include: ^models/marts
              version_pattern: >- # Versions must be numeric
                [0-9]\d*
            - name: check_model_version_allowed
              include: ^models/marts
              version_pattern: ^(stable|latest)$ # Version can be "stable" or "latest", nothing else is permitted
        ```

    """
    compiled = compile_pattern(version_pattern.strip())
    # `is not None` rather than truthiness: `0` is an unusual but legitimate version.
    if model.version is not None and (compiled.match(str(model.version)) is None):
        fail(
            f"Version `{model.version}` in `{model.name}` does not match the supplied regex `{version_pattern.strip()}`."
        )

check_model_version_pinned_in_ref #

Check that the version of the model is always specified in downstream nodes.

Rationale

When a versioned model is referenced without specifying a version, dbt resolves it to the latest_version, meaning a version bump can silently redirect all downstream consumers. Requiring explicit version pins in ref() calls ensures that version upgrades are a deliberate, reviewed change rather than an implicit one.

Only versioned models are checked; a model without a version has nothing for a downstream ref to pin. Only downstream models are considered, so a ref from a test or exposure does not trigger this check.

Receives at execution time:

Name Type Description
manifest_obj ManifestObject

The ManifestObject object parsed from manifest.json.

model ModelNode

The ModelNode object to check.

Other Parameters (passed via config file):

Name Type Description
description str | None

Description of what the check does and why it is implemented.

exclude str | list[str] | None

Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.

include str | list[str] | None

Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.

materialization Literal[ephemeral, incremental, table, view] | None

Limit check to models with the specified materialization.

severity Literal[error, warn] | None

Severity level of the check. Default: error.

Example(s):

manifest_checks:
    - name: check_model_version_pinned_in_ref
      include: ^models/marts

Source code in src/dbt_bouncer/checks/manifest/models/versioning.py
@check(code="MO047")
def check_model_version_pinned_in_ref(model, ctx):
    """Check that the version of the model is always specified in downstream nodes.

    !!! info "Rationale"

        When a versioned model is referenced without specifying a version, dbt resolves it to the `latest_version`, meaning a version bump can silently redirect all downstream consumers. Requiring explicit version pins in `ref()` calls ensures that version upgrades are a deliberate, reviewed change rather than an implicit one.

    Only versioned models are checked; a model without a `version` has nothing for a downstream `ref` to pin. Only downstream models are considered, so a `ref` from a test or exposure does not trigger this check.

    Receives:
        manifest_obj (ManifestObject): The ManifestObject object parsed from `manifest.json`.
        model (ModelNode): The ModelNode object to check.

    Other Parameters:
        description (str | None): Description of what the check does and why it is implemented.
        exclude (str | list[str] | None): Regex pattern(s) to match the model path. Model paths that match any pattern will not be checked.
        include (str | list[str] | None): Regex pattern(s) to match the model path. Only model paths that match any pattern will be checked.
        materialization (Literal["ephemeral", "incremental", "table", "view"] | None): Limit check to models with the specified materialization.
        severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_version_pinned_in_ref
              include: ^models/marts
        ```

    """
    # Only versioned models can be pinned; an unversioned model has no version
    # for a downstream `ref` to specify.
    if model.version is None:
        return

    manifest_obj = ctx.manifest_obj
    child_map = manifest_obj.manifest.child_map
    if child_map and model.unique_id in child_map:
        downstream_models = [
            x for x in child_map[model.unique_id] if x.startswith("model.")
        ]
    else:
        downstream_models = []

    downstream_models_with_unversioned_refs: list[str] = []
    for m in downstream_models:
        node = manifest_obj.manifest.nodes.get(m)
        refs = getattr(node, "refs", None)
        if (
            node
            and refs
            and isinstance(refs, list)
            and any(
                # `name` excludes the version, so compare against the model's
                # `name` rather than the last segment of `unique_id` (which is
                # the version, e.g. `v2`, for a versioned model).
                getattr(ref, "name", None) == model.name
                # `is None` rather than truthiness: `ref(..., v=0)` is pinned.
                and getattr(ref, "version", None) is None
                for ref in refs
            )
        ):
            # `any` rather than a generator of matches, so a downstream model
            # with several unpinned refs to this model is only listed once.
            downstream_models_with_unversioned_refs.append(m)

    if downstream_models_with_unversioned_refs:
        fail(
            f"`{get_clean_model_name(model.unique_id)}` is referenced without a pinned version in downstream models: {downstream_models_with_unversioned_refs}."
        )