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.
- hestia_earth/models/config/Cycle.json +2193 -0
- hestia_earth/models/config/ImpactAssessment.json +2041 -0
- hestia_earth/models/config/Site.json +471 -0
- hestia_earth/models/config/__init__.py +71 -0
- hestia_earth/models/config/run-calculations.json +42 -0
- hestia_earth/models/config/trigger-calculations.json +43 -0
- hestia_earth/models/ipcc2019/animal/hoursWorkedPerDay.py +38 -0
- hestia_earth/models/mocking/search-results.json +4524 -27
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/__init__.py +40 -0
- hestia_earth/orchestrator/log.py +62 -0
- hestia_earth/orchestrator/models/__init__.py +118 -0
- hestia_earth/orchestrator/models/emissions/__init__.py +0 -0
- hestia_earth/orchestrator/models/emissions/deleted.py +15 -0
- hestia_earth/orchestrator/models/transformations.py +103 -0
- hestia_earth/orchestrator/strategies/__init__.py +0 -0
- hestia_earth/orchestrator/strategies/merge/__init__.py +42 -0
- hestia_earth/orchestrator/strategies/merge/merge_append.py +29 -0
- hestia_earth/orchestrator/strategies/merge/merge_default.py +1 -0
- hestia_earth/orchestrator/strategies/merge/merge_list.py +103 -0
- hestia_earth/orchestrator/strategies/merge/merge_node.py +59 -0
- hestia_earth/orchestrator/strategies/run/__init__.py +8 -0
- hestia_earth/orchestrator/strategies/run/add_blank_node_if_missing.py +85 -0
- hestia_earth/orchestrator/strategies/run/add_key_if_missing.py +9 -0
- hestia_earth/orchestrator/strategies/run/always.py +6 -0
- hestia_earth/orchestrator/utils.py +116 -0
- {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/METADATA +27 -5
- {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/RECORD +51 -7
- tests/models/ipcc2019/animal/test_hoursWorkedPerDay.py +22 -0
- tests/models/test_config.py +115 -0
- tests/orchestrator/__init__.py +0 -0
- tests/orchestrator/models/__init__.py +0 -0
- tests/orchestrator/models/emissions/__init__.py +0 -0
- tests/orchestrator/models/emissions/test_deleted.py +21 -0
- tests/orchestrator/models/test_transformations.py +29 -0
- tests/orchestrator/strategies/__init__.py +0 -0
- tests/orchestrator/strategies/merge/__init__.py +0 -0
- tests/orchestrator/strategies/merge/test_merge_append.py +33 -0
- tests/orchestrator/strategies/merge/test_merge_default.py +7 -0
- tests/orchestrator/strategies/merge/test_merge_list.py +327 -0
- tests/orchestrator/strategies/merge/test_merge_node.py +95 -0
- tests/orchestrator/strategies/run/__init__.py +0 -0
- tests/orchestrator/strategies/run/test_add_blank_node_if_missing.py +114 -0
- tests/orchestrator/strategies/run/test_add_key_if_missing.py +14 -0
- tests/orchestrator/strategies/run/test_always.py +5 -0
- tests/orchestrator/test_models.py +69 -0
- tests/orchestrator/test_orchestrator.py +27 -0
- tests/orchestrator/test_utils.py +109 -0
- {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.65.6.dist-info → hestia_earth_models-0.65.7.dist-info}/WHEEL +0 -0
- {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,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'}, {})
|