fameio 3.3.0__py3-none-any.whl → 3.5.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. fameio/__init__.py +2 -1
  2. fameio/cli/__init__.py +2 -0
  3. fameio/cli/convert_results.py +8 -0
  4. fameio/cli/make_config.py +2 -0
  5. fameio/cli/options.py +4 -0
  6. fameio/cli/parser.py +17 -1
  7. fameio/cli/reformat.py +2 -0
  8. fameio/input/__init__.py +2 -1
  9. fameio/input/loader/__init__.py +1 -0
  10. fameio/input/loader/controller.py +12 -0
  11. fameio/input/loader/loader.py +2 -0
  12. fameio/input/metadata.py +2 -0
  13. fameio/input/resolver.py +2 -0
  14. fameio/input/scenario/__init__.py +2 -0
  15. fameio/input/scenario/agent.py +2 -0
  16. fameio/input/scenario/attribute.py +2 -0
  17. fameio/input/scenario/contract.py +57 -10
  18. fameio/input/scenario/exception.py +2 -0
  19. fameio/input/scenario/fameiofactory.py +2 -0
  20. fameio/input/scenario/generalproperties.py +2 -0
  21. fameio/input/scenario/scenario.py +2 -0
  22. fameio/input/scenario/stringset.py +2 -0
  23. fameio/input/schema/__init__.py +1 -0
  24. fameio/input/schema/agenttype.py +2 -0
  25. fameio/input/schema/attribute.py +2 -0
  26. fameio/input/schema/java_packages.py +2 -0
  27. fameio/input/schema/schema.py +8 -3
  28. fameio/input/validator.py +2 -0
  29. fameio/input/writer.py +16 -0
  30. fameio/logs.py +2 -1
  31. fameio/output/__init__.py +1 -0
  32. fameio/output/agent_type.py +14 -0
  33. fameio/output/conversion.py +2 -0
  34. fameio/output/csv_writer.py +4 -2
  35. fameio/output/data_transformer.py +2 -0
  36. fameio/output/execution_dao.py +5 -0
  37. fameio/output/input_dao.py +5 -3
  38. fameio/output/metadata/__init__.py +10 -0
  39. fameio/output/metadata/compiler.py +75 -0
  40. fameio/output/metadata/json_writer.py +37 -0
  41. fameio/output/metadata/locator.py +242 -0
  42. fameio/output/metadata/oeo_template.py +93 -0
  43. fameio/output/metadata/template_reader.py +65 -0
  44. fameio/output/output_dao.py +2 -0
  45. fameio/output/reader.py +1 -0
  46. fameio/output/yaml_writer.py +3 -1
  47. fameio/scripts/__init__.py +4 -0
  48. fameio/scripts/convert_results.py +35 -2
  49. fameio/scripts/exception.py +1 -0
  50. fameio/series.py +14 -6
  51. fameio/time.py +42 -0
  52. fameio/tools.py +1 -0
  53. fameio-3.5.0.dist-info/LICENSES/CC-BY-ND-4.0.txt +392 -0
  54. fameio-3.5.0.dist-info/METADATA +99 -0
  55. fameio-3.5.0.dist-info/RECORD +67 -0
  56. fameio-3.3.0.dist-info/METADATA +0 -976
  57. fameio-3.3.0.dist-info/RECORD +0 -60
  58. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/LICENSE.txt +0 -0
  59. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  60. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
  61. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
  62. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/WHEEL +0 -0
  63. {fameio-3.3.0.dist-info → fameio-3.5.0.dist-info}/entry_points.txt +0 -0
fameio/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
1
+ # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: CC0-1.0
4
+ """Source code for FAME-Io."""
4
5
 
5
6
  FILE_HEADER_V1 = "famecoreprotobufstreamfilev001" # noqa
6
7
  FILE_HEADER_V2 = "fameprotobufstreamfilev002 " # noqa
fameio/cli/__init__.py CHANGED
@@ -1,4 +1,6 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Parsing of the scripts' command line options."""
5
+
4
6
  from fameio.cli.parser import update_default_config # noqa: F401
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Static methods to handle command line arguments for the command that extracts protobuf files."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import argparse
@@ -19,6 +21,8 @@ from fameio.cli.parser import (
19
21
  add_time_argument,
20
22
  add_merge_time_argument,
21
23
  add_inputs_recovery_argument,
24
+ add_output_metadata_argument,
25
+ add_output_template_argument,
22
26
  map_namespace_to_options_dict,
23
27
  )
