Skip to content

Manifest Checks

Note

The below checks require manifest.json to be present.

check_exposures

check_exposure_based_on_non_public_models

Exposures should be based on public models only.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Exposure paths that match the pattern will not be checked.

exposure Exposures

The Exposures object to check.

include Optional[str]

Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Only exposure paths that match the pattern will be checked.

Example(s):

manifest_checks:
    - name: check_exposure_based_on_non_public_models

Source code in src/dbt_bouncer/checks/manifest/check_exposures.py
@pytest.mark.iterate_over_exposures
@bouncer_check
def check_exposure_based_on_non_public_models(
    models: List[DbtBouncerModel],
    request: TopRequest,
    exposure: Union[Exposures, None] = None,
    **kwargs,
) -> None:
    """
    Exposures should be based on public models only.

    Receives:
        exclude (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Exposure paths that match the pattern will not be checked.
        exposure (Exposures): The Exposures object to check.
        include (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Only exposure paths that match the pattern will be checked.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_exposure_based_on_non_public_models
        ```
    """

    non_public_upstream_dependencies = []
    for model in exposure.depends_on.nodes:
        if model.split(".")[0] == "model" and model.split(".")[1] == exposure.package_name:
            model = [m for m in models if m.unique_id == model][0]
            if model.access.value != "public":
                non_public_upstream_dependencies.append(model.name)

    assert (
        not non_public_upstream_dependencies
    ), f"`{exposure.name}` is based on a model(s) that is not public: {non_public_upstream_dependencies}."

check_exposure_based_on_view

Exposures should not be based on views.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Exposure paths that match the pattern will not be checked.

exposure Exposures

The Exposures object to check.

include Optional[str]

Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Only exposure paths that match the pattern will be checked.

materializations_to_include Optional[List[str]]

List of materializations to include in the check. If not provided, defaults to ephemeral and view.

Example(s):

manifest_checks:
    - name: check_exposure_based_on_view
manifest_checks:
    - name: check_exposure_based_on_view
      materializations_to_include:
        - ephemeral
        - my_custom_materialization
        - view

Source code in src/dbt_bouncer/checks/manifest/check_exposures.py
@pytest.mark.iterate_over_exposures
@bouncer_check
def check_exposure_based_on_view(
    models: List[DbtBouncerModel],
    request: TopRequest,
    exposure: Union[Exposures, None] = None,
    materializations_to_include: Union[List[str], None] = None,
    **kwargs,
) -> None:
    """
    Exposures should not be based on views.

    Receives:
        exclude (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Exposure paths that match the pattern will not be checked.
        exposure (Exposures): The Exposures object to check.
        include (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Only exposure paths that match the pattern will be checked.
        materializations_to_include (Optional[List[str]]): List of materializations to include in the check. If not provided, defaults to `ephemeral` and `view`.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_exposure_based_on_view
        ```
        ```yaml
        manifest_checks:
            - name: check_exposure_based_on_view
              materializations_to_include:
                - ephemeral
                - my_custom_materialization
                - view
        ```
    """

    non_table_upstream_dependencies = []
    for model in exposure.depends_on.nodes:
        if model.split(".")[0] == "model" and model.split(".")[1] == exposure.package_name:
            model = [m for m in models if m.unique_id == model][0]
            if model.config.materialized in materializations_to_include:  # type: ignore[operator]
                non_table_upstream_dependencies.append(model.name)

    assert (
        not non_table_upstream_dependencies
    ), f"`{exposure.name}` is based on a model that is not a table: {non_table_upstream_dependencies}."

check_lineage

check_lineage_permitted_upstream_models

Upstream models must have a path that matches the provided upstream_path_pattern.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

upstream_path_pattern str

Regexp pattern to match the upstream model(s) path.

Example(s):

manifest_checks:
    - name: check_lineage_permitted_upstream_models
      include: ^staging
      upstream_path_pattern: $^
    - name: check_lineage_permitted_upstream_models
      include: ^intermediate
      upstream_path_pattern: ^staging|^intermediate
    - name: check_lineage_permitted_upstream_models
      include: ^marts
      upstream_path_pattern: ^staging|^intermediate

