Skip to content

API Reference

This module contains all YOCO functions to load and save configurations.

YOCO is based on Python dictionaries and YAML files to provide a simple, yet powerful way of configuring Python projects. YOCO supports specifying parameters through the command line, YAML-files or directly from a Python dictionary.

load_config(config_dict, current_dict=None, parent=None, search_paths=None)

Load a config dictionary.

If a key is already in current_dict, config_dict will overwrite it.

Parameters:

Name Type Description Default
config_dict dict

Configuration dictionary to be parsed.

required
current_dict Optional[dict]

Current configuration dictionary to be updated, will not be changed.

None
parent Optional[str]

Path of parent config. Used to resolve relative paths.

None
search_paths Optional[List[str]]

Search paths used to resolve config files. See resolve_path for more information.

None

Returns:

Type Description
dict

Loaded / updated configuration dictionary.

Source code in yoco.py
147
148
149
150
151
152
153
154
155
156
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
184
185
186
187
188
189
190
191
192
193
def load_config(
    config_dict: dict,
    current_dict: Optional[dict] = None,
    parent: Optional[str] = None,
    search_paths: Optional[List[str]] = None,
) -> dict:
    """Load a config dictionary.

    If a key is already in current_dict, config_dict will overwrite it.

    Args:
        config_dict: Configuration dictionary to be parsed.
        current_dict:
            Current configuration dictionary to be updated, will not be changed.
        parent: Path of parent config. Used to resolve relative paths.
        search_paths:
            Search paths used to resolve config files.
            See resolve_path for more information.

    Returns:
        Loaded / updated configuration dictionary.
    """
    config_dict = copy.deepcopy(config_dict)
    if current_dict is None:
        current_dict = {}
    else:
        current_dict = copy.deepcopy(current_dict)

    # 1. handle config key if present
    if "config" in config_dict:
        current_dict = _resolve_config_key(
            config_dict, current_dict, parent, search_paths
        )

    config_dict.pop("config", None)

    # 2. handle !include tag
    config_dict = _resolve_include_tags_recursively(config_dict, parent, search_paths)

    # 3. resolve automatically recognized paths (./, ../, ~)
    if parent is not None:
        _resolve_paths_recursively(config_dict, parent)

    # 4. merge config_dict into current_dict
    current_dict = _merge_dictionaries(current_dict, config_dict)

    return current_dict

load_config_from_args(parser, args=None, search_paths=None)

Parse arguments and load configs into a config dictionary.

Strings following -- will be used as key. Dots in that string are used to access nested dictionaries. YAML will be used for type conversion of the value.

Parameters:

Name Type Description Default
parser ArgumentParser

Parser used to parse known and unknown arguments. This function will handle both the known args that have been added before and it tries to parse all other args and integrate them into the config.

required
args Optional[list]

List of arguments to parse. If None, sys.argv[1:] is used.

None
search_paths Optional[List[str]]

Search paths used to resolve config files. See resolve_path for more information.

None

Returns:

Type Description
dict

Loaded configuration dictionary.

Source code in yoco.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 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
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def load_config_from_args(
    parser: _argparse.ArgumentParser,
    args: Optional[list] = None,
    search_paths: Optional[List[str]] = None,
) -> dict:
    """Parse arguments and load configs into a config dictionary.

    Strings following -- will be used as key. Dots in that string are used to access
    nested dictionaries. YAML will be used for type conversion of the value.

    Args:
        parser:
            Parser used to parse known and unknown arguments.
            This function will handle both the known args that have been added before
            and it tries to parse all other args and integrate them into the config.
        args:
            List of arguments to parse. If None, sys.argv[1:] is used.
        search_paths:
            Search paths used to resolve config files.
            See resolve_path for more information.

    Returns:
        Loaded configuration dictionary.
    """
    # parse arguments
    no_default_parser = copy.deepcopy(parser)
    for a in no_default_parser._actions:
        if a.dest != "config":
            a.default = None
    known, other_args = no_default_parser.parse_known_args(args)

    known_with_default, _ = parser.parse_known_args(args)

    config_dict = {k: v for k, v in vars(known).items() if v is not None}
    with_default_config_dict = {
        k: v for k, v in vars(known_with_default).items() if v is not None
    }
    with_default_config_dict.pop("config", None)

    config_dict = load_config(config_dict, search_paths=search_paths)
    current_key = None

    # list of unknown args (all strings) to dictionary
    # [--arg_1, val_1, --arg_2, val_2_a, val_2_b, ...]
    # -> {arg_1: val_1, arg_2: "{val_2_a} {val_2_b}, ...}
    arg_dict = {}
    for arg in other_args:
        if arg.startswith("--"):
            current_key = arg.replace("--", "")
            arg_dict[current_key] = []
        else:
            if current_key is None:
                parser.error(
                    message="General args need to start with --name {values}\n"
                )
            arg_dict[current_key].append(arg)
    arg_dict = {key: " ".join(values) for key, values in arg_dict.items()}
    # done parsing args, stored in arg_dict

    # integrate arg, value pairs into config_dict loaded before, one by one
    # args can set nested values by using dots
    # i.e., a.b.c will result in the following add_dict: {"a": {"b": {"c": value}}}
    # "config" can still be used to load files
    for arg, value in arg_dict.items():
        add_dict = {}
        current_dict = add_dict
        hierarchy = arg.split(".")
        for a in hierarchy[:-1]:
            current_dict[a] = {}
            current_dict = current_dict[a]
        # parse value using yaml (this allows setting lists, dictionaries, etc.)
        current_dict[hierarchy[-1]] = _yaml.load(value)

        if hierarchy[0] == "config":
            # handle special "config" key by loading the nested dict as root dict
            # this will practically replace "config" key with the dict from
            # the specified file
            add_dict = load_config(add_dict, search_paths=search_paths)
            # config file -> lower priority than what is already there
            config_dict = load_config(config_dict, add_dict, search_paths=search_paths)
        else:
            # integrate nested dictionary into config_dict loaded before
            # normal argument -> higher priority than what is arleady there
            config_dict = load_config(add_dict, config_dict)

    # add default values last (lowest priority) if they weren't specified so far
    config_dict = _merge_dictionaries(with_default_config_dict, config_dict)

    return config_dict