24
28
 
@@ -34,6 +38,8 @@ CLI_DEFAULTS = {
34
38
  Options.TIME: TimeOptions.UTC,
35
39
  Options.TIME_MERGING: None,
36
40
  Options.INPUT_RECOVERY: False,
41
+ Options.METADATA: True,
42
+ Options.METADATA_TEMPLATE: None,
37
43
  }
38
44
 
39
45
  _INFILE_PATH_HELP = "Provide path to protobuf file"
@@ -75,6 +81,8 @@ def _prepare_parser(defaults: dict[Options, Any] | None) -> argparse.ArgumentPar
75
81
  add_time_argument(parser, _get_default(defaults, Options.TIME))
76
82
  add_merge_time_argument(parser, _get_default(defaults, Options.TIME_MERGING))
77
83
  add_inputs_recovery_argument(parser, _get_default(defaults, Options.INPUT_RECOVERY))
84
+ add_output_metadata_argument(parser, _get_default(defaults, Options.METADATA))
85
+ add_output_template_argument(parser, _get_default(defaults, Options.METADATA_TEMPLATE))
78
86
 
79
87
  return parser
80
88
 
fameio/cli/make_config.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Static methods to handle command line arguments for the command that creates configuration files."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import argparse
fameio/cli/options.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds allowed command line arguments and value restrictions."""
5
+
4
6
  import argparse
5
7
  from enum import Enum, auto
6
8
 
@@ -36,6 +38,8 @@ class Options(Enum):
36
38
  INPUT_ENCODING = auto()
37
39
  FILE_PATTERN = auto()
38
40
  REPLACE = auto()
41
+ METADATA = auto()
42
+ METADATA_TEMPLATE = auto()
39
43
 
40
44
 
41
45
  class TimeOptions(ParsableEnum, Enum):
fameio/cli/parser.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Methods to add individual command-line arguments to parsers."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import copy
@@ -28,6 +30,8 @@ _OPTION_ARGUMENT_NAME: dict[str, Options] = {
28
30
  "merge_times": Options.TIME_MERGING,
29
31
  "file_pattern": Options.FILE_PATTERN,
30
32
  "replace": Options.REPLACE,
33
+ "metadata": Options.METADATA,
34
+ "template": Options.METADATA_TEMPLATE,
31
35
  }
32
36
 
33
37
 
@@ -168,7 +172,7 @@ def _add_optional_boolean_argument(parser: ArgumentParser, default: bool, arg_na
168
172
  Args:
169
173
  parser: to add the argument to
170
174
  default: of the argument
171
- arg_name: long name of the argument, no short name allowed; will be prepended with 'no-' for negation
175
+ arg_name: long name of the argument without '--', no short name allowed; prepends 'no-' for negation
172
176
  description: to create the help text from: "If --(no-)<arg_name> is specified, <description> (default=X)'
173
177
  """
174
178
  default_str = "--" + ("no-" if not default else "") + arg_name
@@ -189,6 +193,18 @@ def add_replace_argument(parser: ArgumentParser, default_value: bool) -> None:
189
193
  _add_optional_boolean_argument(parser, default_value, "replace", description)
190
194
 
191
195
 
196
+ def add_output_metadata_argument(parser: ArgumentParser, default_value: bool) -> None:
197
+ """Adds optional boolean argument to given `parser` to write output metadata."""
198
+ description = "metadata JSON file accompanying the output files are (not) written"
199
+ _add_optional_boolean_argument(parser, default_value, "metadata", description)
200
+
201
+
202
+ def add_output_template_argument(parser: ArgumentParser, default_value: Path | None) -> None:
203
+ """Adds optional argument to given `parser` to provide a metadata template."""
204
+ help_text = f"Path to metadata template file. Uses provided OEO template if not specified (default={default_value})"
205
+ parser.add_argument("-tmp", "--template", type=Path, help=help_text, required=False, default=default_value)
206
+
207
+
192
208
  def update_default_config(overrides: dict[Options, Any] | None, defaults: dict[Options, Any]) -> dict[Options, Any]:
