fameio 2.3.0__py3-none-any.whl → 3.0.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.
- CHANGELOG.md +28 -0
- fameio/__init__.py +4 -1
- fameio/{source/cli → cli}/__init__.py +2 -0
- fameio/{source/cli → cli}/convert_results.py +8 -8
- fameio/{source/cli → cli}/make_config.py +5 -5
- fameio/{source/cli → cli}/options.py +0 -8
- fameio/{source/cli → cli}/parser.py +26 -63
- fameio/input/__init__.py +27 -0
- fameio/input/loader/__init__.py +68 -0
- fameio/input/loader/controller.py +129 -0
- fameio/input/loader/loader.py +109 -0
- fameio/input/metadata.py +149 -0
- fameio/input/resolver.py +44 -0
- fameio/{source → input}/scenario/__init__.py +1 -2
- fameio/{source → input}/scenario/agent.py +24 -38
- fameio/input/scenario/attribute.py +203 -0
- fameio/{source → input}/scenario/contract.py +50 -61
- fameio/{source → input}/scenario/exception.py +8 -13
- fameio/{source → input}/scenario/fameiofactory.py +6 -6
- fameio/{source → input}/scenario/generalproperties.py +22 -47
- fameio/{source → input}/scenario/scenario.py +34 -31
- fameio/input/scenario/stringset.py +48 -0
- fameio/{source → input}/schema/__init__.py +2 -2
- fameio/input/schema/agenttype.py +125 -0
- fameio/input/schema/attribute.py +268 -0
- fameio/{source → input}/schema/java_packages.py +26 -22
- fameio/{source → input}/schema/schema.py +25 -22
- fameio/{source → input}/validator.py +32 -35
- fameio/{source → input}/writer.py +86 -86
- fameio/{source/logs.py → logs.py} +25 -9
- fameio/{source/results → output}/agent_type.py +21 -22
- fameio/{source/results → output}/conversion.py +34 -31
- fameio/{source/results → output}/csv_writer.py +7 -7
- fameio/{source/results → output}/data_transformer.py +24 -24
- fameio/{source/results → output}/input_dao.py +51 -49
- fameio/{source/results → output}/output_dao.py +16 -17
- fameio/{source/results → output}/reader.py +30 -31
- fameio/{source/results → output}/yaml_writer.py +2 -3
- fameio/scripts/__init__.py +2 -2
- fameio/scripts/convert_results.py +16 -15
- fameio/scripts/make_config.py +9 -9
- fameio/{source/series.py → series.py} +28 -26
- fameio/{source/time.py → time.py} +8 -8
- fameio/{source/tools.py → tools.py} +2 -2
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/METADATA +277 -72
- fameio-3.0.0.dist-info/RECORD +56 -0
- fameio/source/__init__.py +0 -8
- fameio/source/loader.py +0 -181
- fameio/source/metadata.py +0 -32
- fameio/source/path_resolver.py +0 -34
- fameio/source/scenario/attribute.py +0 -130
- fameio/source/scenario/stringset.py +0 -51
- fameio/source/schema/agenttype.py +0 -132
- fameio/source/schema/attribute.py +0 -203
- fameio/source/schema/exception.py +0 -9
- fameio-2.3.0.dist-info/RECORD +0 -55
- /fameio/{source/results → output}/__init__.py +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSE.txt +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/WHEEL +0 -0
- {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from typing import Any, Final
|
7
|
+
|
8
|
+
from fameio.input import InputError, SchemaError
|
9
|
+
from fameio.input.metadata import Metadata, MetadataComponent, ValueContainer
|
10
|
+
from fameio.logs import log_error_and_raise, log
|
11
|
+
from fameio.tools import keys_to_lower
|
12
|
+
from .attribute import AttributeSpecs
|
13
|
+
|
14
|
+
|
15
|
+
class AgentType(Metadata):
|
16
|
+
"""Schema definitions for an Agent type"""
|
17
|
+
|
18
|
+
KEY_ATTRIBUTES: Final[str] = "Attributes".lower()
|
19
|
+
KEY_PRODUCTS: Final[str] = "Products".lower()
|
20
|
+
KEY_OUTPUTS: Final[str] = "Outputs".lower()
|
21
|
+
|
22
|
+
_ERR_NAME_INVALID = "'{}' is not a valid name for AgentTypes"
|
23
|
+
_ERR_NO_STRING = "{} definition of AgentType '{}' contains keys other than string: '{}'"
|
24
|
+
_ERR_UNKNOWN_STRUCTURE = "{} definition of AgentType '{}' is neither list nor dictionary: '{}'"
|
25
|
+
|
26
|
+
_NO_ATTRIBUTES = "Agent '{}' has no specified 'Attributes'."
|
27
|
+
_NO_PRODUCTS = "Agent '{}' has no specified 'Products'."
|
28
|
+
_NO_OUTPUTS = "Agent '{}' has no specified 'Outputs'."
|
29
|
+
|
30
|
+
def __init__(self, name: str):
|
31
|
+
"""
|
32
|
+
Initialise a new AgentType
|
33
|
+
|
34
|
+
Args:
|
35
|
+
name: name of the AgenType - must not be None, empty or only whitespaces
|
36
|
+
"""
|
37
|
+
super().__init__()
|
38
|
+
if not name or name.isspace():
|
39
|
+
log_error_and_raise(SchemaError(AgentType._ERR_NAME_INVALID.format(name)))
|
40
|
+
self._name = name
|
41
|
+
self._attributes: dict[str, AttributeSpecs] = {}
|
42
|
+
self._products: ValueContainer = ValueContainer()
|
43
|
+
self._outputs: ValueContainer = ValueContainer()
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def from_dict(cls, name: str, definitions: dict) -> AgentType:
|
47
|
+
"""
|
48
|
+
Creates AgentType with given `name` from specified dictionary
|
49
|
+
|
50
|
+
Args:
|
51
|
+
name: of the agent type
|
52
|
+
definitions: of the agent type specifying, e.g., its attributes and products
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
a new instance of AgentType
|
56
|
+
"""
|
57
|
+
agent_type = cls(name)
|
58
|
+
agent_type._extract_metadata(definitions)
|
59
|
+
|
60
|
+
definition = keys_to_lower(definitions)
|
61
|
+
if AgentType.KEY_ATTRIBUTES in definition:
|
62
|
+
for attribute_name, attribute_details in definition[AgentType.KEY_ATTRIBUTES].items():
|
63
|
+
full_name = name + "." + attribute_name
|
64
|
+
agent_type._attributes[attribute_name] = AttributeSpecs(full_name, attribute_details)
|
65
|
+
else:
|
66
|
+
log().info(AgentType._NO_ATTRIBUTES.format(name))
|
67
|
+
|
68
|
+
if AgentType.KEY_PRODUCTS in definition and definition[AgentType.KEY_PRODUCTS]:
|
69
|
+
agent_type._products = AgentType._read_values(
|
70
|
+
section="Products", agent_type=name, values=definition[AgentType.KEY_PRODUCTS]
|
71
|
+
)
|
72
|
+
else:
|
73
|
+
log().info(AgentType._NO_PRODUCTS.format(name))
|
74
|
+
|
75
|
+
if AgentType.KEY_OUTPUTS in definition and definition[AgentType.KEY_OUTPUTS]:
|
76
|
+
agent_type._outputs = AgentType._read_values(
|
77
|
+
section="Outputs", agent_type=name, values=definition[AgentType.KEY_OUTPUTS]
|
78
|
+
)
|
79
|
+
else:
|
80
|
+
log().debug(AgentType._NO_OUTPUTS.format(name))
|
81
|
+
|
82
|
+
return agent_type
|
83
|
+
|
84
|
+
@staticmethod
|
85
|
+
def _read_values(section: str, agent_type: str, values: Any) -> ValueContainer:
|
86
|
+
"""Returns ValueContainer for `section` of in specifications of `agent_type` extracted from given `values`"""
|
87
|
+
try:
|
88
|
+
data = ValueContainer(values)
|
89
|
+
except InputError:
|
90
|
+
log_error_and_raise(SchemaError(AgentType._ERR_UNKNOWN_STRUCTURE.format(section, agent_type, values)))
|
91
|
+
else:
|
92
|
+
if not all([isinstance(item, str) for item in data.as_list()]):
|
93
|
+
log_error_and_raise(SchemaError(AgentType._ERR_NO_STRING.format(section, agent_type, data.as_list())))
|
94
|
+
return data
|
95
|
+
|
96
|
+
@property
|
97
|
+
def name(self) -> str:
|
98
|
+
"""Returns the agent type name"""
|
99
|
+
return self._name
|
100
|
+
|
101
|
+
@property
|
102
|
+
def products(self) -> dict[str, MetadataComponent]:
|
103
|
+
"""Returns dict of products or an empty dict if no products are defined"""
|
104
|
+
return self._products.values
|
105
|
+
|
106
|
+
def get_product_names(self) -> list[str]:
|
107
|
+
"""Returns list of product names or an empty list if no products are defined"""
|
108
|
+
return self._products.as_list()
|
109
|
+
|
110
|
+
@property
|
111
|
+
def attributes(self) -> dict[str, AttributeSpecs]:
|
112
|
+
"""Returns list of Attributes of this agent or an empty list if no attributes are defined"""
|
113
|
+
return self._attributes
|
114
|
+
|
115
|
+
@property
|
116
|
+
def outputs(self) -> dict[str, MetadataComponent]:
|
117
|
+
"""Returns list of outputs or an empty list if no outputs are defined"""
|
118
|
+
return self._outputs.values
|
119
|
+
|
120
|
+
def _to_dict(self) -> dict:
|
121
|
+
return {
|
122
|
+
self.KEY_ATTRIBUTES: {name: attribute.to_dict() for name, attribute in self._attributes.items()},
|
123
|
+
self.KEY_PRODUCTS: self._products.to_dict(),
|
124
|
+
self.KEY_OUTPUTS: self._outputs.to_dict(),
|
125
|
+
}
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from enum import Enum, auto
|
7
|
+
from typing import Any, Optional, Final, Union
|
8
|
+
|
9
|
+
from fameio.input import SchemaError
|
10
|
+
from fameio.input.metadata import Metadata, ValueContainer
|
11
|
+
from fameio.logs import log
|
12
|
+
from fameio.time import FameTime
|
13
|
+
from fameio.tools import keys_to_lower
|
14
|
+
|
15
|
+
|
16
|
+
class AttributeType(Enum):
|
17
|
+
"""Data types that Attributes can take"""
|
18
|
+
|
19
|
+
INTEGER = auto()
|
20
|
+
DOUBLE = auto()
|
21
|
+
LONG = auto()
|
22
|
+
TIME_STAMP = auto()
|
23
|
+
STRING = auto()
|
24
|
+
STRING_SET = auto()
|
25
|
+
ENUM = auto()
|
26
|
+
TIME_SERIES = auto()
|
27
|
+
BLOCK = auto()
|
28
|
+
|
29
|
+
def convert_string_to_type(self, value: str) -> Union[int, float, str]:
|
30
|
+
"""
|
31
|
+
Converts a given string to this AttributeType's data format
|
32
|
+
|
33
|
+
Args:
|
34
|
+
value: string to be converted
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
value converted to data format associated with AttributeType
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
ValueError: if data conversion failed, e.g. due to improper string content
|
41
|
+
"""
|
42
|
+
if self is AttributeType.INTEGER or self is AttributeType.LONG:
|
43
|
+
return int(value)
|
44
|
+
elif self is AttributeType.DOUBLE:
|
45
|
+
return float(value)
|
46
|
+
elif self is AttributeType.TIME_STAMP:
|
47
|
+
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:
|
49
|
+
return str(value)
|
50
|
+
elif self is AttributeType.TIME_SERIES:
|
51
|
+
return float(value)
|
52
|
+
else:
|
53
|
+
raise ValueError("String conversion not supported for '{}'.".format(self))
|
54
|
+
|
55
|
+
|
56
|
+
class AttributeSpecs(Metadata):
|
57
|
+
"""Schema Definition of a single Attribute (with possible inner Attributes) of an agent"""
|
58
|
+
|
59
|
+
_DISALLOWED_NAMES = ["value", "values", "metadata"]
|
60
|
+
_SEPARATOR = "."
|
61
|
+
|
62
|
+
KEY_MANDATORY: Final[str] = "Mandatory".lower()
|
63
|
+
KEY_LIST: Final[str] = "List".lower()
|
64
|
+
KEY_TYPE: Final[str] = "AttributeType".lower()
|
65
|
+
KEY_NESTED: Final[str] = "NestedAttributes".lower()
|
66
|
+
KEY_VALUES: Final[str] = "Values".lower()
|
67
|
+
KEY_DEFAULT: Final[str] = "Default".lower()
|
68
|
+
KEY_HELP: Final[str] = "Help".lower()
|
69
|
+
|
70
|
+
_EMPTY_DEFINITION = "Definitions missing for Attribute '{}'."
|
71
|
+
_MISSING_SPEC_DEFAULT = "Missing '{}' specification for Attribute '{}' - assuming {}."
|
72
|
+
_MISSING_TYPE = "'AttributeType' not declare for Attribute '{}'."
|
73
|
+
_INVALID_TYPE = "'{}' is not a valid type for an Attribute."
|
74
|
+
_DEFAULT_NOT_LIST = "Attribute is list, but provided Default '{}' is not a list."
|
75
|
+
_INCOMPATIBLE = "Value '{}' in section '{}' can not be converted to AttributeType '{}'."
|
76
|
+
_DEFAULT_DISALLOWED = "Default '{}' is not an allowed value."
|
77
|
+
_LIST_DISALLOWED = "Attribute '{}' of type TIME_SERIES cannot be a list."
|
78
|
+
_VALUES_ILL_FORMAT = "Only List and Dictionary is supported for 'Values' but was: {}"
|
79
|
+
_NAME_DISALLOWED = f"Attribute name must not be empty and none of: {_DISALLOWED_NAMES}"
|
80
|
+
|
81
|
+
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
|
+
self._full_name = name
|
88
|
+
if not definition:
|
89
|
+
raise SchemaError(AttributeSpecs._EMPTY_DEFINITION.format(name))
|
90
|
+
definition = keys_to_lower(definition)
|
91
|
+
|
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))
|
109
|
+
|
110
|
+
if self._attr_type == AttributeType.TIME_SERIES and self._is_list:
|
111
|
+
raise SchemaError(AttributeSpecs._LIST_DISALLOWED.format(name))
|
112
|
+
|
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)
|
118
|
+
|
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)
|
132
|
+
|
133
|
+
self._help = None
|
134
|
+
if AttributeSpecs.KEY_HELP in definition:
|
135
|
+
self._help = definition[AttributeSpecs.KEY_HELP].strip()
|
136
|
+
|
137
|
+
@staticmethod
|
138
|
+
def _name_is_disallowed(full_name: str) -> bool:
|
139
|
+
"""Returns True if name is not allowed"""
|
140
|
+
if full_name is None:
|
141
|
+
return True
|
142
|
+
short_name = full_name.split(AttributeSpecs._SEPARATOR)[-1]
|
143
|
+
if len(short_name) == 0 or short_name.isspace():
|
144
|
+
return True
|
145
|
+
return short_name.lower() in AttributeSpecs._DISALLOWED_NAMES
|
146
|
+
|
147
|
+
def _read_values(self, definition: [dict, list]) -> ValueContainer:
|
148
|
+
"""
|
149
|
+
Returns accepted values mapped to their additional metadata specifications extracted from given `definition`
|
150
|
+
Accepts lists of accepted values or dictionaries with (optional) metadata assigned to each value
|
151
|
+
|
152
|
+
Args:
|
153
|
+
definition: list of acceptable values or dict with acceptable values as keys and (optional) metadata content
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
Mapping of acceptable values to their associated Metadata
|
157
|
+
"""
|
158
|
+
try:
|
159
|
+
value_container = ValueContainer(definition)
|
160
|
+
for value in value_container.as_list():
|
161
|
+
self._convert_to_data_type(value, self.KEY_VALUES)
|
162
|
+
return value_container
|
163
|
+
except ValueContainer.ParseError:
|
164
|
+
raise SchemaError(AttributeSpecs._VALUES_ILL_FORMAT.format(definition))
|
165
|
+
|
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"""
|
168
|
+
try:
|
169
|
+
return self._attr_type.convert_string_to_type(value)
|
170
|
+
except ValueError:
|
171
|
+
raise SchemaError(AttributeSpecs._INCOMPATIBLE.format(value, section, self._attr_type))
|
172
|
+
|
173
|
+
def _convert_list(self, values) -> list:
|
174
|
+
"""Converts all entries in given `values` list to this attribute data type and returns this new list"""
|
175
|
+
if isinstance(values, list):
|
176
|
+
return [self._convert_and_test(item) for item in values]
|
177
|
+
else:
|
178
|
+
raise SchemaError(AttributeSpecs._DEFAULT_NOT_LIST.format(values))
|
179
|
+
|
180
|
+
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"""
|
182
|
+
if self.has_value_restrictions and (not self._allowed_values.has_value(value)):
|
183
|
+
raise SchemaError(AttributeSpecs._DEFAULT_DISALLOWED.format(value))
|
184
|
+
return self._convert_to_data_type(value, self.KEY_DEFAULT)
|
185
|
+
|
186
|
+
@property
|
187
|
+
def attr_type(self) -> AttributeType:
|
188
|
+
"""Returns AttributeType of this attribute"""
|
189
|
+
return self._attr_type
|
190
|
+
|
191
|
+
@property
|
192
|
+
def values(self) -> list:
|
193
|
+
"""Returns the list of allowed values for this attribute"""
|
194
|
+
return self._allowed_values.as_list()
|
195
|
+
|
196
|
+
@property
|
197
|
+
def has_value_restrictions(self) -> bool:
|
198
|
+
"""Returns True if the attribute can only take a set of certain values"""
|
199
|
+
return not self._allowed_values.is_empty()
|
200
|
+
|
201
|
+
@property
|
202
|
+
def is_list(self) -> bool:
|
203
|
+
"""Return True if this attribute type is a list"""
|
204
|
+
return self._is_list
|
205
|
+
|
206
|
+
@property
|
207
|
+
def has_nested_attributes(self) -> bool:
|
208
|
+
"""Returns True if nested attributes are defined"""
|
209
|
+
return bool(self._nested_attributes)
|
210
|
+
|
211
|
+
@property
|
212
|
+
def nested_attributes(self) -> dict[str, AttributeSpecs]:
|
213
|
+
"""Returns list of nested Attributes of this Attribute or an empty dict if no nested attributes are defined"""
|
214
|
+
return self._nested_attributes
|
215
|
+
|
216
|
+
@property
|
217
|
+
def has_default_value(self) -> bool:
|
218
|
+
"""Return True if a default value is available"""
|
219
|
+
return self._default_value is not None
|
220
|
+
|
221
|
+
@property
|
222
|
+
def default_value(self) -> Optional[Any]:
|
223
|
+
"""Return the default value of this attribute, or None if no default is specified"""
|
224
|
+
return self._default_value
|
225
|
+
|
226
|
+
@property
|
227
|
+
def is_mandatory(self) -> bool:
|
228
|
+
"""Return True if this attribute is mandatory"""
|
229
|
+
return self._is_mandatory
|
230
|
+
|
231
|
+
@property
|
232
|
+
def full_name(self) -> str:
|
233
|
+
"""Returns name including name of enclosing parent attributes"""
|
234
|
+
return self._full_name
|
235
|
+
|
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
|
+
@property
|
245
|
+
def has_help_text(self) -> bool:
|
246
|
+
"""Return True if a help_text is available"""
|
247
|
+
return bool(self._help)
|
248
|
+
|
249
|
+
@property
|
250
|
+
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 ""
|
253
|
+
|
254
|
+
def _to_dict(self) -> dict[str, Any]:
|
255
|
+
definition = {
|
256
|
+
self.KEY_TYPE: self._attr_type.name,
|
257
|
+
self.KEY_MANDATORY: self._is_mandatory,
|
258
|
+
self.KEY_LIST: self._is_list,
|
259
|
+
}
|
260
|
+
if self.has_help_text:
|
261
|
+
definition[self.KEY_HELP] = self._help
|
262
|
+
if self.has_default_value:
|
263
|
+
definition[self.KEY_DEFAULT] = self._default_value
|
264
|
+
if self.has_value_restrictions:
|
265
|
+
definition[self.KEY_VALUES] = self._allowed_values.to_dict()
|
266
|
+
if self.has_nested_attributes:
|
267
|
+
definition[self.KEY_NESTED] = {name: inner.to_dict() for name, inner in self.nested_attributes.items()}
|
268
|
+
return definition
|
@@ -3,31 +3,31 @@
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
from typing import
|
6
|
+
from typing import Final
|
7
7
|
|
8
|
-
from fameio.
|
9
|
-
from fameio.
|
10
|
-
from fameio.
|
8
|
+
from fameio.input import SchemaError
|
9
|
+
from fameio.logs import log_error_and_raise, log
|
10
|
+
from fameio.tools import keys_to_lower
|
11
11
|
|
12
12
|
|
13
13
|
class JavaPackages:
|
14
14
|
"""Schema definitions for Java package names in which model classes reside"""
|
15
15
|
|
16
|
+
KEY_AGENT: Final[str] = "Agents".lower()
|
17
|
+
KEY_DATA_ITEM: Final[str] = "DataItems".lower()
|
18
|
+
KEY_PORTABLE: Final[str] = "Portables".lower()
|
19
|
+
|
16
20
|
_ERR_MISSING_AGENTS = "JavaPackages requires non-empty list for `Agents`. Key was missing or list was empty."
|
17
|
-
|
21
|
+
_INFO_MISSING_DATA_ITEMS = "`DataItems` not specified: Key was missing or list was empty."
|
18
22
|
_ERR_MISSING_PORTABLES = "JavaPackages require non-empty list for `Portables`. Key was missing or list was empty."
|
19
23
|
|
20
|
-
_KEY_AGENT = "Agents".lower()
|
21
|
-
_KEY_DATA_ITEM = "DataItems".lower()
|
22
|
-
_KEY_PORTABLE = "Portables".lower()
|
23
|
-
|
24
24
|
def __init__(self):
|
25
|
-
self._agents:
|
26
|
-
self._data_items:
|
27
|
-
self._portables:
|
25
|
+
self._agents: list[str] = []
|
26
|
+
self._data_items: list[str] = []
|
27
|
+
self._portables: list[str] = []
|
28
28
|
|
29
29
|
@classmethod
|
30
|
-
def from_dict(cls, definitions:
|
30
|
+
def from_dict(cls, definitions: dict[str, list[str]]) -> JavaPackages:
|
31
31
|
"""
|
32
32
|
Creates JavaPackages from a dictionary representation
|
33
33
|
|
@@ -40,30 +40,34 @@ class JavaPackages:
|
|
40
40
|
java_packages = cls()
|
41
41
|
definitions = keys_to_lower(definitions)
|
42
42
|
|
43
|
-
java_packages._agents = definitions.get(JavaPackages.
|
44
|
-
java_packages._data_items = definitions.get(JavaPackages.
|
45
|
-
java_packages._portables = definitions.get(JavaPackages.
|
43
|
+
java_packages._agents = definitions.get(JavaPackages.KEY_AGENT, [])
|
44
|
+
java_packages._data_items = definitions.get(JavaPackages.KEY_DATA_ITEM, [])
|
45
|
+
java_packages._portables = definitions.get(JavaPackages.KEY_PORTABLE, [])
|
46
46
|
|
47
47
|
if not java_packages._agents:
|
48
|
-
log_error_and_raise(
|
48
|
+
log_error_and_raise(SchemaError(JavaPackages._ERR_MISSING_AGENTS))
|
49
49
|
if not java_packages._data_items:
|
50
|
-
|
50
|
+
log().info(JavaPackages._INFO_MISSING_DATA_ITEMS)
|
51
51
|
if not java_packages._portables:
|
52
|
-
log_error_and_raise(
|
52
|
+
log_error_and_raise(SchemaError(JavaPackages._ERR_MISSING_PORTABLES))
|
53
53
|
|
54
54
|
return java_packages
|
55
55
|
|
56
56
|
@property
|
57
|
-
def agents(self) ->
|
57
|
+
def agents(self) -> list[str]:
|
58
58
|
"""Return list of java package names that contain the model's Agents"""
|
59
59
|
return self._agents
|
60
60
|
|
61
61
|
@property
|
62
|
-
def data_items(self) ->
|
62
|
+
def data_items(self) -> list[str]:
|
63
63
|
"""Return list of java package names that contain the model's DataItems"""
|
64
64
|
return self._data_items
|
65
65
|
|
66
66
|
@property
|
67
|
-
def portables(self) ->
|
67
|
+
def portables(self) -> list[str]:
|
68
68
|
"""Return list of java package names that contain the model's Portables"""
|
69
69
|
return self._portables
|
70
|
+
|
71
|
+
def to_dict(self) -> dict[str, list[str]]:
|
72
|
+
"""Return dictionary representation of this JavaPackages object"""
|
73
|
+
return {self.KEY_AGENT: self.agents, self.KEY_DATA_ITEM: self.data_items, self.KEY_PORTABLE: self.portables}
|
@@ -4,52 +4,55 @@
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
6
|
import ast
|
7
|
-
from typing import
|
7
|
+
from typing import Any, Final
|
8
8
|
|
9
|
-
from fameio.
|
10
|
-
from fameio.
|
11
|
-
from fameio.
|
12
|
-
from
|
13
|
-
from
|
9
|
+
from fameio.input import SchemaError
|
10
|
+
from fameio.logs import log_error_and_raise
|
11
|
+
from fameio.tools import keys_to_lower
|
12
|
+
from .agenttype import AgentType
|
13
|
+
from .java_packages import JavaPackages
|
14
14
|
|
15
15
|
|
16
16
|
class Schema:
|
17
17
|
"""Definition of a schema"""
|
18
18
|
|
19
|
+
KEY_AGENT_TYPE: Final[str] = "AgentTypes".lower()
|
20
|
+
KEY_PACKAGES: Final[str] = "JavaPackages".lower()
|
21
|
+
|
19
22
|
_ERR_AGENT_TYPES_MISSING = "Required keyword `AgentTypes` missing in Schema."
|
20
23
|
_ERR_AGENT_TYPES_EMPTY = "`AgentTypes` must not be empty - at least one type of agent is required."
|
21
|
-
|
22
|
-
|
23
|
-
_KEY_AGENT_TYPE = "AgentTypes".lower()
|
24
|
-
_KEY_PACKAGES = "JavaPackages".lower()
|
24
|
+
_ERR_MISSING_PACKAGES = "Missing required section `JavaPackages` in Schema."
|
25
25
|
|
26
26
|
def __init__(self, definitions: dict):
|
27
27
|
self._original_input_dict = definitions
|
28
28
|
self._agent_types = {}
|
29
|
-
self._packages
|
29
|
+
self._packages = None
|
30
30
|
|
31
31
|
@classmethod
|
32
32
|
def from_dict(cls, definitions: dict) -> Schema:
|
33
33
|
"""Load given dictionary `definitions` into a new Schema"""
|
34
34
|
definitions = keys_to_lower(definitions)
|
35
|
-
if Schema._KEY_AGENT_TYPE not in definitions:
|
36
|
-
log_error_and_raise(SchemaException(Schema._ERR_AGENT_TYPES_MISSING))
|
37
35
|
schema = cls(definitions)
|
38
|
-
agent_types = definitions[Schema._KEY_AGENT_TYPE]
|
39
|
-
if len(agent_types) == 0:
|
40
|
-
log_error_and_raise(SchemaException(Schema._ERR_AGENT_TYPES_EMPTY))
|
41
36
|
|
37
|
+
agent_types = cls._get_or_raise(definitions, Schema.KEY_AGENT_TYPE, Schema._ERR_AGENT_TYPES_MISSING)
|
38
|
+
if len(agent_types) == 0:
|
39
|
+
log_error_and_raise(SchemaError(Schema._ERR_AGENT_TYPES_EMPTY))
|
42
40
|
for agent_type_name, agent_definition in agent_types.items():
|
43
41
|
agent_type = AgentType.from_dict(agent_type_name, agent_definition)
|
44
42
|
schema._agent_types[agent_type_name] = agent_type
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
log().warning(Schema._WARN_MISSING_PACKAGES)
|
50
|
-
schema._packages = JavaPackages()
|
44
|
+
java_packages = cls._get_or_raise(definitions, Schema.KEY_PACKAGES, Schema._ERR_MISSING_PACKAGES)
|
45
|
+
schema._packages = JavaPackages.from_dict(java_packages)
|
46
|
+
|
51
47
|
return schema
|
52
48
|
|
49
|
+
@staticmethod
|
50
|
+
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"""
|
52
|
+
if key not in definitions:
|
53
|
+
log_error_and_raise(SchemaError(error_message))
|
54
|
+
return definitions[key]
|
55
|
+
|
53
56
|
@classmethod
|
54
57
|
def from_string(cls, definitions: str) -> Schema:
|
55
58
|
"""Load given string `definitions` into a new Schema"""
|
@@ -64,7 +67,7 @@ class Schema:
|
|
64
67
|
return repr(self.to_dict())
|
65
68
|
|
66
69
|
@property
|
67
|
-
def agent_types(self) ->
|
70
|
+
def agent_types(self) -> dict[str, AgentType]:
|
68
71
|
"""Returns all the agent types by their name"""
|
69
72
|
return self._agent_types
|
70
73
|
|