Skip to content

Added ability to exclude files based on subfolder name #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ jaffle_shop.stg_orders 0/4 0.0%
jaffle_shop.stg_payments 0/4 0.0%
=====================================================================
Total 15/38 39.5%
```

```console
$ dbt-coverage compute test --cov-report coverage-test.json # Compute test coverage, print it and write it to coverage-test.json file

Coverage report
Expand All @@ -97,15 +99,17 @@ jaffle_shop.stg_payments 2/4 50.0%
Total 14/38 36.8%
```

#### Filtering model paths with `--model-path-filter`
#### Filtering model paths with `--model-path-filter` or `--model-path-exclusion-filter`

You can also choose a subset of tables to compare using one or multiple `--model-path-filter` and/or `--model-path-exclusion-filter` options. Here are some examples.

You can also choose a subset of tables to compare using one or multiple `--model-path-filter` options.
Use the `--model-path-filter`.

```console
$ cd jaffle_shop
$ dbt run # Materialize models
$ dbt docs generate # Generate catalog.json and manifest.json
$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-filter models/staging/ # Compute doc coverage for a subset of tables, print it and write it to coverage-doc.json file
$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-filter models/staging/

Coverage report
======================================================
Expand All @@ -114,8 +118,28 @@ jaffle_shop.stg_orders 0/4 0.0%
jaffle_shop.stg_payments 0/4 0.0%
======================================================
Total 0/11 0.0%
```

Use the `--model-path-exclusion-filter`.

```console
$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-exclusion-filter models/staging/

Coverage report (doc)
=====================================================================
dbt_sweco.customers 6/7 85.7%
dbt_sweco.orders 9/9 100.0%
dbt_sweco.raw_customers 0/3 0.0%
dbt_sweco.raw_orders 0/4 0.0%
dbt_sweco.raw_payments 0/4 0.0%
=====================================================================
Total 15/27 55.6%
```

Use multiple paths. The same can be done with `--model-path-exclusion-filter`.

$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-filter models/orders.sql --model-path-filter models/staging/ # Compute doc coverage for a subset of tables, print it and write it to coverage-doc.json file
```console
$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-filter models/orders.sql --model-path-filter models/staging/

Coverage report
======================================================
Expand All @@ -127,6 +151,19 @@ jaffle_shop.stg_payments 0/4 0.0%
Total 0/20 0.0%
```

Use both `--model-path-filter` and `--model-path-exclusion-filter`.

```console
$ dbt-coverage compute doc --cov-report coverage-doc.json --model-path-filter models/staging --model-path-exclusion-filter models/staging/stg_customers