Source code in src/dbt_bouncer/checks/manifest/check_lineage.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_lineage_permitted_upstream_models(
    manifest_obj: DbtBouncerManifest,
    models: List[DbtBouncerModel],
    request: TopRequest,
    model: Union[DbtBouncerModel, None] = None,
    upstream_path_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    Upstream models must have a path that matches the provided `upstream_path_pattern`.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.
        upstream_path_pattern (str): Regexp pattern to match the upstream model(s) path.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_lineage_permitted_upstream_models
              include: ^staging
              upstream_path_pattern: $^
            - name: check_lineage_permitted_upstream_models
              include: ^intermediate
              upstream_path_pattern: ^staging|^intermediate
            - name: check_lineage_permitted_upstream_models
              include: ^marts
              upstream_path_pattern: ^staging|^intermediate
        ```
    """

    upstream_models = [
        x
        for x in model.depends_on.nodes
        if x.split(".")[0] == "model"
        and x.split(".")[1] == manifest_obj.manifest.metadata.project_name
    ]
    not_permitted_upstream_models = [
        upstream_model
        for upstream_model in upstream_models
        if re.compile(upstream_path_pattern.strip()).match(
            [m for m in models if m.unique_id == upstream_model][0].path
        )
        is None
    ]
    assert (
        not not_permitted_upstream_models
    ), f"`{model.name}` references upstream models that are not permitted: {[m.split('.')[-1] for m in not_permitted_upstream_models]}."

check_lineage_seed_cannot_be_used

Seed cannot be referenced in models with a path that matches the specified include config.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_lineage_seed_cannot_be_used
      include: ^intermediate|^marts

Source code in src/dbt_bouncer/checks/manifest/check_lineage.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_lineage_seed_cannot_be_used(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Seed cannot be referenced in models with a path that matches the specified `include` config.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_lineage_seed_cannot_be_used
              include: ^intermediate|^marts
        ```
    """

    assert not [
        x for x in model.depends_on.nodes if x.split(".")[0] == "seed"
    ], f"`{model.name}` references a seed even though this is not permitted."

check_lineage_source_cannot_be_used

Sources cannot be referenced in models with a path that matches the specified include config.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_lineage_source_cannot_be_used
      include: ^intermediate|^marts

Source code in src/dbt_bouncer/checks/manifest/check_lineage.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_lineage_source_cannot_be_used(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Sources cannot be referenced in models with a path that matches the specified `include` config.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_lineage_source_cannot_be_used
              include: ^intermediate|^marts
        ```
    """

    assert not [
        x for x in model.depends_on.nodes if x.split(".")[0] == "source"
    ], f"`{model.name}` references a source even though this is not permitted."

check_macros

check_macro_arguments_description_populated

Macro arguments must have a populated description.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.

macro Macros

The Macro object to check.

Example(s):

manifest_checks:
    - name: check_macro_arguments_description_populated
# Only "common" macros need to have their arguments populated
manifest_checks:
    - name: check_macro_arguments_description_populated
      include: ^macros/common

Source code in src/dbt_bouncer/checks/manifest/check_macros.py
@pytest.mark.iterate_over_macros
@bouncer_check
def check_macro_arguments_description_populated(
    request: TopRequest, macro: Union[Macros, None] = None, **kwargs
) -> None:
    """
    Macro arguments must have a populated description.

    Receives:
        exclude (Optional[str]): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
        macro (Macros): The Macro object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_macro_arguments_description_populated
        ```
        ```yaml
        # Only "common" macros need to have their arguments populated
        manifest_checks:
            - name: check_macro_arguments_description_populated
              include: ^macros/common
        ```
    """

    environment = jinja2.Environment(extensions=[TagExtension])
    ast = environment.parse(macro.macro_sql)

    # Assume macro is a "true" macro, if not see if it's a generic test
    try:
        macro_arguments = [a.name for a in ast.body[0].args]  # type: ignore[attr-defined]
    except AttributeError:
        test_macro = [x for x in ast.body if not isinstance(x.nodes[0], jinja2.nodes.Call)][0]  # type: ignore[attr-defined]
        macro_arguments = [x.name for x in test_macro.nodes if isinstance(x, jinja2.nodes.Name)]  # type: ignore[attr-defined]

    # macro_arguments: List of args parsed from macro SQL
    # macro.arguments: List of args manually added to the properties file

    non_complying_args = []
    for arg in macro_arguments:
        macro_doc_raw = [x for x in macro.arguments if x.name == arg]
        if macro_doc_raw == []:
            non_complying_args.append(arg)
        elif (
            arg not in [x.name for x in macro.arguments]
            or len(macro_doc_raw[0].description.strip()) <= 4
        ):
            non_complying_args.append(arg)

    assert (
        non_complying_args == []
    ), f"Macro `{macro.name}` does not have a populated description for the following argument(s): {non_complying_args}."

check_macro_code_does_not_contain_regexp_pattern

The raw code for a macro must not match the specified regexp pattern.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.

macro Macros

The Macro object to check.

regexp_pattern str

The regexp pattern that should not be matched by the macro code.

Example(s):

manifest_checks:
    # Prefer `coalesce` over `ifnull`: https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02
    - name: check_macro_code_does_not_contain_regexp_pattern
      regexp_pattern: .*[i][f][n][u][l][l].*

Source code in src/dbt_bouncer/checks/manifest/check_macros.py
@pytest.mark.iterate_over_macros
@bouncer_check
def check_macro_code_does_not_contain_regexp_pattern(
    request: TopRequest,
    macro: Union[Macros, None] = None,
    regexp_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """The raw code for a macro must not match the specified regexp pattern.

    Receives:
        exclude (Optional[str]): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
        macro (Macros): The Macro object to check.
        regexp_pattern (str): The regexp pattern that should not be matched by the macro code.

    Example(s):
        ```yaml
        manifest_checks:
            # Prefer `coalesce` over `ifnull`: https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02
            - name: check_macro_code_does_not_contain_regexp_pattern
              regexp_pattern: .*[i][f][n][u][l][l].*
        ```
    """

    assert (
        re.compile(regexp_pattern.strip(), flags=re.DOTALL).match(macro.macro_sql) is None
    ), f"Macro `{macro.name}` contains a banned string: `{regexp_pattern.strip()}`."

check_macro_description_populated

Macros must have a populated description.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.

macro Macros

The Macro object to check.

Example(s):

manifest_checks:
    - name: check_macro_description_populated
# Only "common" macros need to have a populated description
manifest_checks:
    - name: check_macro_description_populated
      include: ^macros/common

Source code in src/dbt_bouncer/checks/manifest/check_macros.py
@pytest.mark.iterate_over_macros
@bouncer_check
def check_macro_description_populated(
    request: TopRequest, macro: Union[Macros, None] = None, **kwargs
) -> None:
    """
    Macros must have a populated description.

    Receives:
        exclude (Optional[str]): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
        macro (Macros): The Macro object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_macro_description_populated
        ```
        ```yaml
        # Only "common" macros need to have a populated description
        manifest_checks:
            - name: check_macro_description_populated
              include: ^macros/common
        ```
    """

    assert (
        len(macro.description.strip()) > 4
    ), f"Macro `{macro.name}` does not have a populated description."

check_macro_name_matches_file_name

Macros names must be the same as the file they are contained in.

Generic tests are also macros, however to document these tests the "name" value must be precededed with "test_".

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.

macro Macros

The Macro object to check.

Example(s):

manifest_checks:
    - name: check_macro_name_matches_file_name

Source code in src/dbt_bouncer/checks/manifest/check_macros.py
@pytest.mark.iterate_over_macros
@bouncer_check
def check_macro_name_matches_file_name(
    request: TopRequest, macro: Union[Macros, None] = None, **kwargs
) -> None:
    """
    Macros names must be the same as the file they are contained in.

    Generic tests are also macros, however to document these tests the "name" value must be precededed with "test_".

    Receives:
        exclude (Optional[str]): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
        macro (Macros): The Macro object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_macro_name_matches_file_name
        ```
    """

    if macro.name.startswith("test_"):
        assert (
            macro.name[5:] == macro.path.split("/")[-1].split(".")[0]
        ), f"Macro `{macro.unique_id}` is not in a file named `{macro.name[5:]}.sql`."
    else:
        assert (
            macro.name == macro.path.split("/")[-1].split(".")[0]
        ), f"Macro `{macro.name}` is not in a file of the same name."

check_macro_property_file_location

Macro properties files must follow the guidance provided by dbt here.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.

macro Macros

The Macro object to check.

Example(s):

manifest_checks:
    - name: check_macro_property_file_location

Source code in src/dbt_bouncer/checks/manifest/check_macros.py
@pytest.mark.iterate_over_macros
@bouncer_check
def check_macro_property_file_location(
    request: TopRequest, macro: Union[Macros, None] = None, **kwargs
) -> None:
    """
    Macro properties files must follow the guidance provided by dbt [here](https://docs.getdbt.com/best-practices/how-we-structure/5-the-rest-of-the-project#how-we-use-the-other-folders).

    Receives:
        exclude (Optional[str]): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
        macro (Macros): The Macro object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_macro_property_file_location
        ```
    """

    expected_substr = "_".join(macro.path[6:].split("/")[:-1])
    properties_yml_name = macro.patch_path.split("/")[-1]

    if macro.path.startswith("tests/"):  # Do not check generic tests (which are also macros)
        pass
    elif expected_substr == "":  # i.e. macro in ./macros
        assert (
            properties_yml_name == "_macros.yml"
        ), f"The properties file for `{macro.name}` (`{properties_yml_name}`) should be `_macros.yml`."
    else:
        assert properties_yml_name.startswith(
            "_"
        ), f"The properties file for `{macro.name}` (`{properties_yml_name}`) does not start with an underscore."
        assert (
            expected_substr in properties_yml_name
        ), f"The properties file for `{macro.name}` (`{properties_yml_name}`) does not contain the expected substring (`{expected_substr}`)."
        assert properties_yml_name.endswith(
            "__macros.yml"
        ), f"The properties file for `{macro.name.name}` (`{properties_yml_name}`) does not end with `__macros.yml`."

check_metadata

check_project_name

Enforce that the name of the dbt project matches a supplied regex. Generally used to enforce that project names conform to something like company_<DOMAIN>.

Receives:

Type Description
None

project_name_pattern str: Regex pattern to match the project name.

Example(s):

manifest_checks:
    - name: check_project_name
      project_name_pattern: ^awesome_company_

Source code in src/dbt_bouncer/checks/manifest/check_metadata.py
@bouncer_check
def check_project_name(
    manifest_obj: DbtBouncerManifest,
    request: TopRequest,
    project_name_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    Enforce that the name of the dbt project matches a supplied regex. Generally used to enforce that project names conform to something like  `company_<DOMAIN>`.

    Receives:
        project_name_pattern str: Regex pattern to match the project name.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_project_name
              project_name_pattern: ^awesome_company_
        ```
    """

    assert (
        re.compile(project_name_pattern.strip()).match(manifest_obj.manifest.metadata.project_name)
        is not None
    ), f"Project name (`{manifest_obj.manifest.metadata.project_name}`) does not conform to the supplied regex `({project_name_pattern.strip()})`."

check_models

check_model_access

Models must have the specified access attribute. Requires dbt 1.7+.

Receives:

Name Type Description
access Literal['private', 'protected', 'public']

The access level to check for.

exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    # Align with dbt best practices that marts should be `public`, everything else should be `protected`
    - name: check_model_access
      access: protected
      include: ^intermediate
    - name: check_model_access
      access: public
      include: ^marts
    - name: check_model_access
      access: protected
      include: ^staging

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_access(
    request: TopRequest,
    access: Union[None, str] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    Models must have the specified access attribute. Requires dbt 1.7+.

    Receives:
        access (Literal["private", "protected", "public"]): The access level to check for.
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            # Align with dbt best practices that marts should be `public`, everything else should be `protected`
            - name: check_model_access
              access: protected
              include: ^intermediate
            - name: check_model_access
              access: public
              include: ^marts
            - name: check_model_access
              access: protected
              include: ^staging
        ```
    """

    assert (
        model.access.value == access
    ), f"`{model.name}` has `{model.access.value}` access, it should have access `{access}`."

check_model_contract_enforced_for_public_model

Public models must have contracts enforced.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_contract_enforced_for_public_model

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_contract_enforced_for_public_model(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Public models must have contracts enforced.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_contract_enforced_for_public_model
        ```
    """

    if model.access.value == "public":
        assert (
            model.contract.enforced is True
        ), f"`{model.name}` is a public model but does not have contracts enforced."

check_model_description_populated

Models must have a populated description.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_description_populated

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_description_populated(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Models must have a populated description.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_description_populated
        ```
    """

    assert (
        len(model.description.strip()) > 4
    ), f"`{model.name}` does not have a populated description."

check_model_documentation_coverage

Set the minimum percentage of models that have a populated description.

Receives:

Name Type Description
min_model_documentation_coverage_pct float

The minimum percentage of models that must have a populated description. Default: 100.

Example(s):

manifest_checks:
    - name: check_model_description_populated
      min_model_documentation_coverage_pct: 90

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@bouncer_check
def check_model_documentation_coverage(
    request: TopRequest,
    models: List[DbtBouncerModel],
    min_model_documentation_coverage_pct: Union[float, None] = None,
    **kwargs,
) -> None:
    """
    Set the minimum percentage of models that have a populated description.

    Receives:
        min_model_documentation_coverage_pct (float): The minimum percentage of models that must have a populated description. Default: 100.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_description_populated
              min_model_documentation_coverage_pct: 90
        ```
    """

    num_models = len(models)
    models_with_description = []
    for model in models:
        if len(model.description.strip()) > 4:
            models_with_description.append(model.unique_id)

    num_models_with_descriptions = len(models_with_description)
    model_description_coverage_pct = (num_models_with_descriptions / num_models) * 100

    assert (
        model_description_coverage_pct >= min_model_documentation_coverage_pct  # type: ignore[operator]
    ), f"Only {model_description_coverage_pct}% of models have a populated description, this is less than the permitted minimum of {min_model_documentation_coverage_pct}%."

check_model_documented_in_same_directory

Models must be documented in the same directory where they are defined (i.e. .yml and .sql files are in the same directory).

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_documented_in_same_directory

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_documented_in_same_directory(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Models must be documented in the same directory where they are defined (i.e. `.yml` and `.sql` files are in the same directory).

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_documented_in_same_directory
        ```
    """

    model_doc_dir = model.patch_path[model.patch_path.find("models") :].split("/")[1:-1]
    model_sql_dir = model.path.split("/")[:-1]

    assert (
        model_doc_dir == model_sql_dir
    ), f"`{model.name}` is documented in a different directory to the `.sql` file: `{'/'.join(model_doc_dir)}` vs `{'/'.join(model_sql_dir)}`."

check_model_code_does_not_contain_regexp_pattern

The raw code for a model must not match the specified regexp pattern.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

regexp_pattern str

The regexp pattern that should not be matched by the model code.

Example(s):

manifest_checks:
    # Prefer `coalesce` over `ifnull`: https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02
    - name: check_model_code_does_not_contain_regexp_pattern
      regexp_pattern: .*[i][f][n][u][l][l].*

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_code_does_not_contain_regexp_pattern(
    request: TopRequest,
    model: Union[DbtBouncerModel, None] = None,
    regexp_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    The raw code for a model must not match the specified regexp pattern.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.
        regexp_pattern (str): The regexp pattern that should not be matched by the model code.

    Example(s):
        ```yaml
        manifest_checks:
            # Prefer `coalesce` over `ifnull`: https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02
            - name: check_model_code_does_not_contain_regexp_pattern
              regexp_pattern: .*[i][f][n][u][l][l].*
        ```
    """

    assert (
        re.compile(regexp_pattern.strip(), flags=re.DOTALL).match(model.raw_code) is None
    ), f"`{model.name}` contains a banned string: `{regexp_pattern.strip()}`."

check_model_depends_on_multiple_sources

Models cannot reference more than one source.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_depends_on_multiple_sources

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_depends_on_multiple_sources(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Models cannot reference more than one source.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_depends_on_multiple_sources
        ```
    """

    num_reffed_sources = sum(x.split(".")[0] == "source" for x in model.depends_on.nodes)
    assert num_reffed_sources <= 1, f"`{model.name}` references more than one source."

check_model_directories

Only specified sub-directories are permitted.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked. Special case: if you want to check ./models, pass "" to include.

model DbtBouncerModel

The DbtBouncerModel object to check.

permitted_sub_directories List[str]

List of permitted sub-directories.

Example(s):

manifest_checks:
# Special case for top level directories within `./models`, pass "" to `include`
- name: check_model_directories
    include: ""
    permitted_sub_directories:
    - intermediate
    - marts
    - staging
# Restrict sub-directories within `./models/staging`
- name: check_model_directories
    include: ^staging
    permitted_sub_directories:
    - crm
    - payments

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_directories(
    request: TopRequest,
    include: Union[None, str] = None,
    model: Union[DbtBouncerModel, None] = None,
    permitted_sub_directories: Union[List[str], None] = None,
    **kwargs,
) -> None:
    """
    Only specified sub-directories are permitted.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked. Special case: if you want to check `./models`, pass "" to `include`.
        model (DbtBouncerModel): The DbtBouncerModel object to check.
        permitted_sub_directories (List[str]): List of permitted sub-directories.

    Example(s):
        ```yaml
        manifest_checks:
        # Special case for top level directories within `./models`, pass "" to `include`
        - name: check_model_directories
            include: ""
            permitted_sub_directories:
            - intermediate
            - marts
            - staging
        ```
        ```yaml
        # Restrict sub-directories within `./models/staging`
        - name: check_model_directories
            include: ^staging
            permitted_sub_directories:
            - crm
            - payments
        ```
    """

    # Special case for `models` directory
    if include == "":
        assert (
            model.path.split("/")[0] in permitted_sub_directories  # type: ignore[operator]
        ), f"`{model.name}` is located in `{model.path.split('/')[0]}`, this is not a valid sub-directory."
    else:
        matched_path = re.compile(include.strip()).match(model.path)
        path_after_match = model.path[matched_path.end() + 1 :]  # type: ignore[union-attr]

        assert (
            path_after_match.split("/")[0] in permitted_sub_directories  # type: ignore[operator]
        ), f"`{model.name}` is located in `{model.path.split('/')[0]}`, this is not a valid sub-directory."

check_model_has_contracts_enforced

Model must have contracts enforced.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_has_contracts_enforced
        include: ^marts

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_has_contracts_enforced(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Model must have contracts enforced.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_has_contracts_enforced
                include: ^marts
        ```
    """

    assert model.contract.enforced is True, f"`{model.name}` does not have contracts enforced."

check_model_has_meta_keys

The meta config for models must have the specified keys.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

keys NestedDict

A list (that may contain sub-lists) of required keys.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_has_meta_keys
      keys:
        - maturity
        - owner

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_has_meta_keys(
    request: TopRequest,
    keys: Union[NestedDict, None] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    The `meta` config for models must have the specified keys.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        keys (NestedDict): A list (that may contain sub-lists) of required keys.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_has_meta_keys
              keys:
                - maturity
                - owner
        ```
    """

    missing_keys = find_missing_meta_keys(
        meta_config=model.meta,
        required_keys=keys,
    )
    assert (
        missing_keys == []
    ), f"`{model.name}` is missing the following keys from the `meta` config: {[x.replace('>>', '') for x in missing_keys]}"

check_model_has_no_upstream_dependencies

Identify if models have no upstream dependencies as this likely indicates hard-coded tables references.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_has_no_upstream_dependencies

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_has_no_upstream_dependencies(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Identify if models have no upstream dependencies as this likely indicates hard-coded tables references.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_has_no_upstream_dependencies
        ```
    """

    assert (
        len(model.depends_on.nodes) > 0
    ), f"`{model.name}` has no upstream dependencies, this likely indicates hard-coded tables references."

check_model_has_tags

Models must have the specified tags.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

tags List[str]

List of tags to check for.

Example(s):

manifest_checks:
    - name: check_model_has_tags
      tags:
        - tag_1
        - tag_2

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_has_tags(
    request: TopRequest,
    model: Union[DbtBouncerModel, None] = None,
    tags: Union[List[str], None] = None,
    **kwargs,
) -> None:
    """
    Models must have the specified tags.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.
        tags (List[str]): List of tags to check for.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_has_tags
              tags:
                - tag_1
                - tag_2
        ```
    """

    missing_tags = [tag for tag in tags if tag not in model.tags]
    assert not missing_tags, f"`{model.name}` is missing required tags: {missing_tags}."

check_model_has_unique_test

Models must have a test for uniqueness of a column.

Receives:

Name Type Description
accepted_uniqueness_tests Optional[List[str]]

List of tests that are accepted as uniqueness tests. If not provided, defaults to expect_compound_columns_to_be_unique, dbt_utils.unique_combination_of_columns and unique.

exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
  - name: check_model_has_unique_test
manifest_checks:
  # Example of allowing a custom uniqueness test
  - name: check_model_has_unique_test
    accepted_uniqueness_tests:
        - expect_compound_columns_to_be_unique
        - my_custom_uniqueness_test
        - unique

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_has_unique_test(
    request: TopRequest,
    tests: List[DbtBouncerModel],
    accepted_uniqueness_tests: Union[List[str], None] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    Models must have a test for uniqueness of a column.

    Receives:
        accepted_uniqueness_tests (Optional[List[str]]): List of tests that are accepted as uniqueness tests. If not provided, defaults to `expect_compound_columns_to_be_unique`, `dbt_utils.unique_combination_of_columns` and `unique`.
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
          - name: check_model_has_unique_test
        ```
        ```yaml
        manifest_checks:
          # Example of allowing a custom uniqueness test
          - name: check_model_has_unique_test
            accepted_uniqueness_tests:
                - expect_compound_columns_to_be_unique
                - my_custom_uniqueness_test
                - unique
        ```
    """

    num_unique_tests = sum(
        test.attached_node == model.unique_id
        and test.test_metadata.name in accepted_uniqueness_tests  # type: ignore[operator]
        for test in tests
    )
    assert (
        num_unique_tests >= 1
    ), f"`{model.name}` does not have a test for uniqueness of a column."

check_model_max_chained_views

Models cannot have more than the specified number of upstream dependents that are not tables (default: 3).

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

materializations_to_include Optional[List[str]]

List of materializations to include in the check. If not provided, defaults to ephemeral and view.

max_chained_views Optional[int]

The maximum number of upstream dependents that are not tables. Default: 3

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_max_chained_views
manifest_checks:
    - name: check_model_max_chained_views
      materializations_to_include:
        - ephemeral
        - my_custom_materialization
        - view
      max_chained_views: 5

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_max_chained_views(
    manifest_obj: DbtBouncerManifest,
    models: List[DbtBouncerModel],
    request: TopRequest,
    materializations_to_include: Union[List[str], None] = None,
    max_chained_views: Union[int, None] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    Models cannot have more than the specified number of upstream dependents that are not tables (default: 3).

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        materializations_to_include (Optional[List[str]]): List of materializations to include in the check. If not provided, defaults to `ephemeral` and `view`.
        max_chained_views (Optional[int]): The maximum number of upstream dependents that are not tables. Default: 3
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_max_chained_views
        ```
        ```yaml
        manifest_checks:
            - name: check_model_max_chained_views
              materializations_to_include:
                - ephemeral
                - my_custom_materialization
                - view
              max_chained_views: 5
        ```
    """

    def return_upstream_view_models(
        materializations,
        max_chained_views,
        models,
        model_unique_ids_to_check,
        package_name,
        depth=0,
    ):
        """
        Recursive function to return model unique_id's of upstream models that are views. Depth of recursion can be specified. If no models meet the criteria then an empty list is returned.
        """

        if depth == max_chained_views or model_unique_ids_to_check == []:
            return model_unique_ids_to_check

        relevant_upstream_models = []
        for model in model_unique_ids_to_check:
            upstream_nodes = list(
                [m2 for m2 in models if m2.unique_id == model][0].depends_on.nodes
            )
            if upstream_nodes != []:
                upstream_models = [
                    m
                    for m in upstream_nodes
                    if m.split(".")[0] == "model" and m.split(".")[1] == package_name
                ]
                for i in upstream_models:
                    if [m for m in models if m.unique_id == i][
                        0
                    ].config.materialized in materializations:
                        relevant_upstream_models.append(i)

        depth += 1
        return return_upstream_view_models(
            materializations=materializations,
            max_chained_views=max_chained_views,
            models=models,
            model_unique_ids_to_check=relevant_upstream_models,
            package_name=package_name,
            depth=depth,
        )

    assert (
        len(
            return_upstream_view_models(
                materializations=materializations_to_include,
                max_chained_views=max_chained_views,
                models=models,
                model_unique_ids_to_check=[model.unique_id],
                package_name=manifest_obj.manifest.metadata.project_name,
            )
        )
        == 0
    ), f"`{model.name}` has more than {max_chained_views} upstream dependents that are not tables."

check_model_max_fanout

Models cannot have more than the specified number of downstream models (default: 3).

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

max_downstream_models Optional[int]

The maximum number of permitted downstream models. Default: 3

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_max_fanout
      max_downstream_models: 2

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_max_fanout(
    models: List[DbtBouncerModel],
    request: TopRequest,
    max_downstream_models: Union[int, None] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    Models cannot have more than the specified number of downstream models (default: 3).

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        max_downstream_models (Optional[int]): The maximum number of permitted downstream models. Default: 3
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_max_fanout
              max_downstream_models: 2
        ```
    """

    num_downstream_models = sum(model.unique_id in m.depends_on.nodes for m in models)

    assert (
        num_downstream_models <= max_downstream_models  # type: ignore[operator]
    ), f"`{model.name}` has {num_downstream_models} downstream models, which is more than the permitted maximum of {max_downstream_models}."

check_model_max_upstream_dependencies

Limit the number of upstream dependencies a model has. Default values are 5 for models, 5 for macros, and 1 for sources.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

max_upstream_macros Optional[int]

The maximum number of permitted upstream macros. Default: 5

max_upstream_models Optional[int]

The maximum number of permitted upstream models. Default: 5

max_upstream_sources Optional[int]

The maximum number of permitted upstream sources. Default: 1

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_max_upstream_dependencies
      max_upstream_models: 3

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_max_upstream_dependencies(
    request: TopRequest,
    max_upstream_macros: Union[int, None] = None,
    max_upstream_models: Union[int, None] = None,
    max_upstream_sources: Union[int, None] = None,
    model: Union[DbtBouncerModel, None] = None,
    **kwargs,
) -> None:
    """
    Limit the number of upstream dependencies a model has. Default values are 5 for models, 5 for macros, and 1 for sources.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        max_upstream_macros (Optional[int]): The maximum number of permitted upstream macros. Default: 5
        max_upstream_models (Optional[int]): The maximum number of permitted upstream models. Default: 5
        max_upstream_sources (Optional[int]): The maximum number of permitted upstream sources. Default: 1
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_max_upstream_dependencies
              max_upstream_models: 3
        ```
    """

    num_upstream_macros = len(list(model.depends_on.macros))
    num_upstream_models = len([m for m in model.depends_on.nodes if m.split(".")[0] == "model"])
    num_upstream_sources = len([m for m in model.depends_on.nodes if m.split(".")[0] == "source"])

    assert (
        num_upstream_macros <= max_upstream_macros  # type: ignore[operator]
    ), f"`{model.name}` has {num_upstream_macros} upstream macros, which is more than the permitted maximum of {max_upstream_macros}."
    assert (
        num_upstream_models <= max_upstream_models  # type: ignore[operator]
    ), f"`{model.name}` has {num_upstream_models} upstream models, which is more than the permitted maximum of {max_upstream_models}."
    assert (
        num_upstream_sources <= max_upstream_sources  # type: ignore[operator]
    ), f"`{model.name}` has {num_upstream_sources} upstream sources, which is more than the permitted maximum of {max_upstream_sources}."

check_model_names

Models must have a name that matches the supplied regex.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model_name_pattern str

Regexp the model name must match.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_names
      include: ^intermediate
      model_name_pattern: ^int_
    - name: check_model_names
      include: ^staging
      model_name_pattern: ^stg_

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_names(
    request: TopRequest,
    model: Union[DbtBouncerModel, None] = None,
    model_name_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    Models must have a name that matches the supplied regex.

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model_name_pattern (str): Regexp the model name must match.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_names
              include: ^intermediate
              model_name_pattern: ^int_
            - name: check_model_names
              include: ^staging
              model_name_pattern: ^stg_
        ```
    """

    assert (
        re.compile(model_name_pattern.strip()).match(model.name) is not None
    ), f"`{model.name}` does not match the supplied regex `{model_name_pattern.strip()})`."

check_model_property_file_location

Model properties files must follow the guidance provided by dbt here.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the model path. Model paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the model path. Only model paths that match the pattern will be checked.

model DbtBouncerModel

The DbtBouncerModel object to check.

Example(s):

manifest_checks:
    - name: check_model_property_file_location

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@pytest.mark.iterate_over_models
@bouncer_check
def check_model_property_file_location(
    request: TopRequest, model: Union[DbtBouncerModel, None] = None, **kwargs
) -> None:
    """
    Model properties files must follow the guidance provided by dbt [here](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview).

    Receives:
        exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
        model (DbtBouncerModel): The DbtBouncerModel object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_property_file_location
        ```
    """

    expected_substr = (
        "_".join(model.path.split("/")[:-1])
        .replace("staging", "stg")
        .replace("intermediate", "int")
        .replace("marts", "")
    )
    properties_yml_name = model.patch_path.split("/")[-1]

    assert properties_yml_name.startswith(
        "_"
    ), f"The properties file for `{model.name}` (`{properties_yml_name}`) does not start with an underscore."
    assert (
        expected_substr in properties_yml_name
    ), f"The properties file for `{model.name}` (`{properties_yml_name}`) does not contain the expected substring (`{expected_substr}`)."
    assert properties_yml_name.endswith(
        "__models.yml"
    ), f"The properties file for `{model.name}` (`{properties_yml_name}`) does not end with `__models.yml`."

check_model_test_coverage

Set the minimum percentage of models that have at least one test.

Receives:

Name Type Description
min_model_test_coverage_pct float

The minimum percentage of models that must have at least one test. Default: 100

Example(s):

manifest_checks:
    - name: check_model_test_coverage
      min_model_test_coverage_pct: 90

Source code in src/dbt_bouncer/checks/manifest/check_models.py
@bouncer_check
def check_model_test_coverage(
    models: List[DbtBouncerModel],
    request: TopRequest,
    tests: List[DbtBouncerModel],
    min_model_test_coverage_pct: Union[float, None] = None,
    **kwargs,
) -> None:
    """
    Set the minimum percentage of models that have at least one test.

    Receives:
        min_model_test_coverage_pct (float): The minimum percentage of models that must have at least one test. Default: 100

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_model_test_coverage
              min_model_test_coverage_pct: 90
        ```
    """

    num_models = len(models)
    models_with_tests = []
    for model in models:
        for test in tests:
            if model.unique_id in test.depends_on.nodes:
                models_with_tests.append(model.unique_id)
    num_models_with_tests = len(set(models_with_tests))
    model_test_coverage_pct = (num_models_with_tests / num_models) * 100

    assert (
        model_test_coverage_pct >= min_model_test_coverage_pct  # type: ignore[operator]
    ), f"Only {model_test_coverage_pct}% of models have at least one test, this is less than the permitted minimum of {min_model_test_coverage_pct}%."

check_sources

check_source_description_populated

Sources must have a populated description.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_description_populated

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_description_populated(
    request: TopRequest, source: Union[DbtBouncerSource, None] = None, **kwargs
) -> None:
    """
    Sources must have a populated description.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_description_populated
        ```
    """

    assert (
        len(source.description.strip()) > 4
    ), f"`{source.source_name}.{source.name}` does not have a populated description."

check_source_freshness_populated

Sources must have a populated freshness.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_freshness_populated

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_freshness_populated(
    request: TopRequest, source: Union[DbtBouncerSource, None] = None, **kwargs
) -> None:
    """
    Sources must have a populated freshness.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_freshness_populated
        ```
    """

    assert (
        source.freshness.error_after.count is not None
        and source.freshness.error_after.period is not None
    ) or (
        source.freshness.warn_after.count is not None
        and source.freshness.warn_after.period is not None
    ), f"`{source.source_name}.{source.name}` does not have a populated freshness."

check_source_has_meta_keys

The meta config for sources must have the specified keys.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

keys NestedDict

A list (that may contain sub-lists) of required keys.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_has_meta_keys
      keys:
        - contact:
            - email
            - slack
        - owner

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_has_meta_keys(
    request,
    keys: Union[NestedDict, None] = None,
    source: Union[DbtBouncerSource, None] = None,
    **kwargs,
) -> None:
    """
    The `meta` config for sources must have the specified keys.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        keys (NestedDict): A list (that may contain sub-lists) of required keys.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_has_meta_keys
              keys:
                - contact:
                    - email
                    - slack
                - owner
        ```
    """

    missing_keys = find_missing_meta_keys(
        meta_config=source.meta,
        required_keys=keys,
    )
    assert (
        missing_keys == []
    ), f"`{source.source_name}.{source.name}` is missing the following keys from the `meta` config: {[x.replace('>>', '') for x in missing_keys]}"

check_source_has_tags

Sources must have the specified tags.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

tags List[str]

List of tags to check for.

Example(s):

manifest_checks:
    - name: check_source_has_tags
      tags:
        - tag_1
        - tag_2

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_has_tags(
    request: TopRequest,
    source: Union[DbtBouncerSource, None] = None,
    tags: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    Sources must have the specified tags.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.
        tags (List[str]): List of tags to check for.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_has_tags
              tags:
                - tag_1
                - tag_2
        ```
    """

    missing_tags = [tag for tag in tags if tag not in source.tags]
    assert (
        not missing_tags
    ), f"`{source.source_name}.{source.name}` is missing required tags: {missing_tags}."

check_source_loader_populated

Sources must have a populated loader.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_loader_populated

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_loader_populated(
    request: TopRequest, source: Union[DbtBouncerSource, None] = None, **kwargs
) -> None:
    """
    Sources must have a populated loader.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_loader_populated
        ```
    """

    assert (
        source.loader != ""
    ), f"`{source.source_name}.{source.name}` does not have a populated loader."

check_source_names

Sources must have a name that matches the supplied regex.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

source_name_pattern str

Regexp the source name must match.

Example(s):

manifest_checks:
    - name: check_source_names
      source_name_pattern: >
        ^[a-z0-9_]*$

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_names(
    request: TopRequest,
    source: Union[DbtBouncerSource, None] = None,
    source_name_pattern: Union[None, str] = None,
    **kwargs,
) -> None:
    """
    Sources must have a name that matches the supplied regex.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.
        source_name_pattern (str): Regexp the source name must match.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_names
              source_name_pattern: >
                ^[a-z0-9_]*$
        ```
    """

    assert (
        re.compile(source_name_pattern.strip()).match(source.name) is not None
    ), f"`{source.source_name}.{source.name}` does not match the supplied regex `({source_name_pattern.strip()})`."

check_source_not_orphaned

Sources must be referenced in at least one model.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_not_orphaned

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_not_orphaned(
    models: List[DbtBouncerModel],
    request: TopRequest,
    source: Union[DbtBouncerSource, None] = None,
    **kwargs,
) -> None:
    """
    Sources must be referenced in at least one model.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_not_orphaned
        ```
    """

    num_refs = sum(source.unique_id in model.depends_on.nodes for model in models)
    assert (
        num_refs >= 1
    ), f"Source `{source.source_name}.{source.name}` is orphaned, i.e. not referenced by any model."

check_source_property_file_location

Source properties files must follow the guidance provided by dbt here.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_property_file_location

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_property_file_location(
    request: TopRequest, source: Union[DbtBouncerSource, None] = None, **kwargs
) -> None:
    """
    Source properties files must follow the guidance provided by dbt [here](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview).

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_property_file_location
        ```
    """

    path_cleaned = source.path.replace("models/staging", "")
    expected_substring = "_".join(path_cleaned.split("/")[:-1])

    assert path_cleaned.split("/")[-1].startswith(
        "_"
    ), f"The properties file for `{source.source_name}.{source.name}` (`{path_cleaned}`) does not start with an underscore."
    assert (
        expected_substring in path_cleaned
    ), f"The properties file for `{source.source_name}.{source.name}` (`{path_cleaned}`) does not contain the expected substring (`{expected_substring}`)."
    assert path_cleaned.split("/")[-1].endswith(
        "__sources.yml"
    ), f"The properties file for `{source.source_name}.{source.name}` (`{path_cleaned}`) does not end with `__sources.yml`."

check_source_used_by_models_in_same_directory

Sources can only be referenced by models that are located in the same directory where the source is defined.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_used_by_models_in_same_directory

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_used_by_models_in_same_directory(
    models: List[DbtBouncerModel],
    request: TopRequest,
    source: Union[DbtBouncerSource, None] = None,
    **kwargs,
) -> None:
    """
    Sources can only be referenced by models that are located in the same directory where the source is defined.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_used_by_models_in_same_directory
        ```
    """

    reffed_models_not_in_same_dir = []
    for model in models:
        if (
            source.unique_id in model.depends_on.nodes
            and model.path.split("/")[:-1] != source.path.split("/")[1:-1]
        ):
            reffed_models_not_in_same_dir.append(model.unique_id.split(".")[0])

    assert (
        len(reffed_models_not_in_same_dir) == 0
    ), f"Source `{source.source_name}.{source.name}` is referenced by models defined in a different directory: {reffed_models_not_in_same_dir}"

check_source_used_by_only_one_model

Each source can be referenced by a maximum of one model.

Receives:

Name Type Description
exclude Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.

include Optional[str]

Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.

source DbtBouncerSource

The DbtBouncerSource object to check.

Example(s):

manifest_checks:
    - name: check_source_used_by_only_one_model

Source code in src/dbt_bouncer/checks/manifest/check_sources.py
@pytest.mark.iterate_over_sources
@bouncer_check
def check_source_used_by_only_one_model(
    models: List[DbtBouncerModel],
    request: TopRequest,
    source: Union[DbtBouncerSource, None] = None,
    **kwargs,
) -> None:
    """
    Each source can be referenced by a maximum of one model.

    Receives:
        exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
        include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
        source (DbtBouncerSource): The DbtBouncerSource object to check.

    Example(s):
        ```yaml
        manifest_checks:
            - name: check_source_used_by_only_one_model
        ```
    """

    num_refs = sum(source.unique_id in model.depends_on.nodes for model in models)
    assert (
        num_refs <= 1
    ), f"Source `{source.source_name}.{source.name}` is referenced by more than one model."