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.
- fameio/cli/__init__.py +2 -3
- fameio/cli/convert_results.py +6 -4
- fameio/cli/make_config.py +6 -4
- fameio/cli/options.py +3 -3
- fameio/cli/parser.py +43 -31
- fameio/input/__init__.py +1 -9
- fameio/input/loader/__init__.py +9 -7
- fameio/input/loader/controller.py +64 -14
- fameio/input/loader/loader.py +14 -7
- fameio/input/metadata.py +37 -18
- fameio/input/resolver.py +5 -4
- fameio/input/scenario/__init__.py +7 -8
- fameio/input/scenario/agent.py +52 -19
- fameio/input/scenario/attribute.py +28 -29
- fameio/input/scenario/contract.py +161 -52
- fameio/input/scenario/exception.py +45 -22
- fameio/input/scenario/fameiofactory.py +63 -7
- fameio/input/scenario/generalproperties.py +17 -6
- fameio/input/scenario/scenario.py +111 -28
- fameio/input/scenario/stringset.py +27 -8
- fameio/input/schema/__init__.py +5 -5
- fameio/input/schema/agenttype.py +29 -11
- fameio/input/schema/attribute.py +174 -84
- fameio/input/schema/java_packages.py +8 -5
- fameio/input/schema/schema.py +35 -9
- fameio/input/validator.py +58 -42
- fameio/input/writer.py +139 -41
- fameio/logs.py +23 -17
- fameio/output/__init__.py +5 -1
- fameio/output/agent_type.py +93 -27
- fameio/output/conversion.py +48 -30
- fameio/output/csv_writer.py +88 -18
- fameio/output/data_transformer.py +12 -21
- fameio/output/input_dao.py +68 -32
- fameio/output/output_dao.py +26 -4
- fameio/output/reader.py +61 -18
- fameio/output/yaml_writer.py +18 -9
- fameio/scripts/__init__.py +9 -2
- fameio/scripts/convert_results.py +144 -52
- fameio/scripts/convert_results.py.license +1 -1
- fameio/scripts/exception.py +7 -0
- fameio/scripts/make_config.py +34 -12
- fameio/scripts/make_config.py.license +1 -1
- fameio/series.py +132 -47
- fameio/time.py +88 -37
- fameio/tools.py +9 -8
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/METADATA +19 -13
- fameio-3.2.0.dist-info/RECORD +56 -0
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/WHEEL +1 -1
- CHANGELOG.md +0 -279
- fameio-3.1.0.dist-info/RECORD +0 -56
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSE.txt +0 -0
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
- {fameio-3.1.0.dist-info → fameio-3.2.0.dist-info}/entry_points.txt +0 -0
fameio/input/schema/attribute.py
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
# SPDX-FileCopyrightText:
|
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
|
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) ->
|
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
|
-
|
46
|
+
if self is AttributeType.DOUBLE:
|
45
47
|
return float(value)
|
46
|
-
|
48
|
+
if self is AttributeType.TIME_STAMP:
|
47
49
|
return FameTime.convert_string_if_is_datetime(value)
|
48
|
-
|
50
|
+
if self is AttributeType.ENUM or self is AttributeType.STRING or self is AttributeType.STRING_SET:
|
49
51
|
return str(value)
|
50
|
-
|
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
|
-
|
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
|
-
|
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
|
-
"""
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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.
|
109
|
+
raise log_error(SchemaError(AttributeSpecs._SERIES_LIST_DISALLOWED.format(name)))
|
112
110
|
|
113
|
-
self._allowed_values
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
139
|
-
"""
|
124
|
+
Raises:
|
125
|
+
SchemaError: if name is not allowed, logged with level "ERROR"
|
126
|
+
"""
|
140
127
|
if full_name is None:
|
141
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
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
|
-
|
167
|
-
|
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
|
-
"""
|
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
|
-
|
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
|
-
"""
|
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) ->
|
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
|
252
|
-
return self._help
|
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:
|
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
|
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
|
-
|
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
|
-
|
55
|
+
raise log_error(SchemaError(JavaPackages._ERR_MISSING_PORTABLES))
|
53
56
|
|
54
57
|
return java_packages
|
55
58
|
|
fameio/input/schema/schema.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# SPDX-FileCopyrightText:
|
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
|
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
|
-
"""
|
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
|
-
|
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
|
-
"""
|
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
|
-
|
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
|