Edit on GitHub

sqlmesh.utils.jinja

  1from __future__ import annotations
  2
  3import base64
  4import importlib
  5import json
  6import re
  7import typing as t
  8import zlib
  9from collections import defaultdict
 10from enum import Enum
 11from sys import exc_info
 12from traceback import walk_tb
 13
 14from jinja2 import Environment, Template, nodes, UndefinedError
 15from jinja2.runtime import Macro
 16from sqlglot import Dialect, Parser, TokenType
 17from sqlglot.expressions import Expression
 18
 19from sqlmesh.core import constants as c
 20from sqlmesh.core import dialect as d
 21from sqlmesh.utils import AttributeDict
 22from sqlmesh.utils.pydantic import PRIVATE_FIELDS, PydanticModel, field_serializer, field_validator
 23from sqlmesh.utils.metaprogramming import SqlValue
 24
 25
 26if t.TYPE_CHECKING:
 27    CallNames = t.Tuple[t.Tuple[str, ...], t.Union[nodes.Call, nodes.Getattr]]
 28
 29SQLMESH_JINJA_PACKAGE = "sqlmesh.utils.jinja"
 30
 31
 32def b64decode(value: t.Union[str, bytes]) -> str:
 33    """Decode a base64-encoded value and return it as UTF-8 text.
 34
 35    Intended for base64-encoded string/JSON secrets (for example a service-account
 36    key stored in an environment variable), not arbitrary binary payloads.
 37    """
 38    decoded = value.encode("utf-8") if isinstance(value, str) else value
 39    return base64.b64decode(decoded).decode("utf-8")
 40
 41
 42def b64encode(value: t.Union[str, bytes]) -> str:
 43    """Base64-encode a value and return the encoding as UTF-8 text.
 44
 45    The input is treated as UTF-8 text, mirroring ``b64decode``; it is intended for
 46    string/JSON secrets rather than arbitrary binary payloads.
 47    """
 48    encoded = value.encode("utf-8") if isinstance(value, str) else value
 49    return base64.b64encode(encoded).decode("utf-8")
 50
 51
 52def create_builtin_filters() -> t.Dict[str, t.Callable]:
 53    return {
 54        "b64decode": b64decode,
 55        "b64encode": b64encode,
 56    }
 57
 58
 59def environment(**kwargs: t.Any) -> Environment:
 60    extensions = kwargs.pop("extensions", [])
 61    extensions.append("jinja2.ext.do")
 62    extensions.append("jinja2.ext.loopcontrols")
 63    env = Environment(extensions=extensions, **kwargs)
 64    env.filters.update(create_builtin_filters())
 65    return env
 66
 67
 68ENVIRONMENT = environment()
 69
 70
 71class MacroReference(PydanticModel, frozen=True):
 72    package: t.Optional[str] = None
 73    name: str
 74
 75    @property
 76    def reference(self) -> str:
 77        if self.package is None:
 78            return self.name
 79        return ".".join((self.package, self.name))
 80
 81    def __str__(self) -> str:
 82        return self.reference
 83
 84
 85class MacroInfo(PydanticModel):
 86    """Class to hold macro and its calls"""
 87
 88    definition: str
 89    depends_on: t.List[MacroReference]
 90    is_top_level: bool = False
 91
 92
 93class MacroReturnVal(Exception):
 94    def __init__(self, val: t.Any):
 95        self.value = val
 96
 97
 98class MacroExtractor(Parser):
 99    def extract(self, jinja: str, dialect: str = "") -> t.Dict[str, MacroInfo]:
