fameio 3.1.0__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. fameio/cli/__init__.py +2 -3
  2. fameio/cli/convert_results.py +6 -4
  3. fameio/cli/make_config.py +6 -4
  4. fameio/cli/options.py +3 -3
  5. fameio/cli/parser.py +43 -31
  6. fameio/input/__init__.py +1 -9
  7. fameio/input/loader/__init__.py +9 -7
  8. fameio/input/loader/controller.py +64 -14
  9. fameio/input/loader/loader.py +14 -7
  10. fameio/input/metadata.py +37 -18
  11. fameio/input/resolver.py +5 -4
  12. fameio/input/scenario/__init__.py +7 -8
  13. fameio/input/scenario/agent.py +52 -19
  14. fameio/input/scenario/attribute.py +28 -29
  15. fameio/input/scenario/contract.py +161 -52
  16. fameio/input/scenario/exception.py +45 -22
  17. fameio/input/scenario/fameiofactory.py +63 -7
  18. fameio/input/scenario/generalproperties.py +17 -6
  19. fameio/input/scenario/scenario.py +111 -28
  20. fameio/input/scenario/stringset.py +27 -8
  21. fameio/input/schema/__init__.py +5 -5
  22. fameio/input/schema/agenttype.py +29 -11
  23. fameio/input/schema/attribute.py +174 -84
  24. fameio/input/schema/java_packages.py +8 -5
  25. fameio/input/schema/schema.py +35 -9
  26. fameio/input/validator.py +58 -42
  27. fameio/input/writer.py +139 -41
  28. fameio/logs.py +23 -17
  29. fameio/output/__init__.py +5 -1
  30. fameio/output/agent_type.py +93 -27
  31. fameio/output/conversion.py +48 -30
  32. fameio/output/csv_writer.py +88 -18
  33. fameio/output/data_transformer.py +12 -21
  34. fameio/output/input_dao.py +68 -32
  35. fameio/output/output_dao.py +26 -4
  36. fameio/output/reader.py +61 -18
  37. fameio/output/yaml_writer.py +18 -9
  38. fameio/scripts/__init__.py +9 -2
  39. fameio/scripts/convert_results.py +144 -52
  40. fameio/scripts/convert_results.py.license +1 -1
  41. fameio/scripts/exception.py +7 -0
  42. fameio/scripts/make_config.py +34 -12
  43. fameio/scripts/make_config.py.license +1 -1
  44. fameio/series.py +132 -47
  45. fameio/time.py +88 -37
  46. fameio/tools.py +9 -8
  47. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/METADATA +19 -13
  48. fameio-3.2.0.dist-info/RECORD +56 -0
  49. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/WHEEL +1 -1
  50. CHANGELOG.md +0 -279
  51. fameio-3.1.0.dist-info/RECORD +0 -56
  52. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSE.txt +0 -0
  53. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  54. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
  55. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
  56. {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,16 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
1
+ # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  from __future__ import annotations
5
5
 
6
6
  from enum import Enum, auto
7
- from typing import Any, Optional, Final, Union
7
+ from pathlib import Path
8
+ from typing import Any, Final
8
9
 
9
10
  from fameio.input import SchemaError
10
11
  from fameio.input.metadata import Metadata, ValueContainer
11
- from fameio.logs import log
12
+ from fameio.logs import log, log_error
13
+ from fameio.series import CSV_FILE_SUFFIX
12
14
  from fameio.time import FameTime
13
15
  from fameio.tools import keys_to_lower
14
16
 
@@ -26,7 +28,7 @@ class AttributeType(Enum):
26
28
  TIME_SERIES = auto()
27
29
  BLOCK = auto()
28
30
 
29
- def convert_string_to_type(self, value: str) -> Union[int, float, str]:
31
+ def convert_string_to_type(self, value: str) -> int | float | str:
30
32
  """
31
33
  Converts a given string to this AttributeType's data format
32
34
 
@@ -41,16 +43,17 @@ class AttributeType(Enum):
41
43
  """
42
44
  if self is AttributeType.INTEGER or self is AttributeType.LONG:
43
45
  return int(value)
44
- elif self is AttributeType.DOUBLE:
46
+ if self is AttributeType.DOUBLE:
45
47
  return float(value)
46
- elif self is AttributeType.TIME_STAMP:
48
+ if self is AttributeType.TIME_STAMP:
47
49
  return FameTime.convert_string_if_is_datetime(value)
48
- elif self is AttributeType.ENUM or self is AttributeType.STRING or self is AttributeType.STRING_SET:
50
+ if self is AttributeType.ENUM or self is AttributeType.STRING or self is AttributeType.STRING_SET:
49
51
  return str(value)
50
- elif self is AttributeType.TIME_SERIES:
52
+ if self is AttributeType.TIME_SERIES:
53
+ if isinstance(value, str) and Path(value).suffix.lower() == CSV_FILE_SUFFIX:
54
+ return value
51
55
  return float(value)
52
- else:
53
- raise ValueError("String conversion not supported for '{}'.".format(self))
56
+ raise ValueError(f"String conversion not supported for '{self}'.")
54
57
 
55
58
 
56
59
  class AttributeSpecs(Metadata):
@@ -72,79 +75,112 @@ class AttributeSpecs(Metadata):
72
75
  _MISSING_TYPE = "'AttributeType' not declare for Attribute '{}'."
73
76
  _INVALID_TYPE = "'{}' is not a valid type for an Attribute."
74
77
  _DEFAULT_NOT_LIST = "Attribute is list, but provided Default '{}' is not a list."
78
+ _DEFAULT_NOT_SIMPLE = "Only a simple Default value is allowed for non-list attributes, but was '{}'"
75
79
  _INCOMPATIBLE = "Value '{}' in section '{}' can not be converted to AttributeType '{}'."
76
80
  _DEFAULT_DISALLOWED = "Default '{}' is not an allowed value."
77
- _LIST_DISALLOWED = "Attribute '{}' of type TIME_SERIES cannot be a list."
81
+ _SERIES_LIST_DISALLOWED = "Attribute '{}' of type TIME_SERIES cannot be a list."
78
82
  _VALUES_ILL_FORMAT = "Only List and Dictionary is supported for 'Values' but was: {}"
79
83
  _NAME_DISALLOWED = f"Attribute name must not be empty and none of: {_DISALLOWED_NAMES}"
80
84
 
81
85
  def __init__(self, name: str, definition: dict):
82
- """Loads Attribute from given `definition`"""
83
- super().__init__(definition)
84
- if self._name_is_disallowed(name):
85
- raise SchemaError(AttributeSpecs._NAME_DISALLOWED)
86
+ """
87
+ Loads Attribute from given `definition`
88
+
89
+ Args:
90
+ name: of attribute type
91
+ definition: of attribute type
86
92
 
93
+ Raises:
94
+ SchemaError: if attribute type is not properly defined, logged with level "ERROR"
95
+ """
96
+ super().__init__(definition)
97
+ self._assert_is_allowed_name(name)
87
98
  self._full_name = name
99
+
88
100
  if not definition:
89
- raise SchemaError(AttributeSpecs._EMPTY_DEFINITION.format(name))
101
+ raise log_error(SchemaError(AttributeSpecs._EMPTY_DEFINITION.format(name)))
90
102
  definition = keys_to_lower(definition)
91
103
 
92
- if AttributeSpecs.KEY_MANDATORY in definition:
93
- self._is_mandatory = definition[AttributeSpecs.KEY_MANDATORY]
94
- else:
95
- self._is_mandatory = True
96
- log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs.KEY_MANDATORY, name, True))
97
-
98
- if AttributeSpecs.KEY_LIST in definition:
99
- self._is_list = definition[AttributeSpecs.KEY_LIST]
100
- else:
101
- self._is_list = False
102
- log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs.KEY_LIST, name, False))
103
-
104
- if AttributeSpecs.KEY_TYPE in definition:
105
- self._attr_type = AttributeSpecs._get_type_for_name(definition[AttributeSpecs.KEY_TYPE])
106
- else:
107
- log().error(AttributeSpecs._MISSING_TYPE.format(name))
108
- raise SchemaError(AttributeSpecs._MISSING_TYPE.format(name))
104
+ self._is_mandatory = self._get_is_mandatory(definition, name)
105
+ self._is_list = self._get_is_list(definition, name)
106
+ self._attr_type = self._get_type(definition, name)
109
107
 
