Skip to content

Commit 3cd5dc2

Browse files
authored
add nomissingaudits rule (#3967)
1 parent 173a4c0 commit 3cd5dc2

5 files changed

Lines changed: 53 additions & 11 deletions

File tree

docs/guides/linter.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Linter guide
22

3+
![Linter](linter_example.png)
4+
35
Linting is a powerful tool for improving code quality and consistency. It enables you to automatically validate model definition, ensuring they adhere to your team's best practices.
46

57
When a SQLMesh command is executed and the project is loaded, each model's code is checked for compliance with a set of rules you choose.
@@ -68,10 +70,10 @@ Here are all of SQLMesh's built-in linting rules:
6870

6971
| Name | Check type | Explanation |
7072
| -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
71-
| ambiguousorinvalidcolumn | Correctness | SQLMesh found duplicate columns or was unable to determine whether a column is duplicated or not |
72-
| invalidselectstarexpansion | Correctness | The query's top-level selection may be `SELECT *`, but only if SQLMesh can expand the `SELECT *` into individual columns |
73-
| noselectstar | Stylistic | The query's top-level selection may not be `SELECT *`, even if SQLMesh can expand the `SELECT *` into individual columns |
74-
73+
| `ambiguousorinvalidcolumn` | Correctness | SQLMesh found duplicate columns or was unable to determine whether a column is duplicated or not |
74+
| `invalidselectstarexpansion` | Correctness | The query's top-level selection may be `SELECT *`, but only if SQLMesh can expand the `SELECT *` into individual columns |
75+
| `noselectstar` | Stylistic | The query's top-level selection may not be `SELECT *`, even if SQLMesh can expand the `SELECT *` into individual columns |
76+
| `nomissingaudits` | Governance | SQLMesh did not find any `audits` in the model's configuration to test data quality. |
7577

7678
### User-defined rules
7779

@@ -211,7 +213,7 @@ MODEL(
211213

212214
Linting rule violations raise an error by default, preventing the project from running until the violation is addressed.
213215

214-
You may specify that a rule's violation should not error and only log a warning by specifying it in the `warning_rules` key instead of the `rules` key.
216+
You may specify that a rule's violation should not error and only log a warning by specifying it in the `warn_rules` key instead of the `rules` key.
215217

216218
=== "YAML"
217219

@@ -221,7 +223,7 @@ You may specify that a rule's violation should not error and only log a warning
221223
# error if `ambiguousorinvalidcolumn` rule violated
222224
rules: ["ambiguousorinvalidcolumn"]
223225
# but only warn if "invalidselectstarexpansion" is violated
224-
warning_rules: ["invalidselectstarexpansion"]
226+
warn_rules: ["invalidselectstarexpansion"]
225227
```
226228

227229
=== "Python"
@@ -235,9 +237,9 @@ You may specify that a rule's violation should not error and only log a warning
235237
# error if `ambiguousorinvalidcolumn` rule violated
236238
rules=["ambiguousorinvalidcolumn"],
237239
# but only warn if "invalidselectstarexpansion" is violated
238-
warning_rules=["invalidselectstarexpansion"],
240+
warn_rules=["invalidselectstarexpansion"],
239241
)
240242
)
241243
```
242244

243-
SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warning_rules`, and `ignored_rules` keys since they should be mutually exclusive.
245+
SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warn_rules`, and `ignored_rules` keys since they should be mutually exclusive.

docs/guides/linter_example.png

113 KB
Loading

sqlmesh/core/linter/rules/builtin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,11 @@ def check_model(self, model: Model) -> t.Optional[RuleViolation]:
4848
return self.violation(violation_msg)
4949

5050

51+
class NoMissingAudits(Rule):
52+
"""Model `audits` must be configured to test data quality."""
53+
54+
def check_model(self, model: Model) -> t.Optional[RuleViolation]:
55+
return self.violation() if not model.audits else None
56+
57+
5158
BUILTIN_RULES = RuleSet(subclasses(__name__, Rule, (Rule,)))

tests/core/test_context.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,7 +1676,9 @@ def assert_cached_violations_exist(cache: OptimizedQueryCache, model: Model):
16761676
# Case: Ensure NoSelectStar only raises for top-level SELECTs, new model shouldn't raise
16771677
# and thus should also be cached
16781678
model2 = load_sql_based_model(
1679-
d.parse("MODEL (name test2); SELECT col FROM (SELECT * FROM tbl)")
1679+
d.parse(
1680+
"MODEL (name test2, audits (at_least_one(column := col))); SELECT col FROM (SELECT * FROM tbl)"
1681+
)
16801682
)
16811683
ctx.upsert_model(model2)
16821684

@@ -1739,7 +1741,7 @@ def assert_cached_violations_exist(cache: OptimizedQueryCache, model: Model):
17391741
create_temp_file(
17401742
tmp_path,
17411743
pathlib.Path(pathlib.Path("models"), "test2.sql"),
1742-
"MODEL(name test2, ignored_rules ['noselectstar']); SELECT * FROM (SELECT 1 AS col);",
1744+
"MODEL(name test2, audits (at_least_one(column := col)), ignored_rules ['noselectstar']); SELECT * FROM (SELECT 1 AS col);",
17431745
)
17441746

17451747
ctx.load()
@@ -1777,7 +1779,12 @@ def model4_entrypoint(context, **kwargs):
17771779
with pytest.raises(LinterError, match=config_err):
17781780
sushi_context.upsert_model(python_model)
17791781

1780-
@model(name="memory.sushi.model5", columns={"col": "int"}, owner="test")
1782+
@model(
1783+
name="memory.sushi.model5",
1784+
columns={"col": "int"},
1785+
owner="test",
1786+
audits=[("at_least_one", {"column": "col"})],
1787+
)
17811788
def model5_entrypoint(context, **kwargs):
17821789
yield pd.DataFrame({"col": []})
17831790

tests/core/test_model.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,32 @@ def test_model_qualification(tmp_path: Path):
307307
)
308308

309309

310+
@use_terminal_console
311+
def test_model_missing_audits(tmp_path: Path):
312+
with patch.object(get_console(), "log_warning") as mock_logger:
313+
expressions = d.parse(
314+
"""
315+
MODEL (
316+
name db.table,
317+
kind FULL,
318+
);
319+
320+
SELECT a
321+
"""
322+
)
323+
324+
ctx = Context(
325+
config=Config(linter=LinterConfig(enabled=True, warn_rules=["nomissingaudits"])),
326+
paths=tmp_path,
327+
)
328+
ctx.upsert_model(load_sql_based_model(expressions))
329+
330+
assert (
331+
"""Model `audits` must be configured to test data quality."""
332+
in mock_logger.call_args[0][0]
333+
)
334+
335+
310336
@pytest.mark.parametrize(
311337
"partition_by_input, partition_by_output, output_dialect, expected_exception",
312338
[

0 commit comments

Comments
 (0)