fameio 1.8.2__py3-none-any.whl → 2.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 (46) hide show
  1. CHANGELOG.md +204 -0
  2. fameio/scripts/__init__.py +8 -6
  3. fameio/scripts/__init__.py.license +3 -0
  4. fameio/scripts/convert_results.py +30 -34
  5. fameio/scripts/convert_results.py.license +3 -0
  6. fameio/scripts/make_config.py +13 -16
  7. fameio/scripts/make_config.py.license +3 -0
  8. fameio/source/cli/__init__.py +3 -0
  9. fameio/source/cli/convert_results.py +75 -0
  10. fameio/source/cli/make_config.py +62 -0
  11. fameio/source/cli/options.py +59 -0
  12. fameio/source/cli/parser.py +238 -0
  13. fameio/source/loader.py +10 -11
  14. fameio/source/logs.py +49 -25
  15. fameio/source/results/conversion.py +11 -13
  16. fameio/source/results/csv_writer.py +16 -5
  17. fameio/source/results/data_transformer.py +3 -2
  18. fameio/source/results/input_dao.py +163 -0
  19. fameio/source/results/reader.py +25 -14
  20. fameio/source/results/yaml_writer.py +28 -0
  21. fameio/source/scenario/agent.py +56 -39
  22. fameio/source/scenario/attribute.py +9 -12
  23. fameio/source/scenario/contract.py +55 -40
  24. fameio/source/scenario/exception.py +11 -9
  25. fameio/source/scenario/generalproperties.py +11 -17
  26. fameio/source/scenario/scenario.py +19 -14
  27. fameio/source/schema/agenttype.py +75 -27
  28. fameio/source/schema/attribute.py +8 -7
  29. fameio/source/schema/schema.py +24 -11
  30. fameio/source/series.py +146 -25
  31. fameio/source/time.py +8 -8
  32. fameio/source/tools.py +13 -2
  33. fameio/source/validator.py +138 -58
  34. fameio/source/writer.py +108 -112
  35. fameio-2.0.0.dist-info/LICENSES/Apache-2.0.txt +178 -0
  36. fameio-2.0.0.dist-info/LICENSES/CC-BY-4.0.txt +395 -0
  37. fameio-2.0.0.dist-info/LICENSES/CC0-1.0.txt +121 -0
  38. {fameio-1.8.2.dist-info → fameio-2.0.0.dist-info}/METADATA +694 -660
  39. fameio-2.0.0.dist-info/RECORD +52 -0
  40. {fameio-1.8.2.dist-info → fameio-2.0.0.dist-info}/WHEEL +1 -2
  41. fameio-2.0.0.dist-info/entry_points.txt +4 -0
  42. fameio/source/cli.py +0 -253
  43. fameio-1.8.2.dist-info/RECORD +0 -40
  44. fameio-1.8.2.dist-info/entry_points.txt +0 -3
  45. fameio-1.8.2.dist-info/top_level.txt +0 -1
  46. {fameio-1.8.2.dist-info → fameio-2.0.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 logger
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
- _MISSING_KEY = "Contract requires key '{}' but is missing it."
26
- _MULTI_CONTRACT_CORRUPT = (
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
- _NEGATIVE_INTERVAL = "Contract delivery interval must be a positive integer but was: {}"
32
- _SENDER_IS_RECEIVER = "Contract sender and receiver have the same id: {}"
33
- _DOUBLE_ATTRIBUTE = "Cannot add attribute '{}' to contract because it already exists."
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._SENDER_IS_RECEIVER.format(sender_id))
48
- if delivery_interval < 0:
49
- raise ValueError(self._NEGATIVE_INTERVAL.format(delivery_interval))
50
+ logger().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._DOUBLE_ATTRIBUTE.format(name))
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) -> "Contract":
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._MISSING_KEY)
127
- receiver_id = get_or_raise(definitions, Contract._KEY_RECEIVER, Contract._MISSING_KEY)
128
- product_name = get_or_raise(definitions, Contract._KEY_PRODUCT, Contract._MISSING_KEY)
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._MISSING_KEY)
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._MISSING_KEY)
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.__init_attributes_from_dict(attribute_definitions)
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
- result[self._KEY_SENDER] = self.sender_id
151
- result[self._KEY_RECEIVER] = self.receiver_id
152
- result[self._KEY_PRODUCT] = self.product_name
153
- result[self._KEY_FIRST_DELIVERY] = self.first_delivery_time
154
- result[self._KEY_INTERVAL] = self.delivery_interval
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._MISSING_KEY))
184
- receivers = ensure_is_list(get_or_raise(multi_definition, Contract._KEY_RECEIVER, Contract._MISSING_KEY))
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._MULTI_CONTRACT_CORRUPT.format(senders, receivers))
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
- contract[Contract._KEY_SENDER] = sender
203
- contract[Contract._KEY_RECEIVER] = receiver
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 logging as log
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, logger
8
8
 
9
+ _DEFAULT_USED = "Using default value '{}' for missing key '{}'"
9
10
 
10
- def log_and_raise(message: str):
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, message: str):
24
- """Raises ScenarioException with given `message` if `assertion` is `False`"""
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(message))
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("Using default value '{}' for missing key '{}'".format(default_value, key))
36
+ logger().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
- import logging as log
6
-
6
+ from fameio.source.logs import logger
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
+ logger().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) -> "GeneralProperties":
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
- result[self._KEY_RUN] = self.run_id
85
-
86
- simulation_dict = {}
87
- simulation_dict[self._KEY_START] = self.simulation_start_time
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: 2023 German Aerospace Center <fame@dlr.de>
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()) -> "Scenario":
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
- result = cls(schema, general_props)
42
+ scenario = cls(schema, general_props)
42
43
 
