Skip to content

Commit aaa7f7c

Browse files
authored
Teghan/0.3.0 (#9)
* fix ephemeral issue, fix models errors, add gitignore * revert fetch_configured_models * rm validate required tests * add regex test filters * update README * update README, check data test naming * grammar on README
1 parent c70f5d0 commit aaa7f7c

8 files changed

+97
-135
lines changed

README.md

+47-29
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ This dbt package contains macros to assert test and documentation coverage from
77
## Table of Contents
88
- [Install](#install)
99
- [Configurations](#configurations)
10-
- [**Required Tests**](#required-tests)
11-
- [**Required Docs**](#required-docs)
10+
- [Required Tests](#required-tests)
11+
- [Required Docs](#required-docs)
1212
- [Usage](#usage)
1313
- [required_tests (source)](#required_tests-source)
1414
- [required_docs (source)](#required_docs-source)
@@ -22,7 +22,7 @@ Include in `packages.yml`:
2222
```yaml
2323
packages:
2424
- package: tnightengale/dbt_meta_testing
25-
version: 0.2.1
25+
version: 0.3.0
2626
```
2727
For latest release, see
2828
https://github.com/tnightengale/dbt-meta-testing/releases.
@@ -33,44 +33,59 @@ This package features two meta configs that can be applied to a dbt project:
3333
[here](https://docs.getdbt.com/reference/model-configs) to learn more about
3434
model configurations in dbt.
3535

36-
### **Required Tests**
36+
### Required Tests
3737
To require test coverage, define the `+required_tests` configuration on a model
3838
path in `dbt_project.yml`:
3939
```yaml
4040
# dbt_project.yml
4141
...
4242
models:
43-
project:
44-
staging:
45-
+required_tests: {"unique": 1, "not_null": 1}
46-
marts:
47-
+required_tests: {"unique": 1}
43+
project:
44+
+required_docs: true
45+
marts:
46+
+required_tests: {"unique.*|not_null": 1}
47+
model_2:
48+
+required_tests:
49+
"mocker.*|unique": 1
50+
"mock_schema_test": 1
51+
".*data_test": 1
4852
```
4953

50-
The `+required_tests` config must be either a `dict` or `None`. All the regular
54+
The `+required_tests` config must be `None` or a `dict` with `str` keys and `int`
55+
values. YAML dictionaries are accepted.
56+
57+
All the regular
5158
dbt configuration hierarchy rules apply. For example, individual model configs
5259
will override configs from the `dbt_project.yml`:
5360
```sql
54-
-- /models/marts/core/your_model.sql
55-
{{
56-
config(required_tests=None)
57-
}}
61+
# /models/marts/core/your_model.sql
62+
63+
-- This overrides the config in dbt_project.yml, and this model will not require tests
64+
{{ config(required_tests=None) }}
5865
5966
SELECT
6067
...
6168
```
62-
The provided dictionary can contain any column schema test as a key, followed by
63-
the minimum number of occurances which must be included on the model. In the
64-
example above, every model in the `models/marts/` path must include at least one
65-
`unique` test.
66-
67-
Custom column-level schema tests are supported. However, in order to appear in
68-
the `graph` context variable (which this package parses), they must be applied
69-
to at least one model in the project prior to compilation.
70-
71-
Model-level schema tests are currently _not supported_. For example the
72-
following model-level `dbt_utils.equal_rowcount` test _cannot_ currently be
73-
asserted via the configuration:
69+
> **_New in Version 0.3.0_**
70+
71+
The keys of the config are evaluated against both data and schema tests
72+
(including any custom tests) using the
73+
[re.match](https://docs.python.org/3/library/re.html#re.match) function.
74+
75+
Therefore, any test restriction which can be expressed in regex can be
76+
evaluated.
77+
78+
For example, in the `dbt_project.yml` above, the path configuration on the `marts` model path
79+
requires each model in that path to have at least one test that either _starts
80+
with_ `unique` **or** is an _exact match_ for the `not_null` test.
81+
82+
Schema tests are matched against their common names, (eg. `not_null`,
83+
`accepted_values`).
84+
85+
Data tests are matched against their macro name.
86+
87+
Custom schema tests are matched against their name, without the `test_` prefix, eg. `mock_schema_test`:
88+
7489
```yaml
7590
# models/schema.yml
7691
...
@@ -88,7 +103,8 @@ asserted via the configuration:
88103
- mock_schema_test
89104
```
90105

91-
Models that do not meet their configured test minimums will be listed in the
106+
Models that do not meet their configured test minimums, because they either lack
107+
the tests or are not documented, will be listed in the
92108
error when validated via a `run-operation`:
93109
```
94110
usr@home dbt-meta-testing $ dbt run-operation required_tests
@@ -104,7 +120,7 @@ Encountered an error while running operation: Compilation Error in macro require
104120
usr@home dbt-meta-testing $
105121
```
106122

107-
### **Required Docs**
123+
### Required Docs
108124
To require documentation coverage, define the `+required_docs` configuration on
109125
a model path in `dbt_project.yml`:
110126
```yaml
@@ -114,7 +130,9 @@ models:
114130
project:
115131
+required_docs: true
116132
```
117-
The `+required_docs` config must be a `bool`. It also **does not check ephemeral
133+
The `+required_docs` config must be a `bool`.
134+
135+
It also **does not check ephemeral
118136
models**. This is because it cannot leverage `adapter.get_columns_in_relation()`
119137
macro on ephemeral models, which it uses to fetch columns from the data
120138
warehouse and detect columns without documentation.

integration_tests/dbt_project.yml

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ models:
2222
+required_tests: true
2323
marts:
2424
+required_tests: {"unique": 1}
25+
model_2:
26+
+required_tests:
27+
"mocker.*|unique": 1
28+
"mock_schema_test": 1
29+
".*data_test": 1
2530

2631
vars:
2732
running_intergration_tests: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
3+
select
4+
*
5+
from {{ ref("model_2") }}
6+
where new != 'a'

macros/utils/errors/error_invalid_config_missing_test.sql

-13
This file was deleted.

macros/utils/required_tests/evaluate_required_tests.sql

+10-39
Original file line numberDiff line numberDiff line change
@@ -7,61 +7,32 @@
77
{# /*
88
Evaluate if each model meets +required_tests minimum.
99
*/ #}
10+
1011
{% set tests_per_model = dbt_meta_testing.tests_per_model() %}
1112
{% set test_errors = [] %}
1213

13-
14-
15-
{{ dbt_meta_testing.logger("models_to_evaluate: " ~ models_to_evaluate | map(attribute="name") | list) }}
1614
{% for model in models_to_evaluate %}
15+
{% for test_key in model.config.required_tests.keys() %}
1716

18-
-- If required_tests is dictionary
19-
{% if model.config.required_tests is mapping %}
20-
{{ dbt_meta_testing.logger(model.name ~ " if reached") }}
21-
22-
{% for test_key in model.config.required_tests.keys() %}
23-
24-
-- If the model has less tests than required by the config
25-
{% set full_model = model.unique_id %}
26-
27-
{{ dbt_meta_testing.logger('tests per model: ' ~ tests_per_model) }}
28-
29-
-- models that are not declared in properties files will not have keys in tests_per_model
30-
{% set provided_test_count = tests_per_model.get(full_model, {}).get(test_key, []) | length %}
31-
{{ dbt_meta_testing.logger('provided_test_count: ' ~ provided_test_count) }}
32-
33-
{% set required_test_count = model.config.required_tests[test_key] %}
34-
35-
{{ dbt_meta_testing.logger(
36-
"test_key_loop | test_key: " ~ test_key ~
37-
" model: " ~ model.name ~
38-
" provided_test_count: " ~ provided_test_count ~
39-
" required_test_count: " ~ required_test_count) }}
40-
41-
{% if provided_test_count < required_test_count %}
17+
{% set provided_test_list = tests_per_model[model.unique_id] %}
4218

43-
{% do test_errors.append((model.name, test_key, provided_test_count, required_test_count)) %}
44-
45-
{% endif %}
19+
{% set required_test_count = model.config.required_tests[test_key] %}
20+
{% set matching_test_count = dbt_meta_testing.get_regex_match_count(provided_test_list, test_key) %}
4621

47-
{% endfor %}
48-
49-
{% endif %}
50-
22+
{% if matching_test_count < required_test_count %}
23+
{% do test_errors.append((model.name, test_key, matching_test_count, required_test_count)) %}
24+
{% endif %}
25+
26+
{% endfor %}
5127
{% endfor %}
5228

5329

5430
{% if test_errors | length > 0 %}
55-
5631
{% set result = dbt_meta_testing.error_required_tests(test_errors) %}
57-
5832
{% else %}
59-
6033
{% set result = none %}
61-
6234
{% endif %}
6335

64-
6536
{{ return(result) }}
6637

6738
{% endmacro %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% macro get_regex_match_count(list_of_strings, regex_to_check) %}
2+
{{ return(adapter.dispatch("get_regex_match_count", packages=dbt_meta_testing._get_meta_test_namespaces())(list_of_strings, regex_to_check))}}
3+
{% endmacro %}
4+
5+
{% macro default__get_regex_match_count(list_of_strings, regex_to_check) %}
6+
7+
{# Return count of strings in list_of_strings that match regex_to_check #}
8+
{% set matches = [] %}
9+
{% for string in list_of_strings %}
10+
{% set match = modules.re.match(regex_to_check, string) %}
11+
{% if match %}{% do matches.append(match) %}{% endif %}
12+
{% endfor %}
13+
14+
{% do return(matches | length) %}
15+
16+
{% endmacro %}

macros/utils/required_tests/tests_per_model.sql

+13-42
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,23 @@
99
Construct a dict of all models and their schema tests in the current project.
1010
*/ #}
1111

12-
{% set tests_per_model = {} %}
13-
{% set all_tests = dbt_meta_testing.fetch_configured_models("enabled", resource_type="test") %}
14-
15-
-- currently only parsing schema tests
16-
{% set schema_tests = all_tests | selectattr("test_metadata", "defined") | list %}
17-
18-
{% for test_node in schema_tests %}
19-
20-
{{ dbt_meta_testing.logger('loop ' ~ loop.index ~ ' test_node ' ~ test_node["test_metadata"]["name"]) }}
21-
22-
{% for dependent_model in test_node.depends_on.nodes %}
23-
{% if dependent_model.startswith('model.') %}
24-
25-
-- if the model has been encountered before
26-
{% if dependent_model in tests_per_model.keys() %}
27-
28-
-- If the test on this model has been encountered before
29-
{% if test_node["test_metadata"]["name"] in tests_per_model[dependent_model].keys() %}
30-
{% do tests_per_model[dependent_model][test_node["test_metadata"]["name"]].append(test_node.unique_id) %}
31-
{% else %} -- Add this test to the list of encountered tests
32-
{% do tests_per_model[dependent_model].setdefault(test_node["test_metadata"]["name"], [test_node.unique_id]) %}
33-
{% endif %}
34-
35-
{% else %}
36-
37-
{% do tests_per_model.setdefault(dependent_model, {test_node["test_metadata"]["name"]: [test_node.unique_id]}) %}
38-
39-
{% endif %}
40-
12+
{% set enabled_model_names = dbt_meta_testing.fetch_configured_models("enabled", resource_type="model") | map(attribute="unique_id") | list %}
13+
{% set enabled_test_nodes = dbt_meta_testing.fetch_configured_models("enabled", resource_type="test") %}
14+
15+
-- Create `result` dict with all enabled models unique_id's as keys and empty lists as values
16+
{% set result = {} %}
17+
{% for id in enabled_model_names %}{% do result.update({id: []}) %}{% endfor %}
18+
19+
{% for test_node in enabled_test_nodes %}
20+
{% for dependent_node in test_node.depends_on.nodes %}
21+
{% if dependent_node.startswith('model.') %}
22+
-- Use common names for schema tests, (e.g. "unique") under the "test_metadata" key
23+
{% set test_identifier = test_node.get("test_metadata",{}).get("name") or test_node["name"] %}
24+
{% do result[dependent_node].append(test_identifier) %}
4125
{% endif %}
42-
4326
{% endfor %}
44-
4527
{% endfor %}
4628

47-
-- Create dict of empty dict with model unique_id as key to ensure all models are included
48-
{% set model_unique_ids = dbt_meta_testing.fetch_configured_models("enabled", resource_type="model") |
49-
map(attribute="unique_id") | list %}
50-
{% set result = {} %}
51-
{% for id in model_unique_ids %}
52-
{% do result.update({id: {}}) %}
53-
{% endfor %}
54-
55-
-- overwrite empty dicts if they have tests
56-
{% do result.update(tests_per_model) %}
57-
5829
{% do return(result) %}
5930

6031
{% endmacro %}

macros/utils/required_tests/validate_required_tests.sql

-12
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,6 @@
6565

6666
{% endfor %}
6767

68-
69-
-- Validate all configured tests are defined
70-
{% for required_test in unique_required_tests %}
71-
72-
{% if required_test not in unique_defined_tests %}
73-
74-
{{ return(dbt_meta_testing.error_invalid_config_missing_test(required_test)) }}
75-
76-
{% endif %}
77-
78-
{% endfor %}
79-
8068
{{ return(none) }}
8169

8270
{% endmacro %}

0 commit comments

Comments
 (0)