fameio 1.8.1__tar.gz → 1.8.2__tar.gz

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 (53) hide show
  1. {fameio-1.8.1/src/fameio.egg-info → fameio-1.8.2}/PKG-INFO +8 -4
  2. {fameio-1.8.1 → fameio-1.8.2}/setup.py +1 -1
  3. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/conversion.py +1 -1
  4. {fameio-1.8.1 → fameio-1.8.2/src/fameio.egg-info}/PKG-INFO +8 -4
  5. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/SOURCES.txt +8 -1
  6. fameio-1.8.2/tests/test_cli.py +324 -0
  7. fameio-1.8.2/tests/test_load_save_reload_compare_outputs.py +107 -0
  8. fameio-1.8.2/tests/test_loader.py +59 -0
  9. fameio-1.8.2/tests/test_series.py +27 -0
  10. fameio-1.8.2/tests/test_time.py +134 -0
  11. fameio-1.8.2/tests/test_tools.py +31 -0
  12. fameio-1.8.2/tests/test_validator.py +490 -0
  13. {fameio-1.8.1 → fameio-1.8.2}/LICENSE.txt +0 -0
  14. {fameio-1.8.1 → fameio-1.8.2}/README.md +0 -0
  15. {fameio-1.8.1 → fameio-1.8.2}/setup.cfg +0 -0
  16. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/__init__.py +0 -0
  17. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/__init__.py +0 -0
  18. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/convert_results.py +0 -0
  19. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/make_config.py +0 -0
  20. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/__init__.py +0 -0
  21. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/cli.py +0 -0
  22. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/loader.py +0 -0
  23. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/logs.py +0 -0
  24. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/path_resolver.py +0 -0
  25. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/__init__.py +0 -0
  26. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/agent_type.py +0 -0
  27. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/csv_writer.py +0 -0
  28. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/data_transformer.py +0 -0
  29. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/output_dao.py +0 -0
  30. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/reader.py +0 -0
  31. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/__init__.py +0 -0
  32. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/agent.py +0 -0
  33. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/attribute.py +0 -0
  34. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/contract.py +0 -0
  35. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/exception.py +0 -0
  36. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/fameiofactory.py +0 -0
  37. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/generalproperties.py +0 -0
  38. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/scenario.py +0 -0
  39. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/__init__.py +0 -0
  40. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/agenttype.py +0 -0
  41. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/attribute.py +0 -0
  42. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/exception.py +0 -0
  43. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/schema.py +0 -0
  44. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/series.py +0 -0
  45. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/time.py +0 -0
  46. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/tools.py +0 -0
  47. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/validator.py +0 -0
  48. {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/writer.py +0 -0
  49. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/dependency_links.txt +0 -0
  50. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/entry_points.txt +0 -0
  51. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/not-zip-safe +0 -0
  52. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/requires.txt +0 -0
  53. {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/top_level.txt +0 -0
@@ -1,20 +1,25 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fameio
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Python scripts for operation of FAME models
5
5
  Home-page: https://gitlab.com/fame-framework/fame-io/
6
6
  Author: Felix Nitsch, Christoph Schimeczek, Ulrich Frey, Marc Deissenroth-Uhrig, Benjamin Fuchs, A. Achraf El Ghazi
7
7
  Author-email: fame@dlr.de
8
8
  License: Apache License 2.0
9
9
  Keywords: FAME,agent-based modelling
10
- Platform: UNKNOWN
11
10
  Classifier: Programming Language :: Python :: 3
12
11
  Classifier: License :: OSI Approved :: Apache Software License
13
12
  Classifier: Operating System :: OS Independent
14
13
  Requires-Python: >=3.8
15
14
  Description-Content-Type: text/markdown
16
- Provides-Extra: dev
17
15
  License-File: LICENSE.txt
16
+ Requires-Dist: pandas
17
+ Requires-Dist: fameprotobuf<1.3,>=1.2
18
+ Requires-Dist: pyyaml
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: mockito; extra == "dev"
22
+ Requires-Dist: pre-commit; extra == "dev"
18
23
 
19
24
  <!-- SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
20
25
 
@@ -653,4 +658,3 @@ To use them, install `dev` packages and then initialise pre-commit in FAME-Io's
653
658
  pip install fameio[dev]
654
659
  pre-commit install -t pre-commit -t pre-push
655
660
  ```
656
-
@@ -26,7 +26,7 @@ def readme():
26
26
 
27
27
  setup(
28
28
  name="fameio",
29
- version="1.8.1",
29
+ version="1.8.2",
30
30
  description="Python scripts for operation of FAME models",
31
31
  long_description=readme(),
32
32
  long_description_content_type="text/markdown",
@@ -37,7 +37,7 @@ def apply_time_merging(data: Dict[Optional[str], pd.DataFrame], config: Optional
37
37
  index_columns = df.index.names
38
38
  df.reset_index(inplace=True)
39
39
  df["TimeStep"] = df["TimeStep"].apply(lambda t: merge_time(t, first_positive_focal_point, offset, period))
40
- data[key] = df.groupby(by=index_columns, axis=0).sum()
40
+ data[key] = df.groupby(by=index_columns).sum()
41
41
 
42
42
 
43
43
  def merge_time(time_step: int, focal_time: int, offset: int, period: int) -> int:
@@ -1,20 +1,25 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fameio
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Python scripts for operation of FAME models
5
5
  Home-page: https://gitlab.com/fame-framework/fame-io/
6
6
  Author: Felix Nitsch, Christoph Schimeczek, Ulrich Frey, Marc Deissenroth-Uhrig, Benjamin Fuchs, A. Achraf El Ghazi
7
7
  Author-email: fame@dlr.de
8
8
  License: Apache License 2.0
9
9
  Keywords: FAME,agent-based modelling
10
- Platform: UNKNOWN
11
10
  Classifier: Programming Language :: Python :: 3
12
11
  Classifier: License :: OSI Approved :: Apache Software License
13
12
  Classifier: Operating System :: OS Independent
14
13
  Requires-Python: >=3.8
15
14
  Description-Content-Type: text/markdown
16
- Provides-Extra: dev
17
15
  License-File: LICENSE.txt
16
+ Requires-Dist: pandas
17
+ Requires-Dist: fameprotobuf<1.3,>=1.2
18
+ Requires-Dist: pyyaml
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: mockito; extra == "dev"
22
+ Requires-Dist: pre-commit; extra == "dev"
18
23
 
19
24
  <!-- SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
20
25
 
@@ -653,4 +658,3 @@ To use them, install `dev` packages and then initialise pre-commit in FAME-Io's
653
658
  pip install fameio[dev]
654
659
  pre-commit install -t pre-commit -t pre-push
655
660
  ```
656
-
@@ -41,4 +41,11 @@ src/fameio/source/schema/__init__.py
41
41
  src/fameio/source/schema/agenttype.py
42
42
  src/fameio/source/schema/attribute.py
43
43
  src/fameio/source/schema/exception.py
44
- src/fameio/source/schema/schema.py
44
+ src/fameio/source/schema/schema.py
45
+ tests/test_cli.py
46
+ tests/test_load_save_reload_compare_outputs.py
47
+ tests/test_loader.py
48
+ tests/test_series.py
49
+ tests/test_time.py
50
+ tests/test_tools.py
51
+ tests/test_validator.py
@@ -0,0 +1,324 @@
1
+ # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ import argparse
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ import pytest
9
+
10
+ from fameio.source.cli import (
11
+ update_default_config,
12
+ non_negative_int,
13
+ ERR_NEGATIVE_INT,
14
+ add_file_argument,
15
+ add_select_agents_argument,
16
+ add_logfile_argument,
17
+ add_output_argument,
18
+ add_log_level_argument,
19
+ add_single_export_argument,
20
+ add_memory_saving_argument,
21
+ add_resolve_complex_argument,
22
+ ResolveOptions,
23
+ add_time_argument,
24
+ TimeOptions,
25
+ add_focal_point_argument,
26
+ add_steps_before_argument,
27
+ add_steps_after_argument,
28
+ add_merge_time_parser,
29
+ get_merging_args,
30
+ MergingOptions,
31
+ )
32
+ from tests.utils import assert_exception_contains
33
+
34
+
35
+ class TestParser:
36
+ @pytest.fixture
37
+ def parser(self):
38
+ return argparse.ArgumentParser()
39
+
40
+ def test_add_file_argument_exit_on_missing_argument(self, parser):
41
+ add_file_argument(parser, "")
42
+ with pytest.raises(SystemExit):
43
+ parser.parse_args([])
44
+
45
+ def test_add_file_argument_exit_on_missing_value(self, parser):
46
+ add_file_argument(parser, "")
47
+ with pytest.raises(SystemExit):
48
+ parser.parse_args(["-f"])
49
+
50
+ @pytest.mark.parametrize("value", ["a", "file.x", "path/to/file", "./rel/path.x", "/abs/path/file.b"])
51
+ def test_add_file_argument_creates_path_for_any_value(self, parser, value: str):
52
+ add_file_argument(parser, "")
53
+ result = parser.parse_args(["-f", value])
54
+ assert result.file == Path(value)
55
+
56
+ def test_add_select_agents_argument_missing_argument_yields_none(self, parser):
57
+ add_select_agents_argument(parser)
58
+ result = parser.parse_args([])
59
+ assert result.agents is None
60
+
61
+ def test_add_select_agents_value_missing_yields_empty_list(self, parser):
62
+ add_select_agents_argument(parser)
63
+ result = parser.parse_args(["-a"])
64
+ assert result.agents == []
65
+
66
+ @pytest.mark.parametrize("values", [["a"], ["x", "b"], ["some", "names", "of", "agents"]])
67
+ def test_add_select_agents_values_added_to_list(self, parser, values: List[str]):
68
+ add_select_agents_argument(parser)
69
+ args = ["-a", *values]
70
+ result = parser.parse_args(args)
71
+ assert len(result.agents) == len(values)
72
+ assert set(result.agents).issubset(values)
73
+
74
+ def test_add_logfile_argument_missing_yields_none(self, parser):
75
+ add_logfile_argument(parser)
76
+ result = parser.parse_args([])
77
+ assert result.logfile is None
78
+
79
+ def test_add_logfile_argument_value_missing_raises(self, parser):
80
+ add_logfile_argument(parser)
81
+ with pytest.raises(SystemExit):
82
+ parser.parse_args(["-lf"])
83
+
84
+ @pytest.mark.parametrize("value", ["a", "file.x", "path/to/file", "./rel/path.x", "/abs/path/file.b"])
85
+ def test_add_logfile_argument_creates_path_from_any_value(self, parser, value):
86
+ add_logfile_argument(parser)
87
+ result = parser.parse_args(["-lf", value])
88
+ assert result.logfile == Path(value)
89
+
90
+ @pytest.mark.parametrize("default", ["some", "/path/to/a", "./default/output.file"])
91
+ def test_add_output_argument_missing_yields_default(self, parser, default):
92
+ add_output_argument(parser, default, "")
93
+ result = parser.parse_args([])
94
+ assert result.output == Path(default)
95
+
96
+ def test_add_output_argument_missing_value_raises(self, parser):
97
+ add_output_argument(parser, "default", "")
98
+ with pytest.raises(SystemExit):
99
+ parser.parse_args(["-o"])
100
+
101
+ @pytest.mark.parametrize("value", ["a", "file.x", "path/to/file", "./rel/path.x", "/abs/path/file.b"])
102
+ def test_add_output_argument_value_overrides_default(self, parser, value):
103
+ add_output_argument(parser, "default", "")
104
+ result = parser.parse_args(["-o", value])
105
+ assert result.output == Path(value)
106
+
107
+ @pytest.mark.parametrize("default", ["ERROR", "DEBUG", "INFO"])
108
+ def test_add_log_level_argument_missing_argument_yields_default(self, parser, default):
109
+ add_log_level_argument(parser, default)
110
+ result = parser.parse_args([])
111
+ assert result.log == default.lower()
112
+
113
+ def test_add_log_level_argument_missing_value_raises(self, parser):
114
+ add_log_level_argument(parser, "ERROR")
115
+ with pytest.raises(SystemExit):
116
+ parser.parse_args(["-l"])
117
+
118
+ def test_add_log_level_argument_unknown_level_raises(self, parser):
119
+ add_log_level_argument(parser, "ERROR")
120
+ with pytest.raises(SystemExit):
121
+ parser.parse_args(["-l", "'not-an-error-level'"])
122
+
123
+ @pytest.mark.parametrize("value", ["error", "ERROR", "eRRoR", "Critical", "WARN", "inFo", "dEbUg"])
124
+ def test_add_log_level_argument_overrides_default_with_valid_value(self, parser, value):
125
+ add_log_level_argument(parser, "warning")
126
+ result = parser.parse_args(["-l", value])
127
+ assert result.log == value.lower()
128
+
129
+ @pytest.mark.parametrize("default", [True, False])
130
+ def test_add_single_export_argument_missing_yields_default(self, parser, default):
131
+ add_single_export_argument(parser, default)
132
+ result = parser.parse_args([])
133
+ assert result.single_export == default
134
+
135
+ def test_add_single_export_argument_present_yields_true(self, parser):
136
+ add_single_export_argument(parser, False)
137
+ result = parser.parse_args(["-se"])
138
+ assert result.single_export
139
+
140
+ @pytest.mark.parametrize("default", [True, False])
141
+ def test_add_memory_saving_argument_missing_yields_default(self, parser, default):
142
+ add_memory_saving_argument(parser, default)
143
+ result = parser.parse_args([])
144
+ assert result.memory_saving == default
145
+
146
+ def test_add_memory_saving_argument_present_yields_true(self, parser):
147
+ add_memory_saving_argument(parser, False)
148
+ result = parser.parse_args(["-m"])
149
+ assert result.memory_saving
150
+
151
+ @pytest.mark.parametrize("default", [e.name for e in ResolveOptions])
152
+ def test_add_resolve_complex_argument_missing_yields_default(self, parser, default: str):
153
+ add_resolve_complex_argument(parser, default)
154
+ result = parser.parse_args([])
155
+ assert result.complex_column == default.upper()
156
+
157
+ def test_add_resolve_complex_argument_missing_value_raises(self, parser):
158
+ add_resolve_complex_argument(parser, ResolveOptions.IGNORE.name)
159
+ with pytest.raises(SystemExit):
160
+ parser.parse_args(["-cc"])
161
+
162
+ def test_add_resolve_complex_argument_invalid_value_raises(self, parser):
163
+ add_resolve_complex_argument(parser, ResolveOptions.IGNORE.name)
164
+ with pytest.raises(SystemExit):
165
+ parser.parse_args(["-cc", "not_a_valid_value"])
166
+
167
+ @pytest.mark.parametrize("value", [e.name for e in ResolveOptions])
168
+ def test_add_resolve_complex_argument_override_default_on_valid_values(self, parser, value: str):
169
+ add_resolve_complex_argument(parser, ResolveOptions.IGNORE.name)
170
+ result = parser.parse_args(["-cc", value])
171
+ assert result.complex_column == value.upper()
172
+
173
+ @pytest.mark.parametrize("default", [e.name for e in TimeOptions])
174
+ def test_add_time_argument_missing_yields_default(self, parser, default):
175
+ add_time_argument(parser, default)
176
+ result = parser.parse_args([])
177
+ assert result.time == default.upper()
178
+
179
+ def test_add_time_argument_missing_value_raises(self, parser):
180
+ add_time_argument(parser, TimeOptions.INT.name)
181
+ with pytest.raises(SystemExit):
182
+ parser.parse_args(["-t"])
183
+
184
+ def test_add_time_argument_invalid_value_raises(self, parser):
185
+ add_time_argument(parser, TimeOptions.INT.name)
186
+ with pytest.raises(SystemExit):
187
+ parser.parse_args(["-t", "not_a_valid_value"])
188
+
189
+ @pytest.mark.parametrize("value", [e.name for e in TimeOptions])
190
+ def test_add_time_argument_valid_value_overrides_default(self, parser, value: str):
191
+ add_time_argument(parser, TimeOptions.INT.name)
192
+ result = parser.parse_args(["-t", value])
193
+ assert result.time == value.upper()
194
+
195
+ def test_add_focal_point_argument_missing_raises(self, parser):
196
+ add_focal_point_argument(parser)
197
+ with pytest.raises(SystemExit):
198
+ parser.parse_args([])
199
+
200
+ def test_add_focal_point_argument_value_missing_raises(self, parser):
201
+ add_focal_point_argument(parser)
202
+ with pytest.raises(SystemExit):
203
+ parser.parse_args(["-fp"])
204
+
205
+ @pytest.mark.parametrize("value", ["", "not_an_int", "(5,8)", "[88]"])
206
+ def test_add_focal_point_argument_non_int_raises(self, parser, value: str):
207
+ add_focal_point_argument(parser)
208
+ with pytest.raises(SystemExit):
209
+ parser.parse_args(["-fp", value])
210
+
211
+ @pytest.mark.parametrize("value", ["-10", "0", "1", "10000"])
212
+ def test_add_focal_point_argument_valid_values(self, parser, value: str):
213
+ add_focal_point_argument(parser)
214
+ result = parser.parse_args(["-fp", value])
215
+ assert result.focal_point == int(value)
216
+
217
+ def test_add_steps_before_argument_missing_raises(self, parser):
218
+ add_steps_before_argument(parser)
219
+ with pytest.raises(SystemExit):
220
+ parser.parse_args([])
221
+
222
+ def test_add_steps_before_argument_missing_value_raises(self, parser):
223
+ add_steps_before_argument(parser)
224
+ with pytest.raises(SystemExit):
225
+ parser.parse_args(["-sb"])
226
+
227
+ @pytest.mark.parametrize("value", ["", "not_an_int", "(5)", "[8]", "-2"])
228
+ def test_add_steps_before_argument_invalid_value_raises(self, parser, value):
229
+ add_steps_before_argument(parser)
230
+ with pytest.raises(SystemExit):
231
+ parser.parse_args(["-sb", value])
232
+
233
+ @pytest.mark.parametrize("value", ["0", "1", "15", "12123123"])
234
+ def test_add_steps_before_argument_valid_values(self, parser, value):
235
+ add_steps_before_argument(parser)
236
+ result = parser.parse_args(["-sb", value])
237
+ assert result.steps_before == int(value)
238
+
239
+ def test_add_steps_after_argument_missing_raises(self, parser):
240
+ add_steps_after_argument(parser)
241
+ with pytest.raises(SystemExit):
242
+ parser.parse_args([])
243
+
244
+ def test_add_steps_after_argument_missing_value_raises(self, parser):
245
+ add_steps_after_argument(parser)
246
+ with pytest.raises(SystemExit):
247
+ parser.parse_args(["-sa"])
248
+
249
+ @pytest.mark.parametrize("value", ["", "not_an_int", "(5)", "[8]", "-2"])
250
+ def test_add_steps_after_argument_invalid_values_raise(self, parser, value: str):
251
+ add_steps_after_argument(parser)
252
+ with pytest.raises(SystemExit):
253
+ parser.parse_args(["-sa", value])
254
+
255
+ @pytest.mark.parametrize("value", ["0", "1", "15", "12123123"])
256
+ def test_add_steps_after_argument_valid_values(self, parser, value: str):
257
+ add_steps_after_argument(parser)
258
+ result = parser.parse_args(["-sa", value])
259
+ assert result.steps_after == int(value)
260
+
261
+ @pytest.mark.parametrize("args", [["-sa", "0", "-sb", "0"], ["-fp", "0", "-sb", "0"], ["-fp", "0", "-sa", "0"]])
262
+ def test_add_merge_time_parser_argument_missing_raises(self, parser, args):
263
+ add_merge_time_parser(parser)
264
+ with pytest.raises(SystemExit):
265
+ parser.parse_args(["merge-times", *args])
266
+
267
+ def test_get_merging_args_missing_key_returns_empty_dict(self):
268
+ assert get_merging_args(argparse.Namespace()) == {}
269
+
270
+ def test_get_merging_args_no_merge_times(self):
271
+ args = argparse.Namespace(**{"time_merging": None})
272
+ assert get_merging_args(args) == {}
273
+
274
+ @pytest.mark.parametrize("args", [["-fp", "-1", "-sb", "0", "-sa", "20"], ["-fp", "5", "-sb", "10", "-sa", "5"]])
275
+ def test_add_merge_time_parser_and_get_merging_args(self, parser, args):
276
+ add_merge_time_parser(parser)
277
+ parsed = parser.parse_args(["merge-times", *args])
278
+ result = get_merging_args(parsed)
279
+ assert result[MergingOptions.FOCAL_POINT] == int(args[1])
280
+ assert result[MergingOptions.STEPS_BEFORE] == int(args[3])
281
+ assert result[MergingOptions.STEPS_AFTER] == int(args[5])
282
+
283
+
284
+ class TestHelpers:
285
+ _default = {"a": 1, "b": 2, "c": 3}
286
+
287
+ def test_get_config_or_default_update_a(self):
288
+ config = {"a": 42}
289
+ expected = {"a": 42, "b": 2, "c": 3}
290
+ assert update_default_config(config, self._default) == expected
291
+
292
+ def test_get_config_or_default_update_abc(self):
293
+ config = {"a": 42, "b": 42, "c": 42}
294
+ expected = {"a": 42, "b": 42, "c": 42}
295
+ assert update_default_config(config, self._default) == expected
296
+
297
+ def test_get_config_or_default_update_abcd(self):
298
+ config = {"d": 42}
299
+ expected = {"a": 1, "b": 2, "c": 3, "d": 42}
300
+ assert update_default_config(config, self._default) == expected
301
+
302
+ def test_get_config_or_default_none(self):
303
+ config = None
304
+ expected = {"a": 1, "b": 2, "c": 3}
305
+ assert update_default_config(config, self._default) == expected
306
+
307
+ def test_non_negative_int_positive(self):
308
+ assert non_negative_int(1) == 1
309
+
310
+ def test_non_negative_int_zero(self):
311
+ assert non_negative_int(0) == 0
312
+
313
+ def test_non_negative_int_err_on_negative_arg(self):
314
+ with pytest.raises(argparse.ArgumentTypeError) as e_info:
315
+ non_negative_int(-1)
316
+ assert_exception_contains(ERR_NEGATIVE_INT, e_info)
317
+
318
+ def test_non_negative_int_err_on_none(self):
319
+ with pytest.raises(TypeError):
320
+ non_negative_int(None)
321
+
322
+ def test_non_negative_int_err_on_string(self):
323
+ with pytest.raises(ValueError):
324
+ non_negative_int("Test")
@@ -0,0 +1,107 @@
1
+ # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import os
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import pytest
11
+ import yaml
12
+ from fameprotobuf import DataStorage_pb2
13
+ from google.protobuf import text_format
14
+
15
+ from fameio.source.loader import load_yaml
16
+ from fameio.source.path_resolver import PathResolver
17
+ from fameio.source.scenario import Scenario
18
+ from fameio.source.validator import SchemaValidator
19
+ from fameio.source.writer import ProtoWriter
20
+
21
+
22
+ class CustomPathResolver(PathResolver):
23
+ def __init__(self, root_dir: str):
24
+ super().__init__()
25
+ self._root_dir = root_dir
26
+
27
+ def resolve_series_file_path(self, file_name: str) -> Optional[str]:
28
+ if not os.path.isabs(file_name):
29
+ file_path = os.path.join(self._root_dir, file_name)
30
+ if os.path.exists(file_path):
31
+ return file_path
32
+ return super().resolve_series_file_path(file_name)
33
+
34
+
35
+ def _load_and_validate_yaml_file(file_path: str, path_resolver: PathResolver) -> Scenario:
36
+ """Returns validated scenario parsed in `file_path`"""
37
+ scenario = Scenario.from_dict(load_yaml(Path(file_path), path_resolver))
38
+ SchemaValidator.ensure_is_valid_scenario(scenario)
39
+ return scenario
40
+
41
+
42
+ def _write_protobuf_file(scenario: Scenario, path_resolver: PathResolver, output_file_name: str) -> None:
43
+ """Writes `scenario` using `path_resolver` as given `output_file_name`"""
44
+ writer = ProtoWriter(Path(output_file_name), path_resolver)
45
+ writer.write_validated_scenario(scenario)
46
+
47
+
48
+ def _convert_protobuf_to_text(file_path: Path) -> str:
49
+ """Returns protobuf file in `file_path` as str"""
50
+ file_path = str(file_path)
51
+ msg = DataStorage_pb2.DataStorage()
52
+ with open(file_path, "rb") as file:
53
+ protobuf_data = file.read()
54
+ msg.ParseFromString(protobuf_data)
55
+ text = text_format.MessageToString(msg)
56
+ return text
57
+
58
+
59
+ class TestLoadSaveReload:
60
+ scenario_name = "scenario.yaml"
61
+ protobuf_name = "scenario.pb"
62
+ scenario_copy_name = "scenario_copy.yaml"
63
+ protobuf_copy_name = "scenario_copy.pb"
64
+ path_to_examples = "../examples/Germany2019"
65
+
66
+ @pytest.fixture(scope="session")
67
+ def _cache_dir(self, tmp_path_factory):
68
+ """Creates temporary directory where files for all tests in this Class are written to"""
69
+ return tmp_path_factory.mktemp("cache")
70
+
71
+ def test_load_dump_reload_compare(self, _cache_dir):
72
+ work_dir = self._setup_workdir_with_protobuf_from_original_and_reloaded_scenario(_cache_dir)
73
+ ref_text = _convert_protobuf_to_text(work_dir / self.protobuf_name)
74
+ copy_text = _convert_protobuf_to_text(work_dir / self.protobuf_copy_name)
75
+
76
+ # we can't convert the protobuf text directly because the order of the field can vary,
77
+ # so we do something simple here: sort the output text lines and check they are similar
78
+ ref_lines = ref_text.splitlines()
79
+ copy_lines = copy_text.splitlines()
80
+ ref_lines.sort()
81
+ copy_lines.sort()
82
+ # compare line by line to help spot the difference on a large output
83
+ for i, line_ref in enumerate(ref_lines):
84
+ assert i < len(copy_lines)
85
+ line_copy = copy_lines[i]
86
+ # the generated series id are likely to be different, so we ignore them
87
+ if "seriesId:" in line_ref:
88
+ assert "seriesId:" in line_copy
89
+ else:
90
+ assert line_ref == line_copy
91
+ # ensure the copy has no extra lines
92
+ assert len(ref_lines) == len(copy_lines)
93
+
94
+ def _setup_workdir_with_protobuf_from_original_and_reloaded_scenario(self, cache_dir) -> Path:
95
+ this_script_dir = os.path.dirname(os.path.realpath(__file__))
96
+ example_dir = os.path.join(this_script_dir, self.path_to_examples)
97
+ work_dir = shutil.copytree(example_dir, cache_dir / "examples/Germany2019")
98
+ path_resolver = CustomPathResolver(work_dir)
99
+
100
+ original_scenario = _load_and_validate_yaml_file(work_dir / self.scenario_name, path_resolver)
101
+ _write_protobuf_file(original_scenario, path_resolver, work_dir / self.protobuf_name)
102
+
103
+ with open(work_dir / self.scenario_copy_name, "w") as f:
104
+ yaml.dump(original_scenario.to_dict(), f)
105
+ copied_scenario = _load_and_validate_yaml_file(work_dir / self.scenario_copy_name, path_resolver)
106
+ _write_protobuf_file(copied_scenario, path_resolver, work_dir / self.protobuf_copy_name)
107
+ return work_dir
@@ -0,0 +1,59 @@
1
+ # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ from fameio.source.loader import PathResolver, load_yaml, check_for_yaml_file_type
9
+
10
+
11
+ class Test:
12
+ def test_load_yaml_plain(self):
13
+ expected = {"ToBe": "ThatIs", "OrNot": "TheQuestion"}
14
+ loaded = load_yaml(Path("tests/yaml/simple.yaml"))
15
+ assert expected == loaded
16
+
17
+ def test_load_yaml_with_simple_include(self):
18
+ expected = {"Is": {"ToBe": "ThatIs", "OrNot": "TheQuestion"}}
19
+ loaded = load_yaml(Path("tests/yaml/simple_include.yaml"))
20
+ assert expected == loaded
21
+
22
+ def test_load_yaml_with_nested_include(self):
23
+ expected = {
24
+ "ToBe": {"ThatIs": {"Or": "maybe"}, "TheQuestion": {"not": "?"}},
25
+ "OrNot": {"not": "?"},
26
+ }
27
+ loaded = load_yaml(Path("tests/yaml/a.yaml"))
28
+ assert expected == loaded
29
+
30
+ def test_with_custom_path_resolver(self):
31
+ class CustomPathResolver(PathResolver):
32
+ def __init__(self):
33
+ super().__init__()
34
+ self.last_file_pattern = ""
35
+
36
+ def resolve_yaml_imported_file_pattern(self, root_path: str, file_pattern: str):
37
+ self.last_file_pattern = file_pattern
38
+ return super().resolve_yaml_imported_file_pattern(root_path, file_pattern)
39
+
40
+ path_resolver = CustomPathResolver()
41
+ load_yaml(Path("tests/yaml/simple_include.yaml"), path_resolver)
42
+ assert path_resolver.last_file_pattern.endswith("simple.yaml")
43
+
44
+ def test_with_failed_path_resolver(self):
45
+ class BadPathResolver(PathResolver):
46
+ def resolve_yaml_imported_file_pattern(self, root_path: str, file_pattern: str):
47
+ return []
48
+
49
+ with pytest.raises(Exception):
50
+ load_yaml(Path("tests/yaml/simple_include.yaml"), BadPathResolver())
51
+
52
+ @pytest.mark.parametrize("file_name", ["my/non_yaml/file.csv", "my/non_yaml/file", "file.", "file.xml"])
53
+ def test_check_for_yaml_file_type_invalid(self, file_name):
54
+ with pytest.raises(Exception):
55
+ check_for_yaml_file_type(Path(file_name))
56
+
57
+ @pytest.mark.parametrize("file_name", ["my/file.yml", "file.Yml", "my/file.yaml", "file.YAML", "file.yAmL"])
58
+ def test_check_for_yaml_file_type_valid(self, file_name):
59
+ check_for_yaml_file_type(Path(file_name))
@@ -0,0 +1,27 @@
1
+ # SPDX-FileCopyrightText: 2023 German Aerospace Center <fame@dlr.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from fameio.source.series import TimeSeriesManager
6
+
7
+
8
+ class TestTimeSeriesManager:
9
+ def test_save_get_time_series_id_same_file_same_id(self):
10
+ manager = TimeSeriesManager()
11
+ id1 = manager.save_get_time_series_id("FILE_NAME")
12
+ id2 = manager.save_get_time_series_id("FILE_NAME")
13
+ assert id1 == id2
14
+
15
+ def test_save_get_time_series_id_different_files_different_id(self):
16
+ manager = TimeSeriesManager()
17
+ id1 = manager.save_get_time_series_id("FILE_NAME")
18
+ id2 = manager.save_get_time_series_id("OTHER_FILE")
19
+ assert id1 != id2
20
+
21
+ def test_get_ids_of_series_by_name(self):
22
+ manager = TimeSeriesManager()
23
+ id1 = manager.save_get_time_series_id("FILE_NAME")
24
+ id2 = manager.save_get_time_series_id("OTHER_FILE")
25
+ ids_by_name = manager.get_ids_of_series_by_name()
26
+ assert ids_by_name["FILE_NAME"] == id1
27
+ assert ids_by_name["OTHER_FILE"] == id2