100        """Extract a dictionary of macro definitions from a jinja string.
101
102        Args:
103            jinja: The jinja string to extract from.
104            dialect: The dialect of SQL.
105
106        Returns:
107            A dictionary of macro name to macro definition.
108        """
109        self.reset()
110        self.sql = jinja
111        self._tokens = Dialect.get_or_raise(dialect).tokenize(jinja)
112
113        # guard for older sqlglot versions (before 30.0.3)
114        if hasattr(self, "_tokens_size"):
115            # keep the cached length in sync
116            self._tokens_size = len(self._tokens)
117        self._index = -1
118        self._advance()
119
120        macros: t.Dict[str, MacroInfo] = {}
121
122        while self._curr:
123            if self._curr.token_type == TokenType.BLOCK_START:
124                macro_start = self._curr
125            elif self._tag == "MACRO" and self._next:
126                name = self._next.text
127                while self._curr and self._curr.token_type != TokenType.BLOCK_END:
128                    self._advance()
129
130                while self._curr and self._tag != "ENDMACRO":
131                    self._advance()
132
133                macro_str = self._find_sql(macro_start, self._next)
134                macros[name] = MacroInfo(
135                    definition=macro_str,
136                    depends_on=list(extract_macro_references_and_variables(macro_str)[0]),
137                )
138
139            self._advance()
140
141        return macros
142
143    def _advance(self, times: int = 1) -> None:
144        super()._advance(times)
145        self._tag = (
146            self._curr.text.upper()
147            if self._curr and self._prev and self._prev.token_type == TokenType.BLOCK_START
148            else ""
149        )
150
151
152def call_name(node: nodes.Expr) -> t.Tuple[str, ...]:
153    if isinstance(node, nodes.Name):
154        return (node.name,)
155    if isinstance(node, nodes.Const):
156        return (f"'{node.value}'",)
157    if isinstance(node, nodes.Getattr):
158        return call_name(node.node) + (node.attr,)
159    if isinstance(node, (nodes.Getitem, nodes.Call)):
160        return call_name(node.node)
161    return ()
162
163
164def render_jinja(query: str, methods: t.Optional[t.Dict[str, t.Any]] = None) -> str:
165    return ENVIRONMENT.from_string(query).render(methods or {})
166
167
168def find_call_names(node: nodes.Node, vars_in_scope: t.Set[str]) -> t.Iterator[CallNames]:
169    vars_in_scope = vars_in_scope.copy()
170    for child_node in node.iter_child_nodes():
171        if "target" in child_node.fields:
172            # For nodes with assignment targets (Assign, AssignBlock, For, Import),
173            # the target name could shadow a reference in the right hand side.
174            # So we need to process the RHS before adding the target to scope.
175            # For example: {% set model = model.path %} should track model.path.
176            yield from find_call_names(child_node, vars_in_scope)
177
178            target = getattr(child_node, "target")
179            if isinstance(target, nodes.Name):
180                vars_in_scope.add(target.name)
181            elif isinstance(target, nodes.Tuple):
182                for item in target.items:
183                    if isinstance(item, nodes.Name):
184                        vars_in_scope.add(item.name)
185        elif isinstance(child_node, nodes.Macro):
186            for arg in child_node.args:
187                vars_in_scope.add(arg.name)
188        elif isinstance(child_node, nodes.Call) or (
189            isinstance(child_node, nodes.Getattr) and not isinstance(child_node.node, nodes.Getattr)
190        ):
191            name = call_name(child_node)
192            if name[0][0] != "'" and name[0] not in vars_in_scope:
193                yield (name, child_node)
194
195        if "target" not in child_node.fields:
196            yield from find_call_names(child_node, vars_in_scope)
197
198
199def extract_call_names(
200    jinja_str: str, cache: t.Optional[t.Dict[str, t.Tuple[t.List[CallNames], bool]]] = None
201) -> t.List[CallNames]:
202    def parse() -> t.List[CallNames]:
203        return list(find_call_names(ENVIRONMENT.parse(jinja_str), set()))
204
205    if cache is not None:
206        key = str(zlib.crc32(jinja_str.encode("utf-8")))
207        if key in cache:
208            names = cache[key][0]
209        else:
210            names = parse()
211        cache[key] = (names, True)
212        return names
213    return parse()
214
215
216def is_variable_node(n: nodes.Node) -> bool:
217    return (
218        isinstance(n, nodes.Call)
219        and isinstance(n.node, nodes.Name)
220        and n.node.name in (c.VAR, c.BLUEPRINT_VAR)
221    )
222
223
224def extract_macro_references_and_variables(
225    *jinja_strs: str,
226) -> t.Tuple[t.Set[MacroReference], t.Set[str]]:
227    macro_references = set()
228    variables = set()
229    for jinja_str in jinja_strs:
230        for call_name, node in extract_call_names(jinja_str):
231            if call_name[0] in (c.VAR, c.BLUEPRINT_VAR):
232                if not is_variable_node(node):
233                    # Find the variable node which could be nested
234                    for n in node.find_all(nodes.Call):
235                        if is_variable_node(n):
236                            node = n
237                            break
238                    else:
239                        raise ValueError(f"Could not find variable name in {jinja_str}")
240                node = t.cast(nodes.Call, node)
241                args = [jinja_call_arg_name(arg) for arg in node.args]
242                if args and args[0]:
243                    variables.add(args[0].lower())
244            elif call_name[0] == c.GATEWAY:
245                variables.add(c.GATEWAY)
246            elif len(call_name) == 1:
247                macro_references.add(MacroReference(name=call_name[0]))
248            elif len(call_name) == 2:
249                macro_references.add(MacroReference(package=call_name[0], name=call_name[1]))
250    return macro_references, variables
251
252
253def sort_dict_recursive(
254    item: t.Dict[str, t.Any],
255) -> t.Dict[str, t.Any]:
256    sorted_dict: t.Dict[str, t.Any] = {}
257    for k, v in sorted(item.items()):
258        if isinstance(v, list):
259            sorted_dict[k] = sorted(v)
260        elif isinstance(v, dict):
261            sorted_dict[k] = sort_dict_recursive(v)
262        else:
263            sorted_dict[k] = v
264    return sorted_dict
265
266
267JinjaGlobalAttribute = t.Union[str, int, float, bool, AttributeDict]
268
269
270class JinjaMacroRegistry(PydanticModel):
271    """Registry for Jinja macros.
272
273    Args:
274        packages: The mapping from package name to a collection of macro definitions.
275        root_macros: The collection of top-level macro definitions.
276        global_objs: The global objects.
277        create_builtins_module: The name of a module which defines the `create_builtins` factory
278            function that will be used to construct builtin variables and functions.
279        root_package_name: The name of the root package. If specified root macros will be available
280            as both `root_package_name.macro_name` and `macro_name`.
281        top_level_packages: The list of top-level packages. Macros in this packages will be available
282            as both `package_name.macro_name` and `macro_name`.
283    """
284
285    packages: t.Dict[str, t.Dict[str, MacroInfo]] = {}
286    root_macros: t.Dict[str, MacroInfo] = {}
287    global_objs: t.Dict[str, JinjaGlobalAttribute] = {}
288    create_builtins_module: t.Optional[str] = SQLMESH_JINJA_PACKAGE
289    root_package_name: t.Optional[str] = None
290    top_level_packages: t.List[str] = []
291
292    _parser_cache: t.Dict[t.Tuple[t.Optional[str], str], Template] = {}
293    _trimmed: bool = False
294    __environment: t.Optional[Environment] = None
295
296    def __getstate__(self) -> t.Dict[t.Any, t.Any]:
297        state = super().__getstate__()
298        private = state[PRIVATE_FIELDS]
299        private["_parser_cache"] = {}
300        private["_JinjaMacroRegistry__environment"] = None
301        return state
302
303    @field_validator("global_objs", mode="before")
304    @classmethod
305    def _validate_global_objs(cls, value: t.Any) -> t.Any:
306        def _normalize(val: t.Any) -> t.Any:
307            if isinstance(val, dict):
308                return AttributeDict({k: _normalize(v) for k, v in val.items()})
309            if isinstance(val, list):
310                return [_normalize(v) for v in val]
311            if isinstance(val, set):
312                return [_normalize(v) for v in sorted(val)]
313            if isinstance(val, Enum):
314                return val.value
315            return val
316
317        return _normalize(value)
318
319    @field_serializer("global_objs")
320    def _serialize_attribute_dict(
321        self, value: t.Dict[str, JinjaGlobalAttribute]
322    ) -> t.Dict[str, t.Any]:
323        # NOTE: This is called only when used with Pydantic V2.
324        def _convert(
325            val: t.Union[t.Dict[str, JinjaGlobalAttribute], t.Dict[str, t.Any]],
326        ) -> t.Dict[str, t.Any]:
327            return {k: _convert(v) if isinstance(v, AttributeDict) else v for k, v in val.items()}
328
329        return _convert(value)
330
331    @property
332    def trimmed(self) -> bool:
333        return self._trimmed
334
335    def add_macros(self, macros: t.Dict[str, MacroInfo], package: t.Optional[str] = None) -> None:
336        """Adds macros to the target package.
337
338        Args:
339            macros: Macros that should be added.
340            package: The name of the package the given macros belong to. If not specified, the provided
341            macros will be added to the root namespace.
342        """
343
344        if package is not None:
345            package_macros = self.packages.get(package, {})
346            package_macros.update(macros)
347            self.packages[package] = package_macros
348        else:
349            self.root_macros.update(macros)
350
351    def add_globals(self, globals: t.Dict[str, JinjaGlobalAttribute]) -> None:
352        """Adds global objects to the registry.
353
354        Args:
355            globals: The global objects that should be added.
356        """
357        # Keep the registry lightweight when the graph is not needed
358        if not "graph" in self.packages:
359            globals.pop("flat_graph", None)
360        self.global_objs.update(**self._validate_global_objs(globals))
361
362    def build_macro(self, reference: MacroReference, **kwargs: t.Any) -> t.Optional[t.Callable]:
363        """Builds a Python callable for a macro with the given reference.
364
365        Args:
366            reference: The macro reference.
367        Returns:
368            The macro as a Python callable or None if not found.
369        """
370        env: Environment = self.build_environment(**kwargs)
371        if reference.package is not None:
372            package = env.globals.get(reference.package, {})
373            return package.get(reference.name)  # type: ignore
374        return env.globals.get(reference.name)  # type: ignore
375
376    def build_environment(self, **kwargs: t.Any) -> Environment:
377        """Builds a new Jinja environment based on this registry."""
378
379        context: t.Dict[str, t.Any] = {}
380
381        root_macros = {
382            name: self._MacroWrapper(name, None, self, context)
383            for name, macro in self.root_macros.items()
384        }
385
386        package_macros: t.Dict[str, t.Any] = defaultdict(AttributeDict)
387        for package_name, macros in self.packages.items():
388            for macro_name, macro in macros.items():
389                macro_wrapper = self._MacroWrapper(macro_name, package_name, self, context)
390                package_macros[package_name][macro_name] = macro_wrapper
391                if macro.is_top_level and macro_name not in root_macros:
392                    root_macros[macro_name] = macro_wrapper
393
394        top_level_packages = self.top_level_packages.copy()
395
396        if self.root_package_name is not None:
397            package_macros[self.root_package_name].update(root_macros)
398            top_level_packages.append(self.root_package_name)
399
400        env = environment()
401
402        builtin_globals = self._create_builtin_globals(kwargs)
403        for top_level_package_name in top_level_packages:
404            # Make sure that the top-level package doesn't fully override the same builtin package.
405            package_macros[top_level_package_name] = AttributeDict(
406                {
407                    **(builtin_globals.pop(top_level_package_name, None) or {}),
408                    **(package_macros.get(top_level_package_name) or {}),
409                }
410            )
411            root_macros.update(package_macros[top_level_package_name])
412
413        context.update(builtin_globals)
414        context.update(root_macros)
415        context.update(package_macros)
416        context["render"] = lambda input: env.from_string(input).render()
417
418        env.globals.update(context)
419        env.filters.update(self._environment.filters)
420        return env
421
422    def trim(
423        self, dependencies: t.Iterable[MacroReference], package: t.Optional[str] = None
424    ) -> JinjaMacroRegistry:
425        """Trims the registry by keeping only macros with given references and their transitive dependencies.
426
427        Args:
428            dependencies: References to macros that should be kept.
429            package: The name of the package in the context of which the trimming should be performed.
430
431        Returns:
432            A new trimmed registry.
433        """
434        dependencies_by_package: t.Dict[t.Optional[str], t.Set[str]] = defaultdict(set)
435        for dep in dependencies:
436            dependencies_by_package[dep.package or package].add(dep.name)
437
438        top_level_packages = self.top_level_packages.copy()
439        if package is not None:
440            top_level_packages.append(package)
441
442        result = JinjaMacroRegistry(
443            global_objs=self.global_objs.copy(),
444            create_builtins_module=self.create_builtins_module,
445            root_package_name=self.root_package_name,
446            top_level_packages=top_level_packages,
447        )
448        for package, names in dependencies_by_package.items():
449            result = result.merge(self._trim_macros(names, package))
450
451        result._trimmed = True
452
453        return result
454
455    def merge(self, other: JinjaMacroRegistry) -> JinjaMacroRegistry:
456        """Returns a copy of the registry which contains macros from both this and `other` instances.
457
458        Args:
459            other: The other registry instance.
460
461        Returns:
462            A new merged registry.
463        """
464
465        root_macros = {
466            **self.root_macros,
467            **other.root_macros,
468        }
469
470        packages = {}
471        for package in {*self.packages, *other.packages}:
472            packages[package] = {
473                **self.packages.get(package, {}),
474                **other.packages.get(package, {}),
475            }
476
477        global_objs = {
478            **self.global_objs,
479            **other.global_objs,
480        }
481
482        return JinjaMacroRegistry(
483            packages=packages,
484            root_macros=root_macros,
485            global_objs=global_objs,
486            create_builtins_module=self.create_builtins_module or other.create_builtins_module,
487            root_package_name=self.root_package_name or other.root_package_name,
488            top_level_packages=[*self.top_level_packages, *other.top_level_packages],
489        )
490
491    def to_expressions(self) -> t.List[Expression]:
492        output: t.List[Expression] = []
493
494        filtered_objs = {
495            k: v for k, v in self.global_objs.items() if k in ("refs", "sources", "vars")
496        }
497        if filtered_objs:
498            output.append(
499                d.PythonCode(
500                    expressions=[
501                        f"{k} = '{v}'" if isinstance(v, str) else f"{k} = {v}"
502                        for k, v in sort_dict_recursive(filtered_objs).items()
503                    ]
504                )
505            )
506
507        for macro_name, macro_info in sorted(self.root_macros.items()):
508            output.append(d.jinja_statement(macro_info.definition))
509
510        for _, package in sorted(self.packages.items()):
511            for macro_name, macro_info in sorted(package.items()):
512                output.append(d.jinja_statement(macro_info.definition))
513
514        return output
515
516    @property
517    def data_hash_values(self) -> t.List[str]:
518        data = []
519
520        for macro_name, macro in sorted(self.root_macros.items()):
521            data.append(macro_name)
522            data.append(macro.definition)
523
524        for _, package in sorted(self.packages.items()):
525            for macro_name, macro in sorted(package.items()):
526                data.append(macro_name)
527                data.append(macro.definition)
528
529        trimmed_global_objs = {
530            k: self.global_objs[k] for k in ("refs", "sources", "vars") if k in self.global_objs
531        }
532        data.append(json.dumps(trimmed_global_objs, sort_keys=True))
533
534        return data
535
536    def __deepcopy__(self, memo: t.Optional[t.Dict[int, t.Any]] = None) -> JinjaMacroRegistry:
537        return JinjaMacroRegistry.parse_obj(self.dict())
538
539    def _parse_macro(self, name: str, package: t.Optional[str]) -> Template:
540        cache_key = (package, name)
541        if cache_key not in self._parser_cache:
542            macro = self._get_macro(name, package)
543
544            definition: nodes.Template = self._environment.parse(macro.definition)
545            if _is_private_macro(name):
546                # A workaround to expose private jinja macros.
547                definition = self._to_non_private_macro_def(name, definition)
548
549            self._parser_cache[cache_key] = self._environment.from_string(definition)
550        return self._parser_cache[cache_key]
551
552    @property
553    def _environment(self) -> Environment:
554        if self.__environment is None:
555            self.__environment = environment()
556            self.__environment.filters.update(self._create_builtin_filters())
557        return self.__environment
558
559    def _trim_macros(
560        self,
561        names: t.Set[str],
562        package: t.Optional[str] = None,
563        visited: t.Optional[t.Dict[t.Optional[str], t.Set[str]]] = None,
564    ) -> JinjaMacroRegistry:
565        if visited is None:
566            visited = defaultdict(set)
567
568        macros = self.packages.get(package, {}) if package is not None else self.root_macros
569        trimmed_macros = {}
570
571        dependencies: t.Dict[t.Optional[str], t.Set[str]] = defaultdict(set)
572
573        for name in names:
574            if name in macros and name not in visited[package]:
575                macro = macros[name]
576                trimmed_macros[name] = macro
577                for dependency in macro.depends_on:
578                    dependencies[dependency.package or package].add(dependency.name)
579                visited[package].add(name)
580
581        if package is not None:
582            result = JinjaMacroRegistry(packages={package: trimmed_macros})
583        else:
584            result = JinjaMacroRegistry(root_macros=trimmed_macros)
585
586        for upstream_package, upstream_names in dependencies.items():
587            result = result.merge(
588                self._trim_macros(upstream_names, upstream_package, visited=visited)
589            )
590
591        return result
592
593    def _macro_exists(self, name: str, package: t.Optional[str]) -> bool:
594        return (
595            name in self.packages.get(package, {})
596            if package is not None
597            else name in self.root_macros
598        )
599
600    def _get_macro(self, name: str, package: t.Optional[str]) -> MacroInfo:
601        return self.packages[package][name] if package is not None else self.root_macros[name]
602
603    def _to_non_private_macro_def(self, name: str, template: nodes.Template) -> nodes.Template:
604        for node in template.find_all((nodes.Macro, nodes.Call)):
605            if isinstance(node, nodes.Macro):
606                node.name = _non_private_name(name)
607            elif isinstance(node, nodes.Call) and isinstance(node.node, nodes.Name):
608                node.node.name = _non_private_name(name)
609
610        return template
611
612    def _create_builtin_globals(self, global_vars: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
613        """Creates Jinja builtin globals using a factory function defined in the provided module."""
614        engine_adapter = global_vars.pop("engine_adapter", None)
615        global_vars = {**self.global_objs, **global_vars}
616        if self.create_builtins_module is not None:
617            module = importlib.import_module(self.create_builtins_module)
618            if hasattr(module, "create_builtin_globals"):
619                return module.create_builtin_globals(self, global_vars, engine_adapter)
620        return global_vars
621
622    def _create_builtin_filters(self) -> t.Dict[str, t.Any]:
623        """Creates Jinja builtin filters using a factory function defined in the provided module."""
624        if self.create_builtins_module is not None:
625            module = importlib.import_module(self.create_builtins_module)
626            if hasattr(module, "create_builtin_filters"):
627                return module.create_builtin_filters()
628        return {}
629
630    class _MacroWrapper:
631        def __init__(
632            self,
633            name: str,
634            package: t.Optional[str],
635            registry: JinjaMacroRegistry,
636            context: t.Dict[str, t.Any],
637        ):
638            self.name = name
639            self.package = package
640            self.context = context
641            self.registry = registry
642
643        def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
644            context = self.context.copy()
645            if self.package is not None and self.package in context:
646                context.update(context[self.package])
647
648            template = self.registry._parse_macro(self.name, self.package)
649            macro_callable = getattr(
650                template.make_module(vars=context), _non_private_name(self.name)
651            )
652            try:
653                return macro_callable(*args, **kwargs)
654            except MacroReturnVal as ret:
655                return ret.value
656
657
658def _is_private_macro(name: str) -> bool:
659    return name.startswith("_")
660
661
662def _non_private_name(name: str) -> str:
663    return name.lstrip("_")
664
665
666JINJA_REGEX = re.compile(r"({{|{%)")
667
668
669def has_jinja(value: str) -> bool:
670    return JINJA_REGEX.search(value) is not None
671
672
673def jinja_call_arg_name(node: nodes.Node) -> str:
674    if isinstance(node, nodes.Const):
675        return node.value
676    return ""
677
678
679def create_var(variables: t.Dict[str, t.Any]) -> t.Callable:
680    def _var(var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]:
681        value = variables.get(var_name.lower(), default)
682        if isinstance(value, SqlValue):
683            return value.sql
684        return value
685
686    return _var
687
688
689def create_builtin_globals(
690    jinja_macros: JinjaMacroRegistry, global_vars: t.Dict[str, t.Any], *args: t.Any, **kwargs: t.Any
691) -> t.Dict[str, t.Any]:
692    global_vars.pop(c.GATEWAY, None)
693    variables = global_vars.pop(c.SQLMESH_VARS, None) or {}
694    blueprint_variables = global_vars.pop(c.SQLMESH_BLUEPRINT_VARS, None) or {}
695    return {
696        **global_vars,
697        c.VAR: create_var(variables),
698        c.GATEWAY: lambda: variables.get(c.GATEWAY, None),
699        c.BLUEPRINT_VAR: create_var(blueprint_variables),
700    }
701
702
703def make_jinja_registry(
704    jinja_macros: JinjaMacroRegistry, package_name: str, jinja_references: t.Set[MacroReference]
705) -> JinjaMacroRegistry:
706    """
707    Creates a Jinja macro registry for a specific package.
708
709    This function takes an existing Jinja macro registry and returns a new
710    registry that includes only the macros associated with the specified
711    package and trims the registry to include only the macros referenced
712    in the provided set of macro references.
713
714    Args:
715        jinja_macros: The original Jinja macro registry containing all macros.
716        package_name: The name of the package for which to create the registry.
717        jinja_references: A set of macro references to retain in the new registry.
718
719    Returns:
720        A new JinjaMacroRegistry containing only the macros for the specified
721        package and the referenced macros.
722    """
723
724    jinja_registry = jinja_macros.copy()
725    jinja_registry.root_macros = jinja_registry.packages.get(package_name) or {}
726    jinja_registry = jinja_registry.trim(jinja_references)
727
728    return jinja_registry
729
730
731def extract_error_details(ex: Exception) -> str:
732    """Extracts a readable message from a Jinja2 error, to include missing name and macro."""
733
734    error_details = ""
735    if isinstance(ex, UndefinedError):
736        if match := re.search(r"'(\w+)'", str(ex)):
737            error_details += f"\nUndefined macro/variable: '{match.group(1)}'"
738        try:
739            _, _, exc_traceback = exc_info()
740            for frame, _ in walk_tb(exc_traceback):
741                if frame.f_code.co_name == "_invoke":
742                    macro = frame.f_locals.get("self")
743                    if isinstance(macro, Macro):
744                        error_details += f" in macro: '{macro.name}'\n"
745                        break
746        except:
747            # to fall back to the generic error message if frame analysis fails
748            pass
749    return error_details or str(ex)
SQLMESH_JINJA_PACKAGE = 'sqlmesh.utils.jinja'
def b64decode(value: Union[str, bytes]) -> str:
33def b64decode(value: t.Union[str, bytes]) -> str:
34    """Decode a base64-encoded value and return it as UTF-8 text.
35
36    Intended for base64-encoded string/JSON secrets (for example a service-account
37    key stored in an environment variable), not arbitrary binary payloads.
38    """
39    decoded = value.encode("utf-8") if isinstance(value, str) else value
40    return base64.b64decode(decoded).decode("utf-8")

Decode a base64-encoded value and return it as UTF-8 text.

Intended for base64-encoded string/JSON secrets (for example a service-account key stored in an environment variable), not arbitrary binary payloads.

def b64encode(value: Union[str, bytes]) -> str:
43def b64encode(value: t.Union[str, bytes]) -> str:
44    """Base64-encode a value and return the encoding as UTF-8 text.
45
46    The input is treated as UTF-8 text, mirroring ``b64decode``; it is intended for
47    string/JSON secrets rather than arbitrary binary payloads.
48    """
49    encoded = value.encode("utf-8") if isinstance(value, str) else value
50    return base64.b64encode(encoded).decode("utf-8")

Base64-encode a value and return the encoding as UTF-8 text.

The input is treated as UTF-8 text, mirroring b64decode; it is intended for string/JSON secrets rather than arbitrary binary payloads.

def create_builtin_filters() -> Dict[str, Callable]:
53def create_builtin_filters() -> t.Dict[str, t.Callable]:
54    return {
55        "b64decode": b64decode,
56        "b64encode": b64encode,
57    }
def environment(**kwargs: Any) -> jinja2.environment.Environment:
60def environment(**kwargs: t.Any) -> Environment:
61    extensions = kwargs.pop("extensions", [])
62    extensions.append("jinja2.ext.do")
63    extensions.append("jinja2.ext.loopcontrols")
64    env = Environment(extensions=extensions, **kwargs)
65    env.filters.update(create_builtin_filters())
66    return env
ENVIRONMENT = <jinja2.environment.Environment object>
class MacroReference(sqlmesh.utils.pydantic.PydanticModel):
72class MacroReference(PydanticModel, frozen=True):
73    package: t.Optional[str] = None
74    name: str
75
76    @property
77    def reference(self) -> str:
78        if self.package is None:
79            return self.name
80        return ".".join((self.package, self.name))
81
82    def __str__(self) -> str:
83        return self.reference

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes:
  • __class_vars__: The names of the class variables defined on the model.
  • __private_attributes__: Metadata about the private attributes of the model.
  • __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.
  • __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
  • __pydantic_core_schema__: The core schema of the model.
  • __pydantic_custom_init__: Whether the model has a custom __init__ function.
  • __pydantic_decorators__: Metadata containing the decorators defined on the model. This replaces Model.__validators__ and Model.__root_validators__ from Pydantic V1.
  • __pydantic_generic_metadata__: A dictionary containing metadata about generic Pydantic models. The origin and args items map to the [__origin__][genericalias.__origin__] and [__args__][genericalias.__args__] attributes of [generic aliases][types-genericalias], and the parameter item maps to the __parameter__ attribute of generic classes.
  • __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
  • __pydantic_post_init__: The name of the post-init method for the model, if defined.
  • __pydantic_root_model__: Whether the model is a [RootModel][pydantic.root_model.RootModel].
  • __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the model.
  • __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the model.
  • __pydantic_fields__: A dictionary of field names and their corresponding [FieldInfo][pydantic.fields.FieldInfo] objects.
  • __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [ComputedFieldInfo][pydantic.fields.ComputedFieldInfo] objects.
  • __pydantic_extra__: A dictionary containing extra values, if [extra][pydantic.config.ConfigDict.extra] is set to 'allow'.
  • __pydantic_fields_set__: The names of fields explicitly set during instantiation.
  • __pydantic_private__: Values of private attributes set on the model instance.
package: Optional[str]
name: str
reference: str
76    @property
77    def reference(self) -> str:
78        if self.package is None:
79            return self.name
80        return ".".join((self.package, self.name))
model_config = {'json_encoders': {<class 'sqlglot.expressions.core.Expr'>: <function _expression_encoder>, <class 'sqlglot.expressions.datatypes.DataType'>: <function _expression_encoder>, <class 'sqlglot.expressions.query.Tuple'>: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery]: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery, sqlmesh.core.dialect.MacroFunc]: <function _expression_encoder>, <class 'datetime.tzinfo'>: <function PydanticModel.<lambda>>}, 'arbitrary_types_allowed': True, 'extra': 'forbid', 'protected_namespaces': (), 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Inherited Members
pydantic.main.BaseModel
BaseModel
model_fields
model_computed_fields
model_extra
model_fields_set
model_construct
model_copy
model_dump
model_dump_json
model_json_schema
model_parametrized_name
model_post_init
model_rebuild
model_validate
model_validate_json
model_validate_strings
parse_file
from_orm
construct
schema
schema_json
validate
update_forward_refs
sqlmesh.utils.pydantic.PydanticModel
dict
json
copy
fields_set
parse_obj
parse_raw
missing_required_fields
extra_fields
all_fields
all_field_infos
required_fields
class MacroInfo(sqlmesh.utils.pydantic.PydanticModel):
86class MacroInfo(PydanticModel):
87    """Class to hold macro and its calls"""
88
89    definition: str
90    depends_on: t.List[MacroReference]
91    is_top_level: bool = False

