Coverage for pyTooling/Configuration/__init__.py: 96%
70 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-22 23:12 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-22 23:12 +0000
1# ==================================================================================================================== #
2# _____ _ _ ____ __ _ _ _ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ __ #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` || | / _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| || |__| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| #
7# |_| |___/ |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2021-2026 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32Abstract configuration reader.
34.. hint::
36 See :ref:`high-level help <CONFIG>` for explanations and usage examples.
38.. seealso::
40 :mod:`pyTooling.Configuration.JSON`
41 |rarr| A configuration read from a JSON file.
42 :mod:`pyTooling.Configuration.YAML`
43 |rarr| A configuration read from a YAML file.
44 :mod:`pyTooling.GenericPath`
45 |rarr| The path expressions a configuration is queried with.
46"""
47from __future__ import annotations
49from pathlib import Path
50from typing import Union, ClassVar, Iterator, Optional as Nullable
52from pyTooling.Decorators import export, readonly
53from pyTooling.MetaClasses import ExtendedType, abstractmethod, mixin
54from pyTooling.Exceptions import ConfigurationError
57__all__ = ["KeyT", "NodeT", "ValueT"]
60KeyT = Union[str, int] #: Type variable for keys.
61NodeT = Union["Dictionary", "Sequence"] #: Type variable for nodes.
62ValueT = Union[NodeT, str, int, float] #: Type variable for values.
65@export
66class KeyNotFoundError(ConfigurationError):
67 """
68 The requested key or index doesn't exist in the configuration node.
70 The key was neither found as a string, nor converted to an integer or float. A note lists the keys or the index
71 range offered by the node.
72 """
75@export
76class UnsupportedValueTypeError(ConfigurationError):
77 """
78 The configuration file parser returned a value of a type that isn't supported by :mod:`pyTooling.Configuration`.
80 Supported are scalars (:class:`str`, :class:`int`, :class:`float`) and the parser's dictionary and sequence types.
81 """
84@export
85class InterpolationError(ConfigurationError):
86 """A variable reference (``${...}``) in a configuration value is malformed or can't be resolved."""
89@export
90class PathExpressionError(ConfigurationError):
91 """A path expression (``a:b:c``) doesn't describe a valid node or value in the configuration."""
94@export
95class Node(metaclass=ExtendedType, slots=True):
96 """Abstract node in a configuration data structure."""
98 DICT_TYPE: ClassVar[type[Dictionary]] #: Type reference used when instantiating new dictionaries
99 SEQ_TYPE: ClassVar[type[Sequence]] #: Type reference used when instantiating new sequences
100 _root: Configuration #: Reference to the root node.
101 _parent: Dictionary #: Reference to a parent node.
103 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None:
104 """
105 Initializes a node.
107 :param root: Optional, reference to the root node.
108 :param parent: Optional, reference to the parent node.
109 """
110 self._root = root
111 self._parent = parent
113 @abstractmethod
114 def __len__(self) -> int: # type: ignore[empty-body]
115 """
116 Returns the number of sub-elements.
118 :returns: Number of sub-elements.
119 """
121 @abstractmethod
122 def __getitem__(self, key: KeyT) -> ValueT: # type: ignore[empty-body]
123 """
124 Access an element in the node by index or key.
126 :param key: Index or key of the element.
127 :returns: A node (sequence or dictionary) or scalar value (int, float, str).
128 """
130 def __setitem__(self, key: KeyT, value: ValueT) -> None:
131 """
132 Set an element in the node by index or key.
134 .. attention::
136 A configuration is **read-only**: the file format doesn't implements writing.
138 :param key: Index or key of the element.
139 :param value: The new value of that element.
140 :raises NotImplementedError: Always - a configuration is read-only.
141 """
142 raise NotImplementedError("Currently, the configuration is read-only. Writing isn't implemented.")
144 @abstractmethod
145 def __iter__(self) -> Iterator[ValueT]: # type: ignore[empty-body]
146 """
147 Returns an iterator to iterate a node.
149 :returns: Node iterator.
150 """
152 @property
153 def Key(self) -> KeyT:
154 """
155 Property to access the node's key.
157 :returns: Key of the node.
158 """
159 raise NotImplementedError()
161 @Key.setter
162 def Key(self, value: KeyT) -> None:
163 raise NotImplementedError()
165 @abstractmethod
166 def QueryPath(self, query: str) -> ValueT: # type: ignore[empty-body]
167 """
168 Return a node or value based on a path description to that node or value.
170 :param query: String describing the path to the node or value.
171 :returns: A node (sequence or dictionary) or scalar value (int, float, str).
172 """
175@export
176@mixin
177class Dictionary(Node):
178 """Abstract dictionary node in a configuration."""
180 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None:
181 """
182 Initializes a dictionary.
184 :param root: Optional, reference to the root node.
185 :param parent: Optional, reference to the parent node.
186 """
187 Node.__init__(self, root, parent)
189 def __contains__(self, key: KeyT) -> bool: # type: ignore[empty-body]
190 """
191 Check if a key exists in this dictionary node.
193 :param key: The key to check for.
194 :returns: ``True``, if the key exists in this node.
195 """
196 raise NotImplementedError()
199@export
200@mixin
201class Sequence(Node):
202 """Abstract sequence node in a configuration."""
204 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None:
205 """
206 Initializes a sequence.
208 :param root: Optional, reference to the root node.
209 :param parent: Optional, reference to the parent node.
210 """
211 Node.__init__(self, root, parent)
213 def __getitem__(self, index: int) -> ValueT: # type: ignore[empty-body]
214 """
215 Read an element of this sequence node by index.
217 :param index: Index of the element to read.
218 :returns: A node (sequence or dictionary) or scalar value (int, float, str).
219 """
220 raise NotImplementedError()
222 def __setitem__(self, index: int, value: ValueT) -> None:
223 """
224 Write an element of this sequence node by index.
226 .. attention::
228 A configuration is **read-only** - see :meth:`Node.__setitem__`.
230 :param index: Index of the element to write.
231 :param value: The new value of that element.
232 :raises NotImplementedError: Always - a configuration is read-only.
233 """
234 raise NotImplementedError("Currently, the configuration is read-only. Writing isn't implemented.")
237setattr(Node, "DICT_TYPE", Dictionary)
238setattr(Node, "SEQ_TYPE", Sequence)
241@export
242@mixin
243class Configuration(Node):
244 """Abstract root node in a configuration."""
246 _configFile: Path #: Path to the configuration file.
248 def __init__(self, configFile: Path, root: Configuration = None, parent: Nullable[NodeT] = None) -> None:
249 """
250 Initializes a configuration.
252 :param configFile: Configuration file.
253 :param root: Optional, reference to the root node.
254 :param parent: Optional, reference to the parent node.
255 """
256 Node.__init__(self, root, parent)
257 self._configFile = configFile
259 @readonly
260 def ConfigFile(self) -> Path:
261 """
262 Read-only property to access the configuration file's path.
264 :returns: Path to the configuration file.
265 """
266 return self._configFile
269# ==================================================================================================================== #
270# Deprecated names, kept for backwards compatibility. Removed in v10.0.0.
271# ==================================================================================================================== #
272ConfigurationException = ConfigurationError
273InterpolationException = InterpolationError
274KeyNotFoundException = KeyNotFoundError
275PathExpressionException = PathExpressionError
276UnsupportedValueTypeException = UnsupportedValueTypeError