From dad0eb82d76f6acc7695d22a1f8ac66794adea21 Mon Sep 17 00:00:00 2001 From: Markus Sagen Date: Mon, 5 Oct 2026 14:15:43 +0200 Subject: [PATCH] fix!: pass macros to the last audit in load_multiple_audits The final load_audit call in load_multiple_audits did not pass module_path, macros or jinja_macros, so the last (or only) AUDIT in a file was loaded with an empty python_env and no project jinja macros. Rendering a standalone audit that used a project macro then failed with "MacroEvalError: Macro '' does not exist." Affected standalone audits now include the macros they reference, which changes their metadata hash and fingerprint. Fixes #6119 Signed-off-by: Markus Sagen --- sqlmesh/core/audit/definition.py | 3 + tests/core/test_audit.py | 97 ++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/sqlmesh/core/audit/definition.py b/sqlmesh/core/audit/definition.py index 4c90151ee4..13eb9d3219 100644 --- a/sqlmesh/core/audit/definition.py +++ b/sqlmesh/core/audit/definition.py @@ -528,6 +528,9 @@ def load_multiple_audits( yield load_audit( expressions=audit_block, path=path, + module_path=module_path, + macros=macros, + jinja_macros=jinja_macros, dialect=dialect, default_catalog=default_catalog, variables=variables, diff --git a/tests/core/test_audit.py b/tests/core/test_audit.py index b5226563b5..91f5a28922 100644 --- a/tests/core/test_audit.py +++ b/tests/core/test_audit.py @@ -221,6 +221,103 @@ def test_load_standalone_with_macros(assert_exp_eq): assert "extra_macro" not in audit.python_env +def test_load_multiple_single_standalone_with_macros(): + # Regression test for https://github.com/SQLMesh/sqlmesh/issues/6119 + # A standalone audit that is the *last* (and in this case only) AUDIT in its + # file must still have access to project python macros passed into + # `load_multiple_audits`. + expressions = parse( + """ + AUDIT ( + name only_allowed, + standalone true, + ); + + SELECT 1 FROM some_table WHERE name NOT IN @allowed() + """ + ) + + macros = { + "allowed": Executable(payload="def allowed(evaluator):\n return ['a']"), + } + + (audit,) = load_multiple_audits(expressions, path="/path/to/audit", macros=macros) + + assert "allowed" in audit.python_env + + +def test_load_multiple_last_standalone_with_macros(): + # Same as above, but for the last audit of a *multi*-audit file, to make sure + # the fix doesn't just special-case single-audit files. + expressions = parse( + """ + AUDIT ( + name first_audit, + standalone true, + ); + + SELECT 1 FROM some_table WHERE col1 IS NULL; + + AUDIT ( + name second_audit, + standalone true, + ); + + SELECT 1 FROM some_table WHERE name NOT IN @allowed() + """ + ) + + macros = { + "allowed": Executable(payload="def allowed(evaluator):\n return ['a']"), + } + + first_audit, second_audit = load_multiple_audits( + expressions, path="/path/to/audit", macros=macros + ) + + assert "allowed" not in first_audit.python_env + assert "allowed" in second_audit.python_env + + +def test_load_multiple_last_standalone_with_jinja_macros(): + # Regression test for https://github.com/SQLMesh/sqlmesh/issues/6119 + # Same as test_load_multiple_single_standalone_with_macros, but for jinja macros: + # a standalone audit that is the last (and in this case only) AUDIT in its file + # must still have access to project jinja macros passed into `load_multiple_audits`. + expressions = parse( + """ + AUDIT ( + name only_allowed, + standalone true, + ); + + JINJA_QUERY_BEGIN; + SELECT + *, + {{ test_macro(1) }}, + FROM + db.table t1 + WHERE + col IS NULL + JINJA_QUERY_END; + """ + ) + + macros = """ + {% macro test_macro(v) %}{{ v }}{% endmacro %} + + {% macro extra_macro(v) %}{{ v + 1 }}{% endmacro %} + """ + + jinja_macros = JinjaMacroRegistry() + jinja_macros.add_macros(MacroExtractor().extract(macros)) + + (audit,) = load_multiple_audits(expressions, path="/path/to/audit", jinja_macros=jinja_macros) + + assert "test_macro" in audit.jinja_macros.root_macros + assert "extra_macro" not in audit.jinja_macros.root_macros + + def test_load_standalone_with_jinja_macros(assert_exp_eq): expressions = parse( """