Skip to content

Commit d0a1d4d

Browse files
Feat: Make this_env and views available in macros of before after all (#4298)
1 parent 7e09d18 commit d0a1d4d

5 files changed

Lines changed: 84 additions & 37 deletions

File tree

docs/concepts/macros/macro_variables.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,5 @@ SQLMesh provides additional predefined variables used to modify model behavior b
140140
The following variables are also available in [`before_all` and `after_all` statements](../../guides/configuration.md#before_all-and-after_all-statements), as well as in macros invoked within them.
141141

142142
* @this_env - A string value containing the name of the current [environment](../environments.md).
143-
* @schemas - A list of the schema names of the [virtual layer](../../concepts/glossary.md#virtual-layer) of the current environment.
143+
* @schemas - A list of the schema names of the [virtual layer](../../concepts/glossary.md#virtual-layer) of the current environment.
144+
* @views - A list of the view names of the [virtual layer](../../concepts/glossary.md#virtual-layer) of the current environment.

docs/guides/configuration.md

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ Configuration for a connection used to run unit tests. An in-memory DuckDB datab
675675

676676
### Scheduler
677677

678-
Identifies which scheduler backend to use. The scheduler backend is used both for storing metadata and for executing [plans](../concepts/plans.md). By default, the scheduler type is set to `builtin`, which uses the existing SQL engine to store metadata.
678+
Identifies which scheduler backend to use. The scheduler backend is used both for storing metadata and for executing [plans](../concepts/plans.md). By default, the scheduler type is set to `builtin`, which uses the existing SQL engine to store metadata.
679679

680680
These options are in the [scheduler](../reference/configuration.md#scheduler) section of the configuration reference page.
681681

@@ -1019,20 +1019,18 @@ For example, rather than using an `on_virtual_update` statement in each model to
10191019

10201020
```python linenums="1"
10211021
from sqlmesh.core.macros import macro
1022-
from sqlmesh.core.snapshot.definition import to_view_mapping
10231022
10241023
@macro()
10251024
def grant_select_privileges(evaluator):
1026-
if evaluator._environment_naming_info:
1027-
mapping = to_view_mapping(
1028-
evaluator._snapshots.values(), evaluator._environment_naming_info
1029-
)
1025+
if evaluator.views:
10301026
return [
1031-
f"GRANT SELECT ON VIEW {view_name} TO ROLE admin_role;"
1032-
for view_name in mapping.values()
1027+
f"GRANT SELECT ON VIEW {view_name} /* sqlglot.meta replace=false */ TO ROLE admin_role;"
1028+
for view_name in evaluator.views
10331029
]
10341030
```
10351031

1032+
By including the comment `/* sqlglot.meta replace=false */`, you further ensure that the evaluator does not replace the view name with the physical table name during rendering.
1033+
10361034
##### Example: Granting Schema Privileges
10371035

10381036
Similarly, you can define a macro to grant schema usage privileges and, as demonstrated in the configuration above, using `this_env` macro conditionally execute it only in the production environment.
@@ -1042,21 +1040,14 @@ from sqlmesh import macro
10421040
10431041
@macro()
10441042
def grant_schema_usage(evaluator):
1045-
if evaluator._environment_naming_info:
1046-
schemas = {
1047-
snapshot.qualified_view_name.schema_for_environment(
1048-
evaluator._environment_naming_info
1049-
)
1050-
for snapshot in evaluator._snapshots.values()
1051-
if snapshot.is_model
1052-
}
1043+
if evaluator.this_env == "prod" and evaluator.schemas:
10531044
return [
10541045
f"GRANT USAGE ON SCHEMA {schema} TO admin_role;"
1055-
for schema in schemas
1046+
for schema in evaluator.schemas
10561047
]
10571048
```
10581049

1059-
As demonstrated in these examples, the `environment_naming_info` is available within the macro evaluator for macros invoked within the `before_all` and `after_all` statements. Additionally, the macro `this_env` provides access to the current environment name, which can be helpful for more advanced use cases that require fine-grained control over their behaviour.
1050+
As demonstrated in these examples, the `schemas` and `views` are available within the macro evaluator for macros invoked within the `before_all` and `after_all` statements. Additionally, the macro `this_env` provides access to the current environment name, which can be helpful for more advanced use cases that require fine-grained control over their behaviour.
10601051

10611052
### Linting
10621053

sqlmesh/core/macros.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,32 @@ def gateway(self) -> t.Optional[str]:
491491
"""Returns the gateway name."""
492492
return self.var(c.GATEWAY)
493493

494+
@property
495+
def snapshots(self) -> t.Dict[str, Snapshot]:
496+
"""Returns the snapshots if available."""
497+
return self._snapshots
498+
499+
@property
500+
def this_env(self) -> str:
501+
"""Returns the name of the current environment in before after all."""
502+
if "this_env" not in self.locals:
503+
raise SQLMeshError("Environment name is only available in before_all and after_all")
504+
return self.locals["this_env"]
505+
506+
@property
507+
def schemas(self) -> t.List[str]:
508+
"""Returns the schemas of the current environment in before after all macros."""
509+
if "schemas" not in self.locals:
510+
raise SQLMeshError("Schemas are only available in before_all and after_all")
511+
return self.locals["schemas"]
512+
513+
@property
514+
def views(self) -> t.List[str]:
515+
"""Returns the views of the current environment in before after all macros."""
516+
if "views" not in self.locals:
517+
raise SQLMeshError("Views are only available in before_all and after_all")
518+
return self.locals["views"]
519+
494520
def var(self, var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]:
495521
"""Returns the value of the specified variable, or the default value if it doesn't exist."""
496522
return (self.locals.get(c.SQLMESH_VARS) or {}).get(var_name.lower(), default)

sqlmesh/core/renderer.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,23 @@ def _render(
110110
if environment_naming_info is not None:
111111
kwargs["this_env"] = getattr(environment_naming_info, "name")
112112
if snapshots:
113-
schemas = set(
114-
[
115-
s.qualified_view_name.schema_for_environment(
116-
environment_naming_info, dialect=self._dialect
113+
schemas, views = set(), []
114+
for snapshot in snapshots.values():
115+
if snapshot.is_model and not snapshot.is_symbolic:
116+
schemas.add(
117+
snapshot.qualified_view_name.schema_for_environment(
118+
environment_naming_info, dialect=self._dialect
119+
)
120+
)
121+
views.append(
122+
snapshot.display_name(
123+
environment_naming_info, self._default_catalog, self._dialect
124+
)
117125
)
118-
for s in snapshots.values()
119-
if s.is_model and not s.is_symbolic
120-
]
121-
)
122126
if schemas:
123127
kwargs["schemas"] = list(schemas)
128+
if views:
129+
kwargs["views"] = views
124130

125131
this_model = kwargs.pop("this_model", None)
126132

tests/core/test_context.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,7 @@ def test_environment_statements(tmp_path: pathlib.Path):
14171417
after_all=[
14181418
"@grant_schema_usage()",
14191419
"@grant_usage_role(@schemas, 'admin')",
1420+
"@grant_select_privileges()",
14201421
],
14211422
)
14221423

@@ -1435,6 +1436,21 @@ def test_environment_statements(tmp_path: pathlib.Path):
14351436
expression,
14361437
)
14371438

1439+
create_temp_file(
1440+
tmp_path,
1441+
pathlib.Path(macros_dir, "grant_select_privileges.py"),
1442+
"""
1443+
from sqlmesh.core.macros import macro
1444+
@macro()
1445+
def grant_select_privileges(evaluator):
1446+
if evaluator.this_env and evaluator.views:
1447+
return [
1448+
f"GRANT SELECT ON VIEW {view_name} /* sqlglot.meta replace=false */ TO ROLE admin_role;"
1449+
for view_name in evaluator.views
1450+
]
1451+
""",
1452+
)
1453+
14381454
create_temp_file(
14391455
tmp_path,
14401456
pathlib.Path(macros_dir, "grant_schema_file.py"),
@@ -1484,6 +1500,7 @@ def grant_usage_role(evaluator, schemas, role):
14841500

14851501
assert isinstance(python_env["grant_schema_usage"], Executable)
14861502
assert isinstance(python_env["grant_usage_role"], Executable)
1503+
assert isinstance(python_env["grant_select_privileges"], Executable)
14871504

14881505
before_all_rendered = render_statements(
14891506
statements=before_all,
@@ -1502,27 +1519,33 @@ def grant_usage_role(evaluator, schemas, role):
15021519
python_env=python_env,
15031520
snapshots=snapshots,
15041521
environment_naming_info=EnvironmentNamingInfo(name="prod"),
1505-
runtime_stage=RuntimeStage.BEFORE_ALL,
1522+
runtime_stage=RuntimeStage.AFTER_ALL,
15061523
)
15071524

1508-
assert after_all_rendered == [
1509-
"GRANT USAGE ON SCHEMA db TO user_role",
1510-
'GRANT USAGE ON SCHEMA "db" TO "admin"',
1511-
]
1525+
assert sorted(after_all_rendered) == sorted(
1526+
[
1527+
"GRANT USAGE ON SCHEMA db TO user_role",
1528+
'GRANT USAGE ON SCHEMA "db" TO "admin"',
1529+
"GRANT SELECT ON VIEW memory.db.test_after_model /* sqlglot.meta replace=false */ TO ROLE admin_role",
1530+
]
1531+
)
15121532

15131533
after_all_rendered_dev = render_statements(
15141534
statements=after_all,
15151535
dialect=dialect,
15161536
python_env=python_env,
15171537
snapshots=snapshots,
15181538
environment_naming_info=EnvironmentNamingInfo(name="dev"),
1519-
runtime_stage=RuntimeStage.BEFORE_ALL,
1539+
runtime_stage=RuntimeStage.AFTER_ALL,
15201540
)
15211541

1522-
assert after_all_rendered_dev == [
1523-
"GRANT USAGE ON SCHEMA db__dev TO user_role",
1524-
'GRANT USAGE ON SCHEMA "db__dev" TO "admin"',
1525-
]
1542+
assert sorted(after_all_rendered_dev) == sorted(
1543+
[
1544+
"GRANT USAGE ON SCHEMA db__dev TO user_role",
1545+
'GRANT USAGE ON SCHEMA "db__dev" TO "admin"',
1546+
"GRANT SELECT ON VIEW memory.db__dev.test_after_model /* sqlglot.meta replace=false */ TO ROLE admin_role",
1547+
]
1548+
)
15261549

15271550

15281551
def test_plan_environment_statements(tmp_path: pathlib.Path):

0 commit comments

Comments
 (0)