193
209
  """Returns `defaults` with updated fields received from `overrides`.
194
210
 
fameio/cli/reformat.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Static methods to handle command line arguments for the command that reformats time series files."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import argparse
fameio/input/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
1
+ # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Classes and modules required for the compilation of FAME simulation input files in protobuf format."""
4
5
 
5
6
 
6
7
  class InputError(Exception):
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Loading of YAML files that include custom commands."""
4
5
  from __future__ import annotations
5
6
 
6
7
  from pathlib import Path
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class that spawns the loaders for all YAML files to be read."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from fnmatch import fnmatch
@@ -19,6 +21,10 @@ class LoaderController:
19
21
  """Controls loading of YAML files by spawning one FameYamlLoader per file.
20
22
 
21
23
  Uses same PathResolver and encoding for all files.
24
+
25
+ Attributes:
26
+ DISABLING_YAML_FILE_PREFIX: files starting with this string will be ignored
27
+ NODE_SPLIT_STRING: symbol that separates nodes in include patterns
22
28
  """
23
29
 
24
30
  DISABLING_YAML_FILE_PREFIX: Final[str] = "IGNORE_"
@@ -36,6 +42,12 @@ class LoaderController:
36
42
  _DEBUG_FILES_INCLUDED = "!include directive '{}' yielded these files: '{}'"
37
43
 
38
44
  def __init__(self, path_resolver: PathResolver = PathResolver(), encoding: str | None = None) -> None:
45
+ """Instantiate a new LoaderController.
46
+
47
+ Args:
48
+ path_resolver: to resolve paths to files that are to be included
49
+ encoding: to use when reading the file
50
+ """
39
51
  self._path_resolver = path_resolver
40
52
  self._encoding: str | None = encoding
41
53
 
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class that loads a YAML file supporting custom commands."""
5
+
4
6
  from os import path
5
7
  from typing import IO, Final
6
8
 
