Skip to content

Commit ade3ff8

Browse files
authored
Feat: Run audits as non-blocking on dev previews (#3809)
1 parent 3dfa6e3 commit ade3ff8

2 files changed

Lines changed: 82 additions & 3 deletions

File tree

sqlmesh/core/snapshot/evaluator.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,6 @@ def audit(
461461
"""
462462
deployability_index = deployability_index or DeployabilityIndex.all_deployable()
463463
adapter = self._get_adapter(snapshot.model_gateway)
464-
if not deployability_index.is_deployable(snapshot) and not adapter.SUPPORTS_CLONING:
465-
# We can't audit a temporary table.
466-
return []
467464

468465
if not snapshot.version:
469466
raise ConfigError(
@@ -491,10 +488,24 @@ def audit(
491488

492489
audits_with_args = snapshot.node.audits_with_args
493490

491+
force_non_blocking = False
492+
494493
if audits_with_args:
495494
logger.info("Auditing snapshot %s", snapshot.snapshot_id)
496495

496+
if not deployability_index.is_deployable(snapshot) and not adapter.SUPPORTS_CLONING:
497+
# For dev preview tables that aren't based on clones of the production table, only a subset of the data is typically available
498+
# However, users still expect audits to run anwyay. Some audits (such as row count) are practically guaranteed to fail
499+
# when run on only a subset of data, so we switch all audits to non blocking and the user can decide if they still want to proceed
500+
force_non_blocking = True
501+
497502
for audit, audit_args in audits_with_args:
503+
if force_non_blocking:
504+
# remove any blocking indicator on the model itself
505+
audit_args.pop("blocking", None)
506+
# so that we can fall back to the audit's setting, which we override to blocking: False
507+
audit = audit.model_copy(update={"blocking": False})
508+
498509
results.append(
499510
self._audit(
500511
audit=audit,

tests/core/test_context.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,71 @@ def test_catalog_name_needs_to_be_quoted():
13071307
context.upsert_model(load_sql_based_model(parsed_model, default_catalog='"foo--bar"'))
13081308
context.plan(auto_apply=True, no_prompts=True)
13091309
assert context.fetchdf('select * from "foo--bar".db.x').to_dict() == {"c": {0: 1}}
1310+
1311+
1312+
def test_plan_runs_audits_on_dev_previews(sushi_context: Context, capsys, caplog):
1313+
sushi_context.console = create_console()
1314+
1315+
test_model = """
1316+
MODEL (
1317+
name sushi.test_audit_model,
1318+
kind INCREMENTAL_BY_TIME_RANGE (
1319+
time_column event_date,
1320+
forward_only true
1321+
),
1322+
audits (
1323+
number_of_rows(threshold := 10),
1324+
not_null(columns := id),
1325+
at_least_one_non_blocking(column := waiter_id)
1326+
)
1327+
);
1328+
1329+
SELECT * FROM sushi.orders WHERE event_date BETWEEN @start_ts AND @end_ts
1330+
"""
1331+
1332+
sushi_context.upsert_model(
1333+
load_sql_based_model(parse(test_model), default_catalog=sushi_context.default_catalog)
1334+
)
1335+
plan = sushi_context.plan(auto_apply=True)
1336+
1337+
assert plan.new_snapshots[0].name == '"memory"."sushi"."test_audit_model"'
1338+
assert plan.deployability_index.is_deployable(plan.new_snapshots[0])
1339+
1340+
# now, we mutate the model and run a plan in dev to create a dev preview
1341+
test_model = """
1342+
MODEL (
1343+
name sushi.test_audit_model,
1344+
kind INCREMENTAL_BY_TIME_RANGE (
1345+
time_column event_date,
1346+
forward_only true
1347+
),
1348+
audits (
1349+
not_null(columns := new_col),
1350+
at_least_one_non_blocking(column := new_col)
1351+
)
1352+
);
1353+
1354+
SELECT *, null as new_col FROM sushi.orders WHERE event_date BETWEEN @start_ts AND @end_ts
1355+
"""
1356+
1357+
sushi_context.upsert_model(
1358+
load_sql_based_model(parse(test_model), default_catalog=sushi_context.default_catalog)
1359+
)
1360+
1361+
capsys.readouterr() # clear output buffer
1362+
plan = sushi_context.plan(environment="dev", auto_apply=True)
1363+
1364+
assert len(plan.new_snapshots) == 1
1365+
dev_preview = plan.new_snapshots[0]
1366+
assert dev_preview.name == '"memory"."sushi"."test_audit_model"'
1367+
assert dev_preview.is_forward_only
1368+
assert not plan.deployability_index.is_deployable(
1369+
dev_preview
1370+
) # if something is not deployable to prod, then its by definiton a dev preview
1371+
1372+
# we only see audit results if they fail
1373+
stdout = capsys.readouterr().out
1374+
log = caplog.text
1375+
assert "Audit 'not_null' for model 'sushi.test_audit_model' failed" in log
1376+
assert "Audit is non-blocking so proceeding with execution" in log
1377+
assert "Target environment updated successfully" in stdout

0 commit comments

Comments
 (0)