Skip to content

Commit b9e925f

Browse files
committed
Treat macro refs in signals and audits props as metadata-only
1 parent 67b1b87 commit b9e925f

3 files changed

Lines changed: 110 additions & 18 deletions

File tree

sqlmesh/core/model/common.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ def make_python_env(
5353
if macro_func_or_var.__class__ is d.MacroFunc:
5454
name = macro_func_or_var.this.name.lower()
5555
if name in macros:
56-
used_macros[name] = macros[name]
56+
used_macro = macros[name]
57+
if callable(used_macro) and expression.meta.get("metadata_only"):
58+
setattr(used_macro.func, c.SQLMESH_METADATA, True)
59+
60+
used_macros[name] = used_macro
5761
if name == c.VAR:
5862
args = macro_func_or_var.this.expressions
5963
if len(args) < 1:

sqlmesh/core/model/definition.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,10 +2462,14 @@ def _create_model(
24622462

24632463
statements.extend(audit.query for audit in audit_definitions.values())
24642464
for _, audit_args in model.audits:
2465-
statements.extend(audit_args.values())
2465+
for audit_arg_expression in audit_args.values():
2466+
audit_arg_expression.meta["metadata_only"] = True
2467+
statements.append(audit_arg_expression)
24662468

24672469
for _, kwargs in model.signals:
2468-
statements.extend(kwargs.values())
2470+
for signal_kwarg in kwargs.values():
2471+
signal_kwarg.meta["metadata_only"] = True
2472+
statements.append(signal_kwarg)
24692473

24702474
python_env = python_env or {}
24712475

tests/core/test_model.py

Lines changed: 99 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8696,10 +8696,12 @@ def metadata_macro(evaluator):
86968696
)
86978697

86988698
model = ctx.get_model("test_model")
8699+
python_env = model.python_env
8700+
86998701
empty_executable = Executable(payload="")
87008702

8701-
assert len(model.python_env) == 1
8702-
assert (model.python_env.get("metadata_macro") or empty_executable).is_metadata
8703+
assert len(python_env) == 1
8704+
assert (python_env.get("metadata_macro") or empty_executable).is_metadata
87038705

87048706
ctx.plan(no_prompts=True, auto_apply=True)
87058707

@@ -8708,10 +8710,11 @@ def metadata_macro(evaluator):
87088710

87098711
ctx.load()
87108712
model = ctx.get_model("test_model")
8713+
python_env = model.python_env
87118714

8712-
assert len(model.python_env) == 2
8713-
assert (model.python_env.get("metadata_macro") or empty_executable).is_metadata
8714-
assert (model.python_env.get("parse_one") or empty_executable).is_metadata
8715+
assert len(python_env) == 2
8716+
assert (python_env.get("metadata_macro") or empty_executable).is_metadata
8717+
assert (python_env.get("parse_one") or empty_executable).is_metadata
87158718

87168719
plan = ctx.plan(no_prompts=True, auto_apply=True)
87178720
ctx_diff = plan.context_diff
@@ -8727,11 +8730,12 @@ def metadata_macro(evaluator):
87278730

87288731
ctx.load()
87298732
model = ctx.get_model("test_model")
8733+
python_env = model.python_env
87308734

8731-
assert len(model.python_env) == 3
8732-
assert (model.python_env.get("metadata_macro") or empty_executable).is_metadata
8733-
assert (model.python_env.get("get_parsed_query") or empty_executable).is_metadata
8734-
assert (model.python_env.get("parse_one") or empty_executable).is_metadata
8735+
assert len(python_env) == 3
8736+
assert (python_env.get("metadata_macro") or empty_executable).is_metadata
8737+
assert (python_env.get("get_parsed_query") or empty_executable).is_metadata
8738+
assert (python_env.get("parse_one") or empty_executable).is_metadata
87358739

87368740
plan = ctx.plan(no_prompts=True, auto_apply=True)
87378741
ctx_diff = plan.context_diff
@@ -8783,15 +8787,95 @@ def m2(evaluator):
87838787
model = ctx.get_model("test_model")
87848788
empty_executable = Executable(payload="")
87858789

8790+
python_env = model.python_env
8791+
87868792
# Both `m1` and `m2` refer to `parse_one`, so for `m1` it would be a transitive metadata-only
87878793
# object, but since the python env is a flat namespace and `parse_one` is also a dependency
87888794
# of `m2`, it needs to be treated as non-metadata.
8789-
assert len(model.python_env) == 5
8790-
assert (model.python_env.get("m1") or empty_executable).is_metadata
8791-
assert (model.python_env.get("m1_dep") or empty_executable).is_metadata
8792-
assert not (model.python_env.get("m2") or empty_executable).is_metadata
8793-
assert not (model.python_env.get("parse_one") or empty_executable).is_metadata
8794-
assert not (model.python_env.get("common_dep") or empty_executable).is_metadata
8795+
assert len(python_env) == 5
8796+
assert (python_env.get("m1") or empty_executable).is_metadata
8797+
assert (python_env.get("m1_dep") or empty_executable).is_metadata
8798+
assert not (python_env.get("m2") or empty_executable).is_metadata
8799+
assert not (python_env.get("parse_one") or empty_executable).is_metadata
8800+
assert not (python_env.get("common_dep") or empty_executable).is_metadata
8801+
8802+
8803+
def test_macros_referenced_in_audits_or_signals_are_metadata_only(tmp_path: Path) -> None:
8804+
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
8805+
8806+
test_model = tmp_path / "models/test_model.sql"
8807+
test_model.parent.mkdir(parents=True, exist_ok=True)
8808+
test_model.write_text(
8809+
"""
8810+
MODEL (
8811+
name test_model,
8812+
kind FULL,
8813+
signals (
8814+
test_signal_always_true(arg := @m1())
8815+
),
8816+
audits (
8817+
unique_values(columns := @m2())
8818+
),
8819+
);
8820+
8821+
SELECT
8822+
1 AS c
8823+
"""
8824+
)
8825+
8826+
macro_code = """
8827+
from sqlglot import exp
8828+
from sqlmesh import macro
8829+
8830+
def baz():
8831+
pass
8832+
8833+
@macro()
8834+
def m1(evaluator):
8835+
baz()
8836+
return 1
8837+
8838+
@macro()
8839+
def m2(evaluator):
8840+
return exp.column("c")"""
8841+
8842+
test_macros = tmp_path / "macros/test_macros.py"
8843+
test_macros.parent.mkdir(parents=True, exist_ok=True)
8844+
test_macros.write_text(macro_code)
8845+
8846+
signal_code = """
8847+
import typing as t
8848+
8849+
from sqlmesh import signal
8850+
8851+
def bar():
8852+
pass
8853+
8854+
@signal()
8855+
def test_signal_always_true(batch, arg):
8856+
bar()
8857+
return True"""
8858+
8859+
test_signals = tmp_path / "signals/test_signals.py"
8860+
test_signals.parent.mkdir(parents=True, exist_ok=True)
8861+
test_signals.write_text(signal_code)
8862+
8863+
ctx = Context(
8864+
config=Config(model_defaults=ModelDefaultsConfig(dialect="duckdb")),
8865+
paths=tmp_path,
8866+
)
8867+
model = ctx.get_model("test_model")
8868+
empty_executable = Executable(payload="")
8869+
8870+
python_env = model.python_env
8871+
8872+
assert len(python_env) == 6
8873+
assert (python_env.get("test_signal_always_true") or empty_executable).is_metadata
8874+
assert (python_env.get("bar") or empty_executable).is_metadata
8875+
assert (python_env.get("m1") or empty_executable).is_metadata
8876+
assert (python_env.get("baz") or empty_executable).is_metadata
8877+
assert (python_env.get("m2") or empty_executable).is_metadata
8878+
assert (python_env.get("exp") or empty_executable).is_metadata
87958879

87968880

87978881
def test_scd_type_2_full_history_restatement():

0 commit comments

Comments
 (0)