fameio/input/metadata.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Storing Metadata associated with inputs, outputs, or models."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from abc import ABC, abstractmethod
fameio/input/resolver.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Resolving of file paths."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import glob
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Classes to load, represent, modify, and save scenarios and their components."""
5
+
4
6
  from .agent import Agent # noqa: F401
5
7
  from .attribute import Attribute # noqa: F401
6
8
  from .contract import Contract # noqa: F401
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class that represents agents of scenarios."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import ast
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class that represents agent attributes in scenarios."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from enum import Enum, auto
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class that represents contracts in scenarios."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Any, Final, overload
@@ -10,7 +12,7 @@ from fameio.input.metadata import Metadata
10
12
  from fameio.input.scenario.attribute import Attribute
11
13
  from fameio.logs import log, log_error
12
14
  from fameio.time import FameTime, ConversionError
13
- from fameio.tools import ensure_is_list, keys_to_lower
15
+ from fameio.tools import keys_to_lower
14
16
 
15
17
 
16
18
  class Contract(Metadata):
@@ -24,6 +26,7 @@ class Contract(Metadata):
24
26
  KEY_PRODUCT: Final[str] = "ProductName".lower()
25
27
  KEY_FIRST_DELIVERY: Final[str] = "FirstDeliveryTime".lower()
26
28
  KEY_INTERVAL: Final[str] = "DeliveryIntervalInSteps".lower()
29
+ KEY_EVERY: Final[str] = "Every".lower()
27
30
  KEY_EXPIRE: Final[str] = "ExpirationTime".lower()
28
31
  KEY_ATTRIBUTES: Final[str] = "Attributes".lower()
29
32
 
@@ -33,7 +36,8 @@ class Contract(Metadata):
33
36
  "or N-to-N sender-to-receiver numbers. Found M-to-N pairing in Contract with "
34
37
  "Senders: {} and Receivers: {}."
35
38
  )
36
- _ERR_INTERVAL_NOT_POSITIVE = "Contract delivery interval must be a positive integer but was: {}"
39
+ _ERR_XOR_KEYS = "Contract expects exactly one of the keys '{}' or '{}'. Found either both or none."
40
+ _ERR_INTERVAL_INVALID = "Contract delivery interval must be a positive integer but was: {}"
37
41
  _ERR_SENDER_IS_RECEIVER = "Contract sender and receiver have the same id: {}"
38
42
  _ERR_DOUBLE_ATTRIBUTE = "Cannot add attribute '{}' to contract because it already exists."
39
43
  _ERR_TIME_CONVERSION = "Contract item '{}' is an ill-formatted time: '{}'"
@@ -73,7 +77,7 @@ class Contract(Metadata):
73
77
  if sender_id == receiver_id:
74
78
  log().warning(self._ERR_SENDER_IS_RECEIVER.format(sender_id))
75
79
  if delivery_interval <= 0:
76
- raise log_error(self.ContractError(self._ERR_INTERVAL_NOT_POSITIVE.format(delivery_interval)))
80
+ raise log_error(self.ContractError(self._ERR_INTERVAL_INVALID.format(delivery_interval)))
77
81
  self._sender_id = sender_id
78
82
  self._receiver_id = receiver_id
79
83
  self._product_name = product_name
@@ -164,7 +168,7 @@ class Contract(Metadata):
164
168
  product_name = Contract._get_or_raise(definitions, Contract.KEY_PRODUCT, Contract._ERR_MISSING_KEY)
165
169
 
166
170
  first_delivery_time = Contract._get_time(definitions, Contract.KEY_FIRST_DELIVERY)
167
- delivery_interval = Contract._get_or_raise(definitions, Contract.KEY_INTERVAL, Contract._ERR_MISSING_KEY)
171
+ delivery_interval = Contract._get_interval(definitions)
168
172
  expiration_time = Contract._get_time(definitions, Contract.KEY_EXPIRE, mandatory=False)
169
173
 
170
174
  contract = cls(sender_id, receiver_id, product_name, delivery_interval, first_delivery_time, expiration_time)
@@ -224,6 +228,43 @@ class Contract(Metadata):
224
228
  raise log_error(Contract.ContractError(Contract._ERR_MISSING_KEY.format(key)))
225
229
  return None
226
230
 
231
+ @staticmethod
232
+ def _get_interval(definitions: dict) -> int:
233
+ """Extract delivery interval from Contract definition, or raise an error if not present or ill formatted.
234
+
235
+ Args:
236
+ definitions: to extract the delivery interval from
237
+
238
+ Returns:
239
+ the delivery interval in fame time steps
240
+
241
+ Raises:
242
+ ContractError: if delivery interval is not defined or invalid, logged with level "ERROR"
243
+ """
244
+ has_interval = Contract.KEY_INTERVAL in definitions
245
+ has_every = Contract.KEY_EVERY in definitions
246
+
247
+ if has_interval and not has_every:
248
+ value = definitions[Contract.KEY_INTERVAL]
249
+ if isinstance(value, int):
250
+ return value
251
+ raise log_error(Contract.ContractError(Contract._ERR_INTERVAL_INVALID.format(value)))
252
+ if has_every and not has_interval:
253
+ value = definitions[Contract.KEY_EVERY]
254
+ if isinstance(value, int):
255
+ return value
256
+ if isinstance(value, str):
257
+ try:
258
+ return FameTime.convert_text_to_time_span(value)
259
+ except ConversionError as e:
260
+ raise log_error(
261
+ Contract.ContractError(Contract._ERR_TIME_CONVERSION.format(Contract.KEY_EVERY, value))
262
+ ) from e
263
+ raise log_error(Contract.ContractError(Contract._ERR_TIME_CONVERSION.format(Contract.KEY_EVERY, value)))
264
+ raise log_error(
265
+ Contract.ContractError(Contract._ERR_XOR_KEYS.format(Contract.KEY_INTERVAL, Contract.KEY_EVERY))
266
+ )
267
+
227
268
  def _init_attributes_from_dict(self, attributes: dict[str, Any]) -> None:
228
269
  """Resets Contract `attributes` from dict.
229
270
 
@@ -280,15 +321,14 @@ class Contract(Metadata):
280
321
  Contract.KEY_EXPIRE,
281
322
  Contract.KEY_METADATA,
282
323
  Contract.KEY_ATTRIBUTES,
324
+ Contract.KEY_EVERY,
283
325
  ]:
284
326
  if key in multi_definition:
285
327
  base_data[key] = multi_definition[key]