Class to hold macro and its calls

definition: str
depends_on: List[MacroReference]
is_top_level: bool
model_config = {'json_encoders': {<class 'sqlglot.expressions.core.Expr'>: <function _expression_encoder>, <class 'sqlglot.expressions.datatypes.DataType'>: <function _expression_encoder>, <class 'sqlglot.expressions.query.Tuple'>: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery]: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery, sqlmesh.core.dialect.MacroFunc]: <function _expression_encoder>, <class 'datetime.tzinfo'>: <function PydanticModel.<lambda>>}, 'arbitrary_types_allowed': True, 'extra': 'forbid', 'protected_namespaces': ()}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Inherited Members
pydantic.main.BaseModel
BaseModel
model_fields
model_computed_fields
model_extra
model_fields_set
model_construct
model_copy
model_dump
model_dump_json
model_json_schema
model_parametrized_name
model_post_init
model_rebuild
model_validate
model_validate_json
model_validate_strings
parse_file
from_orm
construct
schema
schema_json
validate
update_forward_refs
sqlmesh.utils.pydantic.PydanticModel
dict
json
copy
fields_set
parse_obj
parse_raw
missing_required_fields
extra_fields
all_fields
all_field_infos
required_fields
class MacroReturnVal(builtins.Exception):
94class MacroReturnVal(Exception):
95    def __init__(self, val: t.Any):
96        self.value = val

