Skip to content

RecursiveLoader

laktory.yaml.RecursiveLoader ¤

RecursiveLoader(stream, parent_loader=None)

Bases: SafeLoader

METHOD DESCRIPTION
preprocess_stream

Reformat content to be YAML safe

load

Load yaml file with support for reference to external yaml and sql files using

inject_constructor

Inject content of another YAML file.

merge_constructor

Merge content of another YAML file into the current dictionary.

append_constructor

Append content of another YAML file to the current list.

custom_mapping_constructor

Custom handling for mappings to support !merge.

custom_sequence_constructor

Custom handling for sequences to support !append.

Source code in laktory/yaml/recursiveloader.py
13
14
15
16
17
18
19
20
def __init__(self, stream, parent_loader=None):
    self.dirpath = Path("./")
    stream = self.preprocess_stream(stream)

    self.variables = []
    if parent_loader:
        self.variables = parent_loader.variables
    super().__init__(stream)

Functions¤

preprocess_stream ¤

preprocess_stream(stream)

Reformat content to be YAML safe

Source code in laktory/yaml/recursiveloader.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def preprocess_stream(self, stream):
    """Reformat content to be YAML safe"""

    if hasattr(stream, "name"):
        self.dirpath = Path(stream.name).parent

    _lines = []
    for line in stream.readlines():
        if "${include." in line:
            raise ValueError(
                "The `${include.}` syntax has been deprecated in laktory 0.6.0. Please use `!use`, `!update` and `!extend` tags instead."
            )
        _lines += [line.replace("<<:", MERGE_KEY + ":")]

    return "\n".join(_lines)

load classmethod ¤

load(stream, parent_loader=None)

Load yaml file with support for reference to external yaml and sql files using !use, !extend and !update tags. Path to external files can be defined using model or environment variables.

Custom Tags

!use {filepath}: Directly inject the content of the file at filepath - !extend {filepath}: Extend the current list with the elements found in the file at filepath. Similar to python list.extend method. <<: !update {filepath}: Merge the current dictionary with the content of the dictionary defined at filepath. Similar to python dict.update method.

PARAMETER DESCRIPTION
stream

file object structured as a yaml file

parent_loader

Parent loader if file loader from another loader.

TYPE: RecursiveLoader DEFAULT: None

RETURNS DESCRIPTION

Dict or list

Examples:

businesses:
  apple:
    symbol: aapl
    address: !use addresses.yaml
    <<: !update common.yaml
    emails:
      - jane.doe@apple.com
      - extend! emails.yaml
  amazon:
    symbol: amzn
    address: !use addresses.yaml
    <<: update! common.yaml
    emails:
      - john.doe@amazon.com
      - extend! emails.yaml
Source code in laktory/yaml/recursiveloader.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@classmethod
def load(cls, stream, parent_loader: "RecursiveLoader" = None):
    """
    Load yaml file with support for reference to external yaml and sql files using
    `!use`, `!extend` and `!update` tags.
    Path to external files can be defined using model or environment variables.

    Custom Tags
    -----------
    !use {filepath}:
        Directly inject the content of the file at `filepath`
    - !extend {filepath}:
        Extend the current list with the elements found in the file at `filepath`.
        Similar to python list.extend method.
    <<: !update {filepath}:
        Merge the current dictionary with the content of the dictionary defined at
        `filepath`. Similar to python dict.update method.

    Parameters
    ----------
    stream:
        file object structured as a yaml file
    parent_loader:
        Parent loader if file loader from another loader.

    Returns
    -------
    :
        Dict or list

    Examples
    --------
    ```yaml
    businesses:
      apple:
        symbol: aapl
        address: !use addresses.yaml
        <<: !update common.yaml
        emails:
          - jane.doe@apple.com
          - extend! emails.yaml
      amazon:
        symbol: amzn
        address: !use addresses.yaml
        <<: update! common.yaml
        emails:
          - john.doe@amazon.com
          - extend! emails.yaml
    ```
    """
    loader = cls(stream, parent_loader)
    try:
        return loader.get_single_data()
    finally:
        loader.dispose()

inject_constructor staticmethod ¤

inject_constructor(loader, node)

Inject content of another YAML file.

Source code in laktory/yaml/recursiveloader.py
115
116
117
118
119
120
121
122
123
124
125
126
127
@staticmethod
def inject_constructor(loader, node):
    """Inject content of another YAML file."""

    filepath = loader.get_path(loader, node)

    if filepath.endswith(".sql"):
        with open(filepath, "r", encoding="utf-8") as _fp:
            data = _fp.read()
        return data

    with open(filepath, "r") as f:
        return RecursiveLoader.load(f, parent_loader=loader)

merge_constructor staticmethod ¤

merge_constructor(loader, node)

Merge content of another YAML file into the current dictionary.

Source code in laktory/yaml/recursiveloader.py
129
130
131
132
133
134
135
136
137
138
139
140
141
@staticmethod
def merge_constructor(loader, node):
    """Merge content of another YAML file into the current dictionary."""

    filepath = loader.get_path(loader, node)
    with open(filepath, "r") as f:
        merge_data = RecursiveLoader.load(f, parent_loader=loader)

    if not isinstance(merge_data, dict):
        raise TypeError(
            f"Expected a dictionary in {filepath}, but got {type(merge_data).__name__}"
        )
    return merge_data

append_constructor staticmethod ¤

append_constructor(loader, node)

Append content of another YAML file to the current list.

Source code in laktory/yaml/recursiveloader.py
143
144
145
146
147
148
149
150
151
152
153
154
@staticmethod
def append_constructor(loader, node):
    """Append content of another YAML file to the current list."""

    filepath = loader.get_path(loader, node)
    with open(filepath, "r") as f:
        append_data = RecursiveLoader.load(f, parent_loader=loader)
    if not isinstance(append_data, list):
        raise TypeError(
            f"Expected a list in {filepath}, but got {type(append_data).__name__}"
        )
    return append_data

custom_mapping_constructor staticmethod ¤

custom_mapping_constructor(loader, node)

Custom handling for mappings to support !merge.

Source code in laktory/yaml/recursiveloader.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
@staticmethod
def custom_mapping_constructor(loader, node):
    """Custom handling for mappings to support !merge."""

    # read variables
    _vars = {}
    for key_node, value_node in node.value:
        key = loader.construct_object(key_node)
        if key == VARIABLES_KEY:
            _vars = loader.construct_object(value_node)
    loader.variables += [_vars]

    # read include and merge
    mapping = {}
    for key_node, value_node in node.value:
        key = loader.construct_object(key_node)
        value = loader.construct_object(value_node)
        if key == MERGE_KEY and isinstance(value, dict):
            # Merge the dictionary directly into the parent mapping
            mapping.update(value)
        else:
            mapping[key] = value

    # remove variables
    del loader.variables[-1]

    return mapping

custom_sequence_constructor staticmethod ¤

custom_sequence_constructor(loader, node)

Custom handling for sequences to support !append.

Source code in laktory/yaml/recursiveloader.py
186
187
188
189
190
191
192
193
194
195
196
197
@staticmethod
def custom_sequence_constructor(loader, node):
    """Custom handling for sequences to support !append."""

    seq = []
    for child in node.value:
        if child.tag == "!extend":
            append_data = loader.construct_object(child)
            seq.extend(append_data)  # Flatten the appended list
        else:
            seq.append(loader.construct_object(child))
    return seq