43
44
  for agent_definition in get_or_default(definitions, Scenario._KEY_AGENTS, []):
44
- result.add_agent(factory.new_agent_from_dict(agent_definition))
45
+ scenario.add_agent(factory.new_agent_from_dict(agent_definition))
45
46
 
46
- for multi_definition in get_or_default(definitions, Scenario._KEY_CONTRACTS, []):
47
- for single_contract_definition in Contract.split_contract_definitions(multi_definition):
48
- result.add_contract(factory.new_contract_from_dict(single_contract_definition))
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 result
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
- for agent in self.agents:
60
- result[Scenario._KEY_AGENTS].append(agent.to_dict())
61
- result[Scenario._KEY_CONTRACTS] = []
62
- for contract in self.contracts:
63
- result[Scenario._KEY_CONTRACTS].append(contract.to_dict())
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, logger
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
- _ERR_PRODUCTS_NO_STRING_LIST = "Product definition of AgentType '{}' contains item(s) other than string: '{}'"
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) -> "AgentType":
33
- """Loads an agent type `definition` from the given input dict"""
34
- definition = keys_to_lower(definitions)
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
- result = cls(name)
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
- result._attributes[attribute_name] = AttributeSpecs(full_name, attribute_details)
64
+ agent_type._attributes[attribute_name] = AttributeSpecs(full_name, attribute_details)
65
+ else:
66
+ logger().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
+ logger().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.info(AgentType._NO_ATTRIBUTES.format(name))
78
+ logger().debug(AgentType._NO_OUTPUTS.format(name))
43
79
 
44
- if AgentType._KEY_PRODUCTS in definition:
45
- products_to_add = definition[AgentType._KEY_PRODUCTS]
46
- if products_to_add:
47
- result._products.extend(AgentType.read_products(products_to_add, name))
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
+ logger().debug(AgentType._NO_METADATA.format(name))
48
86
 
49
- if not result._products:
50
- log.info(AgentType._NO_PRODUCTS.format(name))
51
- return result
87
+ return agent_type
52
88
 
53
89
  @staticmethod
54
- def read_products(products: Any, agent_type: str) -> List[str]:
55
- """Returns a list of product names obtained from given `products` defined for `agent_type`"""
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) -> list:
78
- """Returns list of products or an empty list if no products are defined"""
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 logger
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
+ logger().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
+ logger().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
+ logger().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) -> "AttributeType":
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 ""
@@ -1,7 +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
 
6
+ import ast
5
7
  from typing import Dict
6
8
 
7
9
  from fameio.source.logs import log_error_and_raise
@@ -13,32 +15,43 @@ from fameio.source.tools import keys_to_lower
13
15
  class Schema:
14
16
  """Definition of a schema"""
15
17
 
16
- _AGENT_TYPES_MISSING = "Keyword AgentTypes not found in Schema."
18
+ _AGENT_TYPES_MISSING = "Required keyword `AgentTypes` missing in Schema."
19
+ _AGENT_TYPES_EMPTY = "`AgentTypes` must not be empty - at least one type of agent is required."
17
20
  _KEY_AGENT_TYPE = "AgentTypes".lower()
18
21
 
19
22
  def __init__(self, definitions: dict):
20
- # the current Schema class design is read-only, so it's much simpler to remember the original schema dict
21
- # in order to implement to_dict()
22
23
  self._original_input_dict = definitions
23
-
24
- # fill the agent types
25
24
  self._agent_types = {}
26
- for agent_type_name, agent_definition in definitions[Schema._KEY_AGENT_TYPE].items():
27
- agent_type = AgentType.from_dict(agent_type_name, agent_definition)
28
- self._agent_types[agent_type_name] = agent_type
29
25
 
30
26
  @classmethod
31
- def from_dict(cls, definitions: dict) -> "Schema":
32
- """Load definitions from given `schema`"""
27
+ def from_dict(cls, definitions: dict) -> Schema:
28
+ """Load given dictionary `definitions` into a new Schema"""
33
29
  definitions = keys_to_lower(definitions)
34
30
  if Schema._KEY_AGENT_TYPE not in definitions:
35
31
  log_error_and_raise(SchemaException(Schema._AGENT_TYPES_MISSING))
36
- return cls(definitions)
32
+ schema = cls(definitions)
33
+ agent_types = definitions[Schema._KEY_AGENT_TYPE]
34
+ if len(agent_types) == 0:
35
+ log_error_and_raise(SchemaException(Schema._AGENT_TYPES_EMPTY))
36
+
37
+ for agent_type_name, agent_definition in agent_types.items():
38
+ agent_type = AgentType.from_dict(agent_type_name, agent_definition)
39
+ schema._agent_types[agent_type_name] = agent_type
40
+ return schema
41
+
42
+ @classmethod
43
+ def from_string(cls, definitions: str) -> Schema:
44
+ """Load given string `definitions` into a new Schema"""
45
+ return cls.from_dict(ast.literal_eval(definitions))
37
46
 
38
47
  def to_dict(self) -> dict:
39
48
  """Serializes the schema content to a dict"""
40
49
  return self._original_input_dict
41
50
 
51
+ def to_string(self) -> str:
52
+ """Returns a string representation of the Schema of which the class can be rebuilt"""
53
+ return repr(self.to_dict())
54
+
42
55
  @property
43
56
  def agent_types(self) -> Dict[str, AgentType]:
44
57
  """Returns all the agent types by their name"""