110
108
  if self._attr_type == AttributeType.TIME_SERIES and self._is_list:
111
- raise SchemaError(AttributeSpecs._LIST_DISALLOWED.format(name))
109
+ raise log_error(SchemaError(AttributeSpecs._SERIES_LIST_DISALLOWED.format(name)))
112
110
 
113
- self._allowed_values: ValueContainer = ValueContainer()
114
- if AttributeSpecs.KEY_VALUES in definition:
115
- value_definition = definition[AttributeSpecs.KEY_VALUES]
116
- if value_definition:
117
- self._allowed_values = self._read_values(value_definition)
111
+ self._allowed_values = self._get_allowed_values(definition)
112
+ self._default_value = self._get_default_value(definition)
113
+ self._nested_attributes = self._get_nested_attributes(definition, name)
114
+ self._help = self._get_help(definition)
118
115
 
119
- self._default_value = None
120
- if AttributeSpecs.KEY_DEFAULT in definition:
121
- provided_value = definition[AttributeSpecs.KEY_DEFAULT]
122
- if self._is_list:
123
- self._default_value = self._convert_list(provided_value)
124
- else:
125
- self._default_value = self._convert_and_test(provided_value)
126
-
127
- self._nested_attributes = {}
128
- if AttributeSpecs.KEY_NESTED in definition:
129
- for nested_name, nested_details in definition[AttributeSpecs.KEY_NESTED].items():
130
- full_name = name + self._SEPARATOR + nested_name
131
- self._nested_attributes[nested_name] = AttributeSpecs(full_name, nested_details)
116
+ @staticmethod
117
+ def _assert_is_allowed_name(full_name: str) -> None:
118
+ """
119
+ Raises SchemaError if provided name is not allowed for Attributes
132
120
 
133
- self._help = None
134
- if AttributeSpecs.KEY_HELP in definition:
135
- self._help = definition[AttributeSpecs.KEY_HELP].strip()
121
+ Args:
122
+ full_name: to be checked if it can serve as name for an attribute
136
123
 
137
- @staticmethod
138
- def _name_is_disallowed(full_name: str) -> bool:
139
- """Returns True if name is not allowed"""
124
+ Raises:
125
+ SchemaError: if name is not allowed, logged with level "ERROR"
126
+ """
140
127
  if full_name is None:
