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