hestia-earth-models 0.65.6__py3-none-any.whl → 0.65.7__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 (51) hide show
  1. hestia_earth/models/config/Cycle.json +2193 -0
  2. hestia_earth/models/config/ImpactAssessment.json +2041 -0
  3. hestia_earth/models/config/Site.json +471 -0
  4. hestia_earth/models/config/__init__.py +71 -0
  5. hestia_earth/models/config/run-calculations.json +42 -0
  6. hestia_earth/models/config/trigger-calculations.json +43 -0
  7. hestia_earth/models/ipcc2019/animal/hoursWorkedPerDay.py +38 -0
  8. hestia_earth/models/mocking/search-results.json +4524 -27
  9. hestia_earth/models/version.py +1 -1
  10. hestia_earth/orchestrator/__init__.py +40 -0
  11. hestia_earth/orchestrator/log.py +62 -0
  12. hestia_earth/orchestrator/models/__init__.py +118 -0
  13. hestia_earth/orchestrator/models/emissions/__init__.py +0 -0
  14. hestia_earth/orchestrator/models/emissions/deleted.py +15 -0
  15. hestia_earth/orchestrator/models/transformations.py +103 -0
  16. hestia_earth/orchestrator/strategies/__init__.py +0 -0
  17. hestia_earth/orchestrator/strategies/merge/__init__.py +42 -0
  18. hestia_earth/orchestrator/strategies/merge/merge_append.py +29 -0
  19. hestia_earth/orchestrator/strategies/merge/merge_default.py +1 -0
  20. hestia_earth/orchestrator/strategies/merge/merge_list.py +103 -0
  21. hestia_earth/orchestrator/strategies/merge/merge_node.py +59 -0
  22. hestia_earth/orchestrator/strategies/run/__init__.py +8 -0
  23. hestia_earth/orchestrator/strategies/run/add_blank_node_if_missing.py +85 -0
  24. hestia_earth/orchestrator/strategies/run/add_key_if_missing.py +9 -0
  25. hestia_earth/orchestrator/strategies/run/always.py +6 -0
  26. hestia_earth/orchestrator/utils.py +116 -0
  27. {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/METADATA +27 -5
  28. {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/RECORD +51 -7
  29. tests/models/ipcc2019/animal/test_hoursWorkedPerDay.py +22 -0
  30. tests/models/test_config.py +115 -0
  31. tests/orchestrator/__init__.py +0 -0
  32. tests/orchestrator/models/__init__.py +0 -0
  33. tests/orchestrator/models/emissions/__init__.py +0 -0
  34. tests/orchestrator/models/emissions/test_deleted.py +21 -0
  35. tests/orchestrator/models/test_transformations.py +29 -0
  36. tests/orchestrator/strategies/__init__.py +0 -0
  37. tests/orchestrator/strategies/merge/__init__.py +0 -0
  38. tests/orchestrator/strategies/merge/test_merge_append.py +33 -0
  39. tests/orchestrator/strategies/merge/test_merge_default.py +7 -0
  40. tests/orchestrator/strategies/merge/test_merge_list.py +327 -0
  41. tests/orchestrator/strategies/merge/test_merge_node.py +95 -0
  42. tests/orchestrator/strategies/run/__init__.py +0 -0
  43. tests/orchestrator/strategies/run/test_add_blank_node_if_missing.py +114 -0
  44. tests/orchestrator/strategies/run/test_add_key_if_missing.py +14 -0
  45. tests/orchestrator/strategies/run/test_always.py +5 -0
  46. tests/orchestrator/test_models.py +69 -0
  47. tests/orchestrator/test_orchestrator.py +27 -0
  48. tests/orchestrator/test_utils.py +109 -0
  49. {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/LICENSE +0 -0
  50. {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/WHEEL +0 -0
  51. {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ from unittest.mock import patch
2
+ import pydash
3
+
4
+ from hestia_earth.orchestrator.strategies.merge.merge_list import merge
5
+
6
+ class_path = 'hestia_earth.orchestrator.strategies.merge.merge_list'
7
+ version = '1'
8
+
9
+
10
+ def _default_merge(a, b, *args): return pydash.objects.merge({}, a, b)
11
+
12
+
13
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
14
+ def test_merge_new_node(*args):
15
+ old_node = {
16
+ 'term': {'@id': 'old-term'},
17
+ 'value': 1
18
+ }
19
+ new_node = {
20
+ 'term': {'@id': 'new-term'},
21
+ 'value': 2
22
+ }
23
+ result = merge([old_node], [new_node], version)
24
+ assert result == [old_node, new_node]
25
+
26
+
27
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
28
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
29
+ def test_merge_existing_node(*args):
30
+ term = {'@id': 'term'}
31
+
32
+ node_type = 'Site'
33
+ model = {'key': 'measurements'}
34
+
35
+ # with different value => should merge
36
+ old_node = {
37
+ 'term': term,
38
+ 'value': 1
39
+ }
40
+ new_node = {
41
+ 'term': term,
42
+ 'value': 2
43
+ }
44
+ result = merge([old_node], [new_node], version, model=model, node_type=node_type)
45
+ assert len(result) == 1
46
+
47
+ # with different depths => should not merge
48
+ result = merge([{
49
+ **old_node,
50
+ 'depthUpper': 100
51
+ }, {
52
+ **old_node,
53
+ 'depthUpper': 150
54
+ }], [{
55
+ **new_node,
56
+ 'depthUpper': 50
57
+ }], version, model=model, node_type=node_type)
58
+ assert len(result) == 3
59
+
60
+ node_type = 'Cycle'
61
+ model = {'key': 'emissions'}
62
+
63
+ # with same inputs => should merge
64
+ result = merge([{
65
+ **old_node,
66
+ 'inputs': [{'@id': 'input-1'}]
67
+ }], [{
68
+ **new_node,
69
+ 'inputs': [{'@id': 'input-1'}]
70
+ }], version, model=model, node_type=node_type)
71
+ assert len(result) == 1
72
+
73
+ # with different inputs => should not merge
74
+ result = merge([{
75
+ **old_node,
76
+ 'inputs': [{'@id': 'input-1'}]
77
+ }], [{
78
+ **new_node,
79
+ 'inputs': [{'@id': 'input-2'}]
80
+ }], version, model=model, node_type=node_type)
81
+ assert len(result) == 2
82
+
83
+ result = merge([{
84
+ **old_node,
85
+ 'inputs': [{'@id': 'input-1'}]
86
+ }], [{
87
+ **new_node,
88
+ 'inputs': [{'@id': 'input-1'}, {'@id': 'input-2'}]
89
+ }], version, model=model, node_type=node_type)
90
+ assert len(result) == 2
91
+
92
+ # with no inputs => should not merge
93
+ result = merge([old_node], [{
94
+ **new_node,
95
+ 'inputs': [{'@id': 'input-2'}]
96
+ }], version, model=model, node_type=node_type)
97
+ assert len(result) == 2
98
+
99
+
100
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
101
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
102
+ def test_merge_existing_node_skip_same_term(*args):
103
+ term = {'@id': 'term'}
104
+ node_type = 'Site'
105
+ model = {'key': 'measurements'}
106
+
107
+ old_node = {
108
+ 'term': term,
109
+ 'value': 1
110
+ }
111
+ new_node = {
112
+ 'term': term,
113
+ 'value': 2
114
+ }
115
+ result = merge([old_node], [new_node], version, model, {'skipSameTerm': True}, node_type)
116
+ assert len(result) == 1
117
+ assert result[0]['value'] == 1
118
+
119
+
120
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
121
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
122
+ def test_merge_existing_node_new_unique_key(*args):
123
+ term = {'@id': 'term'}
124
+ node_type = 'Cycle'
125
+ model = {'key': 'inputs'}
126
+
127
+ old_node = {
128
+ 'term': term,
129
+ 'value': 1
130
+ }
131
+ new_node = {
132
+ 'term': term,
133
+ 'value': 1,
134
+ 'impactAssessment': {'@id': 'ia-1'}
135
+ }
136
+ result = merge([old_node], [new_node], version, model, {}, node_type)
137
+ assert len(result) == 1
138
+ assert result[0]['impactAssessment'] == {'@id': 'ia-1'}
139
+
140
+
141
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
142
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
143
+ def test_merge_authors(*args):
144
+ node_type = 'Bibliography'
145
+ model = {'key': 'authors'}
146
+
147
+ old_node = {
148
+ 'lastName': 'name 1'
149
+ }
150
+ new_node = {
151
+ 'lastName': 'name 2'
152
+ }
153
+ result = merge([old_node], [new_node], version, model, {}, node_type)
154
+ # no unique keys, should just append data
155
+ assert result == [old_node, new_node]
156
+
157
+
158
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
159
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
160
+ def test_merge_different_terms_same_unique_properties(*args):
161
+ method = {'@id': 'method1'}
162
+ operation = {'@id': 'operation1'}
163
+ inputs = [{'@id': 'input1'}, {'@id': 'input2'}]
164
+ node_type = 'ImpactAssessment'
165
+ model = {'key': 'emissionsResourceUse'}
166
+
167
+ node1 = {
168
+ 'term': {'@id': 'term1'},
169
+ 'value': 1,
170
+ 'methodModel': method,
171
+ 'operation': operation,
172
+ 'inputs': inputs
173
+ }
174
+ node2 = {
175
+ 'term': {'@id': 'term2'},
176
+ 'value': 2,
177
+ 'methodModel': method,
178
+ 'operation': operation,
179
+ 'inputs': inputs
180
+ }
181
+ node3 = {
182
+ 'term': {'@id': 'term3'},
183
+ 'value': 3,
184
+ 'inputs': inputs
185
+ }
186
+ node4 = {
187
+ 'term': {'@id': 'term1'},
188
+ 'value': 2,
189
+ 'methodModel': method,
190
+ 'operation': operation,
191
+ 'inputs': inputs
192
+ }
193
+ # different term should not merge
194
+ assert merge([node1], [node2], version, model, {'sameMethodModel': True}, node_type) == [node1, node2]
195
+ assert merge([node1, node2], [node3], version, model, {'sameMethodModel': True}, node_type) == [node1, node2, node3]
196
+ assert merge([node1], [node2, node3], version, model, {}, node_type) == [node1, node2, node3]
197
+ # same term, methodModel, operation and inputs should merge
198
+ assert merge([node1], [node4], version, model, {}, node_type) == [node4]
199
+
200
+
201
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
202
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
203
+ def test_merge_multiple_identical_terms(*args):
204
+ term1 = {'@id': 'term1'}
205
+ node_type = 'Cycle'
206
+ model = {'key': 'inputs'}
207
+
208
+ node1 = {
209
+ 'term': term1,
210
+ 'value': 1
211
+ }
212
+ node2 = {
213
+ 'term': term1,
214
+ 'value': 2,
215
+ 'impactAssessment': {'id': 'ia-1'}
216
+ }
217
+ # merging the same unique nodes should make no changes
218
+ result = merge([node1, node2], [node1, node2], version, model, {}, node_type)
219
+ assert result == [node1, node2]
220
+
221
+ # adding the same first node with a new unique key
222
+ node3 = {
223
+ 'term': term1,
224
+ 'value': 3,
225
+ 'impactAssessment': {'@id': 'ia-1'}
226
+ }
227
+ result = merge([node1, node2], [node3], version, model, {}, node_type)
228
+ assert result == [node3, node2]
229
+
230
+ node4 = {
231
+ 'term': term1,
232
+ 'value': 4,
233
+ 'impactAssessment': {'id': 'ia-1'}
234
+ }
235
+ result = merge([node1, node2], [node4], version, model, {}, node_type)
236
+ assert result == [node1, node4]
237
+
238
+
239
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
240
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
241
+ def test_merge_animals(*args):
242
+ term1 = {'@id': 'term1'}
243
+ node_type = 'Cycle'
244
+ model = {'key': 'animals'}
245
+
246
+ node1 = {
247
+ 'animalId': 'animal-1',
248
+ 'term': term1,
249
+ 'value': 1,
250
+ 'properties': [
251
+ {
252
+ 'term': {"@id": "liveweightPerHead"},
253
+ 'value': 40
254
+ }
255
+ ]
256
+ }
257
+ node2 = {
258
+ 'animalId': 'animal-2',
259
+ 'term': term1,
260
+ 'value': 1,
261
+ 'properties': [
262
+ {
263
+ 'term': {"@id": "liveweightPerHead"},
264
+ 'value': 40
265
+ },
266
+ {
267
+ 'term': {"@id": "age"},
268
+ 'value': 10
269
+ }
270
+ ]
271
+ }
272
+ result = merge([node1], [node2], version, model, {}, node_type)
273
+ # can not merge as properties is used to determine uniqueness
274
+ assert result == [node1, node2]
275
+
276
+
277
+ @patch(f"{class_path}.merge_node", side_effect=_default_merge)
278
+ @patch(f"{class_path}.update_node_version", side_effect=lambda _v, n: n)
279
+ def test_merge_with_properties(*args):
280
+ node_type = 'Cycle'
281
+ model = {'key': 'inputs'}
282
+
283
+ node1 = {
284
+ "term": {
285
+ "@type": "Term",
286
+ "@id": "concentrateFeedBlend"
287
+ },
288
+ "isAnimalFeed": True,
289
+ "value": [
290
+ 100
291
+ ],
292
+ "impactAssessment": {
293
+ "@id": "_djxbkdk2wnx",
294
+ "@type": "ImpactAssessment"
295
+ },
296
+ "impactAssessmentIsProxy": False,
297
+ "@type": "Input"
298
+ }
299
+
300
+ node2 = {
301
+ "term": {
302
+ "@type": "Term",
303
+ "@id": "concentrateFeedBlend"
304
+ },
305
+ "isAnimalFeed": True,
306
+ "value": [
307
+ 200
308
+ ],
309
+ "impactAssessment": {
310
+ "@id": "uug7pcaas6aa",
311
+ "@type": "ImpactAssessment"
312
+ },
313
+ "impactAssessmentIsProxy": False,
314
+ "@type": "Input"
315
+ }
316
+
317
+ properties = [
318
+ {
319
+ 'term': {'@id': 'property-1'},
320
+ 'value': 100
321
+ }
322
+ ]
323
+ node3 = node2 | {'properties': properties}
324
+
325
+ result = merge([node1, node2], [node3], version, model, {}, node_type)
326
+ # can not merge as properties is used to determine uniqueness
327
+ assert result == [node1, node3]
@@ -0,0 +1,95 @@
1
+ from unittest.mock import patch
2
+ from hestia_earth.schema import EmissionMethodTier
3
+
4
+ from hestia_earth.orchestrator.strategies.merge.merge_node import merge, _has_threshold_diff, _should_merge_lower_tier
5
+
6
+ class_path = 'hestia_earth.orchestrator.strategies.merge.merge_node'
7
+
8
+
9
+ def test_has_threshold_diff():
10
+ key = 'key'
11
+ threshold = 0.1 # 10%
12
+ source = {}
13
+ dest = {}
14
+
15
+ # key not in source => merge
16
+ assert _has_threshold_diff(source, dest, key, threshold) is True
17
+
18
+ # key not in dest => no merge
19
+ source[key] = [100]
20
+ assert not _has_threshold_diff(source, dest, key, threshold)
21
+
22
+ dest[key] = [90]
23
+ assert not _has_threshold_diff(source, dest, key, threshold)
24
+
25
+ dest[key] = [89]
26
+ assert _has_threshold_diff(source, dest, key, threshold) is True
27
+
28
+ # edge cases
29
+ source[key] = [0]
30
+ dest[key] = [1]
31
+ assert _has_threshold_diff(source, dest, key, threshold) is True
32
+
33
+ source[key] = [0]
34
+ dest[key] = [0]
35
+ assert not _has_threshold_diff(source, dest, key, threshold)
36
+
37
+ source[key] = [1]
38
+ dest[key] = [0]
39
+ assert _has_threshold_diff(source, dest, key, threshold)
40
+
41
+
42
+ def test_should_merge_lower_tier():
43
+ source = {}
44
+ dest = {}
45
+
46
+ # always merge if replacing lower tier
47
+ assert _should_merge_lower_tier(source, dest, {'replaceLowerTier': True}) is True
48
+
49
+ # new value has lower tier
50
+ source['methodTier'] = EmissionMethodTier.TIER_3.value
51
+ dest['methodTier'] = EmissionMethodTier.TIER_1.value
52
+ assert not _should_merge_lower_tier(source, dest, {'replaceLowerTier': False})
53
+
54
+ source['methodTier'] = EmissionMethodTier.TIER_1.value
55
+ dest['methodTier'] = EmissionMethodTier.NOT_RELEVANT.value
56
+ assert not _should_merge_lower_tier(source, dest, {'replaceLowerTier': False})
57
+
58
+ # new value has identical tier
59
+ source['methodTier'] = EmissionMethodTier.TIER_1.value
60
+ dest['methodTier'] = EmissionMethodTier.TIER_1.value
61
+ assert _should_merge_lower_tier(source, dest, {'replaceLowerTier': False}) is True
62
+
63
+ # new value has higher tier
64
+ source['methodTier'] = EmissionMethodTier.TIER_1.value
65
+ dest['methodTier'] = EmissionMethodTier.TIER_3.value
66
+ assert _should_merge_lower_tier(source, dest, {'replaceLowerTier': False}) is True
67
+
68
+
69
+ @patch(f"{class_path}._has_threshold_diff", return_value=False)
70
+ def test_merge_no_merge(*args):
71
+ source = {'value': [100]}
72
+ dest = {'value': [50]}
73
+ args = {'replaceThreshold': ['value', 50]}
74
+ # simply return the source
75
+ assert merge(source, dest, '0', {}, args) == source
76
+
77
+
78
+ @patch(f"{class_path}._has_threshold_diff", return_value=True)
79
+ @patch(f"{class_path}.update_node_version", return_value={})
80
+ def test_merge_no_threshold(mock_update, *args):
81
+ source = {'value': [100]}
82
+ dest = {'value': [50]}
83
+ args = {}
84
+ merge(source, dest, '0', {}, args)
85
+ mock_update.assert_called_once()
86
+
87
+
88
+ @patch(f"{class_path}._has_threshold_diff", return_value=True)
89
+ @patch(f"{class_path}.update_node_version", return_value={})
90
+ def test_merge_with_threshold(mock_update, *args):
91
+ source = {'value': [100]}
92
+ dest = {'value': [50]}
93
+ args = {'replaceThreshold': ['value', 50]}
94
+ merge(source, dest, '0', {}, args)
95
+ mock_update.assert_called_once()
File without changes
@@ -0,0 +1,114 @@
1
+ from unittest.mock import patch
2
+
3
+ from hestia_earth.orchestrator.strategies.run.add_blank_node_if_missing import should_run
4
+
5
+ class_path = 'hestia_earth.orchestrator.strategies.run.add_blank_node_if_missing'
6
+ FAKE_EMISSION = {'@id': 'n2OToAirExcretaDirect', 'termType': 'emission'}
7
+
8
+
9
+ @patch(f"{class_path}.get_required_model_param", return_value='')
10
+ @patch(f"{class_path}.find_term_match")
11
+ def test_should_run(mock_node_exists, *args):
12
+ data = {}
13
+ model = {}
14
+
15
+ # node does not exists => run
16
+ mock_node_exists.return_value = None
17
+ assert should_run(data, model) is True
18
+
19
+ # node exists but no value => run
20
+ mock_node_exists.return_value = {}
21
+ assert should_run(data, model) is True
22
+
23
+ # node exists with value + no params => no run
24
+ node = {'value': 10}
25
+ mock_node_exists.return_value = node
26
+ assert not should_run(data, model)
27
+
28
+ # node exists with added value `0` and `Emission` => run
29
+ node = {'@type': 'Emission', 'value': [0], 'added': ['value']}
30
+ mock_node_exists.return_value = node
31
+ assert should_run(data, model) is True
32
+
33
+
34
+ @patch(f"{class_path}.get_required_model_param", return_value='')
35
+ @patch(f"{class_path}.find_term_match")
36
+ def test_should_run_skipEmptyValue(mock_node_exists, *args):
37
+ data = {}
38
+
39
+ # no value and not skip => run
40
+ mock_node_exists.return_value = {}
41
+ model = {'runArgs': {'skipEmptyValue': False}}
42
+ assert should_run(data, model) is True
43
+
44
+ # no value and skip => no run
45
+ mock_node_exists.return_value = {}
46
+ model = {'runArgs': {'skipEmptyValue': True}}
47
+ assert not should_run(data, model)
48
+
49
+
50
+ @patch(f"{class_path}.get_required_model_param", return_value='')
51
+ def test_should_run_skipAggregated(*args):
52
+ data = {}
53
+ model = {'runArgs': {'skipAggregated': True}}
54
+
55
+ # not aggregated => run
56
+ data = {'aggregated': False}
57
+ assert should_run(data, model) is True
58
+
59
+ # aggregated => no run
60
+ data = {'aggregated': True}
61
+ assert not should_run(data, model)
62
+
63
+
64
+ @patch(f"{class_path}.get_required_model_param", return_value='')
65
+ @patch(f"{class_path}.find_term_match")
66
+ def test_should_run_runNonAddedTerm(mock_node_exists, *args):
67
+ data = {}
68
+ node = {'value': 10}
69
+ mock_node_exists.return_value = node
70
+ model = {'runArgs': {'runNonAddedTerm': True}}
71
+
72
+ # term has been added => no run
73
+ node['added'] = ['term']
74
+ assert not should_run(data, model)
75
+
76
+ # term has not been added => run
77
+ node['added'] = []
78
+ assert should_run(data, model) is True
79
+
80
+
81
+ @patch(f"{class_path}.get_required_model_param", return_value='')
82
+ @patch(f"{class_path}.find_term_match")
83
+ def test_should_run_runNonMeasured(mock_node_exists, *args):
84
+ data = {}
85
+ node = {'value': 10}
86
+ mock_node_exists.return_value = node
87
+ model = {'runArgs': {'runNonMeasured': True}}
88
+
89
+ # term measured => no run
90
+ node['methodTier'] = 'measured'
91
+ assert not should_run(data, model)
92
+
93
+ # term not measured => run
94
+ node['methodTier'] = 'background'
95
+ assert should_run(data, model) is True
96
+
97
+
98
+ @patch(f"{class_path}.get_table_value", return_value='Cycle')
99
+ @patch(f"{class_path}.download_hestia", return_value=FAKE_EMISSION)
100
+ @patch(f"{class_path}.get_required_model_param", return_value='')
101
+ @patch(f"{class_path}.find_term_match")
102
+ def test_should_run_check_typeAllowed(mock_node_exists, *args):
103
+ data = {}
104
+ node = {'term': FAKE_EMISSION}
105
+ mock_node_exists.return_value = node
106
+ model = {}
107
+
108
+ # type is not allowed => no run
109
+ data['@type'] = 'Transformation'
110
+ assert not should_run(data, model)
111
+
112
+ # type is allowed => run
113
+ data['@type'] = 'Cycle'
114
+ assert should_run(data, model) is True
@@ -0,0 +1,14 @@
1
+ from hestia_earth.orchestrator.strategies.run.add_key_if_missing import should_run
2
+
3
+
4
+ def test_should_run():
5
+ data = {}
6
+ key = 'model-key'
7
+ model = {'key': key}
8
+
9
+ # key not in data => run
10
+ assert should_run(data, model) is True
11
+
12
+ # key in data => no run
13
+ data[key] = 10
14
+ assert not should_run(data, model)
@@ -0,0 +1,5 @@
1
+ from hestia_earth.orchestrator.strategies.run.always import should_run
2
+
3
+
4
+ def test_should_run():
5
+ assert should_run({}, {}) is True
@@ -0,0 +1,69 @@
1
+ from unittest.mock import patch
2
+ import pytest
3
+ import json
4
+ import os
5
+
6
+ from tests.utils import fixtures_path
7
+ from hestia_earth.orchestrator.models import run, _run_parallel, _filter_models_stage
8
+
9
+ class_path = 'hestia_earth.orchestrator.models'
10
+ folder_path = os.path.join(fixtures_path, 'orchestrator', 'cycle')
11
+
12
+
13
+ @patch('hestia_earth.orchestrator.strategies.merge._merge_version', return_value='0.0.0')
14
+ def test_run(*args):
15
+ with open(os.path.join(folder_path, 'config.json'), encoding='utf-8') as f:
16
+ config = json.load(f)
17
+ with open(os.path.join(folder_path, 'cycle.jsonld'), encoding='utf-8') as f:
18
+ cycle = json.load(f)
19
+ with open(os.path.join(folder_path, 'result.jsonld'), encoding='utf-8') as f:
20
+ expected = json.load(f)
21
+
22
+ result = run(cycle, config.get('models'))
23
+ assert result == expected
24
+
25
+
26
+ @patch(f"{class_path}._run_model", side_effect=Exception('error'))
27
+ def test_run_parallel_with_errors(*args):
28
+ data = {
29
+ '@type': 'Cycle',
30
+ '@id': 'cycle'
31
+ }
32
+ model = [{'key': 'model1'}, {'key': 'model2'}]
33
+
34
+ with pytest.raises(Exception):
35
+ _run_parallel(data, model, [])
36
+
37
+
38
+ def test_filter_models_stage():
39
+ models = json.load(open(os.path.join(fixtures_path, 'orchestrator', 'stages', 'config.json'))).get('models')
40
+ assert _filter_models_stage(models) == models
41
+ assert _filter_models_stage(models, 1) == [
42
+ {
43
+ "key": "model1",
44
+ "stage": 1
45
+ },
46
+ {
47
+ "key": "model2",
48
+ "stage": 1
49
+ }
50
+ ]
51
+ assert _filter_models_stage(models, 2) == [
52
+ [
53
+ {
54
+ "key": "model3",
55
+ "stage": 2
56
+ },
57
+ {
58
+ "key": "model4",
59
+ "stage": 2
60
+ }
61
+ ]
62
+ ]
63
+ assert _filter_models_stage(models, 3) == [
64
+ {
65
+ "key": "model5",
66
+ "stage": 3
67
+ }
68
+ ]
69
+ assert _filter_models_stage(models, [1, 2, 3]) == models
@@ -0,0 +1,27 @@
1
+ from unittest.mock import patch
2
+ import pytest
3
+
4
+ from hestia_earth.orchestrator import run
5
+
6
+ config = {'models': []}
7
+
8
+
9
+ @patch('hestia_earth.orchestrator.run_models', return_value={})
10
+ def test_run(mock_run_models, *args):
11
+ run({'@type': 'Cycle'}, config)
12
+ mock_run_models.assert_called_once()
13
+
14
+
15
+ def test_run_missing_type():
16
+ with pytest.raises(Exception, match='Please provide an "@type" key in your data.'):
17
+ run({}, config)
18
+
19
+
20
+ def test_run_missing_config():
21
+ with pytest.raises(Exception, match='Please provide a valid configuration.'):
22
+ run({'@type': 'Cycle'}, None)
23
+
24
+
25
+ def test_run_missing_models():
26
+ with pytest.raises(Exception, match='Please provide a valid configuration.'):
27
+ run({'@type': 'Cycle'}, {})