Common base class for all non-exit exceptions.

MacroReturnVal(val: Any)
95    def __init__(self, val: t.Any):
96        self.value = val
value
Inherited Members
builtins.BaseException
with_traceback
args
class MacroExtractor(sqlglot.parser.Parser):
 99class MacroExtractor(Parser):
100    def extract(self, jinja: str, dialect: str = "") -> t.Dict[str, MacroInfo]:
101        """Extract a dictionary of macro definitions from a jinja string.
102
103        Args:
104            jinja: The jinja string to extract from.
105            dialect: The dialect of SQL.
106
107        Returns:
108            A dictionary of macro name to macro definition.
109        """
110        self.reset()
111        self.sql = jinja
112        self._tokens = Dialect.get_or_raise(dialect).tokenize(jinja)
113
114        # guard for older sqlglot versions (before 30.0.3)
115        if hasattr(self, "_tokens_size"):
116            # keep the cached length in sync
117            self._tokens_size = len(self._tokens)
118        self._index = -1
119        self._advance()
120
121        macros: t.Dict[str, MacroInfo] = {}
122
123        while self._curr:
124            if self._curr.token_type == TokenType.BLOCK_START:
125                macro_start = self._curr
126            elif self._tag == "MACRO" and self._next:
127                name = self._next.text
128                while self._curr and self._curr.token_type != TokenType.BLOCK_END:
129                    self._advance()
130
131                while self._curr and self._tag != "ENDMACRO":
132                    self._advance()
133
134                macro_str = self._find_sql(macro_start, self._next)
135                macros[name] = MacroInfo(
136                    definition=macro_str,
137                    depends_on=list(extract_macro_references_and_variables(macro_str)[0]),
138                )
139
140            self._advance()
141
142        return macros
143
144    def _advance(self, times: int = 1) -> None:
145        super()._advance(times)
146        self._tag = (
147            self._curr.text.upper()
148            if self._curr and self._prev and self._prev.token_type == TokenType.BLOCK_START
149            else ""
150        )

Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.

Arguments:
  • error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
  • error_message_context: The amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
  • max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
  • max_nodes: Maximum number of AST nodes to prevent memory exhaustion. Set to -1 (default) to disable the check.
def extract( self, jinja: str, dialect: str = '') -> Dict[str, MacroInfo]:
100    def extract(self, jinja: str, dialect: str = "") -> t.Dict[str, MacroInfo]:
101        """Extract a dictionary of macro definitions from a jinja string.
102
103        Args:
104            jinja: The jinja string to extract from.
105            dialect: The dialect of SQL.
106
107        Returns:
108            A dictionary of macro name to macro definition.
109        """
110        self.reset()
111        self.sql = jinja
112        self._tokens = Dialect.get_or_raise(dialect).tokenize(jinja)
113
114        # guard for older sqlglot versions (before 30.0.3)
115        if hasattr(self, "_tokens_size"):
116            # keep the cached length in sync
117            self._tokens_size = len(self._tokens)
118        self._index = -1
119        self._advance()
120
121        macros: t.Dict[str, MacroInfo] = {}
122
123        while self._curr:
124            if self._curr.token_type == TokenType.BLOCK_START:
125                macro_start = self._curr
126            elif self._tag == "MACRO" and self._next:
127                name = self._next.text
128                while self._curr and self._curr.token_type != TokenType.BLOCK_END:
129                    self._advance()
130
131                while self._curr and self._tag != "ENDMACRO":
132                    self._advance()
133
134                macro_str = self._find_sql(macro_start, self._next)
135                macros[name] = MacroInfo(
136                    definition=macro_str,
137                    depends_on=list(extract_macro_references_and_variables(macro_str)[0]),
138                )
139
140            self._advance()
141
142        return macros

Extract a dictionary of macro definitions from a jinja string.

Arguments:
  • jinja: The jinja string to extract from.
  • dialect: The dialect of SQL.
Returns:

A dictionary of macro name to macro definition.