141
- return True
128
+ raise log_error(SchemaError(AttributeSpecs._NAME_DISALLOWED))
142
129
  short_name = full_name.split(AttributeSpecs._SEPARATOR)[-1]
143
130
  if len(short_name) == 0 or short_name.isspace():
144
- return True
145
- return short_name.lower() in AttributeSpecs._DISALLOWED_NAMES
131
+ raise log_error(SchemaError(AttributeSpecs._NAME_DISALLOWED))
132
+ if short_name.lower() in AttributeSpecs._DISALLOWED_NAMES:
133
+ raise log_error(SchemaError(AttributeSpecs._NAME_DISALLOWED))
134
+
135
+ @staticmethod
136
+ def _get_is_mandatory(definition: dict, name: str) -> bool:
137
+ """Returns True if `Mandatory` is set to True or if specification is missing; False otherwise"""
138
+ if AttributeSpecs.KEY_MANDATORY in definition:
139
+ return definition[AttributeSpecs.KEY_MANDATORY]
140
+ log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs.KEY_MANDATORY, name, True))
141
+ return True
142
+
143
+ @staticmethod
144
+ def _get_is_list(definition: dict, name: str) -> bool:
145
+ """Returns True if `List` is set to True; Returns False otherwise or if specification is missing"""
146
+ if AttributeSpecs.KEY_LIST in definition:
147
+ return definition[AttributeSpecs.KEY_LIST]
148
+ log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs.KEY_LIST, name, False))
149
+ return False
146
150
 
