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.
- {fameio-1.8.1/src/fameio.egg-info → fameio-1.8.2}/PKG-INFO +8 -4
- {fameio-1.8.1 → fameio-1.8.2}/setup.py +1 -1
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/conversion.py +1 -1
- {fameio-1.8.1 → fameio-1.8.2/src/fameio.egg-info}/PKG-INFO +8 -4
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/SOURCES.txt +8 -1
- fameio-1.8.2/tests/test_cli.py +324 -0
- fameio-1.8.2/tests/test_load_save_reload_compare_outputs.py +107 -0
- fameio-1.8.2/tests/test_loader.py +59 -0
- fameio-1.8.2/tests/test_series.py +27 -0
- fameio-1.8.2/tests/test_time.py +134 -0
- fameio-1.8.2/tests/test_tools.py +31 -0
- fameio-1.8.2/tests/test_validator.py +490 -0
- {fameio-1.8.1 → fameio-1.8.2}/LICENSE.txt +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/README.md +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/setup.cfg +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/convert_results.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/scripts/make_config.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/cli.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/loader.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/logs.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/path_resolver.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/agent_type.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/csv_writer.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/data_transformer.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/output_dao.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/results/reader.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/agent.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/attribute.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/contract.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/exception.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/fameiofactory.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/generalproperties.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/scenario/scenario.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/__init__.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/agenttype.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/attribute.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/exception.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/schema/schema.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/series.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/time.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/tools.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/validator.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio/source/writer.py +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/dependency_links.txt +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/entry_points.txt +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/not-zip-safe +0 -0
- {fameio-1.8.1 → fameio-1.8.2}/src/fameio.egg-info/requires.txt +0 -0
- {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.
|
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
|
-
|
@@ -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
|
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.
|
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
|