11# Create rules
22
3- In order to lint and score models, ` dbt-score ` uses a set of rules that are
4- applied to each model . A rule can pass or fail when it is run. Based on the
5- severity of the rule, models are scored with the weighted average of the rules
6- results. Note that ` dbt-score ` comes bundled with a
3+ In order to lint and score models or sources , ` dbt-score ` uses a set of rules
4+ that are applied to each item . A rule can pass or fail when it is run. Based on
5+ the severity of the rule, items are scored with the weighted average of the
6+ rules results. Note that ` dbt-score ` comes bundled with a
77[ set of default rules] ( rules/generic.md ) .
88
99On top of the generic rules, it's possible to add your own rules. Two ways exist
@@ -21,7 +21,7 @@ The `@rule` decorator can be used to easily create a new rule:
2121from dbt_score import Model, rule, RuleViolation
2222
2323@rule
24- def has_description (model : Model) -> RuleViolation | None :
24+ def model_has_description (model : Model) -> RuleViolation | None :
2525 """ A model should have a description."""
2626 if not model.description:
2727 return RuleViolation(message = " Model lacks a description." )
@@ -31,6 +31,21 @@ The name of the function is the name of the rule and the docstring of the
3131function is its description. Therefore, it is important to use a
3232self-explanatory name for the function and document it well.
3333
34+ The type annotation for the rule's argument dictates whether the rule should be
35+ applied to dbt models or sources.
36+
37+ Here is the same example rule, applied to sources:
38+
39+ ``` python
40+ from dbt_score import rule, RuleViolation, Source
41+
42+ @rule
43+ def source_has_description (source : Source) -> RuleViolation | None :
44+ """ A source should have a description."""
45+ if not source.description:
46+ return RuleViolation(message = " Source lacks a description." )
47+ ```
48+
3449The severity of a rule can be set using the ` severity ` argument:
3550
3651``` python
@@ -45,15 +60,23 @@ For more advanced use cases, a rule can be created by inheriting from the `Rule`
4560class:
4661
4762``` python
48- from dbt_score import Model, Rule, RuleViolation
63+ from dbt_score import Model, Rule, RuleViolation, Source
4964
50- class HasDescription (Rule ):
65+ class ModelHasDescription (Rule ):
5166 description = " A model should have a description."
5267
5368 def evaluate (self , model : Model) -> RuleViolation | None :
5469 """ Evaluate the rule."""
5570 if not model.description:
5671 return RuleViolation(message = " Model lacks a description." )
72+
73+ class SourceHasDescription (Rule ):
74+ description = " A source should have a description."
75+
76+ def evaluate (self , source : Source) -> RuleViolation | None :
77+ """ Evaluate the rule."""
78+ if not source.description:
79+ return RuleViolation(message = " Source lacks a description." )
5780```
5881
5982### Rules location
@@ -91,30 +114,48 @@ def sql_has_reasonable_number_of_lines(model: Model, max_lines: int = 200) -> Ru
91114 )
92115```
93116
94- ### Filtering models
117+ ### Filtering rules
95118
96- Custom and standard rules can be configured to have model filters. Filters allow
97- models to be ignored by one or multiple rules.
119+ Custom and standard rules can be configured to have filters. Filters allow
120+ models or sources to be ignored by one or multiple rules if the item doesn't
121+ satisfy the filter criteria.
98122
99123Filters are created using the same discovery mechanism and interface as custom
100124rules, except they do not accept parameters. Similar to Python's built-in
101- ` filter ` function, when the filter evaluation returns ` True ` the model should be
125+ ` filter ` function, when the filter evaluation returns ` True ` the item should be
102126evaluated, otherwise it should be ignored.
103127
104128``` python
105- from dbt_score import ModelFilter, model_filter
129+ from dbt_score import Model, RuleFilter, rule_filter
106130
107- @model_filter
131+ @rule_filter
108132def only_schema_x (model : Model) -> bool :
109133 """ Only applies a rule to schema X."""
110134 return model.schema.lower() == ' x'
111135
112- class SkipSchemaY (ModelFilter ):
136+ class SkipSchemaY (RuleFilter ):
113137 description = " Applies a rule to every schema but Y."
114138 def evaluate (self , model : Model) -> bool :
115139 return model.schema.lower() != ' y'
116140```
117141
142+ Filters also rely on type-annotations to dictate whether they apply to models or
143+ sources:
144+
145+ ``` python
146+ from dbt_score import RuleFilter, rule_filter, Source
147+
148+ @rule_filter
149+ def only_from_source_a (source : Source) -> bool :
150+ """ Only applies a rule to source tables from source X."""
151+ return source.source_name.lower() == ' a'
152+
153+ class SkipSourceDatabaseB (RuleFilter ):
154+ description = " Applies a rule to every source except Database B."
155+ def evaluate (self , source : Source) -> bool :
156+ return source.database.lower() != ' b'
157+ ```
158+
118159Similar to setting a rule severity, standard rules can have filters set in the
119160[ configuration file] ( configuration.md/#tooldbt-scorerulesrule_namespacerule_name ) ,
120161while custom rules accept the configuration file or a decorator parameter.
@@ -123,7 +164,7 @@ while custom rules accept the configuration file or a decorator parameter.
123164from dbt_score import Model, rule, RuleViolation
124165from my_project import only_schema_x
125166
126- @rule (model_filters = {only_schema_x()})
167+ @rule (rule_filters = {only_schema_x()})
127168def models_in_x_follow_naming_standard (model : Model) -> RuleViolation | None :
128169 """ Models in schema X must follow the naming standard."""
129170 if some_regex_fails(model.name):
0 commit comments