147
- def _read_values(self, definition: [dict, list]) -> ValueContainer:
151
+ @staticmethod
152
+ def _get_type(definition: dict, name: str) -> AttributeType:
153
+ """
154
+ Returns `AttributeType` from given definition
155
+
156
+ Args:
157
+ definition: of the attribute
158
+ name: of the attribute
159
+
160
+ Returns:
161
+ type of attribute
162
+
163
+ Raises:
164
+ SchemaError: if no proper type can be extracted, logged with level "ERROR"
165
+ """
166
+ if AttributeSpecs.KEY_TYPE in definition:
167
+ type_name = definition[AttributeSpecs.KEY_TYPE]
168
+ try:
169
+ return AttributeType[type_name.upper()]
170
+ except KeyError as e:
171
+ raise log_error(SchemaError(AttributeSpecs._INVALID_TYPE.format(type_name))) from e
172
+ raise log_error(SchemaError(AttributeSpecs._MISSING_TYPE.format(name)))
173
+
174
+ def _get_allowed_values(self, definition: dict) -> ValueContainer:
175
+ """Returns ValueContainer with allowed values if defined; otherwise an empty ValueContainer"""
176
+ allowed_values: ValueContainer = ValueContainer()
177
+ if AttributeSpecs.KEY_VALUES in definition:
178
+ value_definition = definition[AttributeSpecs.KEY_VALUES]
179
+ if value_definition:
180
+ allowed_values = self._read_values(value_definition)
181
+ return allowed_values
182
+
183
+ def _read_values(self, definition: dict | list) -> ValueContainer:
148
184
  """
149
185
  Returns accepted values mapped to their additional metadata specifications extracted from given `definition`
150
186
  Accepts lists of accepted values or dictionaries with (optional) metadata assigned to each value
@@ -154,35 +190,97 @@ class AttributeSpecs(Metadata):
154
190
 
155
191
  Returns:
156
192
  Mapping of acceptable values to their associated Metadata
193
+
194
+ Raises:
195
+ SchemaError: if values could not be parsed, logged with level "ERROR"
157
196
  """
158
197
  try:
159
198
  value_container = ValueContainer(definition)
160
199
  for value in value_container.as_list():
161
200
  self._convert_to_data_type(value, self.KEY_VALUES)
162
201
  return value_container
163
- except ValueContainer.ParseError:
164
- raise SchemaError(AttributeSpecs._VALUES_ILL_FORMAT.format(definition))
202
+ except ValueContainer.ParseError as e:
203
+ raise log_error(SchemaError(AttributeSpecs._VALUES_ILL_FORMAT.format(definition))) from e
204
+
205
+ def _convert_to_data_type(self, value: str, section: str) -> int | float | str:
206
+ """
207
+ Returns a given single `value` in `section` converted to this Attribute's data type
208
+
209
+ Args:
210
+ value: to be converted
211
+ section: that contains the value
212
+
213
+ Returns:
214
+ value with type matching this attribute type
165
215
 
166
- def _convert_to_data_type(self, value: str, section: str) -> Union[int, float, str]:
167
- """Returns a given single `value` in `section` converted to this Attribute's data type"""
216
+ Raises:
217
+ SchemaError: if value does not match this type of attribute, logged with level "ERROR"
218
+ """
168
219
  try:
169
220
  return self._attr_type.convert_string_to_type(value)
170
- except ValueError:
171
- raise SchemaError(AttributeSpecs._INCOMPATIBLE.format(value, section, self._attr_type))
221
+ except ValueError as e:
222
+ raise log_error(SchemaError(AttributeSpecs._INCOMPATIBLE.format(value, section, self._attr_type))) from e
223
+
224
+ def _get_default_value(self, definition: dict) -> int | float | str | list | None:
225
+ """Returns default value(s) from given definitions, or None if no default is specified"""
226
+ if AttributeSpecs.KEY_DEFAULT in definition:
227
+ provided_value = definition[AttributeSpecs.KEY_DEFAULT]
228
+ if self._is_list:
229
+ return self._convert_list(provided_value)
230
+ return self._convert_and_test(provided_value)
231
+ return None
172
232
 
173
233
  def _convert_list(self, values) -> list:
174
- """Converts all entries in given `values` list to this attribute data type and returns this new list"""
234
+ """
235
+ Converts all entries in given `values` list to this attribute data type and returns this new list
236
+
237
+ Args:
238
+ values: to be converted to a list of default values
239
+
240
+ Returns:
241
+ default values
242
+
243
+ Raises:
244
+ SchemaError: if provided default is not a list, logged with level "ERROR"
245
+ """
175
246
  if isinstance(values, list):
176
247
  return [self._convert_and_test(item) for item in values]
177
- else:
178
- raise SchemaError(AttributeSpecs._DEFAULT_NOT_LIST.format(values))
248
+ raise log_error(SchemaError(AttributeSpecs._DEFAULT_NOT_LIST.format(values)))
179
249
 
180
250
  def _convert_and_test(self, value: str):
181
- """Converts a given single `value` to this Attribute's data type and tests if the value is allowed"""
251
+ """
252
+ Converts a given single `value` to this Attribute's data type and tests if the value is allowed
253
+
254
+ Args:
255
+ value: to be converted and tested
256
+
257
+ Returns:
258
+ the value converted to the data type matching this attribute type
259
+
260
+ Raises:
261
+ SchemaError: if the provided default could not be converted or is not allowed, logged with level "ERROR"
262
+ """
263
+ if isinstance(value, (list, dict)):
264
+ raise log_error(SchemaError(self._DEFAULT_NOT_SIMPLE.format(value)))
182
265
  if self.has_value_restrictions and (not self._allowed_values.has_value(value)):
