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

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. 

33 

34.. hint:: 

35 

36 See :ref:`high-level help <CONFIG>` for explanations and usage examples. 

37 

38.. seealso:: 

39 

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 

48 

49from pathlib import Path 

50from typing import Union, ClassVar, Iterator, Optional as Nullable 

51 

52from pyTooling.Decorators import export, readonly 

53from pyTooling.MetaClasses import ExtendedType, abstractmethod, mixin 

54from pyTooling.Exceptions import ConfigurationError 

55 

56 

57__all__ = ["KeyT", "NodeT", "ValueT"] 

58 

59 

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. 

63 

64 

65@export 

66class KeyNotFoundError(ConfigurationError): 

67 """ 

68 The requested key or index doesn't exist in the configuration node. 

69 

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 """ 

73 

74 

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`. 

79 

80 Supported are scalars (:class:`str`, :class:`int`, :class:`float`) and the parser's dictionary and sequence types. 

81 """ 

82 

83 

84@export 

85class InterpolationError(ConfigurationError): 

86 """A variable reference (``${...}``) in a configuration value is malformed or can't be resolved.""" 

87 

88 

89@export 

90class PathExpressionError(ConfigurationError): 

91 """A path expression (``a:b:c``) doesn't describe a valid node or value in the configuration.""" 

92 

93 

94@export 

95class Node(metaclass=ExtendedType, slots=True): 

96 """Abstract node in a configuration data structure.""" 

97 

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. 

102 

103 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None: 

104 """ 

105 Initializes a node. 

106 

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 

112 

113 @abstractmethod 

114 def __len__(self) -> int: # type: ignore[empty-body] 

115 """ 

116 Returns the number of sub-elements. 

117 

118 :returns: Number of sub-elements. 

119 """ 

120 

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. 

125 

126 :param key: Index or key of the element. 

127 :returns: A node (sequence or dictionary) or scalar value (int, float, str). 

128 """ 

129 

130 def __setitem__(self, key: KeyT, value: ValueT) -> None: 

131 """ 

132 Set an element in the node by index or key. 

133 

134 .. attention:: 

135 

136 A configuration is **read-only**: the file format doesn't implements writing. 

137 

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.") 

143 

144 @abstractmethod 

145 def __iter__(self) -> Iterator[ValueT]: # type: ignore[empty-body] 

146 """ 

147 Returns an iterator to iterate a node. 

148 

149 :returns: Node iterator. 

150 """ 

151 

152 @property 

153 def Key(self) -> KeyT: 

154 """ 

155 Property to access the node's key. 

156 

157 :returns: Key of the node. 

158 """ 

159 raise NotImplementedError() 

160 

161 @Key.setter 

162 def Key(self, value: KeyT) -> None: 

163 raise NotImplementedError() 

164 

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. 

169 

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 """ 

173 

174 

175@export 

176@mixin 

177class Dictionary(Node): 

178 """Abstract dictionary node in a configuration.""" 

179 

180 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None: 

181 """ 

182 Initializes a dictionary. 

183 

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) 

188 

189 def __contains__(self, key: KeyT) -> bool: # type: ignore[empty-body] 

190 """ 

191 Check if a key exists in this dictionary node. 

192 

193 :param key: The key to check for. 

194 :returns: ``True``, if the key exists in this node. 

195 """ 

196 raise NotImplementedError() 

197 

198 

199@export 

200@mixin 

201class Sequence(Node): 

202 """Abstract sequence node in a configuration.""" 

203 

204 def __init__(self, root: Configuration = None, parent: Nullable[NodeT] = None) -> None: 

205 """ 

206 Initializes a sequence. 

207 

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) 

212 

213 def __getitem__(self, index: int) -> ValueT: # type: ignore[empty-body] 

214 """ 

215 Read an element of this sequence node by index. 

216 

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() 

221 

222 def __setitem__(self, index: int, value: ValueT) -> None: 

223 """ 

224 Write an element of this sequence node by index. 

225 

226 .. attention:: 

227 

228 A configuration is **read-only** - see :meth:`Node.__setitem__`. 

229 

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.") 

235 

236 

237setattr(Node, "DICT_TYPE", Dictionary) 

238setattr(Node, "SEQ_TYPE", Sequence) 

239 

240 

241@export 

242@mixin 

243class Configuration(Node): 

244 """Abstract root node in a configuration.""" 

245 

246 _configFile: Path #: Path to the configuration file. 

247 

248 def __init__(self, configFile: Path, root: Configuration = None, parent: Nullable[NodeT] = None) -> None: 

249 """ 

250 Initializes a configuration. 

251 

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 

258 

259 @readonly 

260 def ConfigFile(self) -> Path: 

261 """ 

262 Read-only property to access the configuration file's path. 

263 

264 :returns: Path to the configuration file. 

265 """ 

266 return self._configFile 

267 

268 

269# ==================================================================================================================== # 

270# Deprecated names, kept for backwards compatibility. Removed in v10.0.0. 

271# ==================================================================================================================== # 

272ConfigurationException = ConfigurationError 

273InterpolationException = InterpolationError 

274KeyNotFoundException = KeyNotFoundError 

275PathExpressionException = PathExpressionError 

276UnsupportedValueTypeException = UnsupportedValueTypeError