fameio 2.3.1__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 +24 -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 -83
  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.1.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.1.dist-info/RECORD +0 -55
  57. /fameio/{source/results → output}/__init__.py +0 -0
  58. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/LICENSE.txt +0 -0
  59. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  60. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
  61. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
  62. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/WHEEL +0 -0
  63. {fameio-2.3.1.dist-info → fameio-3.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,56 @@
1
+ CHANGELOG.md,sha256=PLSHTL6fgGuJ_zdIDiJi_LoJ5HpekTlG2MooflQ14ls,13801
2
+ fameio/__init__.py,sha256=LiE7kRXW0pMIB4hTPC0T_ppGz9O0swd0Ca1-b99hOMc,229
3
+ fameio/cli/__init__.py,sha256=xAS0gRfzq1qepCW6PjIozRC6t3DOxzdNvHU9beFOGHU,167
4
+ fameio/cli/convert_results.py,sha256=-nAAuO_CznggZnZHeGctP_uXcQsQGjTDDZmHAqlBMJQ,3438
5
+ fameio/cli/make_config.py,sha256=Dbn_ITlhpumc-bK9ONKZyGnyYLXsc8YIWQN9OHLBHe4,2712
6
+ fameio/cli/options.py,sha256=2XNwCAloiy_lC77C7LEngMvvT7rVmzASeE_JxQlpHak,1257
7
+ fameio/cli/parser.py,sha256=fR28uzjf6MV3do4AefrXbRu2m4-x0bR-Xn-1Di1e0Lk,8391
8
+ fameio/input/__init__.py,sha256=mDpz4hKZqpzrxtuvwK-eZl-zwHhegRbiH5ew1LpjaAg,550
9
+ fameio/input/loader/__init__.py,sha256=ShZq2zBGz3-423Fd6DLCi1wAtD2JDgFiaknJ24lGToU,2659
10
+ fameio/input/loader/controller.py,sha256=Ph7QvGwbJJ4MNb21nPiYJNTdjpPcRFms95Fd-evzGD0,5800
11
+ fameio/input/loader/loader.py,sha256=uVCUXN2FqtO058zxDBQth2rabP8BPtInMZnAcI7SX8Q,5060
12
+ fameio/input/metadata.py,sha256=l28BPnwjb62RRt-tBJ-HVmOJVO1QIRlKdMx-gjoP_mw,5957
13
+ fameio/input/resolver.py,sha256=86APkFnupWSJNAaX4RP_SOYNv5W3WUwV_I6Iptkur_Q,1917
14
+ fameio/input/scenario/__init__.py,sha256=azglGZTK39jpkDOmeVAhiLH9zIsUx2SZ_umQIlJiq6Q,322
15
+ fameio/input/scenario/agent.py,sha256=Zl83x3U1f4nVv1gbCOhjQ911u-GwWXoWR5_QW0cRHtM,4377
16
+ fameio/input/scenario/attribute.py,sha256=-u-dCzs8lUhSEc98hDot28nl6BLA7sEzAg9GzmNci4k,9635
17
+ fameio/input/scenario/contract.py,sha256=D6iy-oVNwIqQxeZQ3rITERwIgFRrhzYHRysygn-ceDk,9046
18
+ fameio/input/scenario/exception.py,sha256=zavHELqzpIJvb_4GhskYQyi2u5Y6LppwWQt-yGecMQY,1434
19
+ fameio/input/scenario/fameiofactory.py,sha256=_8R3q_e5lHGd75DDbgJvTX6PJNn0i6nZSnW4DyGgdOA,1558
20
+ fameio/input/scenario/generalproperties.py,sha256=gTSLS0bgoZYAMDYhaFTGcJxipgik9AntSPihQBIcX6k,3504
21
+ fameio/input/scenario/scenario.py,sha256=f9gIV_FdtS52L_nWdstwSoXlN8gL7ovxGPAaWpEzOjc,4608
22
+ fameio/input/scenario/stringset.py,sha256=ORW0_4M9CGtB5NQAzQhzZJliHmyW0yQjG_tzMjnRxUw,1814
23
+ fameio/input/schema/__init__.py,sha256=V1m0Cx97lNeZwaQBFo-a5WMbohdsCwDfvmjtxD2vaBo,271
24
+ fameio/input/schema/agenttype.py,sha256=NLcdIuMKjFsqhHG9zisYaHDnP7eDOctHWG1yEH9_ugg,5149
25
+ fameio/input/schema/attribute.py,sha256=4nH-lA0Wh7pe8ZPOWXjuVkPa6Jl5cYQnxU5_e4Y54L0,11369
26
+ fameio/input/schema/java_packages.py,sha256=jkwEgR3mOGky-cvSgR3pGQEJ84hXN5NZS_oyoeYt-9g,2845
27
+ fameio/input/schema/schema.py,sha256=-Hm7r9Hka8NDnNZLe9GCeaULcBgvvhHQhEYjxQ4VzTg,3039
28
+ fameio/input/validator.py,sha256=-X0aEEzKj6s5laxXhDzGcwAa0f4Ssp5waLmuriO-3u0,18359
29
+ fameio/input/writer.py,sha256=TyFkS3KKdAaXiMq-yLDriwOcy65-P5ZeEtzOtUM1y38,12810
30
+ fameio/logs.py,sha256=JxT4JkyKCxxB9QybGOhAcncCexmoIx6_dkGczjG-h8A,3992
31
+ fameio/output/__init__.py,sha256=IQm0MNOXkhBexiMXBoNZDK5xHUYgazH7oXm-lc0Vm04,109
32
+ fameio/output/agent_type.py,sha256=6niFUKOhxUdGw98NUVNtIM543hAQaCkyy7hacxlc9dU,4328
33
+ fameio/output/conversion.py,sha256=2DQ6T2AuU0iDrKsUQ1HcM1TO_prqlENtq2n28wPKwcU,3991
34
+ fameio/output/csv_writer.py,sha256=hU3TM_c8bltY_j_6NZInNB7fyLsNZsYsOrfVvDNYwwE,5117
35
+ fameio/output/data_transformer.py,sha256=2fBXQ8byS3Lhsmocr2AB5B_I9NmDQrwzIViUtQeKYYg,5473
36
+ fameio/output/input_dao.py,sha256=aTNdRM_uaVXB8X7EkWZ3eEd1IMH93Bfx8jCpFvhlBVE,6983
37
+ fameio/output/output_dao.py,sha256=f7xnsjY80E6OolHlFDzWkJG2J86wxXJJ2nRRmgUnVVc,4085
38
+ fameio/output/reader.py,sha256=wiTCkmETtcZR_ybb4CxnfyTtBF74bTNp2w96fFD8Qo0,5097
39
+ fameio/output/yaml_writer.py,sha256=YIUKQB10BnDF9LghLf5bD23ryUI340lGxslB5j88_dk,805
40
+ fameio/scripts/__init__.py,sha256=Owg46sNUIkq7qLJpfa75Cuhjtl3HGkR6l1-LEaNE54A,727
41
+ fameio/scripts/__init__.py.license,sha256=2-OqCNxP4504xY2XQqseYypJi1_Qx4xJSzO3t7c3ACM,107
42
+ fameio/scripts/convert_results.py,sha256=Olrw4l9nGzstgdVyhJJthHCKyaTubVXSlM26729Sjmk,4173
43
+ fameio/scripts/convert_results.py.license,sha256=2-OqCNxP4504xY2XQqseYypJi1_Qx4xJSzO3t7c3ACM,107
44
+ fameio/scripts/make_config.py,sha256=LvwXbBlaGdKC25BRlk4LJDEwvZzxzCzYyVewtyHhIMM,1351
45
+ fameio/scripts/make_config.py.license,sha256=2-OqCNxP4504xY2XQqseYypJi1_Qx4xJSzO3t7c3ACM,107
46
+ fameio/series.py,sha256=BMnJMAdx38duao5w9165b9XB1JRIFOLZ83cjzN5wRUg,9110
47
+ fameio/time.py,sha256=iiCVpEmBSxHgKft_X-E_D-dpOT-L2Y_xN-6pVFtJhDQ,6949
48
+ fameio/tools.py,sha256=8Ia-J-mgjf1NCXMvjLDj10hDwEKzp6jS6eq6z8W005w,1056
49
+ fameio-3.0.0.dist-info/entry_points.txt,sha256=jvQVfwJjZXPWQjJlhj1Dt6PTeblryTc1GxjKeK90twI,123
50
+ fameio-3.0.0.dist-info/LICENSE.txt,sha256=eGHBZnhr9CWjE95SWjRfmhtK1lvVn5X4Fpf3KrrAZDg,10391
51
+ fameio-3.0.0.dist-info/LICENSES/Apache-2.0.txt,sha256=eGHBZnhr9CWjE95SWjRfmhtK1lvVn5X4Fpf3KrrAZDg,10391
52
+ fameio-3.0.0.dist-info/LICENSES/CC-BY-4.0.txt,sha256=y9WvMYKGt0ZW8UXf9QkZB8wj1tjJrQngKR7CSXeSukE,19051
53
+ fameio-3.0.0.dist-info/LICENSES/CC0-1.0.txt,sha256=9Ofzc7m5lpUDN-jUGkopOcLZC3cl6brz1QhKInF60yg,7169
54
+ fameio-3.0.0.dist-info/METADATA,sha256=q0ibxLgLicduMZ0Xe9fvYAPaY8FMjOu66_0pTC-QRAQ,37270
55
+ fameio-3.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
56
+ fameio-3.0.0.dist-info/RECORD,,
fameio/source/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from .path_resolver import PathResolver
6
- from .time import FameTime
7
- from .validator import SchemaValidator
8
- from .writer import ProtoWriter, ProtoWriterException
fameio/source/loader.py DELETED
@@ -1,181 +0,0 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import os
6
- from pathlib import Path
7
- from fnmatch import fnmatch
8
- from typing import IO, Any, Callable
9
-
10
- import yaml
11
- from fameio.source.logs import log_and_raise_critical, log
12
- from fameio.source.path_resolver import PathResolver
13
-
14
- DISABLING_YAML_FILE_PREFIX = "IGNORE_"
15
-
16
- CRIT_NO_YAML_SUFFIX = "File must have a `.yaml` or `.yml` suffix. Received `-f/--file {}` instead."
17
-
18
- FILE_ENCODINGS = [None]
19
-
20
-
21
- class Args:
22
- def __init__(self, file_string, node_string):
23
- self.file_string = file_string
24
- self.node_string = node_string
25
-
26
-
27
- def read_args(loader, args):
28
- """Returns two Strings to be interpreted as files to be read and a sub-node_address from the given `args`"""
29
- node_string = ""
30
- file_string = None
31
- if isinstance(args, yaml.nodes.ScalarNode):
32
- file_string = loader.construct_scalar(args)
33
- log().debug("Found instance `ScalarNode` in {}".format(file_string))
34
- elif isinstance(args, yaml.nodes.SequenceNode):
35
- argument_list = loader.construct_sequence(args)
36
- if len(argument_list) not in [1, 2]:
37
- log_and_raise_critical("!include supports but one or two arguments in list")
38
- elif len(argument_list) == 2:
39
- node_string = argument_list[1]
40
- file_string = argument_list[0]
41
- log().debug("Found instance `SequenceNode` in {}".format(file_string))
42
- elif isinstance(args, yaml.nodes.MappingNode):
43
- argument_map = loader.construct_mapping(args)
44
- for key, value in argument_map.items():
45
- if key.lower() == "file":
46
- file_string = value
47
- elif key.lower() == "node":
48
- node_string = value
49
- else:
50
- log_and_raise_critical("!include supports only keys 'file' and 'node'")
51
- if not file_string:
52
- log_and_raise_critical("!include: file must be specified.")
53
- else:
54
- log_and_raise_critical("YAML node type not implemented: {}".format(args))
55
- return Args(file_string, node_string)
56
-
57
-
58
- def split_nodes(node_string):
59
- """Returns a list of nodes created from the given `node_string`"""
60
- log().debug("Splitting given node_string `{}`".format(node_string))
61
- return node_string.split(":")
62
-
63
-
64
- class FameYamlLoader(yaml.SafeLoader):
65
- """Custom YAML Loader for `!include` constructor"""
66
-
67
- def __init__(self, stream: IO, path_resolver=PathResolver()) -> None:
68
- log().debug("Initialize custom YAML loader")
69
- self._path_resolver = path_resolver
70
- try:
71
- self._root_path = os.path.split(stream.name)[0]
72
- except AttributeError:
73
- self._root_path = os.path.curdir
74
- super().__init__(stream)
75
-
76
- @property
77
- def root_path(self) -> str:
78
- return self._root_path
79
-
80
- @property
81
- def path_resolver(self) -> PathResolver:
82
- return self._path_resolver
83
-
84
-
85
- def make_yaml_loader_builder(
86
- path_resolver: PathResolver,
87
- ) -> Callable[[Any], FameYamlLoader]:
88
- """Utility function used to control the creation of a FameYamlLoader by the YAML library."""
89
- return lambda stream: FameYamlLoader(stream, path_resolver)
90
-
91
-
92
- def resolve_imported_path(loader: FameYamlLoader, included_path: str):
93
- """
94
- Returns a list of file paths matching the given `included_path` based on path resolution performed by the loader.
95
-
96
- Ignores the files starting with `DISABLING_YAML_FILE_PREFIX`
97
- """
98
- file_list = loader.path_resolver.resolve_yaml_imported_file_pattern(loader.root_path, included_path)
99
-
100
- ignore_filter = "*" + DISABLING_YAML_FILE_PREFIX + "*"
101
- cleaned_file_list = []
102
- for file in file_list:
103
- if fnmatch(file, ignore_filter):
104
- log().debug("Ignoring file {} due to prefix {}".format(file, DISABLING_YAML_FILE_PREFIX))
105
- else:
106
- cleaned_file_list.append(file)
107
- if not cleaned_file_list:
108
- log_and_raise_critical("Failed to find any file matching the `!include` directive `{}`".format(included_path))
109
- log().debug("Collected file(s) `{}` from given included path `{}`".format(cleaned_file_list, included_path))
110
- return cleaned_file_list
111
-
112
-
113
- def read_data_from_file(file, node_address, path_resolver: PathResolver):
114
- """Returns data of the specified `node_address` from the specified `file`"""
115
- data = yaml.load(file, make_yaml_loader_builder(path_resolver))
116
- for node in node_address:
117
- if node:
118
- try:
119
- data = data[node]
120
- except KeyError:
121
- log_and_raise_critical("'!include_node [{}, {}]': Cannot find '{}'.".format(file, node_address, node))
122
- log().debug("Searched file `{}` for node `{}`".format(file, node_address))
123
- return data
124
-
125
-
126
- def join_data(new_data, previous_data):
127
- """Joins data from multiple files if both are in list format, otherwise returns"""
128
- if not previous_data:
129
- return new_data
130
- if isinstance(new_data, list) and isinstance(previous_data, list):
131
- previous_data.extend(new_data)
132
- return previous_data
133
- else:
134
- log_and_raise_critical("!include can only combine list-like elements from multiple files!")
135
-
136
-
137
- def construct_include(loader: FameYamlLoader, args: yaml.Node) -> Any:
138
- """
139
- Loads one or many YAML file(s) with specifications provided in `args` in different formats
140
- To load all content of a specified file, use:
141
- !include "path/to/file.yaml"
142
- To load only specific content (e.g. data of "Super:Sub:Node") from a given file, use:
143
- !include ["path/to/file.yml", "Super:Sub:Node"]
144
- For a slightly more verbose version of the above commands, use a dictionary argument:
145
- !include {"file":"path/to/file.yml", "node": "Super:Sub:Node"}
146
-
147
- Instead of "path/to/file.yaml" one can also use asterisks to select multiple files, e.g. "path/to/files/*.yaml"
148
- The given file path is either
149
- * relative to the path of the including YAML file, if it starts with a character other than "/"
150
- * an absolute path if its starts with "/"
151
- """
152
- args = read_args(loader, args)
153
- nodes = split_nodes(args.node_string)
154
- files = resolve_imported_path(loader, args.file_string)
155
-
156
- joined_data = None
157
- for file_name in files:
158
- with open(file_name, "r", encoding=FILE_ENCODINGS[0]) as open_file:
159
- data = read_data_from_file(open_file, nodes, loader.path_resolver)
160
- joined_data = join_data(data, joined_data)
161
- log().debug("Joined all files `{}` to joined data `{}`".format(files, joined_data))
162
- return joined_data
163
-
164
-
165
- FameYamlLoader.add_constructor("!include", construct_include)
166
-
167
-
168
- def load_yaml(yaml_file_path: Path, path_resolver=PathResolver(), encoding: str = None) -> dict:
169
- """Loads the yaml file from given `yaml_file_path` and returns its content"""
170
- log().info("Loading yaml from {}".format(yaml_file_path))
171
- FILE_ENCODINGS[0] = encoding
172
- with open(yaml_file_path, "r", encoding=encoding) as configfile:
173
- data = yaml.load(configfile, make_yaml_loader_builder(path_resolver))
174
- FILE_ENCODINGS[0] = None
175
- return data
176
-
177
-
178
- def check_for_yaml_file_type(yaml_file: Path) -> None:
179
- """Raises Exception if given `yaml_file` has no YAML-associated file suffix"""
180
- if yaml_file.suffix.lower() not in [".yaml", ".yml"]:
181
- log_and_raise_critical(CRIT_NO_YAML_SUFFIX.format(yaml_file))
fameio/source/metadata.py DELETED
@@ -1,32 +0,0 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
- from abc import ABC
5
- from typing import Dict, Any
6
-
7
- from fameio.source.tools import keys_to_lower
8
-
9
-
10
- class Metadata(ABC):
11
- """Hosts Metadata"""
12
-
13
- _KEY_METADATA = "MetaData".lower()
14
-
15
- def __init__(self):
16
- self._metadata = {}
17
-
18
- @property
19
- def metadata(self) -> dict:
20
- """Returns list of metadata or an empty list if no metadata are defined"""
21
- return self._metadata
22
-
23
- def _extract_metadata(self, definitions: Dict[str, Any]) -> None:
24
- """If metadata is found in definitions, it is extracted and set"""
25
- definitions = keys_to_lower(definitions)
26
- if self._KEY_METADATA in definitions:
27
- self._metadata = definitions[self._KEY_METADATA]
28
-
29
- def _enrich_with_metadata(self, data: Dict) -> Dict:
30
- """Returns data enriched with metadata field"""
31
- data[self._KEY_METADATA] = self._metadata
32
- return data
@@ -1,34 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import glob
6
- import os
7
- from typing import List, Optional
8
-
9
-
10
- class PathResolver:
11
- """Class responsible for locating files referenced in a scenario.
12
-
13
- Such files can be the ones referenced via the YAML `!include` extension, or simply the data files (time_series)
14
- referenced in attributes.
15
-
16
- This class provides a default behaviour that can easily be customized by the caller."""
17
-
18
- def resolve_yaml_imported_file_pattern(self, root_path: str, file_pattern: str) -> List[str]:
19
- """Returns a list of file paths matching the given `file_pattern` based on the configured resolver."""
20
- absolute_path = os.path.abspath(os.path.join(root_path, file_pattern))
21
- return glob.glob(absolute_path)
22
-
23
- def resolve_series_file_path(self, file_name: str) -> Optional[str]:
24
- """Returns the absolute file path for the given series (relative) file name, or None on failure."""
25
- if os.path.isabs(file_name):
26
- return file_name
27
-
28
- # try to locate in the current dir
29
- file_path = os.path.join(os.path.curdir, file_name)
30
- if os.path.exists(file_path):
31
- return file_path
32
-
33
- # not found
34
- return None
@@ -1,130 +0,0 @@
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, Dict, List
7
-
8
- from fameio.source.scenario.exception import log_and_raise
9
-
10
-
11
- class Attribute:
12
- """An Attribute of an agent in a scenario"""
13
-
14
- _VALUE_MISSING = "Value not specified for Attribute '{}' - leave out if default shall be used (if defined)."
15
- _LIST_EMPTY = "Attribute '{}' was assigned an empty list - please remove or fill empty assignments."
16
- _DICT_EMPTY = "Attribute '{}' was assigned an empty dictionary - please remove or fill empty assignments."
17
- _MIXED_DATA = "Attribute '{}' was assigned a list with mixed complex and simple entries - please fix."
18
-
19
- _NAME_STRING_SEPARATOR = "."
20
-
21
- def __init__(self, name: str, definitions) -> None:
22
- """Parses an Attribute's definition"""
23
- self._full_name = name
24
-
25
- if definitions is None:
26
- log_and_raise(Attribute._VALUE_MISSING.format(name))
27
-
28
- if isinstance(definitions, dict):
29
- self._value = None
30
- self._nested_list = None
31
- self._nested = Attribute._build_attribute_dict(name, definitions)
32
- elif Attribute._is_list_of_dict(name, definitions):
33
- self._nested = None
34
- self._value = None
35
- self._nested_list = []
36
- for entry in definitions:
37
- self._nested_list.append(Attribute._build_attribute_dict(name, entry))
38
- else:
39
- self._nested = None
40
- self._nested_list = None
41
- self._value = definitions
42
-
43
- @staticmethod
44
- def _build_attribute_dict(name: str, definitions: Dict[str, Any]) -> Dict[str, "Attribute"]:
45
- """Returns a new dictionary containing Attributes generated from given `definitions`"""
46
- if not definitions:
47
- log_and_raise(Attribute._DICT_EMPTY.format(name))
48
-
49
- inner_elements = {}
50
- for nested_name, value in definitions.items():
51
- full_name = name + Attribute._NAME_STRING_SEPARATOR + nested_name
52
- inner_elements[nested_name] = Attribute(full_name, value)
53
- return inner_elements
54
-
55
- @staticmethod
56
- def _is_list_of_dict(name: str, definitions: Any) -> bool:
57
- """Returns True if given `definitions` is a list of dict"""
58
- if isinstance(definitions, list):
59
- if not definitions:
60
- log_and_raise(Attribute._LIST_EMPTY.format(name))
61
-
62
- all_dicts = no_dicts = True
63
- for item in definitions:
64
- if not isinstance(item, dict):
65
- all_dicts = False
66
- else:
67
- no_dicts = False
68
- if (not all_dicts) and (not no_dicts):
69
- log_and_raise(Attribute._MIXED_DATA.format(name))
70
- return all_dicts
71
- return False
72
-
73
- @property
74
- def generic_content(self) -> Any:
75
- """Returns the full content of the attribute (and its children) as a generic value"""
76
- if self.has_value:
77
- return self.value
78
- elif self.has_nested_list:
79
- result = []
80
- for attr_dict in self.nested_list:
81
- inner_elements = {}
82
- for name, attr in attr_dict.items():
83
- inner_elements[name] = attr.generic_content
84
- result.append(inner_elements)
85
- return result
86
- elif self.has_nested:
87
- result = {}
88
- for name, attr in self.nested.items():
89
- result[name] = attr.generic_content
90
- return result
91
- else:
92
- log_and_raise(Attribute._VALUE_MISSING.format(self._full_name))
93
-
94
- @property
95
- def has_value(self) -> bool:
96
- """Returns True if Attribute has any value assigned"""
97
- return self._value is not None
98
-
99
- @property
100
- def value(self) -> Any:
101
- return self._value
102
-
103
- @property
104
- def has_nested(self) -> bool:
105
- """Returns True if nested Attributes are present"""
106
- return bool(self._nested)
107
-
108
- @property
109
- def nested(self) -> Dict[str, Attribute]:
110
- """Returns dictionary of all nested Attributes"""
111
- assert self.has_nested
112
- return self._nested
113
-
114
- def get_nested_by_name(self, key: str) -> Attribute:
115
- """Returns nested Attribute by specified name"""
116
- return self._nested[key]
117
-
118
- @property
119
- def has_nested_list(self) -> bool:
120
- """Returns True if list of nested items is present"""
121
- return bool(self._nested_list)
122
-
123
- @property
124
- def nested_list(self) -> List[Dict[str, "Attribute"]]:
125
- """Return list of all nested Attribute dictionaries"""
126
- assert self.has_nested_list
127
- return self._nested_list
128
-
129
- def __repr__(self) -> str:
130
- return self._full_name
@@ -1,51 +0,0 @@
1
- # SPDX-FileCopyrightText: 2024 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
- from typing import Dict, List, Union
5
-
6
- from fameio.source.metadata import Metadata
7
- from fameio.source.scenario.exception import log_and_raise
8
- from fameio.source.tools import keys_to_lower
9
-
10
-
11
- class StringSet(Metadata):
12
- """Hosts a StringSet in the given format"""
13
-
14
- ValueType = Union[List[str], Dict[str, Dict]]
15
- StringSetType = Dict[str, Union[Dict, ValueType]]
16
-
17
- _ERR_NO_STRING_SET_VALUES = "Missing mandatory key '{}' in StringSet definition {}."
18
-
19
- _KEY_VALUES = "Values".lower()
20
-
21
- def __init__(self):
22
- super().__init__()
23
- self._values = {}
24
-
25
- @classmethod
26
- def from_dict(cls, definition: StringSetType) -> "StringSet":
27
- """Returns StringSet initialised from `definition`"""
28
- string_set = cls()
29
- string_set._extract_metadata(definition)
30
- definition = keys_to_lower(definition)
31
- if cls._KEY_VALUES in definition:
32
- string_set._values = string_set._read_values(definition)
33
- else:
34
- log_and_raise(cls._ERR_NO_STRING_SET_VALUES.format(cls._KEY_VALUES, definition))
35
- return string_set
36
-
37
- def _read_values(self, definition: ValueType) -> Dict[str, Dict]:
38
- """Ensures values are returned as dictionary representation by converting `definitions` of type 'List[str]'"""
39
- values = definition[self._KEY_VALUES]
40
- if isinstance(values, list):
41
- return {name: {} for name in values}
42
- return values
43
-
44
- def to_dict(self) -> Dict:
45
- """Serializes the StringSet to a dict"""
46
- result = {self._KEY_VALUES: self._values}
47
- return self._enrich_with_metadata(result)
48
-
49
- def is_in_set(self, key: str) -> bool:
50
- """Returns True if `key` is a valid name in this StringSet"""
51
- return key in self._values
@@ -1,132 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
- from __future__ import annotations
5
-
6
- from typing import Dict, List, Any
7
-
8
- from fameio.source.logs import log_error_and_raise, log
9
- from fameio.source.schema.exception import SchemaException
10
- from fameio.source.schema.attribute import AttributeSpecs
11
- from fameio.source.tools import keys_to_lower
12
-
13
-
14
- class AgentType:
15
- """Schema definitions for an Agent type"""
16
-
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: '{}'"
19
- _ERR_PRODUCTS_UNKNOWN_STRUCTURE = "Product definition of AgentType '{}' is neither list nor dictionary: '{}'"
20
-
21
- _NO_ATTRIBUTES = "Agent '{}' has no specified 'Attributes'."
22
- _NO_PRODUCTS = "Agent '{}' has no specified 'Products'."
23
- _NO_OUTPUTS = "Agent '{}' has no specified 'Outputs'."
24
- _NO_METADATA = "Agent '{}' has no specified 'Metadata'."
25
-
26
- _KEY_ATTRIBUTES = "Attributes".lower()
27
- _KEY_PRODUCTS = "Products".lower()
28
- _KEY_OUTPUTS = "Outputs".lower()
29
- _KEY_METADATA = "MetaData".lower()
30
-
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)))
40
- self._name = name
41
- self._attributes = {}
42
- self._products = {}
43
- self._outputs = {}
44
- self._metadata = {}
45
-
46
- @classmethod
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
54
-
55
- Returns:
56
- a new instance of AgentType
57
- """
58
- agent_type = cls(name)
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.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)
77
- else:
78
- log().debug(AgentType._NO_OUTPUTS.format(name))
79
-
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))
86
-
87
- return agent_type
88
-
89
- @staticmethod
90
- def _read_products(products: Any, agent_type: str) -> Dict[str, Any]:
91
- """Returns a dict obtained from given `products` defined for `agent_type`"""
92
- product_names = None
93
- if isinstance(products, dict):
94
- product_names = products
95
- elif isinstance(products, list):
96
- product_names = {key: None for key in products}
97
- else:
98
- log_error_and_raise(SchemaException(AgentType._ERR_PRODUCTS_UNKNOWN_STRUCTURE.format(agent_type, products)))
99
-
100
- if all([isinstance(item, str) for item in product_names.keys()]):
101
- return product_names
102
- else:
103
- log_error_and_raise(SchemaException(AgentType._ERR_PRODUCTS_NO_STRING.format(agent_type, product_names)))
104
-
105
- @property
106
- def name(self) -> str:
107
- """Returns the agent type name"""
108
- return self._name
109
-
110
- @property
111
- def products(self) -> dict:
112
- """Returns dict of products or an empty dict if no products are defined"""
113
- return self._products
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
-
119
- @property
120
- def attributes(self) -> Dict[str, AttributeSpecs]:
121
- """Returns list of Attributes of this agent or an empty list if no attributes are defined"""
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