286
- senders = ensure_is_list(
287
- Contract._get_or_raise(multi_definition, Contract.KEY_SENDER, Contract._ERR_MISSING_KEY)
288
- )
289
- receivers = ensure_is_list(
290
- Contract._get_or_raise(multi_definition, Contract.KEY_RECEIVER, Contract._ERR_MISSING_KEY)
291
- )
328
+ sender_value = Contract._get_or_raise(multi_definition, Contract.KEY_SENDER, Contract._ERR_MISSING_KEY)
329
+ senders = Contract._unpack_list(sender_value)
330
+ receiver_value = Contract._get_or_raise(multi_definition, Contract.KEY_RECEIVER, Contract._ERR_MISSING_KEY)
331
+ receivers = Contract._unpack_list(receiver_value)
292
332
  if len(senders) > 1 and len(receivers) == 1:
293
333
  for index, sender in enumerate(senders):
294
334
  contracts.append(Contract._copy_contract(sender, receivers[0], base_data))
@@ -302,6 +342,13 @@ class Contract(Metadata):
302
342
  raise log_error(Contract.ContractError(Contract._ERR_MULTI_CONTRACT_CORRUPT.format(senders, receivers)))
303
343
  return contracts
304
344
 
345
+ @staticmethod
346
+ def _unpack_list(obj: Any | list) -> list[Any]:
347
+ """Returns the given value as a flat list - unpacks potential nested list(s)"""
348
+ if isinstance(obj, list):
349
+ return [item for element in obj for item in Contract._unpack_list(element)]
350
+ return [obj]
351
+
305
352
  @staticmethod
306
353
  def _copy_contract(sender: int, receiver: int, base_data: dict) -> dict:
307
354
  """Returns a new contract definition dictionary, with given `sender` and `receiver` and copied `base_data`."""
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds static methods to create or log exceptions."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Any
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds a class that helps to create scenario."""
5
+
4
6
  from fameio.input.schema import Schema
5
7
  from .agent import Agent
6
8
  from .contract import Contract
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds a class to describe the general properties of a simulation."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Final
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds a class to describe scenarios."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Final, Any
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds a class to describe StringSets."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Final, Any, Union
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Classes to load, represent, and save schemas and their components."""
4
5
 
5
6
  from .agenttype import AgentType # noqa: F401
6
7
  from .attribute import AttributeSpecs, AttributeType # noqa: F401
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Hold a class to describe a type of agent."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Any, Final
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds classes to describe an attribute of an agent."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from enum import Enum, auto
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the class to describe the names of java packages that contain the model."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Final
@@ -1,19 +1,23 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Holds the basic schema class."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import ast
9
+ from copy import deepcopy
7
10
  from typing import Any, Final
8
11
 
9
12
  from fameio.input import SchemaError
13
+ from fameio.input.metadata import Metadata
10
14
  from fameio.logs import log_error
11
15
  from fameio.tools import keys_to_lower
12
16
  from .agenttype import AgentType
13
17
  from .java_packages import JavaPackages
14
18
 
15
19
 
16
- class Schema:
20
+ class Schema(Metadata):
17
21
  """Definition of a schema."""
18
22
 
19
23
  KEY_AGENT_TYPE: Final[str] = "AgentTypes".lower()
@@ -24,7 +28,8 @@ class Schema:
24
28
  _ERR_MISSING_PACKAGES = "Missing required section `JavaPackages` in Schema."
25
29
 
26
30
  def __init__(self, definitions: dict) -> None:
27
- self._original_input_dict = definitions
31
+ super().__init__(definitions)
32
+ self._original_input_dict = deepcopy(definitions)
28
33
  self._agent_types: dict[str, AgentType] = {}
29
34
  self._packages: JavaPackages | None = None
30
35
 
@@ -80,7 +85,7 @@ class Schema:
80
85
  """Load given string `definitions` into a new Schema."""
81
86
  return cls.from_dict(ast.literal_eval(definitions))
82
87
 
83
- def to_dict(self) -> dict:
88
+ def _to_dict(self) -> dict:
84
89
  """Serializes the schema content to a dict."""
85
90
  return self._original_input_dict
86
91
 