Inherited Members
sqlglot.parser.Parser
Parser
FUNCTIONS
NO_PAREN_FUNCTIONS
STRUCT_TYPE_TOKENS
NESTED_TYPE_TOKENS
ENUM_TYPE_TOKENS
AGGREGATE_TYPE_TOKENS
TYPE_TOKENS
SIGNED_TO_UNSIGNED_TYPE_TOKEN
SUBQUERY_PREDICATES
SUBQUERY_TOKENS
RESERVED_TOKENS
DB_CREATABLES
CREATABLES
TRIGGER_EVENTS
ALTERABLES
ID_VAR_TOKENS
TABLE_ALIAS_TOKENS
ALIAS_TOKENS
COLON_PLACEHOLDER_TOKENS
ARRAY_CONSTRUCTORS
COMMENT_TABLE_ALIAS_TOKENS
UPDATE_ALIAS_TOKENS
TRIM_TYPES
IDENTIFIER_TOKENS
BRACKETS
COLUMN_POSTFIX_TOKENS
TABLE_POSTFIX_TOKENS
FUNC_TOKENS
CONJUNCTION
ASSIGNMENT
DISJUNCTION
EQUALITY
COMPARISON
BITWISE
TERM
FACTOR
EXPONENT
TIMES
TIMESTAMPS
SET_OPERATIONS
JOIN_METHODS
JOIN_SIDES
JOIN_KINDS
JOIN_HINTS
TABLE_TERMINATORS
LAMBDAS
TYPED_LAMBDA_ARGS
LAMBDA_ARG_TERMINATORS
COLUMN_OPERATORS
CAST_COLUMN_OPERATORS
EXPRESSION_PARSERS
STATEMENT_PARSERS
UNARY_PARSERS
STRING_PARSERS
NUMERIC_PARSERS
PRIMARY_PARSERS
PLACEHOLDER_PARSERS
RANGE_PARSERS
PIPE_SYNTAX_TRANSFORM_PARSERS
PROPERTY_PARSERS
CONSTRAINT_PARSERS
ALTER_PARSERS
ALTER_ALTER_PARSERS
SCHEMA_UNNAMED_CONSTRAINTS
NO_PAREN_FUNCTION_PARSERS
INVALID_FUNC_NAME_TOKENS
FUNCTIONS_WITH_ALIASED_ARGS
KEY_VALUE_DEFINITIONS
FUNCTION_PARSERS
QUERY_MODIFIER_PARSERS
QUERY_MODIFIER_TOKENS
SET_PARSERS
SHOW_PARSERS
TYPE_LITERAL_PARSERS
TYPE_CONVERTERS
DDL_SELECT_TOKENS
PRE_VOLATILE_TOKENS
TRANSACTION_KIND
TRANSACTION_CHARACTERISTICS
CONFLICT_ACTIONS
TRIGGER_TIMING
TRIGGER_DEFERRABLE
CREATE_SEQUENCE
ISOLATED_LOADING_OPTIONS
USABLES
CAST_ACTIONS
SCHEMA_BINDING_OPTIONS
PROCEDURE_OPTIONS
EXECUTE_AS_OPTIONS
KEY_CONSTRAINT_OPTIONS
WINDOW_EXCLUDE_OPTIONS
INSERT_ALTERNATIVES
CLONE_KEYWORDS
HISTORICAL_DATA_PREFIX
HISTORICAL_DATA_KIND
OPCLASS_FOLLOW_KEYWORDS
OPTYPE_FOLLOW_TOKENS
TABLE_INDEX_HINT_TOKENS
VIEW_ATTRIBUTES
WINDOW_ALIAS_TOKENS
WINDOW_BEFORE_PAREN_TOKENS
WINDOW_SIDES
JSON_KEY_VALUE_SEPARATOR_TOKENS
FETCH_TOKENS
ADD_CONSTRAINT_TOKENS
DISTINCT_TOKENS
UNNEST_OFFSET_ALIAS_TOKENS
SELECT_START_TOKENS
COPY_INTO_VARLEN_OPTIONS
IS_JSON_PREDICATE_KIND
ODBC_DATETIME_LITERALS
ON_CONDITION_TOKENS
PRIVILEGE_FOLLOW_TOKENS
DESCRIBE_STYLES
SET_ASSIGNMENT_DELIMITERS
ANALYZE_STYLES
ANALYZE_EXPRESSION_PARSERS
PARTITION_KEYWORDS
AMBIGUOUS_ALIAS_TOKENS
OPERATION_MODIFIERS
RECURSIVE_CTE_SEARCH_KIND
SECURITY_PROPERTY_KEYWORDS
MODIFIABLES
STRICT_CAST
PREFIXED_PIVOT_COLUMNS
IDENTIFY_PIVOT_STRINGS
LOG_DEFAULTS_TO_LN
TABLESAMPLE_CSV
DEFAULT_SAMPLING_METHOD
SET_REQUIRES_ASSIGNMENT_DELIMITER
TRIM_PATTERN_FIRST
STRING_ALIASES
MODIFIERS_ATTACHED_TO_SET_OP
SET_OP_MODIFIERS
NO_PAREN_IF_COMMANDS
JSON_ARROWS_REQUIRE_JSON_TYPE
COLON_IS_VARIANT_EXTRACT
VALUES_FOLLOWED_BY_PAREN
SUPPORTS_IMPLICIT_UNNEST
INTERVAL_SPANS
SUPPORTS_PARTITION_SELECTION
WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
OPTIONAL_ALIAS_TOKEN_CTE
ALTER_RENAME_REQUIRES_COLUMN
ALTER_TABLE_PARTITIONS
JOINS_HAVE_EQUAL_PRECEDENCE
ZONE_AWARE_TIMESTAMP_CONSTRUCTOR
MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS
JSON_EXTRACT_REQUIRES_JSON_EXPRESSION
ADD_JOIN_ON_TRUE
SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
SHOW_TRIE
SET_TRIE
error_level
error_message_context
max_errors
max_nodes
dialect
sql
errors
reset
raise_error
validate_expression
parse
parse_into
check_errors
expression
parse_set_operation
build_cast
def call_name(node: jinja2.nodes.Expr) -> Tuple[str, ...]:
153def call_name(node: nodes.Expr) -> t.Tuple[str, ...]:
154    if isinstance(node, nodes.Name):
155        return (node.name,)
156    if isinstance(node, nodes.Const):
157        return (f"'{node.value}'",)
158    if isinstance(node, nodes.Getattr):
159        return call_name(node.node) + (node.attr,)
160    if isinstance(node, (nodes.Getitem, nodes.Call)):
161        return call_name(node.node)
162    return ()
def render_jinja(query: str, methods: Optional[Dict[str, Any]] = None) -> str:
165def render_jinja(query: str, methods: t.Optional[t.Dict[str, t.Any]] = None) -> str:
166    return ENVIRONMENT.from_string(query).render(methods or {})
def find_call_names( node: jinja2.nodes.Node, vars_in_scope: Set[str]) -> Iterator[Tuple[Tuple[str, ...], Union[jinja2.nodes.Call, jinja2.nodes.Getattr]]]:
169def find_call_names(node: nodes.Node, vars_in_scope: t.Set[str]) -> t.Iterator[CallNames]:
170    vars_in_scope = vars_in_scope.copy()
171    for child_node in node.iter_child_nodes():
172        if "target" in child_node.fields:
173            # For nodes with assignment targets (Assign, AssignBlock, For, Import),
174            # the target name could shadow a reference in the right hand side.
175            # So we need to process the RHS before adding the target to scope.
176            # For example: {% set model = model.path %} should track model.path.
177            yield from find_call_names(child_node, vars_in_scope)
178
179            target = getattr(child_node, "target")
180            if isinstance(target, nodes.Name):
181                vars_in_scope.add(target.name)
182            elif isinstance(target, nodes.Tuple):
183                for item in target.items:
184                    if isinstance(item, nodes.Name):
185                        vars_in_scope.add(item.name)
186        elif isinstance(child_node, nodes.Macro):
187            for arg in child_node.args:
188                vars_in_scope.add(arg.name)
189        elif isinstance(child_node, nodes.Call) or (
190            isinstance(child_node, nodes.Getattr) and not isinstance(child_node.node, nodes.Getattr)
191        ):
192            name = call_name(child_node)
193            if name[0][0] != "'" and name[0] not in vars_in_scope:
194                yield (name, child_node)
195
196        if "target" not in child_node.fields:
197            yield from find_call_names(child_node, vars_in_scope)
def extract_call_names( jinja_str: str, cache: Optional[Dict[str, Tuple[List[Tuple[Tuple[str, ...], Union[jinja2.nodes.Call, jinja2.nodes.Getattr]]], bool]]] = None) -> List[Tuple[Tuple[str, ...], Union[jinja2.nodes.Call, jinja2.nodes.Getattr]]]:
200def extract_call_names(
201    jinja_str: str, cache: t.Optional[t.Dict[str, t.Tuple[t.List[CallNames], bool]]] = None
202) -> t.List[CallNames]:
203    def parse() -> t.List[CallNames]:
204        return list(find_call_names(ENVIRONMENT.parse(jinja_str), set()))
205
206    if cache is not None:
207        key = str(zlib.crc32(jinja_str.encode("utf-8")))
208        if key in cache:
209            names = cache[key][0]
210        else:
211            names = parse()
212        cache[key] = (names, True)
213        return names
214    return parse()
def is_variable_node(n: jinja2.nodes.Node) -> bool:
217def is_variable_node(n: nodes.Node) -> bool:
218    return (
219        isinstance(n, nodes.Call)
220        and isinstance(n.node, nodes.Name)
221        and n.node.name in (c.VAR, c.BLUEPRINT_VAR)
222    )
def extract_macro_references_and_variables( *jinja_strs: str) -> Tuple[Set[MacroReference], Set[str]]:
225def extract_macro_references_and_variables(
226    *jinja_strs: str,
227) -> t.Tuple[t.Set[MacroReference], t.Set[str]]:
228    macro_references = set()
229    variables = set()
230    for jinja_str in jinja_strs:
231        for call_name, node in extract_call_names(jinja_str):
232            if call_name[0] in (c.VAR, c.BLUEPRINT_VAR):
233                if not is_variable_node(node):
234                    # Find the variable node which could be nested
235                    for n in node.find_all(nodes.Call):
236                        if is_variable_node(n):
237                            node = n
238                            break
239                    else:
240                        raise ValueError(f"Could not find variable name in {jinja_str}")
241                node = t.cast(nodes.Call, node)
242                args = [jinja_call_arg_name(arg) for arg in node.args]
243                if args and args[0]:
244                    variables.add(args[0].lower())
245            elif call_name[0] == c.GATEWAY:
246                variables.add(c.GATEWAY)
247            elif len(call_name) == 1:
248                macro_references.add(MacroReference(name=call_name[0]))
249            elif len(call_name) == 2:
250                macro_references.add(MacroReference(package=call_name[0], name=call_name[1]))
251    return macro_references, variables
def sort_dict_recursive(item: Dict[str, Any]) -> Dict[str, Any]:
254def sort_dict_recursive(
255    item: t.Dict[str, t.Any],
256) -> t.Dict[str, t.Any]:
257    sorted_dict: t.Dict[str, t.Any] = {}
258    for k, v in sorted(item.items()):
259        if isinstance(v, list):
260            sorted_dict[k] = sorted(v)
261        elif isinstance(v, dict):
262            sorted_dict[k] = sort_dict_recursive(v)
263        else:
264            sorted_dict[k] = v
265    return sorted_dict
JinjaGlobalAttribute = typing.Union[str, int, float, bool, sqlmesh.utils.AttributeDict]
class JinjaMacroRegistry(sqlmesh.utils.pydantic.PydanticModel):
271class JinjaMacroRegistry(PydanticModel):
272    """Registry for Jinja macros.
273
274    Args:
275        packages: The mapping from package name to a collection of macro definitions.
276        root_macros: The collection of top-level macro definitions.
277        global_objs: The global objects.
278        create_builtins_module: The name of a module which defines the `create_builtins` factory
279            function that will be used to construct builtin variables and functions.
280        root_package_name: The name of the root package. If specified root macros will be available
281            as both `root_package_name.macro_name` and `macro_name`.
282        top_level_packages: The list of top-level packages. Macros in this packages will be available
283            as both `package_name.macro_name` and `macro_name`.
284    """
285
286    packages: t.Dict[str, t.Dict[str, MacroInfo]] = {}
287    root_macros: t.Dict[str, MacroInfo] = {}
288    global_objs: t.Dict[str, JinjaGlobalAttribute] = {}
289    create_builtins_module: t.Optional[str] = SQLMESH_JINJA_PACKAGE
290    root_package_name: t.Optional[str] = None
291    top_level_packages: t.List[str] = []
292
293    _parser_cache: t.Dict[t.Tuple[t.Optional[str], str], Template] = {}
294    _trimmed: bool = False
295    __environment: t.Optional[Environment] = None
296
297    def __getstate__(self) -> t.Dict[t.Any, t.Any]:
298        state = super().__getstate__()
299        private = state[PRIVATE_FIELDS]
300        private["_parser_cache"] = {}
301        private["_JinjaMacroRegistry__environment"] = None
302        return state
303
304    @field_validator("global_objs", mode="before")
305    @classmethod
306    def _validate_global_objs(cls, value: t.Any) -> t.Any:
307        def _normalize(val: t.Any) -> t.Any:
308            if isinstance(val, dict):
309                return AttributeDict({k: _normalize(v) for k, v in val.items()})
310            if isinstance(val, list):
311                return [_normalize(v) for v in val]
312            if isinstance(val, set):
313                return [_normalize(v) for v in sorted(val)]
314            if isinstance(val, Enum):
315                return val.value
316            return val
317
318        return _normalize(value)
319
320    @field_serializer("global_objs")
321    def _serialize_attribute_dict(
322        self, value: t.Dict[str, JinjaGlobalAttribute]
323    ) -> t.Dict[str, t.Any]:
324        # NOTE: This is called only when used with Pydantic V2.
325        def _convert(
326            val: t.Union[t.Dict[str, JinjaGlobalAttribute], t.Dict[str, t.Any]],
327        ) -> t.Dict[str, t.Any]:
328            return {k: _convert(v) if isinstance(v, AttributeDict) else v for k, v in val.items()}
329
330        return _convert(value)
331
332    @property
333    def trimmed(self) -> bool:
334        return self._trimmed
335
336    def add_macros(self, macros: t.Dict[str, MacroInfo], package: t.Optional[str] = None) -> None:
337        """Adds macros to the target package.
338
339        Args:
340            macros: Macros that should be added.
341            package: The name of the package the given macros belong to. If not specified, the provided
342            macros will be added to the root namespace.
343        """
344
345        if package is not None:
346            package_macros = self.packages.get(package, {})
347            package_macros.update(macros)
348            self.packages[package] = package_macros
349        else:
350            self.root_macros.update(macros)
351
352    def add_globals(self, globals: t.Dict[str, JinjaGlobalAttribute]) -> None:
353        """Adds global objects to the registry.
354
355        Args:
356            globals: The global objects that should be added.
357        """
358        # Keep the registry lightweight when the graph is not needed
359        if not "graph" in self.packages:
360            globals.pop("flat_graph", None)
361        self.global_objs.update(**self._validate_global_objs(globals))
362
363    def build_macro(self, reference: MacroReference, **kwargs: t.Any) -> t.Optional[t.Callable]:
364        """Builds a Python callable for a macro with the given reference.
365
366        Args:
367            reference: The macro reference.
368        Returns:
369            The macro as a Python callable or None if not found.
370        """
371        env: Environment = self.build_environment(**kwargs)
372        if reference.package is not None:
373            package = env.globals.get(reference.package, {})
374            return package.get(reference.name)  # type: ignore
375        return env.globals.get(reference.name)  # type: ignore
376
377    def build_environment(self, **kwargs: t.Any) -> Environment:
378        """Builds a new Jinja environment based on this registry."""
379
380        context: t.Dict[str, t.Any] = {}
381
382        root_macros = {
383            name: self._MacroWrapper(name, None, self, context)
384            for name, macro in self.root_macros.items()
385        }
386
387        package_macros: t.Dict[str, t.Any] = defaultdict(AttributeDict)
388        for package_name, macros in self.packages.items():
389            for macro_name, macro in macros.items():
390                macro_wrapper = self._MacroWrapper(macro_name, package_name, self, context)
391                package_macros[package_name][macro_name] = macro_wrapper
392                if macro.is_top_level and macro_name not in root_macros:
393                    root_macros[macro_name] = macro_wrapper
394
395        top_level_packages = self.top_level_packages.copy()
396
397        if self.root_package_name is not None:
398            package_macros[self.root_package_name].update(root_macros)
399            top_level_packages.append(self.root_package_name)
400
401        env = environment()
402
403        builtin_globals = self._create_builtin_globals(kwargs)
404        for top_level_package_name in top_level_packages:
405            # Make sure that the top-level package doesn't fully override the same builtin package.
406            package_macros[top_level_package_name] = AttributeDict(
407                {
408                    **(builtin_globals.pop(top_level_package_name, None) or {}),
409                    **(package_macros.get(top_level_package_name) or {}),
410                }
411            )
412            root_macros.update(package_macros[top_level_package_name])
413
414        context.update(builtin_globals)
415        context.update(root_macros)
416        context.update(package_macros)
417        context["render"] = lambda input: env.from_string(input).render()
418
419        env.globals.update(context)
420        env.filters.update(self._environment.filters)
421        return env
422
423    def trim(
424        self, dependencies: t.Iterable[MacroReference], package: t.Optional[str] = None
425    ) -> JinjaMacroRegistry:
426        """Trims the registry by keeping only macros with given references and their transitive dependencies.
427
428        Args:
429            dependencies: References to macros that should be kept.
430            package: The name of the package in the context of which the trimming should be performed.
431
432        Returns:
433            A new trimmed registry.
434        """
435        dependencies_by_package: t.Dict[t.Optional[str], t.Set[str]] = defaultdict(set)
436        for dep in dependencies:
437            dependencies_by_package[dep.package or package].add(dep.name)
438
439        top_level_packages = self.top_level_packages.copy()
440        if package is not None:
441            top_level_packages.append(package)
442
443        result = JinjaMacroRegistry(
444            global_objs=self.global_objs.copy(),
445            create_builtins_module=self.create_builtins_module,
446            root_package_name=self.root_package_name,
447            top_level_packages=top_level_packages,
448        )
449        for package, names in dependencies_by_package.items():
450            result = result.merge(self._trim_macros(names, package))
451
452        result._trimmed = True
453
454        return result
455
456    def merge(self, other: JinjaMacroRegistry) -> JinjaMacroRegistry:
457        """Returns a copy of the registry which contains macros from both this and `other` instances.
458
459        Args:
460            other: The other registry instance.
461
462        Returns:
463            A new merged registry.
464        """
465
466        root_macros = {
467            **self.root_macros,
468            **other.root_macros,
469        }
470
471        packages = {}
472        for package in {*self.packages, *other.packages}:
473            packages[package] = {
474                **self.packages.get(package, {}),
475                **other.packages.get(package, {}),
476            }
477
478        global_objs = {
479            **self.global_objs,
480            **other.global_objs,
481        }
482
483        return JinjaMacroRegistry(
484            packages=packages,
485            root_macros=root_macros,
486            global_objs=global_objs,
487            create_builtins_module=self.create_builtins_module or other.create_builtins_module,
488            root_package_name=self.root_package_name or other.root_package_name,
489            top_level_packages=[*self.top_level_packages, *other.top_level_packages],
490        )
491
492    def to_expressions(self) -> t.List[Expression]:
493        output: t.List[Expression] = []
494
495        filtered_objs = {
496            k: v for k, v in self.global_objs.items() if k in ("refs", "sources", "vars")
497        }
498        if filtered_objs:
499            output.append(
500                d.PythonCode(
501                    expressions=[
502                        f"{k} = '{v}'" if isinstance(v, str) else f"{k} = {v}"
503                        for k, v in sort_dict_recursive(filtered_objs).items()
504                    ]
505                )
506            )
507
508        for macro_name, macro_info in sorted(self.root_macros.items()):
509            output.append(d.jinja_statement(macro_info.definition))
510
511        for _, package in sorted(self.packages.items()):
512            for macro_name, macro_info in sorted(package.items()):
513                output.append(d.jinja_statement(macro_info.definition))
514
515        return output
516
517    @property
518    def data_hash_values(self) -> t.List[str]:
519        data = []
520
521        for macro_name, macro in sorted(self.root_macros.items()):
522            data.append(macro_name)
523            data.append(macro.definition)
524
525        for _, package in sorted(self.packages.items()):
526            for macro_name, macro in sorted(package.items()):
527                data.append(macro_name)
528                data.append(macro.definition)
529
530        trimmed_global_objs = {
531            k: self.global_objs[k] for k in ("refs", "sources", "vars") if k in self.global_objs
532        }
533        data.append(json.dumps(trimmed_global_objs, sort_keys=True))
534
535        return data
536
537    def __deepcopy__(self, memo: t.Optional[t.Dict[int, t.Any]] = None) -> JinjaMacroRegistry:
538        return JinjaMacroRegistry.parse_obj(self.dict())
539
540    def _parse_macro(self, name: str, package: t.Optional[str]) -> Template:
541        cache_key = (package, name)
542        if cache_key not in self._parser_cache:
543            macro = self._get_macro(name, package)
544
545            definition: nodes.Template = self._environment.parse(macro.definition)
546            if _is_private_macro(name):
547                # A workaround to expose private jinja macros.
548                definition = self._to_non_private_macro_def(name, definition)
549
550            self._parser_cache[cache_key] = self._environment.from_string(definition)
551        return self._parser_cache[cache_key]
552
553    @property
554    def _environment(self) -> Environment:
555        if self.__environment is None:
556            self.__environment = environment()
557            self.__environment.filters.update(self._create_builtin_filters())
558        return self.__environment
559
560    def _trim_macros(
561        self,
562        names: t.Set[str],
563        package: t.Optional[str] = None,
564        visited: t.Optional[t.Dict[t.Optional[str], t.Set[str]]] = None,
565    ) -> JinjaMacroRegistry:
566        if visited is None:
567            visited = defaultdict(set)
568
569        macros = self.packages.get(package, {}) if package is not None else self.root_macros
570        trimmed_macros = {}
571
572        dependencies: t.Dict[t.Optional[str], t.Set[str]] = defaultdict(set)
573
574        for name in names:
575            if name in macros and name not in visited[package]:
576                macro = macros[name]
577                trimmed_macros[name] = macro
578                for dependency in macro.depends_on:
579                    dependencies[dependency.package or package].add(dependency.name)
580                visited[package].add(name)
581
582        if package is not None:
583            result = JinjaMacroRegistry(packages={package: trimmed_macros})
584        else:
585            result = JinjaMacroRegistry(root_macros=trimmed_macros)
586
587        for upstream_package, upstream_names in dependencies.items():
588            result = result.merge(
589                self._trim_macros(upstream_names, upstream_package, visited=visited)
590            )
591
592        return result
593
594    def _macro_exists(self, name: str, package: t.Optional[str]) -> bool:
595        return (
596            name in self.packages.get(package, {})
597            if package is not None
598            else name in self.root_macros
599        )
600
601    def _get_macro(self, name: str, package: t.Optional[str]) -> MacroInfo:
602        return self.packages[package][name] if package is not None else self.root_macros[name]
603
604    def _to_non_private_macro_def(self, name: str, template: nodes.Template) -> nodes.Template:
605        for node in template.find_all((nodes.Macro, nodes.Call)):
606            if isinstance(node, nodes.Macro):
607                node.name = _non_private_name(name)
608            elif isinstance(node, nodes.Call) and isinstance(node.node, nodes.Name):
609                node.node.name = _non_private_name(name)
610
611        return template
612
613    def _create_builtin_globals(self, global_vars: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
614        """Creates Jinja builtin globals using a factory function defined in the provided module."""
615        engine_adapter = global_vars.pop("engine_adapter", None)
616        global_vars = {**self.global_objs, **global_vars}
617        if self.create_builtins_module is not None:
618            module = importlib.import_module(self.create_builtins_module)
619            if hasattr(module, "create_builtin_globals"):
620                return module.create_builtin_globals(self, global_vars, engine_adapter)
621        return global_vars
622
623    def _create_builtin_filters(self) -> t.Dict[str, t.Any]:
624        """Creates Jinja builtin filters using a factory function defined in the provided module."""
625        if self.create_builtins_module is not None:
626            module = importlib.import_module(self.create_builtins_module)
627            if hasattr(module, "create_builtin_filters"):
628                return module.create_builtin_filters()
629        return {}
630
631    class _MacroWrapper:
632        def __init__(
633            self,
634            name: str,
635            package: t.Optional[str],
636            registry: JinjaMacroRegistry,
637            context: t.Dict[str, t.Any],
638        ):
639            self.name = name
640            self.package = package
641            self.context = context
642            self.registry = registry
643
644        def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
645            context = self.context.copy()
646            if self.package is not None and self.package in context:
647                context.update(context[self.package])
648
649            template = self.registry._parse_macro(self.name, self.package)
650            macro_callable = getattr(
651                template.make_module(vars=context), _non_private_name(self.name)
652            )
653            try:
654                return macro_callable(*args, **kwargs)
655            except MacroReturnVal as ret:
656                return ret.value

Registry for Jinja macros.

Arguments:
  • packages: The mapping from package name to a collection of macro definitions.
  • root_macros: The collection of top-level macro definitions.
  • global_objs: The global objects.
  • create_builtins_module: The name of a module which defines the create_builtins factory function that will be used to construct builtin variables and functions.
  • root_package_name: The name of the root package. If specified root macros will be available as both root_package_name.macro_name and macro_name.
  • top_level_packages: The list of top-level packages. Macros in this packages will be available as both package_name.macro_name and macro_name.
packages: Dict[str, Dict[str, MacroInfo]]
root_macros: Dict[str, MacroInfo]
global_objs: Dict[str, Union[str, int, float, bool, sqlmesh.utils.AttributeDict]]
create_builtins_module: Optional[str]
root_package_name: Optional[str]
top_level_packages: List[str]
trimmed: bool
332    @property
333    def trimmed(self) -> bool:
334        return self._trimmed
def add_macros( self, macros: Dict[str, MacroInfo], package: Optional[str] = None) -> None:
336    def add_macros(self, macros: t.Dict[str, MacroInfo], package: t.Optional[str] = None) -> None:
337        """Adds macros to the target package.
338
339        Args:
340            macros: Macros that should be added.
341            package: The name of the package the given macros belong to. If not specified, the provided
342            macros will be added to the root namespace.
343        """
344
345        if package is not None:
346            package_macros = self.packages.get(package, {})
347            package_macros.update(macros)
348            self.packages[package] = package_macros
349        else:
350            self.root_macros.update(macros)

Adds macros to the target package.

Arguments:
  • macros: Macros that should be added.
  • package: The name of the package the given macros belong to. If not specified, the provided
  • macros will be added to the root namespace.
def add_globals( self, globals: Dict[str, Union[str, int, float, bool, sqlmesh.utils.AttributeDict]]) -> None:
352    def add_globals(self, globals: t.Dict[str, JinjaGlobalAttribute]) -> None:
353        """Adds global objects to the registry.
354
355        Args:
356            globals: The global objects that should be added.
357        """
358        # Keep the registry lightweight when the graph is not needed
359        if not "graph" in self.packages:
360            globals.pop("flat_graph", None)
361        self.global_objs.update(**self._validate_global_objs(globals))

Adds global objects to the registry.

Arguments:
  • globals: The global objects that should be added.
def build_macro( self, reference: MacroReference, **kwargs: Any) -> Optional[Callable]:
363    def build_macro(self, reference: MacroReference, **kwargs: t.Any) -> t.Optional[t.Callable]:
364        """Builds a Python callable for a macro with the given reference.
365
366        Args:
367            reference: The macro reference.
368        Returns:
369            The macro as a Python callable or None if not found.
370        """
371        env: Environment = self.build_environment(**kwargs)
372        if reference.package is not None:
373            package = env.globals.get(reference.package, {})
374            return package.get(reference.name)  # type: ignore
375        return env.globals.get(reference.name)  # type: ignore

Builds a Python callable for a macro with the given reference.

Arguments:
  • reference: The macro reference.
Returns:

The macro as a Python callable or None if not found.

def build_environment(self, **kwargs: Any) -> jinja2.environment.Environment:
377    def build_environment(self, **kwargs: t.Any) -> Environment:
378        """Builds a new Jinja environment based on this registry."""
379
380        context: t.Dict[str, t.Any] = {}
381
382        root_macros = {
383            name: self._MacroWrapper(name, None, self, context)
384            for name, macro in self.root_macros.items()
385        }
386
387        package_macros: t.Dict[str, t.Any] = defaultdict(AttributeDict)
388        for package_name, macros in self.packages.items():
389            for macro_name, macro in macros.items():
390                macro_wrapper = self._MacroWrapper(macro_name, package_name, self, context)
391                package_macros[package_name][macro_name] = macro_wrapper
392                if macro.is_top_level and macro_name not in root_macros:
393                    root_macros[macro_name] = macro_wrapper
394
395        top_level_packages = self.top_level_packages.copy()
396
397        if self.root_package_name is not None:
398            package_macros[self.root_package_name].update(root_macros)
399            top_level_packages.append(self.root_package_name)
400
401        env = environment()
402
403        builtin_globals = self._create_builtin_globals(kwargs)
404        for top_level_package_name in top_level_packages:
405            # Make sure that the top-level package doesn't fully override the same builtin package.
406            package_macros[top_level_package_name] = AttributeDict(
407                {
408                    **(builtin_globals.pop(top_level_package_name, None) or {}),
409                    **(package_macros.get(top_level_package_name) or {}),
410                }
411            )
412            root_macros.update(package_macros[top_level_package_name])
413
414        context.update(builtin_globals)
415        context.update(root_macros)
416        context.update(package_macros)
417        context["render"] = lambda input: env.from_string(input).render()
418
419        env.globals.update(context)
420        env.filters.update(self._environment.filters)
421        return env

Builds a new Jinja environment based on this registry.

def trim( self, dependencies: Iterable[MacroReference], package: Optional[str] = None) -> JinjaMacroRegistry:
423    def trim(
424        self, dependencies: t.Iterable[MacroReference], package: t.Optional[str] = None
425    ) -> JinjaMacroRegistry:
426        """Trims the registry by keeping only macros with given references and their transitive dependencies.
427
428        Args:
429            dependencies: References to macros that should be kept.
430            package: The name of the package in the context of which the trimming should be performed.
431
432        Returns:
433            A new trimmed registry.
434        """
435        dependencies_by_package: t.Dict[t.Optional[str], t.Set[str]] = defaultdict(set)
436        for dep in dependencies:
437            dependencies_by_package[dep.package or package].add(dep.name)
438
439        top_level_packages = self.top_level_packages.copy()
440        if package is not None:
441            top_level_packages.append(package)
442
443        result = JinjaMacroRegistry(
444            global_objs=self.global_objs.copy(),
445            create_builtins_module=self.create_builtins_module,
446            root_package_name=self.root_package_name,
447            top_level_packages=top_level_packages,
448        )
449        for package, names in dependencies_by_package.items():
450            result = result.merge(self._trim_macros(names, package))
451
452        result._trimmed = True
453
454        return result

Trims the registry by keeping only macros with given references and their transitive dependencies.

Arguments:
  • dependencies: References to macros that should be kept.
  • package: The name of the package in the context of which the trimming should be performed.
Returns:

A new trimmed registry.

def merge( self, other: JinjaMacroRegistry) -> JinjaMacroRegistry:
456    def merge(self, other: JinjaMacroRegistry) -> JinjaMacroRegistry:
457        """Returns a copy of the registry which contains macros from both this and `other` instances.
458
459        Args:
460            other: The other registry instance.
461
462        Returns:
463            A new merged registry.
464        """
465
466        root_macros = {
467            **self.root_macros,
468            **other.root_macros,
469        }
470
471        packages = {}
472        for package in {*self.packages, *other.packages}:
473            packages[package] = {
474                **self.packages.get(package, {}),
475                **other.packages.get(package, {}),
476            }
477
478        global_objs = {
479            **self.global_objs,
480            **other.global_objs,
481        }
482
483        return JinjaMacroRegistry(
484            packages=packages,
485            root_macros=root_macros,
486            global_objs=global_objs,
487            create_builtins_module=self.create_builtins_module or other.create_builtins_module,
488            root_package_name=self.root_package_name or other.root_package_name,
489            top_level_packages=[*self.top_level_packages, *other.top_level_packages],
490        )

Returns a copy of the registry which contains macros from both this and other instances.

Arguments:
  • other: The other registry instance.
Returns:

A new merged registry.

def to_expressions(self) -> List[sqlglot.expressions.core.Expression]:
492    def to_expressions(self) -> t.List[Expression]:
493        output: t.List[Expression] = []
494
495        filtered_objs = {
496            k: v for k, v in self.global_objs.items() if k in ("refs", "sources", "vars")
497        }
498        if filtered_objs:
499            output.append(
500                d.PythonCode(
501                    expressions=[
502                        f"{k} = '{v}'" if isinstance(v, str) else f"{k} = {v}"
503                        for k, v in sort_dict_recursive(filtered_objs).items()
504                    ]
505                )
506            )
507
508        for macro_name, macro_info in sorted(self.root_macros.items()):
509            output.append(d.jinja_statement(macro_info.definition))
510
511        for _, package in sorted(self.packages.items()):
512            for macro_name, macro_info in sorted(package.items()):
513                output.append(d.jinja_statement(macro_info.definition))
514
515        return output
data_hash_values: List[str]
517    @property
518    def data_hash_values(self) -> t.List[str]:
519        data = []
520
521        for macro_name, macro in sorted(self.root_macros.items()):
522            data.append(macro_name)
523            data.append(macro.definition)
524
525        for _, package in sorted(self.packages.items()):
526            for macro_name, macro in sorted(package.items()):
527                data.append(macro_name)
528                data.append(macro.definition)
529
530        trimmed_global_objs = {
531            k: self.global_objs[k] for k in ("refs", "sources", "vars") if k in self.global_objs
532        }
533        data.append(json.dumps(trimmed_global_objs, sort_keys=True))
534
535        return data
model_config = {'json_encoders': {<class 'sqlglot.expressions.core.Expr'>: <function _expression_encoder>, <class 'sqlglot.expressions.datatypes.DataType'>: <function _expression_encoder>, <class 'sqlglot.expressions.query.Tuple'>: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery]: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery, sqlmesh.core.dialect.MacroFunc]: <function _expression_encoder>, <class 'datetime.tzinfo'>: <function PydanticModel.<lambda>>}, 'arbitrary_types_allowed': True, 'extra': 'forbid', 'protected_namespaces': ()}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Arguments:
  • self: The BaseModel instance.
  • context: The context.
