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.
Files changed (63) hide show
  1. CHANGELOG.md +28 -0
  2. fameio/__init__.py +4 -1
  3. fameio/{source/cli → cli}/__init__.py +2 -0
  4. fameio/{source/cli → cli}/convert_results.py +8 -8
  5. fameio/{source/cli → cli}/make_config.py +5 -5
  6. fameio/{source/cli → cli}/options.py +0 -8
  7. fameio/{source/cli → cli}/parser.py +26 -63
  8. fameio/input/__init__.py +27 -0
  9. fameio/input/loader/__init__.py +68 -0
  10. fameio/input/loader/controller.py +129 -0
  11. fameio/input/loader/loader.py +109 -0
  12. fameio/input/metadata.py +149 -0
  13. fameio/input/resolver.py +44 -0
  14. fameio/{source → input}/scenario/__init__.py +1 -2
  15. fameio/{source → input}/scenario/agent.py +24 -38
  16. fameio/input/scenario/attribute.py +203 -0
  17. fameio/{source → input}/scenario/contract.py +50 -61
  18. fameio/{source → input}/scenario/exception.py +8 -13
  19. fameio/{source → input}/scenario/fameiofactory.py +6 -6
  20. fameio/{source → input}/scenario/generalproperties.py +22 -47
  21. fameio/{source → input}/scenario/scenario.py +34 -31
  22. fameio/input/scenario/stringset.py +48 -0
  23. fameio/{source → input}/schema/__init__.py +2 -2
  24. fameio/input/schema/agenttype.py +125 -0
  25. fameio/input/schema/attribute.py +268 -0
  26. fameio/{source → input}/schema/java_packages.py +26 -22
  27. fameio/{source → input}/schema/schema.py +25 -22
  28. fameio/{source → input}/validator.py +32 -35
  29. fameio/{source → input}/writer.py +86 -86
  30. fameio/{source/logs.py → logs.py} +25 -9
  31. fameio/{source/results → output}/agent_type.py +21 -22
  32. fameio/{source/results → output}/conversion.py +34 -31
  33. fameio/{source/results → output}/csv_writer.py +7 -7
  34. fameio/{source/results → output}/data_transformer.py +24 -24
  35. fameio/{source/results → output}/input_dao.py +51 -49
  36. fameio/{source/results → output}/output_dao.py +16 -17
  37. fameio/{source/results → output}/reader.py +30 -31
  38. fameio/{source/results → output}/yaml_writer.py +2 -3
  39. fameio/scripts/__init__.py +2 -2
  40. fameio/scripts/convert_results.py +16 -15
  41. fameio/scripts/make_config.py +9 -9
  42. fameio/{source/series.py → series.py} +28 -26
  43. fameio/{source/time.py → time.py} +8 -8
  44. fameio/{source/tools.py → tools.py} +2 -2
  45. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/METADATA +277 -72
  46. fameio-3.0.0.dist-info/RECORD +56 -0
  47. fameio/source/__init__.py +0 -8
  48. fameio/source/loader.py +0 -181
  49. fameio/source/metadata.py +0 -32
  50. fameio/source/path_resolver.py +0 -34
  51. fameio/source/scenario/attribute.py +0 -130
  52. fameio/source/scenario/stringset.py +0 -51
  53. fameio/source/schema/agenttype.py +0 -132
  54. fameio/source/schema/attribute.py +0 -203
  55. fameio/source/schema/exception.py +0 -9
  56. fameio-2.3.0.dist-info/RECORD +0 -55
  57. /fameio/{source/results → output}/__init__.py +0 -0
  58. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSE.txt +0 -0
  59. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  60. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
  61. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
  62. {fameio-2.3.0.dist-info → fameio-3.0.0.dist-info}/WHEEL +0 -0
  63. {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 List, Dict
6
+ from typing import Final
7
7
 
8
- from fameio.source.logs import log_error_and_raise
9
- from fameio.source.schema import SchemaException
10
- from fameio.source.tools import keys_to_lower
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
- _ERR_MISSING_DATA_ITEMS = "JavaPackages requires non-empty list for `DataItems`. Key was missing or list was empty."
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: List[str] = []
26
- self._data_items: List[str] = []
27
- self._portables: List[str] = []
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: Dict[str, List[str]]) -> JavaPackages:
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._KEY_AGENT, [])
44
- java_packages._data_items = definitions.get(JavaPackages._KEY_DATA_ITEM, [])
45
- java_packages._portables = definitions.get(JavaPackages._KEY_PORTABLE, [])
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(SchemaException(JavaPackages._ERR_MISSING_AGENTS))
48
+ log_error_and_raise(SchemaError(JavaPackages._ERR_MISSING_AGENTS))
49
49
  if not java_packages._data_items:
50
- log_error_and_raise(SchemaException(JavaPackages._ERR_MISSING_DATA_ITEMS))
50
+ log().info(JavaPackages._INFO_MISSING_DATA_ITEMS)
51
51
  if not java_packages._portables:
52
- log_error_and_raise(SchemaException(JavaPackages._ERR_MISSING_PORTABLES))
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) -> List[str]:
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) -> List[str]:
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) -> List[str]:
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 Dict, Optional
7
+ from typing import Any, Final
8
8
 
9
- from fameio.source.logs import log_error_and_raise, log
10
- from fameio.source.schema.agenttype import AgentType
11
- from fameio.source.schema.exception import SchemaException
12
- from fameio.source.schema.java_packages import JavaPackages
13
- from fameio.source.tools import keys_to_lower
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
- _WARN_MISSING_PACKAGES = "No `JavaPackages` defined Schema. Future versions of fameio will require it."
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: Optional[JavaPackages] = None
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
- if Schema._KEY_PACKAGES in definitions:
47
- schema._packages = JavaPackages.from_dict(definitions[Schema._KEY_PACKAGES])
48
- else:
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) -> Dict[str, AgentType]:
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