Coverage for pyTooling / Configuration / __init__.py: 95%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-08 17:46 +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""" 

38from pathlib import Path 

39from typing import Union, ClassVar, Iterator, Type, Optional as Nullable 

40 

41try: 

42 from pyTooling.Decorators import export, readonly 

43 from pyTooling.MetaClasses import ExtendedType, mixin 

44 from pyTooling.Exceptions import ToolingException 

45except (ImportError, ModuleNotFoundError): # pragma: no cover 

46 print("[pyTooling.Configuration] Could not import from 'pyTooling.*'!") 

47 

48 try: 

49 from Decorators import export, readonly 

50 from MetaClasses import ExtendedType, mixin 

51 from Exceptions import ToolingException 

52 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover 

53 print("[pyTooling.Configuration] Could not import directly!") 

54 raise ex 

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 ConfigurationException(ToolingException): 

67 """Base-exception of all exceptions raised by :mod:`pyTooling.Configuration`.""" 

68 

69 

70@export 

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

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

73 

74 DICT_TYPE: ClassVar[Type["Dictionary"]] #: Type reference used when instantiating new dictionaries 

75 SEQ_TYPE: ClassVar[Type["Sequence"]] #: Type reference used when instantiating new sequences 

76 _root: "Configuration" #: Reference to the root node. 

77 _parent: "Dictionary" #: Reference to a parent node. 

78 

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

80 """ 

81 Initializes a node. 

82 

83 :param root: Reference to the root node. 

84 :param parent: Reference to the parent node. 

85 """ 

86 self._root = root 

87 self._parent = parent 

88 

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

90 """ 

91 Returns the number of sub-elements. 

92 

93 :returns: Number of sub-elements. 

94 """ 

95 

96 def __getitem__(self, key: KeyT) -> ValueT: # type: ignore[empty-body] 

97 """ 

98 Access an element in the node by index or key. 

99 

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

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

102 """ 

103 raise NotImplementedError() 

104 

105 def __setitem__(self, key: KeyT, value: ValueT) -> None: # type: ignore[empty-body] 

106 """ 

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

108 

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

110 :param value: Value to set 

111 """ 

112 raise NotImplementedError() 

113 

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

115 """ 

116 Returns an iterator to iterate a node. 

117 

118 :returns: Node iterator. 

119 """ 

120 raise NotImplementedError() 

121 

122 @property 

123 def Key(self) -> KeyT: 

124 raise NotImplementedError() 

125 

126 @Key.setter 

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

128 raise NotImplementedError() 

129 

130 def QueryPath(self, query: str) -> ValueT: # type: ignore[empty-body] 

131 """ 

132 Return a node or value based on a path description to that node or value. 

133 

134 :param query: String describing the path to the node or value. 

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

136 """ 

137 raise NotImplementedError() 

138 

139 

140@export 

141@mixin 

142class Dictionary(Node): 

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

144 

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

146 """ 

147 Initializes a dictionary. 

148 

149 :param root: Reference to the root node. 

150 :param parent: Reference to the parent node. 

151 """ 

152 Node.__init__(self, root, parent) 

153 

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

155 raise NotImplementedError() 

156 

157 

158@export 

159@mixin 

160class Sequence(Node): 

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

162 

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

164 """ 

165 Initializes a sequence. 

166 

167 :param root: Reference to the root node. 

168 :param parent: Reference to the parent node. 

169 """ 

170 Node.__init__(self, root, parent) 

171 

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

173 raise NotImplementedError() 

174 

175 def __setitem__(self, index: int, value: ValueT) -> None: # type: ignore[empty-body] 

176 raise NotImplementedError() 

177 

178 

179setattr(Node, "DICT_TYPE", Dictionary) 

180setattr(Node, "SEQ_TYPE", Sequence) 

181 

182 

183@export 

184@mixin 

185class Configuration(Node): 

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

187 

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

189 

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

191 """ 

192 Initializes a configuration. 

193 

194 :param configFile: Configuration file. 

195 :param root: Reference to the root node. 

196 :param parent: Reference to the parent node. 

197 """ 

198 Node.__init__(self, root, parent) 

199 self._configFile = configFile 

200 

201 @readonly 

202 def ConfigFile(self) -> Path: 

203 """ 

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

205 

206 :returns: Path to the configuration file. 

207 """ 

208 return self._configFile