From d607aae9a2b8486861f1a97e5943ef6358e3c285 Mon Sep 17 00:00:00 2001 From: Trey Spiller Date: Tue, 12 Aug 2025 14:39:21 -0500 Subject: [PATCH 1/2] Support auto_restatement_cron in python models --- sqlmesh/core/model/definition.py | 3 +++ tests/core/test_model.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sqlmesh/core/model/definition.py b/sqlmesh/core/model/definition.py index dbbb8ff3a8..d5a9a658ac 100644 --- a/sqlmesh/core/model/definition.py +++ b/sqlmesh/core/model/definition.py @@ -2866,6 +2866,9 @@ def render_field_value(value: t.Any) -> t.Any: for key, value in field_value.items(): if key in RUNTIME_RENDERED_MODEL_FIELDS: rendered_dict[key] = parse_strings_with_macro_refs(value, dialect) + elif key == "auto_restatement_cron": + # Don't parse auto_restatement_cron="@..." kwarg (e.g. @daily) into MacroVar + rendered_dict[key] = value elif (rendered := render_field_value(value)) is not None: rendered_dict[key] = rendered diff --git a/tests/core/test_model.py b/tests/core/test_model.py index ebe4d11a20..31f68b4aaf 100644 --- a/tests/core/test_model.py +++ b/tests/core/test_model.py @@ -2898,7 +2898,15 @@ def my_model_2(context): # no warning with valid kind dict with patch.object(get_console(), "log_warning") as mock_logger: - @model("kind_valid_dict", kind=dict(name=ModelKindName.FULL), columns={'"COL"': "int"}) + @model( + "kind_valid_dict", + kind=dict( + name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, + time_column="ds", + auto_restatement_cron="@hourly", + ), + columns={'"ds"': "date", '"COL"': "int"}, + ) def my_model(context): pass @@ -2907,7 +2915,7 @@ def my_model(context): path=Path("."), ) - assert isinstance(python_model.kind, FullKind) + assert isinstance(python_model.kind, IncrementalByTimeRangeKind) assert not mock_logger.call_args From ab5922ebb73dc94d0c4a1df0697f546620ffd144 Mon Sep 17 00:00:00 2001 From: Trey Spiller Date: Thu, 21 Aug 2025 12:53:47 -0500 Subject: [PATCH 2/2] Mirror existing cron within kind --- sqlmesh/core/model/definition.py | 8 ++++++-- tests/core/test_model.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/sqlmesh/core/model/definition.py b/sqlmesh/core/model/definition.py index d5a9a658ac..c9eaa43b3e 100644 --- a/sqlmesh/core/model/definition.py +++ b/sqlmesh/core/model/definition.py @@ -2866,8 +2866,12 @@ def render_field_value(value: t.Any) -> t.Any: for key, value in field_value.items(): if key in RUNTIME_RENDERED_MODEL_FIELDS: rendered_dict[key] = parse_strings_with_macro_refs(value, dialect) - elif key == "auto_restatement_cron": - # Don't parse auto_restatement_cron="@..." kwarg (e.g. @daily) into MacroVar + elif ( + # don't parse kind auto_restatement_cron="@..." kwargs (e.g. @daily) into MacroVar + key == "auto_restatement_cron" + and isinstance(value, str) + and value.lower() in CRON_SHORTCUTS + ): rendered_dict[key] = value elif (rendered := render_field_value(value)) is not None: rendered_dict[key] = rendered diff --git a/tests/core/test_model.py b/tests/core/test_model.py index 31f68b4aaf..00ff48b0d2 100644 --- a/tests/core/test_model.py +++ b/tests/core/test_model.py @@ -2920,6 +2920,28 @@ def my_model(context): assert not mock_logger.call_args +def test_python_model_decorator_auto_restatement_cron() -> None: + @model( + "auto_restatement_model", + cron="@daily", + kind=dict( + name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, + time_column="ds", + auto_restatement_cron="@hourly", + ), + columns={'"ds"': "date", '"COL"': "int"}, + ) + def my_model(context): + pass + + python_model = model.get_registry()["auto_restatement_model"].model( + module_path=Path("."), + path=Path("."), + ) + + assert python_model.auto_restatement_cron == "@hourly" + + def test_python_model_decorator_col_descriptions() -> None: # `columns` and `column_descriptions` column names are different cases, but name normalization makes both lower @model("col_descriptions", columns={"col": "int"}, column_descriptions={"COL": "a column"})