Coverage report (doc)
=====================================================================
dbt_sweco.stg_orders 0/4 0.0%
dbt_sweco.stg_payments 0/4 0.0%
=====================================================================
Total 0/8 0.0%
```

#### Markdown output with `--cov-format`

You can also choose to print the output in the Markdown table format by specifying the `--cov-format` option.
Expand Down
56 changes: 41 additions & 15 deletions dbt_coverage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,46 @@ class Catalog:

tables: Dict[str, Table]

def filter_tables(self, model_path_filter: List[str]) -> Catalog:
def filter_tables(
self, model_path_filter: List[str] | None, model_path_exclusion_filter: List[str] | None
) -> Catalog:
"""
Filters ``Catalog``'s ``tables`` attribute to ``Tables`` that have the
``model_path_filter`` value at the start of their ``original_file_path``.
Filters ``Catalog``'s ``tables`` based on their paths.

Two filters are applied (if their respective arguments are provided):

- ``Tables`` whose ``original_file_path`` start with the ``model_path_filter`` are kept.
- ``Tables`` whose ``original_file_path`` start with the ``model_path_exclusion_filter``
are excluded.

Args:
model_path_filter: the model_path string(s) to filter tables on, (matches using
the ``startswith`` operator)
model_path_filter: The model_path string(s) to filter tables on. Tables matched using
the ``startswith`` operator are kept. Use None if the filter should not be applied.
model_path_exclusion_filter: The model_path string(s) to filter tables on. Tables
matched using the ``startswith`` operator are excluded. Use None if the filter
should not be applied.

Returns:
New ``Catalog`` instance containing only ``Table``s that passed the filter
New ``Catalog`` instance containing only ``Table``s that passed the filter.
"""

model_path_filter = tuple(model_path_filter)
tables = {
t_id: t
for t_id, t in self.tables.items()
if t.original_file_path.startswith(model_path_filter)
}
tables = self.tables.copy()

if model_path_filter is not None:
model_path_filter = tuple(model_path_filter)
tables = {
t_id: t
for t_id, t in tables.items()
if t.original_file_path.startswith(model_path_filter)
}

if model_path_exclusion_filter is not None:
model_path_exclusion_filter = tuple(model_path_exclusion_filter)
tables = {
t_id: t
for t_id, t in tables.items()
if not t.original_file_path.startswith(model_path_exclusion_filter)
}

logging.info(
"Successfully filtered tables. Total tables post-filtering: %d tables", len(tables)
Expand Down Expand Up @@ -812,6 +833,7 @@ def do_compute(
cov_fail_under: float = None,
cov_fail_compare: Path = None,
model_path_filter: Optional[List[str]] = None,
model_path_exclusion_filter: Optional[List[str]] = None,
cov_format: CoverageFormat = CoverageFormat.STRING_TABLE,
):
"""
Expand All @@ -821,8 +843,8 @@ def do_compute(
"""

catalog = load_files(project_dir, run_artifacts_dir)
if model_path_filter:
catalog = catalog.filter_tables(model_path_filter)
if model_path_filter or model_path_exclusion_filter:
catalog = catalog.filter_tables(model_path_filter, model_path_exclusion_filter)
if not catalog.tables:
raise ValueError(
"After filtering, the Catalog contains no tables. Ensure your model_path_filter "
Expand Down Expand Up @@ -879,7 +901,10 @@ def compute(
"Normally used to prevent coverage drop between subsequent tests.",
),
model_path_filter: Optional[List[str]] = typer.Option(
None, help="The model_path string(s) to filter tables on."
None, help="The model_path string(s) to filter tables on keeping tables that match."
),
model_path_exclusion_filter: Optional[List[str]] = typer.Option(
None, help="The model_path string(s) to filter tables on excluding tables that match."
),
cov_format: CoverageFormat = typer.Option(
CoverageFormat.STRING_TABLE,
Expand All @@ -896,6 +921,7 @@ def compute(
cov_fail_under,
cov_fail_compare,
model_path_filter,
model_path_exclusion_filter,
cov_format,
)

Expand Down
53 changes: 47 additions & 6 deletions tests/integration/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,55 @@ def setup_dbt(setup_postgres):


def test_compute_doc(setup_dbt):
res = do_compute(Path(DBT_PROJECT_PATH), cov_type=CoverageType.DOC)
report = do_compute(Path(DBT_PROJECT_PATH), cov_type=CoverageType.DOC)

assert len(res.covered) == 15
assert len(res.total) == 38
assert len(report.covered) == 15
assert len(report.total) == 38


def test_compute_test(setup_dbt):
res = do_compute(Path(DBT_PROJECT_PATH), cov_type=CoverageType.TEST)
report = do_compute(Path(DBT_PROJECT_PATH), cov_type=CoverageType.TEST)

assert len(res.covered) == 14
assert len(res.total) == 38
assert len(report.covered) == 14
assert len(report.total) == 38


def test_compute_path_filter(setup_dbt):
report = do_compute(
Path(DBT_PROJECT_PATH),
cov_type=CoverageType.DOC,
model_path_filter=["models/staging"],
)

assert len(report.subentities) == 3
assert all("stg" in table for table in report.subentities)
assert len(report.covered) == 0
assert len(report.total) == 11


def test_compute_path_exclusion_filter(setup_dbt):
report = do_compute(
Path(DBT_PROJECT_PATH),
cov_type=CoverageType.DOC,
model_path_exclusion_filter=["models/staging"],
)

assert len(report.subentities) == 5
assert not any("stg" in table for table in report.subentities)
assert len(report.covered) == 15
assert len(report.total) == 27


def test_compute_both_path_filters(setup_dbt):
report = do_compute(
Path(DBT_PROJECT_PATH),
cov_type=CoverageType.DOC,
model_path_filter=["models/staging"],
model_path_exclusion_filter=["models/staging/stg_customers"],
)

assert len(report.subentities) == 2
assert all("stg" in table for table in report.subentities)
assert "jaffle_shop.stg_customers" not in report.subentities
assert len(report.covered) == 0
assert len(report.total) == 8