Skip to content

Commit e7ae574

Browse files
committed
Feat: add suport for @EACH dynamic blueprints, improve docs
1 parent d0a1d4d commit e7ae574

5 files changed

Lines changed: 131 additions & 10 deletions

File tree

docs/concepts/models/python_models.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,30 @@ def entrypoint(
367367
)
368368
```
369369

370-
!!! note
370+
Blueprint variable mappings can also be constructed dynamically, e.g., by using a macro: `blueprints="@gen_blueprints()"`. This is useful in cases where the `blueprints` list needs to be sourced from external sources, such as CSV files. For example, the definition of the `gen_blueprints` may look like this:
371+
372+
```python linenums="1"
373+
from sqlmesh import macro
374+
375+
@macro()
376+
def gen_blueprints(evaluator):
377+
return (
378+
"((customer := customer1, field_a := x, field_b := y),"
379+
" (customer := customer2, field_a := z, field_b := w))"
380+
)
381+
```
382+
383+
It's also possible to use the `@EACH` macro, combined with a global list variable (`@values`):
371384

372-
Blueprint variable mappings can also be evaluated dynamically, by using a macro (i.e. `blueprints="@gen_blueprints()"`). This is useful in cases where the `blueprints` list needs to be sourced from external sources, e.g. CSV files.
385+
```python linenums="1"
386+
387+
@model(
388+
"@{customer}.some_table",
389+
blueprints="@EACH(@values, x -> (customer := schema_@x))",
390+
...
391+
)
392+
...
393+
```
373394

374395
## Examples
375396
### Basic

docs/concepts/models/sql_models.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,31 @@ SELECT
175175
FROM customer2.some_source
176176
```
177177

178-
!!! note
178+
Blueprint variable mappings can also be constructed dynamically, e.g., by using a macro: `blueprints @gen_blueprints()`. This is useful in cases where the `blueprints` list needs to be sourced from external sources, such as CSV files. For example, the definition of the `gen_blueprints` may look like this:
179+
180+
```python linenums="1"
181+
from sqlmesh import macro
182+
183+
@macro()
184+
def gen_blueprints(evaluator):
185+
return (
186+
"((customer := customer1, field_a := x, field_b := y),"
187+
" (customer := customer2, field_a := z, field_b := w))"
188+
)
189+
```
190+
191+
It's also possible to use the `@EACH` macro, combined with a global list variable (`@values`):
179192

180-
Blueprint variable mappings can also be evaluated dynamically, by using a macro (i.e. `blueprints @gen_blueprints()`). This is useful in cases where the `blueprints` list needs to be sourced from external sources, e.g. CSV files.
193+
```sql linenums="1"
194+
MODEL (
195+
name @customer.some_table,
196+
kind FULL,
197+
blueprints @EACH(@values, x -> (customer := schema_@x)),
198+
);
199+
200+
SELECT
201+
1 AS c
202+
```
181203

182204
## Python-based definition
183205

@@ -262,9 +284,31 @@ def entrypoint(evaluator: MacroEvaluator) -> str | exp.Expression:
262284

