4141from sqlmesh .utils .date import DatetimeRanges , to_datetime , to_date
4242from sqlmesh .utils .errors import MacroEvalError , SQLMeshError
4343from sqlmesh .utils .jinja import JinjaMacroRegistry , has_jinja
44- from sqlmesh .utils .metaprogramming import Executable , prepare_env , print_exception
44+ from sqlmesh .utils .metaprogramming import Executable , SqlValue , prepare_env , print_exception
4545
4646if t .TYPE_CHECKING :
4747 from sqlglot .dialects .dialect import DialectType
@@ -173,14 +173,15 @@ def __init__(
173173 "MacroEvaluator" : MacroEvaluator ,
174174 }
175175 self .python_env = python_env or {}
176- self ._jinja_env : t .Optional [Environment ] = jinja_env
177176 self .macros = {normalize_macro_name (k ): v .func for k , v in macro .get_registry ().items ()}
177+ self .columns_to_types_called = False
178+ self .default_catalog = default_catalog
179+
180+ self ._jinja_env : t .Optional [Environment ] = jinja_env
178181 self ._schema = schema
179182 self ._resolve_table = resolve_table
180183 self ._resolve_tables = resolve_tables
181- self .columns_to_types_called = False
182184 self ._snapshots = snapshots if snapshots is not None else {}
183- self .default_catalog = default_catalog
184185 self ._path = path
185186 self ._environment_naming_info = environment_naming_info
186187
@@ -191,7 +192,18 @@ def __init__(
191192 elif v .is_import and getattr (self .env .get (k ), c .SQLMESH_MACRO , None ):
192193 self .macros [normalize_macro_name (k )] = self .env [k ]
193194 elif v .is_value :
194- self .locals [k ] = self .env [k ]
195+ value = self .env [k ]
196+ if k in (c .SQLMESH_VARS , c .SQLMESH_BLUEPRINT_VARS ):
197+ value = {
198+ var_name : (
199+ self .parse_one (var_value .sql )
200+ if isinstance (var_value , SqlValue )
201+ else var_value
202+ )
203+ for var_name , var_value in value .items ()
204+ }
205+
206+ self .locals [k ] = value
195207
196208 def send (
197209 self , name : str , * args : t .Any , ** kwargs : t .Any
@@ -219,20 +231,23 @@ def evaluate_macros(
219231
220232 if isinstance (node , MacroVar ):
221233 changed = True
222- variables = self .locals .get (c .SQLMESH_VARS , {})
234+ variables = self .variables
235+
223236 if node .name not in self .locals and node .name .lower () not in variables :
224237 if not isinstance (node .parent , StagedFilePath ):
225238 raise SQLMeshError (f"Macro variable '{ node .name } ' is undefined." )
226239
227240 return node
228241
242+ # Precedence order is locals (e.g. @DEF) > blueprint variables > config variables
229243 value = self .locals .get (node .name , variables .get (node .name .lower ()))
230244 if isinstance (value , list ):
231245 return exp .convert (
232246 tuple (
233247 self .transform (v ) if isinstance (v , exp .Expression ) else v for v in value
234248 )
235249 )
250+
236251 return exp .convert (
237252 self .transform (value ) if isinstance (value , exp .Expression ) else value
238253 )
@@ -279,17 +294,12 @@ def template(self, text: t.Any, local_variables: t.Dict[str, t.Any]) -> str:
279294 Returns:
280295 The rendered string.
281296 """
282- mapping = {}
283-
284- variables = self .locals .get (c .SQLMESH_VARS , {})
285-
286- for k , v in chain (variables .items (), self .locals .items (), local_variables .items ()):
287- # try to convert all variables into sqlglot expressions
288- # because they're going to be converted into strings in sql
289- # we don't convert strings because that would result in adding quotes
290- if k != c .SQLMESH_VARS :
291- mapping [k ] = convert_sql (v , self .dialect )
292-
297+ # We try to convert all variables into sqlglot expressions because they're going to be converted
298+ # into strings; in sql we don't convert strings because that would result in adding quotes
299+ mapping = {
300+ k : convert_sql (v , self .dialect )
301+ for k , v in chain (self .variables .items (), self .locals .items (), local_variables .items ())
302+ }
293303 return MacroStrTemplate (str (text )).safe_substitute (mapping )
294304
295305 def evaluate (self , node : MacroFunc ) -> exp .Expression | t .List [exp .Expression ] | None :
@@ -467,6 +477,17 @@ def var(self, var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.
467477 """Returns the value of the specified variable, or the default value if it doesn't exist."""
468478 return (self .locals .get (c .SQLMESH_VARS ) or {}).get (var_name .lower (), default )
469479
480+ def blueprint_var (self , var_name : str , default : t .Optional [t .Any ] = None ) -> t .Optional [t .Any ]:
481+ """Returns the value of the specified blueprint variable, or the default value if it doesn't exist."""
482+ return (self .locals .get (c .SQLMESH_BLUEPRINT_VARS ) or {}).get (var_name .lower (), default )
483+
484+ @property
485+ def variables (self ) -> t .Dict [str , t .Any ]:
486+ return {
487+ ** self .locals .get (c .SQLMESH_VARS , {}),
488+ ** self .locals .get (c .SQLMESH_BLUEPRINT_VARS , {}),
489+ }
490+
470491 def _coerce (self , expr : exp .Expression , typ : t .Any , strict : bool = False ) -> t .Any :
471492 """Coerces the given expression to the specified type on a best-effort basis."""
472493 return _coerce (expr , typ , self .dialect , self ._path , strict )
@@ -1054,6 +1075,19 @@ def var(
10541075 return exp .convert (evaluator .var (var_name .this , default ))
10551076
10561077
1078+ @macro ("BLUEPRINT_VAR" )
1079+ def blueprint_var (
1080+ evaluator : MacroEvaluator , var_name : exp .Expression , default : t .Optional [exp .Expression ] = None
1081+ ) -> exp .Expression :
1082+ """Returns the value of a blueprint variable or the default value if the variable is not set."""
1083+ if not var_name .is_string :
1084+ raise SQLMeshError (
1085+ f"Invalid blueprint variable name '{ var_name .sql ()} '. Expected a string literal."
1086+ )
1087+
1088+ return exp .convert (evaluator .blueprint_var (var_name .this , default ))
1089+
1090+
10571091@macro ()
10581092def deduplicate (
10591093 evaluator : MacroEvaluator ,
0 commit comments