Skip to content

Commit 1109d97

Browse files
authored
Fix: Support blueprint variables in jinja macros (#4289)
1 parent 7938dbd commit 1109d97

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

docs/concepts/macros/jinja_macros.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,31 @@ Access gateway variables in models using the same methods as [global variables](
122122

123123
Gateway-specific variable values take precedence over variables with the same name specified in the configuration file's root `variables` key.
124124

125+
### Blueprint variables
126+
127+
Blueprint variables are defined as a property of the `MODEL` statement, and serve as a mechanism for [creating model templates](../models/sql_models.md):
128+
129+
```sql linenums="1"
130+
MODEL (
131+
name @customer.some_table,
132+
kind FULL,
133+
blueprints (
134+
(customer := customer1, field_a := x, field_b := y),
135+
(customer := customer2, field_a := z)
136+
)
137+
);
138+
139+
JINJA_QUERY_BEGIN;
140+
SELECT
141+
{{ blueprint_var('field_a') }}
142+
{{ blueprint_var('field_b', 'default_b') }} AS field_b
143+
FROM {{ blueprint_var('customer') }}.some_source
144+
JINJA_END;
145+
```
146+
147+
Blueprint variables can be accessed using the `{{ blueprint_var() }}` macro function, which also supports specifying default values in case the variable is undefined (similar to `{{ var() }}`).
148+
149+
125150
### Local variables
126151

127152
Define your own variables with the Jinja statement `{% set ... %}`. For example, we could specify the name of the `num_orders` column in the `sqlmesh_example.full_model` like this:

sqlmesh/core/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
SQLMESH_BLUEPRINT_VARS = "__sqlmesh__blueprint__vars__"
8080

8181
VAR = "var"
82+
BLUEPRINT_VAR = "blueprint_var"
8283
GATEWAY = "gateway"
8384

8485
SQLMESH_MACRO = "__sqlmesh__macro__"

sqlmesh/utils/jinja.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sqlmesh.core import dialect as d
1616
from sqlmesh.utils import AttributeDict
1717
from sqlmesh.utils.pydantic import PRIVATE_FIELDS, PydanticModel, field_serializer, field_validator
18+
from sqlmesh.utils.metaprogramming import SqlValue
1819

1920

2021
if t.TYPE_CHECKING:
@@ -593,7 +594,10 @@ def jinja_call_arg_name(node: nodes.Node) -> str:
593594

594595
def create_var(variables: t.Dict[str, t.Any]) -> t.Callable:
595596
def _var(var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]:
596-
return variables.get(var_name.lower(), default)
597+
value = variables.get(var_name.lower(), default)
598+
if isinstance(value, SqlValue):
599+
return value.sql
600+
return value
597601

598602
return _var
599603

@@ -603,10 +607,12 @@ def create_builtin_globals(
603607
) -> t.Dict[str, t.Any]:
604608
global_vars.pop(c.GATEWAY, None)
605609
variables = global_vars.pop(c.SQLMESH_VARS, None) or {}
610+
blueprint_variables = global_vars.pop(c.SQLMESH_BLUEPRINT_VARS, None) or {}
606611
return {
612+
**global_vars,
607613
c.VAR: create_var(variables),
608614
c.GATEWAY: lambda: variables.get(c.GATEWAY, None),
609-
**global_vars,
615+
c.BLUEPRINT_VAR: create_var(blueprint_variables),
610616
}
611617

612618

tests/core/test_model.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8662,6 +8662,55 @@ def test_blueprint_variable_precedence_sql(tmp_path: Path, assert_exp_eq: t.Call
86628662
)
86638663

86648664

8665+
def test_blueprint_variable_jinja(tmp_path: Path, assert_exp_eq: t.Callable) -> None:
8666+
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
8667+
8668+
blueprint_variables = tmp_path / "models/blueprint_variables.sql"
8669+
blueprint_variables.parent.mkdir(parents=True, exist_ok=True)
8670+
blueprint_variables.write_text(
8671+
"""
8672+
MODEL (
8673+
name s.@{bp_name},
8674+
blueprints (
8675+
(bp_name := m1, var1 := 'v1', var2 := v2),
8676+
(bp_name := m2, var1 := 'v3'),
8677+
),
8678+
);
8679+
8680+
@DEF(bp_name, override);
8681+
8682+
JINJA_QUERY_BEGIN;
8683+
SELECT
8684+
{{ blueprint_var('var1') }} AS var1,
8685+
'{{ blueprint_var('var2') }}' AS var2,
8686+
'{{ blueprint_var('var2', 'var2_default') }}' AS var2_default,
8687+
'{{ blueprint_var('bp_name') }}' AS bp_name
8688+
FROM s.{{ blueprint_var('bp_name') }}_source;
8689+
JINJA_END;
8690+
"""
8691+
)
8692+
ctx = Context(
8693+
config=Config(
8694+
model_defaults=ModelDefaultsConfig(dialect="duckdb"),
8695+
variables={"var2": "1"},
8696+
),
8697+
paths=tmp_path,
8698+
)
8699+
assert len(ctx.models) == 2
8700+
8701+
m1 = ctx.get_model("s.m1", raise_if_missing=True)
8702+
m2 = ctx.get_model("s.m2", raise_if_missing=True)
8703+
8704+
assert_exp_eq(
8705+
m1.render_query(),
8706+
"""SELECT 'v1' AS "var1", 'v2' AS "var2", 'v2' AS "var2_default", 'm1' AS "bp_name" FROM "memory"."s"."m1_source" AS "m1_source" """,
8707+
)
8708+
assert_exp_eq(
8709+
m2.render_query(),
8710+
"""SELECT 'v3' AS "var1", 'None' AS "var2", 'var2_default' AS "var2_default", 'm2' AS "bp_name" FROM "memory"."s"."m2_source" AS "m2_source" """,
8711+
)
8712+
8713+
86658714
def test_blueprint_variable_precedence_python(tmp_path: Path, mocker: MockerFixture) -> None:
86668715
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
86678716

0 commit comments

Comments
 (0)