263285
The two models produced from this template are the same as in the [example](#SQL-model-blueprinting) for SQL-based blueprinting.
264286

265-
!!! note
287+
Blueprint variable mappings can also be constructed dynamically, e.g., by using a macro: `blueprints="@gen_blueprints()"`. This is useful in cases where the `blueprints` list needs to be sourced from external sources, such as CSV files. For example, the definition of the `gen_blueprints` may look like this:
288+
289+
```python linenums="1"
290+
from sqlmesh import macro
291+
292+
@macro()
293+
def gen_blueprints(evaluator):
294+
return (
295+
"((customer := customer1, field_a := x, field_b := y),"
296+
" (customer := customer2, field_a := z, field_b := w))"
297+
)
298+
```
299+
300+
It's also possible to use the `@EACH` macro, combined with a global list variable (`@values`):
266301

267-
Blueprint variable mappings can also be evaluated dynamically, by using a macro (i.e. `blueprints="@gen_blueprints()"`). This is useful in cases where the `blueprints` list needs to be sourced from external sources, e.g. CSV files.
302+
```python linenums="1"
303+
304+
@model(
305+
"@{customer}.some_table",
306+
is_sql=True,
307+
blueprints="@EACH(@values, x -> (customer := schema_@x))",
308+
...
309+
)
310+
...
311+
```
268312

269313
## Automatic dependencies
270314

sqlmesh/core/model/decorator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def models(
117117
if not blueprints:
118118
raise_config_error("Failed to render blueprints property", path)
119119

120+
if len(blueprints) > 1:
121+
blueprints = [exp.Tuple(expressions=blueprints)]
122+
120123
blueprints = blueprints[0]
121124

122125
return create_models_from_blueprints(

sqlmesh/core/model/definition.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1966,7 +1966,13 @@ def load_sql_based_models(
19661966
if not rendered_blueprints:
19671967
raise_config_error("Failed to render blueprints property", path)
19681968

1969-
blueprints = t.cast(t.List, rendered_blueprints)[0]
1969+
# Help mypy see that rendered_blueprints is can't be None
1970+
assert rendered_blueprints
1971+
1972+
if len(rendered_blueprints) > 1:
1973+
rendered_blueprints = [exp.Tuple(expressions=rendered_blueprints)]
1974+
1975+
blueprints = rendered_blueprints[0]
19701976

19711977
return create_models_from_blueprints(
19721978
gateway=gateway,

tests/core/test_model.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8493,10 +8493,10 @@ def entrypoint(evaluator):
84938493
)
84948494

84958495

8496-
def test_dynamic_blueprinting(tmp_path: Path) -> None:
8496+
def test_dynamic_blueprinting_using_custom_macro(tmp_path: Path) -> None:
84978497
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
84988498

8499-
dynamic_template_sql = tmp_path / "models/dynamic_template.sql"
8499+
dynamic_template_sql = tmp_path / "models/dynamic_template_custom_macro.sql"
85008500
dynamic_template_sql.parent.mkdir(parents=True, exist_ok=True)
85018501
dynamic_template_sql.write_text(
85028502
"""
@@ -8514,7 +8514,7 @@ def test_dynamic_blueprinting(tmp_path: Path) -> None:
85148514
"""
85158515
)
85168516

8517-
dynamic_template_py = tmp_path / "models/dynamic_template.py"
8517+
dynamic_template_py = tmp_path / "models/dynamic_template_custom_macro.py"
85188518
dynamic_template_py.parent.mkdir(parents=True, exist_ok=True)
85198519
dynamic_template_py.write_text(
85208520
"""
@@ -8556,6 +8556,53 @@ def gen_blueprints(evaluator):
85568556
assert '"memory"."customer2"."some_other_table"' in ctx.models
85578557

85588558

8559+
def test_dynamic_blueprinting_using_each(tmp_path: Path) -> None:
8560+
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
8561+
8562+
dynamic_template_sql = tmp_path / "models/dynamic_template_each.sql"
8563+
dynamic_template_sql.parent.mkdir(parents=True, exist_ok=True)
8564+
dynamic_template_sql.write_text(
8565+
"""
8566+
MODEL (
8567+
name @customer.some_table,
8568+
kind FULL,
8569+
blueprints @EACH(@values, x -> (customer := schema_@x)),
8570+
);
8571+
8572+
SELECT
8573+
1 AS c
8574+
"""
8575+
)
8576+
8577+
dynamic_template_py = tmp_path / "models/dynamic_template_each.py"
8578+
dynamic_template_py.parent.mkdir(parents=True, exist_ok=True)
8579+
dynamic_template_py.write_text(
8580+
"""
8581+
from sqlmesh import model
8582+
8583+
@model(
8584+
"@{customer}.some_other_table",
8585+
kind="FULL",
8586+
blueprints="@EACH(@values, x -> (customer := schema_@x))",
8587+
is_sql=True,
8588+
)
8589+
def entrypoint(evaluator):
8590+
return "SELECT 1 AS c"
8591+
"""
8592+
)
8593+
8594+
model_defaults = ModelDefaultsConfig(dialect="duckdb")
8595+
variables = {"values": ["customer1", "customer2"]}
8596+
config = Config(model_defaults=model_defaults, variables=variables)
8597+
ctx = Context(config=config, paths=tmp_path)
8598+
8599+
assert len(ctx.models) == 4
8600+
assert '"memory"."schema_customer1"."some_table"' in ctx.models
8601+
assert '"memory"."schema_customer2"."some_table"' in ctx.models
8602+
assert '"memory"."schema_customer1"."some_other_table"' in ctx.models
8603+
assert '"memory"."schema_customer2"."some_other_table"' in ctx.models
8604+
8605+
85598606
def test_single_blueprint(tmp_path: Path) -> None:
85608607
init_example_project(tmp_path, dialect="duckdb", template=ProjectTemplate.EMPTY)
85618608

0 commit comments

Comments
 (0)