fameio/input/validator.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Checking scenarios for consistency with their model schema."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import math
fameio/input/writer.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Writing simulation configuration files in protobuf format."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import sys
@@ -88,6 +90,7 @@ class ProtoWriter:
88
90
  self._set_time_series(pb_input)
89
91
  self._set_schema(pb_input, scenario.schema)
90
92
  self._set_string_sets(pb_input, scenario.string_sets)
93
+ self._set_scenario_metadata(pb_input, scenario.metadata)
91
94
 
92
95
  self._set_java_package_names(pb_data_storage.model, scenario.schema.packages)
93
96
  self._set_execution_versions(pb_data_storage.execution.version_data)
@@ -146,6 +149,7 @@ class ProtoWriter:
146
149
  values_not_set = list(specs.keys())
147
150
  for name, attribute in attributes.items():
148
151
  pb_field = self._add_field(pb_parent, name)
152
+ self._set_field_metadata(pb_field, attribute.metadata)
149
153
  attribute_specs = specs[name]
150
154
  values_not_set.remove(name)
151
155
  attribute_type = attribute_specs.attr_type
@@ -172,6 +176,12 @@ class ProtoWriter:
172
176
  pb_field.field_name = name
173
177
  return pb_field
174
178
 
179
+ @staticmethod
180
+ def _set_field_metadata(pb_field: NestedField, attribute_metadata: dict) -> None:
181
+ """Sets metadata of given `pb_field`, provided that given `attribute_metadata` are not empty"""
182
+ if attribute_metadata:
183
+ pb_field.metadata = repr(attribute_metadata)
184
+
175
185
  def _set_attribute(self, pb_field: NestedField, value: Any, attribute_type: AttributeType) -> None:
176
186
  """Sets given `value` to given protobuf `pb_field` depending on specified `attribute_type`.
177
187
 
@@ -285,6 +295,12 @@ class ProtoWriter:
285
295
  if string_set.has_metadata():
286
296
  pb_set.metadata = string_set.get_metadata_string()
287
297
 
298
+ @staticmethod
299
+ def _set_scenario_metadata(pb_input: InputData, scenario_metadata: dict) -> None:
300
+ """Adds the given metadata to the provided `pb_input.`"""
301
+ if scenario_metadata:
302
+ pb_input.metadata = repr(scenario_metadata)
303
+
288
304
  @staticmethod
289
305
  def _set_java_package_names(pb_model: ModelData, java_packages: JavaPackages) -> None:
290
306
  """Adds given JavaPackages names to given ModelData section."""
fameio/logs.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Handling of fameio-specific logger, log levels, and log formatting."""
4
5
  from __future__ import annotations
5
6
 
6
7
  import logging as pylog
@@ -49,7 +50,7 @@ def log_critical(exception: T) -> T:
49
50
  """Logs a critical error with the exception's message and returns the exception for raising it.
50
51
 
51
52
  Does **not** raise the exception, i.e. the command must be preceded by a `raise`.
52
- Example: `raise log_critical(MyException("My error message"))
53
+ Example: `raise log_critical(MyException("My error message"))`
53
54
 
54
55
  Args:
55
56
  exception: to extract the error message from
fameio/output/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: CC0-1.0
4
+ """Classes and modules required for the transformation of protobuf files to human-readable file formats."""
4
5
 
5
6
 
6
7
  class OutputError(Exception):
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Description of types of agents and their output data."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from fameprotobuf.services_pb2 import Output
@@ -151,3 +153,15 @@ class AgentTypeLog:
151
153
  def get_agents_with_output(self) -> list[str]:
152
154
  """Returns all names of agents that had output."""
153
155
  return self._agents_with_output
156
+
157
+ def get_agent_columns(self) -> dict[str, list[str]]:
158
+ """Returns all agents with output mapped to their simple output columns.
159
+
160
+ Raises:
161
+ AgentTypeError: if - somehow - an agent type is not registered but has data, logged with level "ERROR"
162
+ """
163
+ result = {}
164
+ for agent in self.get_agents_with_output():
165
+ agent_type = self.get_agent_type(agent)
166
+ result[agent] = list(agent_type.get_simple_column_map().values())
167
+ return result
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2025 German Aerospace Center <fame@dlr.de>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ """Methods to convert or merge time stamps of agent timeseries output."""
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  import math