Coverage for physioblocks / configuration / aliases.py: 100%
66 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
1# SPDX-FileCopyrightText: Copyright INRIA
2#
3# SPDX-License-Identifier: LGPL-3.0-only
4#
5# Copyright INRIA
6#
7# This file is part of PhysioBlocks, a library mostly developed by the
8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9#
10# Authors:
11# - Colin Drieu
12# - Dominique Chapelle
13# - François Kimmig
14# - Philippe Moireau
15#
16# PhysioBlocks is free software: you can redistribute it and/or modify it under the
17# terms of the GNU Lesser General Public License as published by the Free Software
18# Foundation, version 3 of the License.
19#
20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23#
24# You should have received a copy of the GNU Lesser General Public License along with
25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
27"""
28Declares functions to handle configuration aliases.
30**Aliases** are :class:`~physioblocks.configuration.base.Configuration`
31instances representing a complete or incomplete type and that can be loaded
32and completed or overwritten with the load function.
33"""
35import functools
36from copy import copy
37from pathlib import Path
38from typing import Any, TypeAlias
40from physioblocks.configuration.base import Configuration
41from physioblocks.io.configuration import read_json
43ConfigurationAlias: TypeAlias = Configuration | dict[str, Any]
44"""type alias for configuration aliases"""
46__aliases_register: dict[str, ConfigurationAlias] = {}
49def add_alias(alias_key: str, alias: ConfigurationAlias) -> None:
50 """
51 Add an alias to the global alias register
53 :param alias_key: the alias unique key
54 :type alias_key: str
56 :param alias_key: the alias unique key
57 :type alias_key: str
58 """
59 if alias_key in __aliases_register:
60 raise KeyError(str.format("Alias key {0} is already registered.", alias_key))
62 if isinstance(alias, Configuration | dict) is False:
63 raise TypeError(
64 str.format(
65 "Incorrect type for alias: {0}, expected {1}",
66 type(alias).__name__,
67 Configuration.__name__,
68 )
69 )
71 __aliases_register[alias_key] = alias
74def has_alias(alias_key: str) -> bool:
75 """
76 Get if an alias is defined for the given key.
78 :param alias_key: the key to test
79 :type alias_key: str
81 :return: True if an alias is defined for the key, False otherwise
82 :rtype: bool
83 """
84 if alias_key in __aliases_register:
85 return True
87 # if the alias is not registered, check if the given alias key
88 # is a path to the alias configuration file.
89 alias_path = Path(alias_key)
90 return alias_path.exists() is True and alias_path.is_file() is True
93@functools.singledispatch
94def unwrap_aliases(item: Any) -> Any:
95 """
96 Process the given item and create a new one with all its alias replaced with
97 their matching configuration object recursivly.
99 :param item: the item to process
100 :param item: Configuration | dict | list
102 :return: the item to process
103 :rtype: Configuration | dict | list
104 """
105 raise TypeError(str.format("Type {0} can not load aliases.", type(item).__name__))
108@unwrap_aliases.register
109def _unwrap_aliases_config(configuration: Configuration) -> Any:
110 new_config: ConfigurationAlias = configuration.copy()
112 # recursively unwrap the alias defined by the label
113 while isinstance(new_config, Configuration) and has_alias(new_config.label):
114 new_alias = get_alias(new_config.label)
115 new_config = _update_configuration(new_config, new_alias)
117 # update the alias values with the current config values
118 new_config = _update_configuration(configuration, new_config)
120 # The unwrap configuration values
121 if isinstance(new_config, Configuration):
122 values_config: dict[str, Any] = unwrap_aliases(new_config.configuration_items)
123 new_config.configuration_items = values_config
124 return new_config
125 else:
126 return unwrap_aliases(new_config)
129@unwrap_aliases.register
130def _unwrap_aliases_dict(items: dict) -> dict[str, Any]: # type: ignore
131 return {
132 key: value
133 if isinstance(value, Configuration | dict | list) is False
134 else unwrap_aliases(value)
135 for key, value in items.items()
136 }
139@unwrap_aliases.register
140def _unwrap_aliases_list(items: list) -> list[Any]: # type: ignore
141 return [
142 value
143 if isinstance(value, Configuration | dict | list) is False
144 else unwrap_aliases(value)
145 for value in items
146 ]
149def get_alias(alias_key: str) -> Any:
150 """
151 Get an alias from the global alias register.
153 :param alias_key: the alias unique key
154 :type alias_key: str
156 :return: the alias
157 :rtype: Configuration | dict | list
158 """
160 alias_config = None
161 if alias_key in __aliases_register:
162 alias_config = copy(__aliases_register[alias_key])
163 else:
164 alias_config = read_json(alias_key)
166 return alias_config
169def _update_configuration(
170 configuration: Configuration, alias: ConfigurationAlias
171) -> ConfigurationAlias:
172 if isinstance(alias, Configuration):
173 new_values = alias.configuration_items.copy()
174 new_values = _recursive_update(new_values, configuration.configuration_items)
175 return Configuration(alias.label, new_values)
176 elif isinstance(alias, dict):
177 new_values = alias.copy()
178 new_values = _recursive_update(new_values, configuration.configuration_items)
179 return new_values
180 raise TypeError(str.format("{0} is not a valid alias type.", type(alias).__name__))
183def _recursive_update(
184 updated: dict[str, Any], updater: dict[str, Any]
185) -> dict[str, Any]:
186 new_values = updated.copy()
187 for key, value in updater.items():
188 if key in updated and isinstance(value, dict):
189 new_values[key] = _recursive_update(updated[key], value)
190 else:
191 new_values[key] = value
193 return new_values
196def remove_alias(alias_key: str) -> None:
197 """
198 Remove an alias from the global alias register
200 :param alias_key: the alias unique key
201 :type alias_key: str
202 """
203 __aliases_register.pop(alias_key)