Inherited Members
pydantic.main.BaseModel
BaseModel
model_fields
model_computed_fields
model_extra
model_fields_set
model_construct
model_copy
model_dump
model_dump_json
model_json_schema
model_parametrized_name
model_rebuild
model_validate
model_validate_json
model_validate_strings
parse_file
from_orm
construct
schema
schema_json
validate
update_forward_refs
sqlmesh.utils.pydantic.PydanticModel
dict
json
copy
fields_set
parse_obj
parse_raw
missing_required_fields
extra_fields
all_fields
all_field_infos
required_fields
JINJA_REGEX = re.compile('({{|{%)')
def has_jinja(value: str) -> bool:
670def has_jinja(value: str) -> bool:
671    return JINJA_REGEX.search(value) is not None
def jinja_call_arg_name(node: jinja2.nodes.Node) -> str:
674def jinja_call_arg_name(node: nodes.Node) -> str:
675    if isinstance(node, nodes.Const):
676        return node.value
677    return ""
def create_var(variables: Dict[str, Any]) -> Callable:
680def create_var(variables: t.Dict[str, t.Any]) -> t.Callable:
681    def _var(var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]:
682        value = variables.get(var_name.lower(), default)
683        if isinstance(value, SqlValue):
684            return value.sql
685        return value
686
687    return _var
def create_builtin_globals( jinja_macros: JinjaMacroRegistry, global_vars: Dict[str, Any], *args: Any, **kwargs: Any) -> Dict[str, Any]:
690def create_builtin_globals(
691    jinja_macros: JinjaMacroRegistry, global_vars: t.Dict[str, t.Any], *args: t.Any, **kwargs: t.Any
692) -> t.Dict[str, t.Any]:
693    global_vars.pop(c.GATEWAY, None)
694    variables = global_vars.pop(c.SQLMESH_VARS, None) or {}
695    blueprint_variables = global_vars.pop(c.SQLMESH_BLUEPRINT_VARS, None) or {}
696    return {
697        **global_vars,
698        c.VAR: create_var(variables),
699        c.GATEWAY: lambda: variables.get(c.GATEWAY, None),
700        c.BLUEPRINT_VAR: create_var(blueprint_variables),
701    }
def make_jinja_registry( jinja_macros: JinjaMacroRegistry, package_name: str, jinja_references: Set[MacroReference]) -> JinjaMacroRegistry:
704def make_jinja_registry(
705    jinja_macros: JinjaMacroRegistry, package_name: str, jinja_references: t.Set[MacroReference]
706) -> JinjaMacroRegistry:
707    """
708    Creates a Jinja macro registry for a specific package.
709
710    This function takes an existing Jinja macro registry and returns a new
711    registry that includes only the macros associated with the specified
712    package and trims the registry to include only the macros referenced
713    in the provided set of macro references.
714
715    Args:
716        jinja_macros: The original Jinja macro registry containing all macros.
717        package_name: The name of the package for which to create the registry.
718        jinja_references: A set of macro references to retain in the new registry.
719
720    Returns:
721        A new JinjaMacroRegistry containing only the macros for the specified
722        package and the referenced macros.
723    """
724
725    jinja_registry = jinja_macros.copy()
726    jinja_registry.root_macros = jinja_registry.packages.get(package_name) or {}
727    jinja_registry = jinja_registry.trim(jinja_references)
728
729    return jinja_registry

