fameio 1.8.2__py3-none-any.whl → 2.1.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 +224 -0
- fameio/scripts/__init__.py +8 -6
- fameio/scripts/__init__.py.license +3 -0
- fameio/scripts/convert_results.py +31 -35
- fameio/scripts/convert_results.py.license +3 -0
- fameio/scripts/make_config.py +14 -17
- fameio/scripts/make_config.py.license +3 -0
- fameio/source/cli/__init__.py +3 -0
- fameio/source/cli/convert_results.py +84 -0
- fameio/source/cli/make_config.py +62 -0
- fameio/source/cli/options.py +58 -0
- fameio/source/cli/parser.py +238 -0
- fameio/source/loader.py +10 -11
- fameio/source/logs.py +90 -35
- fameio/source/results/conversion.py +11 -13
- fameio/source/results/csv_writer.py +16 -5
- fameio/source/results/data_transformer.py +6 -22
- fameio/source/results/input_dao.py +163 -0
- fameio/source/results/reader.py +25 -14
- fameio/source/results/yaml_writer.py +28 -0
- fameio/source/scenario/agent.py +56 -39
- fameio/source/scenario/attribute.py +9 -12
- fameio/source/scenario/contract.py +55 -40
- fameio/source/scenario/exception.py +11 -9
- fameio/source/scenario/generalproperties.py +11 -17
- fameio/source/scenario/scenario.py +19 -14
- fameio/source/schema/agenttype.py +75 -27
- fameio/source/schema/attribute.py +8 -7
- fameio/source/schema/java_packages.py +69 -0
- fameio/source/schema/schema.py +44 -15
- fameio/source/series.py +148 -25
- fameio/source/time.py +8 -8
- fameio/source/tools.py +13 -2
- fameio/source/validator.py +138 -58
- fameio/source/writer.py +120 -113
- fameio-2.1.0.dist-info/LICENSES/Apache-2.0.txt +178 -0
- fameio-2.1.0.dist-info/LICENSES/CC-BY-4.0.txt +395 -0
- fameio-2.1.0.dist-info/LICENSES/CC0-1.0.txt +121 -0
- {fameio-1.8.2.dist-info → fameio-2.1.0.dist-info}/METADATA +706 -660
- fameio-2.1.0.dist-info/RECORD +53 -0
- {fameio-1.8.2.dist-info → fameio-2.1.0.dist-info}/WHEEL +1 -2
- fameio-2.1.0.dist-info/entry_points.txt +4 -0
- fameio/source/cli.py +0 -253
- fameio-1.8.2.dist-info/RECORD +0 -40
- fameio-1.8.2.dist-info/entry_points.txt +0 -3
- fameio-1.8.2.dist-info/top_level.txt +0 -1
- {fameio-1.8.2.dist-info → fameio-2.1.0.dist-info}/LICENSE.txt +0 -0
@@ -1,10 +1,11 @@
|
|
1
1
|
# SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
4
5
|
|
5
|
-
import logging as log
|
6
6
|
from typing import Any, Dict, List, Optional
|
7
7
|
|
8
|
+
from fameio.source.logs import log
|
8
9
|
from fameio.source.scenario.attribute import Attribute
|
9
10
|
from fameio.source.scenario.exception import get_or_default, get_or_raise, log_and_raise
|
10
11
|
from fameio.source.time import FameTime
|
@@ -20,17 +21,18 @@ class Contract:
|
|
20
21
|
_KEY_FIRST_DELIVERY = "FirstDeliveryTime".lower()
|
21
22
|
_KEY_INTERVAL = "DeliveryIntervalInSteps".lower()
|
22
23
|
_KEY_EXPIRE = "ExpirationTime".lower()
|
24
|
+
_KEY_METADATA = "MetaData".lower()
|
23
25
|
_KEY_ATTRIBUTES = "Attributes".lower()
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
_ERR_MISSING_KEY = "Contract requires key '{}' but is missing it."
|
28
|
+
_ERR_MULTI_CONTRACT_CORRUPT = (
|
27
29
|
"Definition of Contracts is valid only for One-to-One, One-to-many, Many-to-one, "
|
28
30
|
"or N-to-N sender-to-receiver numbers. Found M-to-N pairing in Contract with "
|
29
31
|
"Senders: {} and Receivers: {}."
|
30
32
|
)
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
_ERR_INTERVAL_NOT_POSITIVE = "Contract delivery interval must be a positive integer but was: {}"
|
34
|
+
_ERR_SENDER_IS_RECEIVER = "Contract sender and receiver have the same id: {}"
|
35
|
+
_ERR_DOUBLE_ATTRIBUTE = "Cannot add attribute '{}' to contract because it already exists."
|
34
36
|
|
35
37
|
def __init__(
|
36
38
|
self,
|
@@ -40,19 +42,21 @@ class Contract:
|
|
40
42
|
delivery_interval: int,
|
41
43
|
first_delivery_time: int,
|
42
44
|
expiration_time: Optional[int] = None,
|
43
|
-
|
45
|
+
meta_data: Optional[dict] = None,
|
46
|
+
) -> None:
|
44
47
|
"""Constructs a new Contract"""
|
45
48
|
assert product_name != ""
|
46
49
|
if sender_id == receiver_id:
|
47
|
-
log.warning(self.
|
48
|
-
if delivery_interval
|
49
|
-
raise ValueError(self.
|
50
|
+
log().warning(self._ERR_SENDER_IS_RECEIVER.format(sender_id))
|
51
|
+
if delivery_interval <= 0:
|
52
|
+
raise ValueError(self._ERR_INTERVAL_NOT_POSITIVE.format(delivery_interval))
|
50
53
|
self._sender_id = sender_id
|
51
54
|
self._receiver_id = receiver_id
|
52
55
|
self._product_name = product_name
|
53
56
|
self._delivery_interval = delivery_interval
|
54
57
|
self._first_delivery_time = first_delivery_time
|
55
58
|
self._expiration_time = expiration_time
|
59
|
+
self._meta_data = meta_data
|
56
60
|
self._attributes = {}
|
57
61
|
|
58
62
|
def _notify_data_changed(self):
|
@@ -99,39 +103,37 @@ class Contract:
|
|
99
103
|
"""Returns the expiration time of the contract if available, None otherwise"""
|
100
104
|
return self._expiration_time
|
101
105
|
|
106
|
+
@property
|
107
|
+
def meta_data(self) -> Optional[dict]:
|
108
|
+
"""Returns the metadata of the contract if available, None otherwise"""
|
109
|
+
return self._meta_data
|
110
|
+
|
102
111
|
@property
|
103
112
|
def attributes(self) -> Dict[str, Attribute]:
|
104
113
|
"""Returns dictionary of all Attributes of the contract"""
|
105
114
|
return self._attributes
|
106
115
|
|
107
|
-
def add_attribute(self, name: str, value: Attribute):
|
116
|
+
def add_attribute(self, name: str, value: Attribute) -> None:
|
108
117
|
"""Adds a new attribute to the Contract (raise an error if it already exists)"""
|
109
118
|
if name in self._attributes:
|
110
|
-
raise ValueError(self.
|
119
|
+
raise ValueError(self._ERR_DOUBLE_ATTRIBUTE.format(name))
|
111
120
|
self._attributes[name] = value
|
112
121
|
self._notify_data_changed()
|
113
122
|
|
114
|
-
def __init_attributes_from_dict(self, attributes: Dict[str, Any]) -> None:
|
115
|
-
"""Resets Contract `attributes` from dict; Must only be called when creating a new Contract"""
|
116
|
-
assert len(self._attributes) == 0
|
117
|
-
self._attributes = {}
|
118
|
-
for name, value in attributes.items():
|
119
|
-
full_name = str(type) + "." + str(id) + name
|
120
|
-
self.add_attribute(name, Attribute(full_name, value))
|
121
|
-
|
122
123
|
@classmethod
|
123
|
-
def from_dict(cls, definitions: dict) ->
|
124
|
+
def from_dict(cls, definitions: dict) -> Contract:
|
124
125
|
"""Parses Contract from given `definitions`"""
|
125
126
|
definitions = keys_to_lower(definitions)
|
126
|
-
sender_id = get_or_raise(definitions, Contract._KEY_SENDER, Contract.
|
127
|
-
receiver_id = get_or_raise(definitions, Contract._KEY_RECEIVER, Contract.
|
128
|
-
product_name = get_or_raise(definitions, Contract._KEY_PRODUCT, Contract.
|
127
|
+
sender_id = get_or_raise(definitions, Contract._KEY_SENDER, Contract._ERR_MISSING_KEY)
|
128
|
+
receiver_id = get_or_raise(definitions, Contract._KEY_RECEIVER, Contract._ERR_MISSING_KEY)
|
129
|
+
product_name = get_or_raise(definitions, Contract._KEY_PRODUCT, Contract._ERR_MISSING_KEY)
|
129
130
|
first_delivery_time = FameTime.convert_string_if_is_datetime(
|
130
|
-
get_or_raise(definitions, Contract._KEY_FIRST_DELIVERY, Contract.
|
131
|
+
get_or_raise(definitions, Contract._KEY_FIRST_DELIVERY, Contract._ERR_MISSING_KEY)
|
131
132
|
)
|
132
|
-
delivery_interval = get_or_raise(definitions, Contract._KEY_INTERVAL, Contract.
|
133
|
+
delivery_interval = get_or_raise(definitions, Contract._KEY_INTERVAL, Contract._ERR_MISSING_KEY)
|
133
134
|
expiration_time = get_or_default(definitions, Contract._KEY_EXPIRE, None)
|
134
135
|
expiration_time = FameTime.convert_string_if_is_datetime(expiration_time) if expiration_time else None
|
136
|
+
meta_data = get_or_default(definitions, Contract._KEY_METADATA, None)
|
135
137
|
result = cls(
|
136
138
|
sender_id,
|
137
139
|
receiver_id,
|
@@ -139,19 +141,29 @@ class Contract:
|
|
139
141
|
delivery_interval,
|
140
142
|
first_delivery_time,
|
141
143
|
expiration_time,
|
144
|
+
meta_data,
|
142
145
|
)
|
143
146
|
attribute_definitions = get_or_default(definitions, Contract._KEY_ATTRIBUTES, dict())
|
144
|
-
result.
|
147
|
+
result._init_attributes_from_dict(attribute_definitions)
|
145
148
|
return result
|
146
149
|
|
150
|
+
def _init_attributes_from_dict(self, attributes: Dict[str, Any]) -> None:
|
151
|
+
"""Resets Contract `attributes` from dict; Must only be called when creating a new Contract"""
|
152
|
+
assert len(self._attributes) == 0
|
153
|
+
self._attributes = {}
|
154
|
+
for name, value in attributes.items():
|
155
|
+
full_name = f"{type}.{id}{name}"
|
156
|
+
self.add_attribute(name, Attribute(full_name, value))
|
157
|
+
|
147
158
|
def to_dict(self) -> dict:
|
148
159
|
"""Serializes the Contract content to a dict"""
|
149
|
-
result = {
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
160
|
+
result = {
|
161
|
+
self._KEY_SENDER: self.sender_id,
|
162
|
+
self._KEY_RECEIVER: self.receiver_id,
|
163
|
+
self._KEY_PRODUCT: self.product_name,
|
164
|
+
self._KEY_FIRST_DELIVERY: self.first_delivery_time,
|
165
|
+
self._KEY_INTERVAL: self.delivery_interval,
|
166
|
+
}
|
155
167
|
|
156
168
|
if self.expiration_time is not None:
|
157
169
|
result[self._KEY_EXPIRE] = self.expiration_time
|
@@ -161,6 +173,8 @@ class Contract:
|
|
161
173
|
for attr_name, attr_value in self.attributes.items():
|
162
174
|
attributes_dict[attr_name] = attr_value.generic_content
|
163
175
|
result[self._KEY_ATTRIBUTES] = attributes_dict
|
176
|
+
if self.meta_data:
|
177
|
+
result[self._KEY_METADATA] = self._meta_data
|
164
178
|
|
165
179
|
return result
|
166
180
|
|
@@ -173,15 +187,15 @@ class Contract:
|
|
173
187
|
for key in [
|
174
188
|
Contract._KEY_PRODUCT,
|
175
189
|
Contract._KEY_FIRST_DELIVERY,
|
176
|
-
Contract._KEY_FIRST_DELIVERY,
|
177
190
|
Contract._KEY_INTERVAL,
|
178
191
|
Contract._KEY_EXPIRE,
|
192
|
+
Contract._KEY_METADATA,
|
179
193
|
Contract._KEY_ATTRIBUTES,
|
180
194
|
]:
|
181
195
|
if key in multi_definition:
|
182
196
|
base_data[key] = multi_definition[key]
|
183
|
-
senders = ensure_is_list(get_or_raise(multi_definition, Contract._KEY_SENDER, Contract.
|
184
|
-
receivers = ensure_is_list(get_or_raise(multi_definition, Contract._KEY_RECEIVER, Contract.
|
197
|
+
senders = ensure_is_list(get_or_raise(multi_definition, Contract._KEY_SENDER, Contract._ERR_MISSING_KEY))
|
198
|
+
receivers = ensure_is_list(get_or_raise(multi_definition, Contract._KEY_RECEIVER, Contract._ERR_MISSING_KEY))
|
185
199
|
if len(senders) > 1 and len(receivers) == 1:
|
186
200
|
for index in range(len(senders)):
|
187
201
|
contracts.append(Contract._copy_contract(senders[index], receivers[0], base_data))
|
@@ -192,14 +206,15 @@ class Contract:
|
|
192
206
|
for index in range(len(senders)):
|
193
207
|
contracts.append(Contract._copy_contract(senders[index], receivers[index], base_data))
|
194
208
|
else:
|
195
|
-
log_and_raise(Contract.
|
209
|
+
log_and_raise(Contract._ERR_MULTI_CONTRACT_CORRUPT.format(senders, receivers))
|
196
210
|
return contracts
|
197
211
|
|
198
212
|
@staticmethod
|
199
213
|
def _copy_contract(sender: int, receiver: int, base_data: dict) -> dict:
|
200
214
|
"""Returns a new contract definition dictionary, with given `sender` and `receiver` and copied `base_data`"""
|
201
|
-
contract = {
|
202
|
-
|
203
|
-
|
215
|
+
contract = {
|
216
|
+
Contract._KEY_SENDER: sender,
|
217
|
+
Contract._KEY_RECEIVER: receiver,
|
218
|
+
}
|
204
219
|
contract.update(base_data)
|
205
220
|
return contract
|
@@ -2,17 +2,19 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
-
import
|
5
|
+
from typing import NoReturn, Any, Union
|
6
6
|
|
7
|
-
from fameio.source.logs import log_error_and_raise
|
7
|
+
from fameio.source.logs import log_error_and_raise, log
|
8
8
|
|
9
|
+
_DEFAULT_USED = "Using default value '{}' for missing key '{}'"
|
9
10
|
|
10
|
-
|
11
|
+
|
12
|
+
def log_and_raise(message: str) -> NoReturn:
|
11
13
|
"""Raises ScenarioException with given `message`"""
|
12
14
|
log_error_and_raise(ScenarioException(message))
|
13
15
|
|
14
16
|
|
15
|
-
def get_or_raise(dictionary: dict, key: str, message: str):
|
17
|
+
def get_or_raise(dictionary: dict, key: str, message: str) -> Union[Any, NoReturn]:
|
16
18
|
"""Returns value associated with `key` in given `dictionary`, or raises ScenarioException if key is missing"""
|
17
19
|
if key not in dictionary or dictionary[key] is None:
|
18
20
|
log_error_and_raise(ScenarioException(message.format(key)))
|
@@ -20,18 +22,18 @@ def get_or_raise(dictionary: dict, key: str, message: str):
|
|
20
22
|
return dictionary[key]
|
21
23
|
|
22
24
|
|
23
|
-
def assert_or_raise(assertion: bool,
|
24
|
-
"""Raises ScenarioException with given `
|
25
|
+
def assert_or_raise(assertion: bool, msg: str) -> None:
|
26
|
+
"""Raises new ScenarioException with given `msg` if `assertion` is False"""
|
25
27
|
if not assertion:
|
26
|
-
log_error_and_raise(ScenarioException(
|
28
|
+
log_error_and_raise(ScenarioException(msg))
|
27
29
|
|
28
30
|
|
29
|
-
def get_or_default(dictionary: dict, key: str, default_value):
|
31
|
+
def get_or_default(dictionary: dict, key: str, default_value) -> Any:
|
30
32
|
"""Returns value associated with `key` in given `dictionary`, or the given `default_value` if key is missing"""
|
31
33
|
if key in dictionary and dictionary[key] is not None:
|
32
34
|
return dictionary[key]
|
33
35
|
else:
|
34
|
-
log.debug(
|
36
|
+
log().debug(_DEFAULT_USED.format(default_value, key))
|
35
37
|
return default_value
|
36
38
|
|
37
39
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
from fameio.source.logs import log
|
7
7
|
from fameio.source.scenario.exception import get_or_default, get_or_raise
|
8
8
|
from fameio.source.time import FameTime
|
9
9
|
from fameio.source.tools import keys_to_lower
|
@@ -34,7 +34,7 @@ class GeneralProperties:
|
|
34
34
|
output_process: int,
|
35
35
|
) -> None:
|
36
36
|
if simulation_stop_time < simulation_start_time:
|
37
|
-
log.warning(GeneralProperties._SIMULATION_DURATION)
|
37
|
+
log().warning(GeneralProperties._SIMULATION_DURATION)
|
38
38
|
self._run_id = run_id
|
39
39
|
self._simulation_start_time = simulation_start_time
|
40
40
|
self._simulation_stop_time = simulation_stop_time
|
@@ -43,7 +43,7 @@ class GeneralProperties:
|
|
43
43
|
self._output_process = output_process
|
44
44
|
|
45
45
|
@classmethod
|
46
|
-
def from_dict(cls, definitions: dict) ->
|
46
|
+
def from_dict(cls, definitions: dict) -> GeneralProperties:
|
47
47
|
"""Parse general properties from provided `definitions`"""
|
48
48
|
definitions = keys_to_lower(definitions)
|
49
49
|
run_id = get_or_default(definitions, GeneralProperties._KEY_RUN, 1)
|
@@ -79,21 +79,15 @@ class GeneralProperties:
|
|
79
79
|
|
80
80
|
def to_dict(self) -> dict:
|
81
81
|
"""Serializes the general properties to a dict"""
|
82
|
-
result = {}
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
simulation_dict[self._KEY_STOP] = self.simulation_stop_time
|
89
|
-
simulation_dict[self._KEY_SEED] = self.simulation_random_seed
|
82
|
+
result = {self._KEY_RUN: self._run_id}
|
83
|
+
simulation_dict = {
|
84
|
+
self._KEY_START: self.simulation_start_time,
|
85
|
+
self._KEY_STOP: self.simulation_stop_time,
|
86
|
+
self._KEY_SEED: self.simulation_random_seed,
|
87
|
+
}
|
90
88
|
result[self._KEY_SIMULATION] = simulation_dict
|
91
|
-
|
92
|
-
output_dict = {}
|
93
|
-
output_dict[self._KEY_INTERVAL] = self.output_interval
|
94
|
-
output_dict[self._KEY_PROCESS] = self.output_process
|
89
|
+
output_dict = {self._KEY_INTERVAL: self.output_interval, self._KEY_PROCESS: self.output_process}
|
95
90
|
result[self._KEY_OUTPUT] = output_dict
|
96
|
-
|
97
91
|
return result
|
98
92
|
|
99
93
|
@property
|
@@ -1,6 +1,7 @@
|
|
1
|
-
# SPDX-FileCopyrightText:
|
1
|
+
# SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
4
5
|
|
5
6
|
from typing import List
|
6
7
|
|
@@ -30,7 +31,7 @@ class Scenario:
|
|
30
31
|
self._contracts = []
|
31
32
|
|
32
33
|
@classmethod
|
33
|
-
def from_dict(cls, definitions: dict, factory=FameIOFactory()) ->
|
34
|
+
def from_dict(cls, definitions: dict, factory=FameIOFactory()) -> Scenario:
|
34
35
|
"""Parse scenario from provided `definitions` using given `factory`"""
|
35
36
|
definitions = keys_to_lower(definitions)
|
36
37
|
|
@@ -38,29 +39,33 @@ class Scenario:
|
|
38
39
|
general_props = factory.new_general_properties_from_dict(
|
39
40
|
get_or_raise(definitions, Scenario._KEY_GENERAL, Scenario._MISSING_KEY)
|
40
41
|
)
|
41
|
-
|
42
|
+
scenario = cls(schema, general_props)
|
42
43
|
|
43
44
|
for agent_definition in get_or_default(definitions, Scenario._KEY_AGENTS, []):
|
44
|
-
|
45
|
+
scenario.add_agent(factory.new_agent_from_dict(agent_definition))
|
45
46
|
|
46
|
-
for
|
47
|
-
for single_contract_definition in Contract.split_contract_definitions(
|
48
|
-
|
47
|
+
for multi_contract_definition in get_or_default(definitions, Scenario._KEY_CONTRACTS, []):
|
48
|
+
for single_contract_definition in Contract.split_contract_definitions(multi_contract_definition):
|
49
|
+
scenario.add_contract(factory.new_contract_from_dict(single_contract_definition))
|
49
50
|
|
50
|
-
return
|
51
|
+
return scenario
|
51
52
|
|
52
53
|
def to_dict(self) -> dict:
|
53
54
|
"""Serializes the scenario content to a dict"""
|
54
55
|
result = {
|
55
56
|
Scenario._KEY_GENERAL: self.general_properties.to_dict(),
|
56
57
|
Scenario._KEY_SCHEMA: self.schema.to_dict(),
|
57
|
-
Scenario._KEY_AGENTS: [],
|
58
58
|
}
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
|
60
|
+
if self.agents:
|
61
|
+
result[Scenario._KEY_AGENTS] = []
|
62
|
+
for agent in self.agents:
|
63
|
+
result[Scenario._KEY_AGENTS].append(agent.to_dict())
|
64
|
+
|
65
|
+
if self.contracts:
|
66
|
+
result[Scenario._KEY_CONTRACTS] = []
|
67
|
+
for contract in self.contracts:
|
68
|
+
result[Scenario._KEY_CONTRACTS].append(contract.to_dict())
|
64
69
|
return result
|
65
70
|
|
66
71
|
@property
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
4
5
|
|
5
|
-
import logging as log
|
6
6
|
from typing import Dict, List, Any
|
7
7
|
|
8
|
-
from fameio.source.logs import log_error_and_raise
|
8
|
+
from fameio.source.logs import log_error_and_raise, log
|
9
9
|
from fameio.source.schema.exception import SchemaException
|
10
10
|
from fameio.source.schema.attribute import AttributeSpecs
|
11
11
|
from fameio.source.tools import keys_to_lower
|
@@ -14,59 +14,93 @@ from fameio.source.tools import keys_to_lower
|
|
14
14
|
class AgentType:
|
15
15
|
"""Schema definitions for an Agent type"""
|
16
16
|
|
17
|
-
|
17
|
+
_ERR_NAME_INVALID = "'{}' is not a valid name for AgentTypes"
|
18
|
+
_ERR_PRODUCTS_NO_STRING = "Product definition of AgentType '{}' contains item(s) / key(s) other than string: '{}'"
|
18
19
|
_ERR_PRODUCTS_UNKNOWN_STRUCTURE = "Product definition of AgentType '{}' is neither list nor dictionary: '{}'"
|
19
20
|
|
20
21
|
_NO_ATTRIBUTES = "Agent '{}' has no specified 'Attributes'."
|
21
22
|
_NO_PRODUCTS = "Agent '{}' has no specified 'Products'."
|
23
|
+
_NO_OUTPUTS = "Agent '{}' has no specified 'Outputs'."
|
24
|
+
_NO_METADATA = "Agent '{}' has no specified 'Metadata'."
|
22
25
|
|
23
26
|
_KEY_ATTRIBUTES = "Attributes".lower()
|
24
27
|
_KEY_PRODUCTS = "Products".lower()
|
28
|
+
_KEY_OUTPUTS = "Outputs".lower()
|
29
|
+
_KEY_METADATA = "MetaData".lower()
|
25
30
|
|
26
31
|
def __init__(self, name: str):
|
32
|
+
"""
|
33
|
+
Initialise a new AgentType
|
34
|
+
|
35
|
+
Args:
|
36
|
+
name: name of the AgenType - must not be None or empty
|
37
|
+
"""
|
38
|
+
if not name or name.isspace():
|
39
|
+
log_error_and_raise(SchemaException(AgentType._ERR_NAME_INVALID.format(name)))
|
27
40
|
self._name = name
|
28
41
|
self._attributes = {}
|
29
|
-
self._products =
|
42
|
+
self._products = {}
|
43
|
+
self._outputs = {}
|
44
|
+
self._metadata = {}
|
30
45
|
|
31
46
|
@classmethod
|
32
|
-
def from_dict(cls, name: str, definitions: dict) ->
|
33
|
-
"""
|
34
|
-
|
47
|
+
def from_dict(cls, name: str, definitions: dict) -> AgentType:
|
48
|
+
"""
|
49
|
+
Creates AgentType with given `name` from specified dictionary
|
50
|
+
|
51
|
+
Args:
|
52
|
+
name: of the agent type
|
53
|
+
definitions: of the agent type specifying, e.g., its attributes and products
|
35
54
|
|
36
|
-
|
55
|
+
Returns:
|
56
|
+
a new instance of AgentType
|
57
|
+
"""
|
58
|
+
agent_type = cls(name)
|
59
|
+
|
60
|
+
definition = keys_to_lower(definitions)
|
37
61
|
if AgentType._KEY_ATTRIBUTES in definition:
|
38
62
|
for attribute_name, attribute_details in definition[AgentType._KEY_ATTRIBUTES].items():
|
39
63
|
full_name = name + "." + attribute_name
|
40
|
-
|
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.update(AgentType._read_products(definition[AgentType._KEY_PRODUCTS], name))
|
70
|
+
else:
|
71
|
+
log().info(AgentType._NO_PRODUCTS.format(name))
|
72
|
+
|
73
|
+
if AgentType._KEY_OUTPUTS in definition:
|
74
|
+
outputs_to_add = definition[AgentType._KEY_OUTPUTS]
|
75
|
+
if outputs_to_add:
|
76
|
+
agent_type._outputs.update(outputs_to_add)
|
41
77
|
else:
|
42
|
-
log.
|
78
|
+
log().debug(AgentType._NO_OUTPUTS.format(name))
|
43
79
|
|
44
|
-
if AgentType.
|
45
|
-
|
46
|
-
if
|
47
|
-
|
80
|
+
if AgentType._KEY_METADATA in definition:
|
81
|
+
metadata_to_add = definition[AgentType._KEY_METADATA]
|
82
|
+
if metadata_to_add:
|
83
|
+
agent_type._metadata.update(metadata_to_add)
|
84
|
+
else:
|
85
|
+
log().debug(AgentType._NO_METADATA.format(name))
|
48
86
|
|
49
|
-
|
50
|
-
log.info(AgentType._NO_PRODUCTS.format(name))
|
51
|
-
return result
|
87
|
+
return agent_type
|
52
88
|
|
53
89
|
@staticmethod
|
54
|
-
def
|
55
|
-
"""Returns a
|
90
|
+
def _read_products(products: Any, agent_type: str) -> Dict[str, Any]:
|
91
|
+
"""Returns a dict obtained from given `products` defined for `agent_type`"""
|
56
92
|
product_names = None
|
57
93
|
if isinstance(products, dict):
|
58
|
-
product_names = [key for key in products.keys()]
|
59
|
-
elif isinstance(products, list):
|
60
94
|
product_names = products
|
95
|
+
elif isinstance(products, list):
|
96
|
+
product_names = {key: None for key in products}
|
61
97
|
else:
|
62
98
|
log_error_and_raise(SchemaException(AgentType._ERR_PRODUCTS_UNKNOWN_STRUCTURE.format(agent_type, products)))
|
63
99
|
|
64
|
-
if all([isinstance(item, str) for item in product_names]):
|
100
|
+
if all([isinstance(item, str) for item in product_names.keys()]):
|
65
101
|
return product_names
|
66
102
|
else:
|
67
|
-
log_error_and_raise(
|
68
|
-
SchemaException(AgentType._ERR_PRODUCTS_NO_STRING_LIST.format(agent_type, product_names))
|
69
|
-
)
|
103
|
+
log_error_and_raise(SchemaException(AgentType._ERR_PRODUCTS_NO_STRING.format(agent_type, product_names)))
|
70
104
|
|
71
105
|
@property
|
72
106
|
def name(self) -> str:
|
@@ -74,11 +108,25 @@ class AgentType:
|
|
74
108
|
return self._name
|
75
109
|
|
76
110
|
@property
|
77
|
-
def products(self) ->
|
78
|
-
"""Returns
|
111
|
+
def products(self) -> dict:
|
112
|
+
"""Returns dict of products or an empty dict if no products are defined"""
|
79
113
|
return self._products
|
80
114
|
|
115
|
+
def get_product_names(self) -> List[str]:
|
116
|
+
"""Returns list of product names or an empty list if no products are defined"""
|
117
|
+
return list(self._products.keys())
|
118
|
+
|
81
119
|
@property
|
82
120
|
def attributes(self) -> Dict[str, AttributeSpecs]:
|
83
121
|
"""Returns list of Attributes of this agent or an empty list if no attributes are defined"""
|
84
122
|
return self._attributes
|
123
|
+
|
124
|
+
@property
|
125
|
+
def outputs(self) -> dict:
|
126
|
+
"""Returns list of outputs or an empty list if no outputs are defined"""
|
127
|
+
return self._outputs
|
128
|
+
|
129
|
+
@property
|
130
|
+
def metadata(self) -> dict:
|
131
|
+
"""Returns list of metadata or an empty list if no metadata are defined"""
|
132
|
+
return self._metadata
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
from __future__ import annotations
|
4
5
|
|
5
|
-
import logging as log
|
6
6
|
import typing
|
7
7
|
from enum import Enum, auto
|
8
8
|
from typing import Any, Dict
|
9
9
|
|
10
|
+
from fameio.source.logs import log
|
10
11
|
from fameio.source.schema.exception import SchemaException
|
11
12
|
from fameio.source.time import FameTime
|
12
13
|
from fameio.source.tools import keys_to_lower
|
@@ -49,7 +50,7 @@ class AttributeSpecs:
|
|
49
50
|
_DEFAULT_NOT_LIST = "Attribute is list, but provided Default '{}' is not a list."
|
50
51
|
_DEFAULT_INCOMPATIBLE = "Default '{}' can not be converted to AttributeType '{}'."
|
51
52
|
_DEFAULT_DISALLOWED = "Default '{}' is not an allowed value."
|
52
|
-
_LIST_DISALLOWED = "Attribute '{}' of type TIME_SERIES cannot be list."
|
53
|
+
_LIST_DISALLOWED = "Attribute '{}' of type TIME_SERIES cannot be a list."
|
53
54
|
_VALUES_ILL_FORMAT = "Only List and Dictionary is supported for 'Values' but was: {}"
|
54
55
|
|
55
56
|
_KEY_MANDATORY = "Mandatory".lower()
|
@@ -69,18 +70,18 @@ class AttributeSpecs:
|
|
69
70
|
self._is_mandatory = definition[AttributeSpecs._KEY_MANDATORY]
|
70
71
|
else:
|
71
72
|
self._is_mandatory = True
|
72
|
-
log.warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs._KEY_MANDATORY, name, True))
|
73
|
+
log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs._KEY_MANDATORY, name, True))
|
73
74
|
|
74
75
|
if AttributeSpecs._KEY_LIST in definition:
|
75
76
|
self._is_list = definition[AttributeSpecs._KEY_LIST]
|
76
77
|
else:
|
77
78
|
self._is_list = False
|
78
|
-
log.warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs._KEY_LIST, name, False))
|
79
|
+
log().warning(AttributeSpecs._MISSING_SPEC_DEFAULT.format(AttributeSpecs._KEY_LIST, name, False))
|
79
80
|
|
80
81
|
if AttributeSpecs._KEY_TYPE in definition:
|
81
82
|
self._attr_type = AttributeSpecs._get_type_for_name(definition[AttributeSpecs._KEY_TYPE])
|
82
83
|
else:
|
83
|
-
log.error(AttributeSpecs._MISSING_TYPE.format(name))
|
84
|
+
log().error(AttributeSpecs._MISSING_TYPE.format(name))
|
84
85
|
raise SchemaException(AttributeSpecs._MISSING_TYPE.format(name))
|
85
86
|
|
86
87
|
if self._attr_type == AttributeType.TIME_SERIES and self._is_list:
|
@@ -183,7 +184,7 @@ class AttributeSpecs:
|
|
183
184
|
return self._full_name
|
184
185
|
|
185
186
|
@staticmethod
|
186
|
-
def _get_type_for_name(name: str) ->
|
187
|
+
def _get_type_for_name(name: str) -> AttributeType:
|
187
188
|
"""Returns the AttributeType matching the given `name` converted to upper case"""
|
188
189
|
try:
|
189
190
|
return AttributeType[name.upper()]
|
@@ -197,5 +198,5 @@ class AttributeSpecs:
|
|
197
198
|
|
198
199
|
@property
|
199
200
|
def help_text(self) -> str:
|
200
|
-
"""Return the help_text of this attribute, if any"""
|
201
|
+
"""Return the help_text of this attribute, if any, otherwise an empty string"""
|
201
202
|
return self._help if self.has_help_text else ""
|
@@ -0,0 +1,69 @@
|
|
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 List, Dict
|
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
|
11
|
+
|
12
|
+
|
13
|
+
class JavaPackages:
|
14
|
+
"""Schema definitions for Java package names in which model classes reside"""
|
15
|
+
|
16
|
+
_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."
|
18
|
+
_ERR_MISSING_PORTABLES = "JavaPackages require non-empty list for `Portables`. Key was missing or list was empty."
|
19
|
+
|
20
|
+
_KEY_AGENT = "Agents".lower()
|
21
|
+
_KEY_DATA_ITEM = "DataItems".lower()
|
22
|
+
_KEY_PORTABLE = "Portables".lower()
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
self._agents: List[str] = []
|
26
|
+
self._data_items: List[str] = []
|
27
|
+
self._portables: List[str] = []
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def from_dict(cls, definitions: Dict[str, List[str]]) -> JavaPackages:
|
31
|
+
"""
|
32
|
+
Creates JavaPackages from a dictionary representation
|
33
|
+
|
34
|
+
Args:
|
35
|
+
definitions: dictionary representation of JavaPackages
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
new instance of JavaPackages
|
39
|
+
"""
|
40
|
+
java_packages = cls()
|
41
|
+
definitions = keys_to_lower(definitions)
|
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, [])
|
46
|
+
|
47
|
+
if not java_packages._agents:
|
48
|
+
log_error_and_raise(SchemaException(JavaPackages._ERR_MISSING_AGENTS))
|
49
|
+
if not java_packages._data_items:
|
50
|
+
log_error_and_raise(SchemaException(JavaPackages._ERR_MISSING_DATA_ITEMS))
|
51
|
+
if not java_packages._portables:
|
52
|
+
log_error_and_raise(SchemaException(JavaPackages._ERR_MISSING_PORTABLES))
|
53
|
+
|
54
|
+
return java_packages
|
55
|
+
|
56
|
+
@property
|
57
|
+
def agents(self) -> List[str]:
|
58
|
+
"""Return list of java package names that contain the model's Agents"""
|
59
|
+
return self._agents
|
60
|
+
|
61
|
+
@property
|
62
|
+
def data_items(self) -> List[str]:
|
63
|
+
"""Return list of java package names that contain the model's DataItems"""
|
64
|
+
return self._data_items
|
65
|
+
|
66
|
+
@property
|
67
|
+
def portables(self) -> List[str]:
|
68
|
+
"""Return list of java package names that contain the model's Portables"""
|
69
|
+
return self._portables
|