183
- raise SchemaError(AttributeSpecs._DEFAULT_DISALLOWED.format(value))
266
+ raise log_error(SchemaError(AttributeSpecs._DEFAULT_DISALLOWED.format(value)))
184
267
  return self._convert_to_data_type(value, self.KEY_DEFAULT)
185
268
 
269
+ @staticmethod
270
+ def _get_nested_attributes(definition: dict, name: str) -> dict[str, AttributeSpecs]:
271
+ """Returns dict of nested attributes read from given definition; empty dict if no nested attributes exist"""
272
+ nested_attributes = {}
273
+ if AttributeSpecs.KEY_NESTED in definition:
274
+ for nested_name, nested_details in definition[AttributeSpecs.KEY_NESTED].items():
275
+ full_name = name + AttributeSpecs._SEPARATOR + nested_name
276
+ nested_attributes[nested_name] = AttributeSpecs(full_name, nested_details)
277
+ return nested_attributes
278
+
279
+ @staticmethod
280
+ def _get_help(definition) -> str:
281
+ """Returns (possible empty) help text if provided in definition; None otherwise"""
282
+ return definition.get(AttributeSpecs.KEY_HELP, "").strip()
283
+
186
284
  @property
187
285
  def attr_type(self) -> AttributeType:
188
286
  """Returns AttributeType of this attribute"""
@@ -219,7 +317,7 @@ class AttributeSpecs(Metadata):
219
317
  return self._default_value is not None
220
318
 
221
319
  @property
222
- def default_value(self) -> Optional[Any]:
320
+ def default_value(self) -> Any | None:
223
321
  """Return the default value of this attribute, or None if no default is specified"""
224
322
  return self._default_value
225
323
 
@@ -233,14 +331,6 @@ class AttributeSpecs(Metadata):
233
331
  """Returns name including name of enclosing parent attributes"""
234
332
  return self._full_name
235
333
 
236
- @staticmethod
237
- def _get_type_for_name(name: str) -> AttributeType:
238
- """Returns the AttributeType matching the given `name` converted to upper case"""
239
- try:
240
- return AttributeType[name.upper()]
241
- except KeyError:
242
- raise SchemaError(AttributeSpecs._INVALID_TYPE.format(name))
243
-
244
334
  @property
245
335
  def has_help_text(self) -> bool:
246
336
  """Return True if a help_text is available"""
@@ -248,8 +338,8 @@ class AttributeSpecs(Metadata):
248
338
 
249
339
  @property
250
340
  def help_text(self) -> str:
251
- """Return the help_text of this attribute, if any, otherwise an empty string"""
252
- return self._help if self.has_help_text else ""
341
+ """Return the help_text of this attribute, if any"""
342
+ return self._help
253
343
 
254
344
  def _to_dict(self) -> dict[str, Any]:
