Skip to content

Commit beaebe3

Browse files
Feat: Extend support of project wide model properties (#3832)
1 parent f24f04f commit beaebe3

4 files changed

Lines changed: 101 additions & 3 deletions

File tree

docs/reference/model_configuration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ The SQLMesh project-level `model_defaults` key supports the following options, d
124124
- audits (described [here](../concepts/audits.md#generic-audits))
125125
- optimize_query
126126
- validate_query
127+
- allow_partials
128+
- enabled
129+
- interval_unit
127130

128131

129132
### Model Naming

sqlmesh/core/config/model.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
model_kind_validator,
1111
on_destructive_change_validator,
1212
)
13+
from sqlmesh.core.node import IntervalUnit
1314
from sqlmesh.utils.date import TimeLike
1415
from sqlmesh.core.model.meta import FunctionCall
1516
from sqlmesh.utils.pydantic import field_validator
@@ -36,7 +37,12 @@ class ModelDefaultsConfig(BaseConfig):
3637
virtual_properties: A key-value mapping of arbitrary properties that are applied to the model view in the virtual layer.
3738
session_properties: A key-value mapping of properties specific to the target engine that are applied to the engine session.
3839
audits: The audits to be applied globally to all models in the project.
39-
optimize_query: Whether the SQL models should be optimized
40+
optimize_query: Whether the SQL models should be optimized.
41+
validate_query: Whether the SQL models should be validated at compile time.
42+
allow_partials: Whether the models can process partial (incomplete) data intervals.
43+
enabled: Whether the models are enabled.
44+
interval_unit: The temporal granularity of the models data intervals. By default computed from cron.
45+
4046
"""
4147

4248
kind: t.Optional[ModelKind] = None
@@ -53,6 +59,9 @@ class ModelDefaultsConfig(BaseConfig):
5359
audits: t.Optional[t.List[FunctionCall]] = None
5460
optimize_query: t.Optional[bool] = None
5561
validate_query: t.Optional[bool] = None
62+
allow_partials: t.Optional[bool] = None
63+
interval_unit: t.Optional[IntervalUnit] = None
64+
enabled: t.Optional[bool] = None
5665

5766
_model_kind_validator = model_kind_validator
5867
_on_destructive_change_validator = on_destructive_change_validator

sqlmesh/core/model/definition.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2153,7 +2153,11 @@ def _create_model(
21532153
) -> Model:
21542154
_validate_model_fields(klass, {"name", *kwargs} - {"grain", "table_properties"}, path)
21552155

2156-
for prop in ["session_properties", "physical_properties", "virtual_properties"]:
2156+
for prop in [
2157+
"session_properties",
2158+
"physical_properties",
2159+
"virtual_properties",
2160+
]:
21572161
kwargs[prop] = _resolve_properties((defaults or {}).get(prop), kwargs.get(prop))
21582162

21592163
dialect = dialect or ""

tests/core/test_model.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3397,16 +3397,23 @@ def test_project_level_properties(sushi_context):
33973397
"target_lag": "1 hour",
33983398
},
33993399
virtual_properties={"creatable_type": "SECURE"},
3400+
enabled=False,
3401+
allow_partials=True,
3402+
interval_unit="quarter_hour",
3403+
validate_query=True,
3404+
optimize_query=True,
3405+
cron="@hourly",
34003406
)
34013407

34023408
model = load_sql_based_model(
34033409
d.parse(
34043410
"""
34053411
MODEL (
34063412
name test_schema.test_model,
3413+
kind FULL,
34073414
virtual_properties (
34083415
creatable_type = None
3409-
)
3416+
),
34103417
);
34113418
SELECT a FROM tbl;
34123419
""",
@@ -3415,6 +3422,14 @@ def test_project_level_properties(sushi_context):
34153422
defaults=model_defaults.dict(),
34163423
)
34173424

3425+
# Validate use of project wide defaults
3426+
assert not model.enabled
3427+
assert model.allow_partials
3428+
assert model.interval_unit == IntervalUnit.QUARTER_HOUR
3429+
assert model.optimize_query
3430+
assert model.validate_query
3431+
assert model.cron == "@hourly"
3432+
34183433
assert model.session_properties == {
34193434
"some_bool": False,
34203435
"some_float": 0.1,
@@ -3429,6 +3444,73 @@ def test_project_level_properties(sushi_context):
34293444
# Validate disabling global property
34303445
assert not model.virtual_properties
34313446

3447+
model_2 = load_sql_based_model(
3448+
d.parse(
3449+
"""
3450+
MODEL (
3451+
name test_schema.test_model_2,
3452+
kind FULL,
3453+
allow_partials False,
3454+
interval_unit hour,
3455+
cron '@daily'
3456+
3457+
);
3458+
SELECT a, b FROM tbl;
3459+
""",
3460+
default_dialect="snowflake",
3461+
),
3462+
defaults=model_defaults.dict(),
3463+
)
3464+
3465+
# Validate overriding of project wide defaults
3466+
assert not model_2.allow_partials
3467+
assert model_2.interval_unit == IntervalUnit.HOUR
3468+
assert model_2.cron == "@daily"
3469+
3470+
3471+
def test_project_level_properties_python_model():
3472+
model_defaults = {
3473+
"physical_properties": {
3474+
"partition_expiration_days": 13,
3475+
"creatable_type": "TRANSIENT",
3476+
},
3477+
"description": "general model description",
3478+
"enabled": False,
3479+
"allow_partials": True,
3480+
"interval_unit": "quarter_hour",
3481+
"validate_query": True,
3482+
"optimize_query": True,
3483+
}
3484+
3485+
@model(
3486+
name="python_model_t_defaults",
3487+
kind="full",
3488+
columns={"some_col": "int"},
3489+
physical_properties={"partition_expiration_days": 7},
3490+
)
3491+
def python_model_prop(context, **kwargs):
3492+
context.resolve_table("foo")
3493+
3494+
m = model.get_registry()["python_model_t_defaults"].model(
3495+
module_path=Path("."),
3496+
path=Path("."),
3497+
dialect="duckdb",
3498+
defaults=model_defaults,
3499+
)
3500+
3501+
assert m.physical_properties == {
3502+
"partition_expiration_days": exp.convert(7),
3503+
"creatable_type": exp.convert("TRANSIENT"),
3504+
}
3505+
3506+
# Even if in the project wide defaults these are ignored for python models
3507+
assert not m.optimize_query
3508+
assert not m.validate_query
3509+
3510+
assert not m.enabled
3511+
assert m.allow_partials
3512+
assert m.interval_unit == IntervalUnit.QUARTER_HOUR
3513+
34323514

34333515
def test_model_session_properties(sushi_context):
34343516
assert sushi_context.models['"memory"."sushi"."items"'].session_properties == {

0 commit comments

Comments
 (0)