Creates a Jinja macro registry for a specific package.

This function takes an existing Jinja macro registry and returns a new registry that includes only the macros associated with the specified package and trims the registry to include only the macros referenced in the provided set of macro references.

Arguments:
  • jinja_macros: The original Jinja macro registry containing all macros.
  • package_name: The name of the package for which to create the registry.
  • jinja_references: A set of macro references to retain in the new registry.
Returns:

A new JinjaMacroRegistry containing only the macros for the specified package and the referenced macros.

def extract_error_details(ex: Exception) -> str:
732def extract_error_details(ex: Exception) -> str:
733    """Extracts a readable message from a Jinja2 error, to include missing name and macro."""
734
735    error_details = ""
736    if isinstance(ex, UndefinedError):
737        if match := re.search(r"'(\w+)'", str(ex)):
738            error_details += f"\nUndefined macro/variable: '{match.group(1)}'"
739        try:
740            _, _, exc_traceback = exc_info()
741            for frame, _ in walk_tb(exc_traceback):
742                if frame.f_code.co_name == "_invoke":
743                    macro = frame.f_locals.get("self")
744                    if isinstance(macro, Macro):
745                        error_details += f" in macro: '{macro.name}'\n"
746                        break
747        except:
748            # to fall back to the generic error message if frame analysis fails
749            pass
750    return error_details or str(ex)

Extracts a readable message from a Jinja2 error, to include missing name and macro.