255
345
  definition = {
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
1
+ # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  from __future__ import annotations
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
  from typing import Final
7
7
 
8
8
  from fameio.input import SchemaError
9
- from fameio.logs import log_error_and_raise, log
9
+ from fameio.logs import log, log_error
10
10
  from fameio.tools import keys_to_lower
11
11
 
12
12
 
@@ -21,7 +21,7 @@ class JavaPackages:
21
21
  _INFO_MISSING_DATA_ITEMS = "`DataItems` not specified: Key was missing or list was empty."
22
22
  _ERR_MISSING_PORTABLES = "JavaPackages require non-empty list for `Portables`. Key was missing or list was empty."
23
23
 
24
- def __init__(self):
24
+ def __init__(self) -> None:
25
25
  self._agents: list[str] = []
26
26
  self._data_items: list[str] = []
27
27
  self._portables: list[str] = []
@@ -36,6 +36,9 @@ class JavaPackages:
36
36
 
37
37
  Returns:
38
38
  new instance of JavaPackages
39
+
40
+ Raises:
41
+ SchemaError: if definitions are incomplete or erroneous, logged on level "ERROR"
39
42
  """
40
43
  java_packages = cls()
41
44
  definitions = keys_to_lower(definitions)
@@ -45,11 +48,11 @@ class JavaPackages:
45
48
  java_packages._portables = definitions.get(JavaPackages.KEY_PORTABLE, [])
46
49
 
47
50
  if not java_packages._agents:
48
- log_error_and_raise(SchemaError(JavaPackages._ERR_MISSING_AGENTS))
51
+ raise log_error(SchemaError(JavaPackages._ERR_MISSING_AGENTS))
49
52
  if not java_packages._data_items:
50
53
  log().info(JavaPackages._INFO_MISSING_DATA_ITEMS)
51
54
  if not java_packages._portables:
52
- log_error_and_raise(SchemaError(JavaPackages._ERR_MISSING_PORTABLES))
55
+ raise log_error(SchemaError(JavaPackages._ERR_MISSING_PORTABLES))
53
56
 
54
57
  return java_packages
55
58
 
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
1
+ # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  from __future__ import annotations
@@ -7,7 +7,7 @@ import ast
7
7
  from typing import Any, Final
8
8
 
9
9
  from fameio.input import SchemaError
10
- from fameio.logs import log_error_and_raise
10
+ from fameio.logs import log_error
11
11
  from fameio.tools import keys_to_lower
12
12
  from .agenttype import AgentType
13
13
  from .java_packages import JavaPackages
@@ -23,20 +23,31 @@ class Schema:
23
23
  _ERR_AGENT_TYPES_EMPTY = "`AgentTypes` must not be empty - at least one type of agent is required."
24
24
  _ERR_MISSING_PACKAGES = "Missing required section `JavaPackages` in Schema."
25
25
 
26
- def __init__(self, definitions: dict):
26
+ def __init__(self, definitions: dict) -> None:
27
27
  self._original_input_dict = definitions
28
- self._agent_types = {}
29
- self._packages = None
28
+ self._agent_types: dict[str, AgentType] = {}
29
+ self._packages: JavaPackages | None = None
30
30
 
31
31
  @classmethod
32
32
  def from_dict(cls, definitions: dict) -> Schema:
33
- """Load given dictionary `definitions` into a new Schema"""
33
+ """
34
+ Load given dictionary `definitions` into a new schema
35
+
36
+ Args:
37
+ definitions: dictionary representation of schema
38
+
39
+ Returns:
40
+ new Schema
41
+
42
+ Raises:
43
+ SchemaError: if definitions are incomplete or erroneous, logged on level "ERROR"
44
+ """
34
45
  definitions = keys_to_lower(definitions)
35
46
  schema = cls(definitions)
36
47
 
37
48
  agent_types = cls._get_or_raise(definitions, Schema.KEY_AGENT_TYPE, Schema._ERR_AGENT_TYPES_MISSING)
38
49
  if len(agent_types) == 0:
39
- log_error_and_raise(SchemaError(Schema._ERR_AGENT_TYPES_EMPTY))
50
+ raise log_error(SchemaError(Schema._ERR_AGENT_TYPES_EMPTY))
40
51
  for agent_type_name, agent_definition in agent_types.items():
41
52
  agent_type = AgentType.from_dict(agent_type_name, agent_definition)
42
53
  schema._agent_types[agent_type_name] = agent_type
@@ -48,9 +59,22 @@ class Schema:
48
59
 
49
60
  @staticmethod
50
61
  def _get_or_raise(definitions: dict[str, Any], key: str, error_message: str) -> Any:
51
- """Get given `key` from given `definitions` - raise error with given `error_message` if not present"""
62
+ """
63
+ Get given `key` from given `definitions` - raise error with given `error_message` if not present
64
+
65
+ Args:
66
+ definitions: to search the key in
67
+ key: to be searched
68
+ error_message: to be logged and included in the raised exception if key is missing
69
+
70
+ Returns:
71
+ value associated with given key in given definitions
72
+
73
+ Raises:
74
+ SchemaError: if given key is not in given definitions, logged on level "ERROR"
75
+ """
52
76
  if key not in definitions:
53
- log_error_and_raise(SchemaError(error_message))
77
+ raise log_error(SchemaError(error_message))
54
78
  return definitions[key]
55
79
 
56
80
  @classmethod
@@ -74,4 +98,6 @@ class Schema:
74
98
  @property
75
99
  def packages(self) -> JavaPackages:
76
100
  """Returns JavaPackages, i.e. names where model classes are defined in"""
101
+ if self._packages is None:
102
+ raise log_error(SchemaError(self._ERR_MISSING_PACKAGES))
77
103
  return self._packages