load_config_from_file(path, current_dict=None, parent=None, search_paths=None)

Load configuration from a file.

Parameters:

Name Type Description Default
path str

Path of YAML file to load.

required
current_dict Optional[dict]

Current configuration dictionary. Will not be modified. If None, an empty dictionary will be created.

None
parent Optional[str]

Parent directory. If not None, path will be assumed to be relative to parent.

None
search_paths Optional[List[str]]

Search paths used to resolve config files. See resolve_path for more information.

None

Returns:

Type Description
dict

Updated configuration dictionary.

Source code in yoco.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def load_config_from_file(
    path: str,
    current_dict: Optional[dict] = None,
    parent: Optional[str] = None,
    search_paths: Optional[List[str]] = None,
) -> dict:
    """Load configuration from a file.

    Args:
        path: Path of YAML file to load.
        current_dict:
            Current configuration dictionary. Will not be modified.
            If None, an empty dictionary will be created.
        parent:
            Parent directory.
            If not None, path will be assumed to be relative to parent.
        search_paths:
            Search paths used to resolve config files.
            See resolve_path for more information.

    Returns:
        Updated configuration dictionary.
    """
    if current_dict is None:
        current_dict = {}

    full_path = resolve_path(path, parent, search_paths)

    parent = _os.path.dirname(full_path)

    with open(full_path) as f:
        config_dict = _yaml.load(f)
        current_dict = load_config(config_dict, current_dict, parent, search_paths)

    return current_dict

resolve_path(path, parent=None, search_paths=None)

Resolves a path to a full absolute path based on parent and search_paths.

This function considers paths of 5 different cases:

  • /... Absolute path, nothing todo.
  • ~/... Home dir, expand user.
  • ./... Relative to parent (current directory if parent is None).
  • ../... Relative to parent (current directory if parent is None).
  • ... Relative to search paths. If search_paths is None: relative to parent and current working directory is assumed, in that order (i.e., search_paths=[".", ""]).

Relative search paths such as "." or "./" will be relative to parent (or current directory if parent is None). The empty search path "" refers to the current directory and is not automatically included.

I.e., if parent is None "." and "" refer to the same path. While if parent is not None, "." means relative to the parent and "" means relative to current directory.

Parameters:

Name Type Description Default
path str

The path to resolve.

required
parent Optional[str]

Parent folder. Will be used for explicit relative paths (./ or ../).

None
search_paths Optional[List[str]]

List of search paths to prepend relative paths which are not explicitly relative to current directory. If None, no search paths are assumed.

None

Returns:

Type Description
str

The normalized resolved path. Original path, if path could not be resolved.

Source code in yoco.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def resolve_path(
    path: str, parent: Optional[str] = None, search_paths: Optional[List[str]] = None
) -> str:
    """Resolves a path to a full absolute path based on parent and search_paths.

    This function considers paths of 5 different cases:

    - `/...` Absolute path, nothing todo.
    - `~/...` Home dir, expand user.
    - `./...` Relative to parent (current directory if parent is None).
    - `../...` Relative to parent (current directory if parent is None).
    - `...`
        Relative to search paths.
        If search_paths is None: relative to parent and current working directory
        is assumed, in that order (i.e., `search_paths=[".", ""]`).

    Relative search paths such as `"."` or `"./"` will be relative to parent (or current
    directory if parent is None). The empty search path `""` refers to the current
    directory and is not automatically included.

    I.e., if parent is None `"."` and `""` refer to the same path. While if parent is
    not None, `"."` means relative to the parent and `""` means relative to current
    directory.

    Args:
        path: The path to resolve.
        parent: Parent folder. Will be used for explicit relative paths (./ or ../).
        search_paths:
            List of search paths to prepend relative paths which are not explicitly
            relative to current directory.
            If None, no search paths are assumed.

    Returns:
        The normalized resolved path. Original path, if path could not be resolved.
    """
    if search_paths is None:
        search_paths = [".", ""]
    if parent is None:
        parent = "."

    # handle paths starting with / or ~
    if _os.path.isabs(path):
        return _os.path.normpath(path)
    elif path.startswith("~/"):
        return _os.path.normpath(_os.path.expanduser(path))

    # handle paths starting with . or ..
    parts = path.split(_os.sep)
    if parts[0] in [".", ".."]:
        return _os.path.normpath(_os.path.join(parent, path))

    # handle paths without prefix
    for search_path in search_paths:
        resolved_path = resolve_path(_os.path.join(search_path, path), parent, [])
        if _os.path.exists(resolved_path):
            return _os.path.normpath(resolved_path)

    return path

save_config_to_file(path, config_dict)

Save config dictionary as a yaml file.

Source code in yoco.py
256
257
258
259
def save_config_to_file(path: str, config_dict: dict) -> None:
    """Save config dictionary as a yaml file."""
    with open(path, "w") as f:
        _yaml.dump(config_dict, f)