Source code for jinjyaml.functions
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Type, Union
import jinja2
import yaml
from .data import Data
if TYPE_CHECKING: # pragma: no cover
from yaml.cyaml import _CLoader
from yaml.loader import _Loader
__all__ = ["extract"]
[docs]
def extract(
obj: Any,
loader_type: Type[Union[_Loader, _CLoader]],
env: Optional[jinja2.Environment] = None,
context: Optional[Mapping[str, Any]] = None,
inplace: bool = False,
) -> Any:
"""Recursively render and parse template tag objects in a YAML document tree.
This function processes an object that may contain :class:`.Data` instances, such as lists or dictionaries.
It can handle the following types of input:
- A mapping or sequence object returned by a `PyYAML Loader`:
1. Recursively searches for :class:`.Data` objects within ``obj``.
2. Renders the :attr:`.Data.source` attribute as a string source for a :class:`jinja2.Template`.
3. Parses the rendered string using the same `PyYAML Loader` that loaded ``obj``.
4. Returns the entire ``obj`` with :class:`.Data` objects replaced by their corresponding parsed Python objects.
- A single :class:`.Data` object:
1. Renders its :attr:`.Data.source` attribute as a string source for a :class:`jinja2.Template`.
2. Parses the rendered string using the same `PyYAML Loader` that loaded ``obj``.
3. Returns the parsed Python object.
- Other scalar objects returned by a `PyYAML Loader`:
Directly returns ``obj`` without any changes.
Note:
- ``obj`` must be a mutable :class:`dict` or :class:`list`-like object if ``inplace`` is :data:`True`.
- If ``obj`` is an instance of :class:`.Data`, it will **not** be changed, even if ``inplace`` is set to :data:`True`.
However, nested :class:`.Data` objects within mutable structures will still be replaced.
- The return value is always the parsed result.
Args:
obj: An object that may contain :class:`.Data` instances.
loader_type: The `PyYAML Loader` used to load ``obj``.
env: The :class:`jinja2.Environment` for template rendering (optional).
context: A dictionary of variable name-value pairs for :mod:`jinja2` template rendering (optional).
inplace: Whether to perform an in-place replacement of :class:`.Data` objects within ``obj``.
- When :data:`True`:
Replaces every :class:`.Data` object with its corresponding parsed Python object within the passed-in ``obj``.
- When :data:`False` (default):
Renders and parses every :class:`.Data` object with its corresponding parsed Python object without modifying the passed-in object.
Returns:
The final parsed and extracted Python object.
"""
if isinstance(obj, Data):
tpl = env.from_string(obj.source) if env else jinja2.Template(obj.source)
s = tpl.render(**(context or dict()))
d = yaml.load(s, loader_type)
return extract(d, loader_type, env, context, inplace)
elif isinstance(obj, Mapping):
if inplace:
if not isinstance(obj, MutableMapping): # pragma: no cover
raise ValueError(f"{obj!r} is not mutable")
for k, v in obj.items():
obj[k] = extract(v, loader_type, env, context, inplace)
else:
return {k: extract(v, loader_type, env, context, inplace) for k, v in obj.items()}
elif isinstance(obj, Sequence) and not isinstance(obj, (bytearray, bytes, memoryview, str)):
if inplace:
if not isinstance(obj, MutableSequence): # pragma: no cover
raise ValueError(f"{obj!r} is not mutable")
for i, v in enumerate(obj):
obj[i] = extract(v, loader_type, env, context, inplace)
else:
return [extract(m, loader_type, env, context, inplace) for m in obj]
return obj