data_pipeline_api 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data_pipeline_api/__init__.py +27 -0
- data_pipeline_api/ext/SEIRSModelRun.py +43 -0
- data_pipeline_api/ext/SEIRSconfig.yaml +31 -0
- data_pipeline_api/ext/SEIRSconfigWindows.yaml +31 -0
- data_pipeline_api/ext/__init__.py +0 -0
- data_pipeline_api/ext/static_params_SEIRS.csv +8 -0
- data_pipeline_api/fdp_utils.py +517 -0
- data_pipeline_api/link.py +218 -0
- data_pipeline_api/pipeline.py +611 -0
- data_pipeline_api/raise_issue.py +255 -0
- data_pipeline_api-0.8.0.dist-info/LICENSE +674 -0
- data_pipeline_api-0.8.0.dist-info/METADATA +75 -0
- data_pipeline_api-0.8.0.dist-info/RECORD +14 -0
- data_pipeline_api-0.8.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"initialise",
|
|
3
|
+
"link_read",
|
|
4
|
+
"link_write",
|
|
5
|
+
"finalise",
|
|
6
|
+
"raise_issue_by_data_product",
|
|
7
|
+
"raise_issue_by_index",
|
|
8
|
+
"raise_issue_with_config",
|
|
9
|
+
"raise_issue_by_type",
|
|
10
|
+
"raise_issue_by_existing_data_product",
|
|
11
|
+
"raise_issue_with_submission_script",
|
|
12
|
+
"raise_issue_with_github_repo",
|
|
13
|
+
"get_handle_index_from_path",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
from .fdp_utils import get_handle_index_from_path
|
|
17
|
+
from .link import link_read, link_write
|
|
18
|
+
from .pipeline import finalise, initialise
|
|
19
|
+
from .raise_issue import (
|
|
20
|
+
raise_issue_by_data_product,
|
|
21
|
+
raise_issue_by_existing_data_product,
|
|
22
|
+
raise_issue_by_index,
|
|
23
|
+
raise_issue_by_type,
|
|
24
|
+
raise_issue_with_config,
|
|
25
|
+
raise_issue_with_github_repo,
|
|
26
|
+
raise_issue_with_submission_script,
|
|
27
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.append(str(os.getcwd()))
|
|
6
|
+
import data_pipeline_api as pipeline # noqa: E402
|
|
7
|
+
import simpleModel as simpleModel # noqa: E402
|
|
8
|
+
|
|
9
|
+
token = str(os.environ.get("FDP_LOCAL_TOKEN"))
|
|
10
|
+
script = os.path.join(str(os.environ.get("FDP_CONFIG_DIR")), "script.sh")
|
|
11
|
+
if platform.system() == "Windows":
|
|
12
|
+
script = os.path.join(str(os.environ.get("FDP_CONFIG_DIR")), "script.bat")
|
|
13
|
+
config = os.path.join(str(os.environ.get("FDP_CONFIG_DIR")), "config.yaml")
|
|
14
|
+
handle = pipeline.initialise(token, config, script)
|
|
15
|
+
|
|
16
|
+
initial_parameters = pipeline.link_read(handle, "SEIRS_model/parameters")
|
|
17
|
+
model_output = pipeline.link_write(
|
|
18
|
+
handle, "SEIRS_model/results/model_output/python"
|
|
19
|
+
)
|
|
20
|
+
model_plot = pipeline.link_write(handle, "SEIRS_model/results/figure/python")
|
|
21
|
+
|
|
22
|
+
# Model Code
|
|
23
|
+
states = {"s": 0.999, "e": 0.001, "i": 0, "r": 0}
|
|
24
|
+
|
|
25
|
+
init_params = simpleModel.readInitialParameters(initial_parameters)
|
|
26
|
+
|
|
27
|
+
alpha = init_params["alpha"]
|
|
28
|
+
beta = init_params["beta"]
|
|
29
|
+
inv_gamma = init_params["inv_gamma"]
|
|
30
|
+
inv_omega = init_params["inv_omega"]
|
|
31
|
+
inv_mu = init_params["inv_mu"]
|
|
32
|
+
inv_sigma = init_params["inv_sigma"]
|
|
33
|
+
R0 = init_params["R0"]
|
|
34
|
+
|
|
35
|
+
sm = simpleModel.SEIRS_Model(
|
|
36
|
+
states, 1000, 5, alpha, beta, inv_gamma, inv_omega, inv_mu, inv_sigma
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
simpleModel.SEIRS_Plot(sm, model_plot)
|
|
40
|
+
|
|
41
|
+
simpleModel.write_model_to_csv(sm, model_output)
|
|
42
|
+
|
|
43
|
+
pipeline.finalise(token, handle)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
run_metadata:
|
|
2
|
+
default_input_namespace: rfield
|
|
3
|
+
description: SEIRS Model python
|
|
4
|
+
script: python3 data_pipeline_api/ext/SEIRSModelRun.py
|
|
5
|
+
remote_repo: https://github.com/FAIRDataPipeline/pyDataPipeline.git
|
|
6
|
+
|
|
7
|
+
register:
|
|
8
|
+
- namespace: PSU
|
|
9
|
+
full_name: Pennsylvania State University
|
|
10
|
+
website: https://ror.org/04p491231
|
|
11
|
+
|
|
12
|
+
- external_object: SEIRS_model/parameters
|
|
13
|
+
namespace_name: PSU
|
|
14
|
+
root: https://raw.githubusercontent.com/
|
|
15
|
+
path: FAIRDataPipeline/pyDataPipeline/dev/data_pipeline_api/ext/static_params_SEIRS.csv
|
|
16
|
+
title: Static parameters of the model
|
|
17
|
+
description: Static parameters of the model
|
|
18
|
+
identifier: https://doi.org/10.1038/s41592-020-0856-2
|
|
19
|
+
file_type: csv
|
|
20
|
+
release_date: 2021-09-20T12:00
|
|
21
|
+
version: "1.0.0"
|
|
22
|
+
primary: True
|
|
23
|
+
|
|
24
|
+
write:
|
|
25
|
+
- data_product: SEIRS_model/results/model_output/python
|
|
26
|
+
description: SEIRS model results
|
|
27
|
+
file_type: csv
|
|
28
|
+
|
|
29
|
+
- data_product: SEIRS_model/results/figure/python
|
|
30
|
+
description: SEIRS output plot
|
|
31
|
+
file_type: png
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
run_metadata:
|
|
2
|
+
default_input_namespace: rfield
|
|
3
|
+
description: SEIRS Model python
|
|
4
|
+
script: python data_pipeline_api/ext/SEIRSModelRun.py
|
|
5
|
+
remote_repo: https://github.com/FAIRDataPipeline/pyDataPipeline.git
|
|
6
|
+
|
|
7
|
+
register:
|
|
8
|
+
- namespace: PSU
|
|
9
|
+
full_name: Pennsylvania State University
|
|
10
|
+
website: https://ror.org/04p491231
|
|
11
|
+
|
|
12
|
+
- external_object: SEIRS_model/parameters
|
|
13
|
+
namespace_name: PSU
|
|
14
|
+
root: https://raw.githubusercontent.com/
|
|
15
|
+
path: FAIRDataPipeline/pyDataPipeline/dev/data_pipeline_api/ext/static_params_SEIRS.csv
|
|
16
|
+
title: Static parameters of the model
|
|
17
|
+
description: Static parameters of the model
|
|
18
|
+
identifier: https://doi.org/10.1038/s41592-020-0856-2
|
|
19
|
+
file_type: csv
|
|
20
|
+
release_date: 2021-09-20T12:00
|
|
21
|
+
version: "1.0.0"
|
|
22
|
+
primary: True
|
|
23
|
+
|
|
24
|
+
write:
|
|
25
|
+
- data_product: SEIRS_model/results/model_output/python
|
|
26
|
+
description: SEIRS model results
|
|
27
|
+
file_type: csv
|
|
28
|
+
|
|
29
|
+
- data_product: SEIRS_model/results/figure/python
|
|
30
|
+
description: SEIRS output plot
|
|
31
|
+
file_type: png
|
|
File without changes
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from urllib.parse import urlsplit
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
FILE_PREFIX = "file://"
|
|
16
|
+
SERVER_RESPONSE_STR = "Server responded with: "
|
|
17
|
+
|
|
18
|
+
def get_first_entry(entries: list) -> dict:
|
|
19
|
+
"""
|
|
20
|
+
get_first_entry helper function for get_entry that return first element
|
|
21
|
+
|
|
22
|
+
exception handling is done in the main code
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
entries : list
|
|
27
|
+
[response list from api]
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
dict
|
|
32
|
+
[dictionary output from api]
|
|
33
|
+
"""
|
|
34
|
+
return entries[0]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_entry(
|
|
38
|
+
url: str,
|
|
39
|
+
endpoint: str,
|
|
40
|
+
query: dict,
|
|
41
|
+
token: str = None,
|
|
42
|
+
api_version: str = "1.0.0",
|
|
43
|
+
) -> list:
|
|
44
|
+
"""
|
|
45
|
+
Internal function to retreive and item from the registry using a query
|
|
46
|
+
Args:
|
|
47
|
+
| url: str of the registry url
|
|
48
|
+
| endpoint: endpoint (table)
|
|
49
|
+
| query: dict forming a query
|
|
50
|
+
| token: (optional) str of the registry token
|
|
51
|
+
Returns:
|
|
52
|
+
| dict: responce from registry
|
|
53
|
+
"""
|
|
54
|
+
headers = get_headers(token=token, api_version=api_version)
|
|
55
|
+
|
|
56
|
+
# Remove api address from query
|
|
57
|
+
for key in query:
|
|
58
|
+
if isinstance(query[key], str):
|
|
59
|
+
if url in query[key]:
|
|
60
|
+
query[key] = extract_id(query[key])
|
|
61
|
+
elif isinstance(query[key], dict):
|
|
62
|
+
for _key in query[key]:
|
|
63
|
+
if url in query[key][_key]:
|
|
64
|
+
query[key][_key] = extract_id(query[key][_key])
|
|
65
|
+
elif isinstance(query[key], list):
|
|
66
|
+
for i in range(len(query[key])):
|
|
67
|
+
if url in query[key][i]:
|
|
68
|
+
query[key][i] = extract_id(query[key][i])
|
|
69
|
+
|
|
70
|
+
if url[-1] != "/":
|
|
71
|
+
url += "/"
|
|
72
|
+
url += endpoint + "/?"
|
|
73
|
+
_query = [f"{k}={v}" for k, v in query.items()]
|
|
74
|
+
url += "&".join(_query)
|
|
75
|
+
response = requests.get(url, headers=headers)
|
|
76
|
+
if response.status_code != 200:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
SERVER_RESPONSE_STR
|
|
79
|
+
+ str(response.status_code)
|
|
80
|
+
+ " Query = "
|
|
81
|
+
+ url
|
|
82
|
+
)
|
|
83
|
+
return response.json()["results"]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_entity(
|
|
87
|
+
url: str,
|
|
88
|
+
endpoint: str,
|
|
89
|
+
id: int,
|
|
90
|
+
token: str = None,
|
|
91
|
+
api_version: str = "1.0.0",
|
|
92
|
+
) -> dict:
|
|
93
|
+
"""
|
|
94
|
+
Internal function to get an item from the registry using it's id
|
|
95
|
+
Args:
|
|
96
|
+
| url: str of the registry url
|
|
97
|
+
| enpoint: endpoint (table)
|
|
98
|
+
| id: id of the item
|
|
99
|
+
| token: (optional) str of the registry token
|
|
100
|
+
Returns:
|
|
101
|
+
| dict: responce from registry
|
|
102
|
+
"""
|
|
103
|
+
headers = get_headers(token=token, api_version=api_version)
|
|
104
|
+
|
|
105
|
+
if url[-1] != "/":
|
|
106
|
+
url += "/"
|
|
107
|
+
url += endpoint + "/" + str(id)
|
|
108
|
+
response = requests.get(url, headers=headers)
|
|
109
|
+
if response.status_code != 200:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
SERVER_RESPONSE_STR
|
|
112
|
+
+ str(response.status_code)
|
|
113
|
+
+ " Query = "
|
|
114
|
+
+ url
|
|
115
|
+
)
|
|
116
|
+
return response.json()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def extract_id(url: str) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Internal function to return the id from an api url
|
|
122
|
+
Args:
|
|
123
|
+
| url: str of the api url
|
|
124
|
+
Returns:
|
|
125
|
+
| str: id derrived from the url
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
split_url_path = urlsplit(url).path.split("/")
|
|
129
|
+
if not split_url_path:
|
|
130
|
+
raise IndexError(f"Unable to extract ID from registry URL: {url}")
|
|
131
|
+
return [s for s in split_url_path if s != ""][-1]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def post_entry(
|
|
135
|
+
url: str, endpoint: str, data: dict, token: str, api_version: str = "1.0.0"
|
|
136
|
+
) -> dict:
|
|
137
|
+
"""
|
|
138
|
+
Internal function to post and entry on the registry
|
|
139
|
+
Args:
|
|
140
|
+
| url: str of the registry url
|
|
141
|
+
| enpoint: str of the endpoint (table)
|
|
142
|
+
| data: a dictionary containing the data to be posted
|
|
143
|
+
| token: str of the registry token
|
|
144
|
+
Returns:
|
|
145
|
+
| dict: responce from registry
|
|
146
|
+
"""
|
|
147
|
+
headers = get_headers(
|
|
148
|
+
request_type="post", token=token, api_version=api_version
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if url[-1] != "/":
|
|
152
|
+
url += "/"
|
|
153
|
+
_url = url + endpoint + "/"
|
|
154
|
+
_data = json.dumps(data)
|
|
155
|
+
|
|
156
|
+
response = requests.post(_url, _data, headers=headers)
|
|
157
|
+
|
|
158
|
+
if response.status_code == 409:
|
|
159
|
+
logging.info("Entry Exists: Attempting to return Existing Entry")
|
|
160
|
+
existing_entry = get_entry(url, endpoint, data)
|
|
161
|
+
if not existing_entry:
|
|
162
|
+
raise ValueError("Could not return existing Entry")
|
|
163
|
+
return existing_entry[0]
|
|
164
|
+
|
|
165
|
+
if response.status_code != 201:
|
|
166
|
+
raise ValueError(SERVER_RESPONSE_STR + str(response.status_code))
|
|
167
|
+
|
|
168
|
+
return response.json()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def patch_entry(
|
|
172
|
+
url: str, data: dict, token: str, api_version: str = "1.0.0"
|
|
173
|
+
) -> dict:
|
|
174
|
+
"""
|
|
175
|
+
Internal function to patch and entry on the registry
|
|
176
|
+
Args:
|
|
177
|
+
| url: str of the url of what to be patched
|
|
178
|
+
| data: a dictionary containing the data to be patched
|
|
179
|
+
| token: str of the registry token
|
|
180
|
+
Returns:
|
|
181
|
+
| dict: responce from registry
|
|
182
|
+
"""
|
|
183
|
+
headers = get_headers(
|
|
184
|
+
request_type="post", token=token, api_version=api_version
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
data_json = json.dumps(data)
|
|
188
|
+
|
|
189
|
+
response = requests.patch(url, data_json, headers=headers)
|
|
190
|
+
if response.status_code != 200:
|
|
191
|
+
raise ValueError(SERVER_RESPONSE_STR + str(response.status_code))
|
|
192
|
+
|
|
193
|
+
return response.json()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_headers(
|
|
197
|
+
request_type: str = "get", token: str = None, api_version: str = "1.0.0"
|
|
198
|
+
) -> dict:
|
|
199
|
+
"""
|
|
200
|
+
Internal function to return headers to be added to a request
|
|
201
|
+
Args:
|
|
202
|
+
| request_type: (optional) type of request e.g. 'post' or 'get' defaults to 'get'
|
|
203
|
+
| token: (optional) token, if a token is supplied this will be added to the headers
|
|
204
|
+
| api_version: (optional) the version of the data registy to interact with, defaults to '1.0.0'
|
|
205
|
+
Returns:
|
|
206
|
+
| dict: a dictionary of appropriate headers to be added to a request
|
|
207
|
+
"""
|
|
208
|
+
headers = {"Accept": "application/json; version=" + api_version}
|
|
209
|
+
if token:
|
|
210
|
+
headers["Authorization"] = "token " + token
|
|
211
|
+
if request_type == "post":
|
|
212
|
+
headers["Content-Type"] = "application/json"
|
|
213
|
+
return headers
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def post_storage_root(
|
|
217
|
+
url: str, data: dict, token: str, api_version: str = "1.0.0"
|
|
218
|
+
) -> dict:
|
|
219
|
+
"""
|
|
220
|
+
Internal function to post a storage root to the registry
|
|
221
|
+
the function first adds file:// if the root is local
|
|
222
|
+
Args:
|
|
223
|
+
| url: str the url for the storage root e.g. https://github.com/
|
|
224
|
+
Returns:
|
|
225
|
+
| dict: repsonse from the local registy
|
|
226
|
+
"""
|
|
227
|
+
if "local" in data and data["local"]:
|
|
228
|
+
data["root"] = FILE_PREFIX + data["root"]
|
|
229
|
+
if data["root"][-1] != os.sep:
|
|
230
|
+
data["root"] = data["root"] + os.sep
|
|
231
|
+
elif data["root"][-1] != "/":
|
|
232
|
+
data["root"] = data["root"] + "/"
|
|
233
|
+
return post_entry(url, "storage_root", data, token, api_version)
|
|
234
|
+
|
|
235
|
+
def post_file_type(
|
|
236
|
+
url: str, data: dict, token: str, api_version: str = "1.0.0"
|
|
237
|
+
) -> dict:
|
|
238
|
+
"""
|
|
239
|
+
Internal wrapper function to return check if a file_type already exists and return it.
|
|
240
|
+
"""
|
|
241
|
+
if "extension" not in data:
|
|
242
|
+
if not data["extension"]:
|
|
243
|
+
raise ValueError("error file_type name not specified")
|
|
244
|
+
file_type_exists = get_entry(url= url,
|
|
245
|
+
endpoint= "file_type",
|
|
246
|
+
query= {"extension": data["extension"]},
|
|
247
|
+
api_version= api_version)
|
|
248
|
+
if file_type_exists : return file_type_exists[0]
|
|
249
|
+
return post_entry(url= url,
|
|
250
|
+
endpoint= "file_type",
|
|
251
|
+
data= data,
|
|
252
|
+
token= token,
|
|
253
|
+
api_version= api_version)
|
|
254
|
+
|
|
255
|
+
def remove_local_from_root(root: str) -> str:
|
|
256
|
+
"""
|
|
257
|
+
Internal function to remove prepending file:// from a given root
|
|
258
|
+
Args:
|
|
259
|
+
| root: the root
|
|
260
|
+
Returns:
|
|
261
|
+
| str: the root without file://
|
|
262
|
+
"""
|
|
263
|
+
if FILE_PREFIX in root:
|
|
264
|
+
root = root.replace(FILE_PREFIX, "")
|
|
265
|
+
|
|
266
|
+
return root
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def random_hash() -> str:
|
|
270
|
+
"""
|
|
271
|
+
Internal function to generate a random unique hash
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
| str: 40 character randomly generated hash.
|
|
275
|
+
"""
|
|
276
|
+
seed = datetime.now().timestamp() * random.uniform(1, 1000000)
|
|
277
|
+
seed_encoded = str(seed).encode("utf-8")
|
|
278
|
+
hashed = hashlib.sha1(seed_encoded)
|
|
279
|
+
|
|
280
|
+
return hashed.hexdigest()
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_file_hash(
|
|
284
|
+
path: str,
|
|
285
|
+
) -> str:
|
|
286
|
+
"""
|
|
287
|
+
Internal function to return a files sha1 hash
|
|
288
|
+
Args:
|
|
289
|
+
| path: str file path
|
|
290
|
+
Returns:
|
|
291
|
+
| str: sha1 hash
|
|
292
|
+
"""
|
|
293
|
+
with open(path, "rb") as data:
|
|
294
|
+
_data = data.read()
|
|
295
|
+
hashed = hashlib.sha1(_data)
|
|
296
|
+
|
|
297
|
+
return hashed.hexdigest()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def read_token(token_path: str) -> str:
|
|
301
|
+
"""
|
|
302
|
+
Internal function read a token from a given file
|
|
303
|
+
Args:
|
|
304
|
+
| token_path: path to token
|
|
305
|
+
Returns:
|
|
306
|
+
| str: token
|
|
307
|
+
"""
|
|
308
|
+
with open(token_path) as token:
|
|
309
|
+
_token = token.readline().strip()
|
|
310
|
+
return _token
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_token(token_path: str) -> str:
|
|
314
|
+
"""
|
|
315
|
+
Internal function alias for read_token()
|
|
316
|
+
Args:
|
|
317
|
+
| token_path: path to token
|
|
318
|
+
Returns:
|
|
319
|
+
| str: token
|
|
320
|
+
"""
|
|
321
|
+
return read_token(token_path)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def is_file(filename: str) -> bool:
|
|
325
|
+
"""
|
|
326
|
+
Internal function to check whether a file exists
|
|
327
|
+
Args:
|
|
328
|
+
| filename: file to check
|
|
329
|
+
Returns:
|
|
330
|
+
| boolean: whether the file exists
|
|
331
|
+
"""
|
|
332
|
+
return os.path.isfile(filename)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def is_yaml(filename: str) -> bool:
|
|
336
|
+
"""
|
|
337
|
+
Internal function to check whether a file can be opened as a YAML file
|
|
338
|
+
! warning returns True if the file can be coerced into yaml format
|
|
339
|
+
Args:
|
|
340
|
+
| filename: path to the yaml file
|
|
341
|
+
Returns:
|
|
342
|
+
| boolean: can the file be coerced into a yaml format?
|
|
343
|
+
"""
|
|
344
|
+
try:
|
|
345
|
+
with open(filename, "r") as data:
|
|
346
|
+
yaml.safe_load(data)
|
|
347
|
+
except Exception as err:
|
|
348
|
+
print(f"{type(err).__name__} was raised: {err}")
|
|
349
|
+
return False
|
|
350
|
+
return True
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def is_valid_yaml(filename: str) -> bool:
|
|
354
|
+
"""
|
|
355
|
+
Internal function validate whether a file exists and can be coerced into a yaml format
|
|
356
|
+
Args:
|
|
357
|
+
| filename: path to the yaml file
|
|
358
|
+
Returns:
|
|
359
|
+
| boolean: does the file exist and can it be coerced into a yaml format
|
|
360
|
+
"""
|
|
361
|
+
return is_file(filename) & is_yaml(filename)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def generate_uuid() -> str:
|
|
365
|
+
"""
|
|
366
|
+
Internal function similar to random hash
|
|
367
|
+
Returns:
|
|
368
|
+
| str: a random unique identifier
|
|
369
|
+
"""
|
|
370
|
+
return datetime.now().strftime("%Y%m-%d%H-%M%S-") + str(uuid.uuid4())
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_handle_index_from_path(handle: dict, path: str) -> Optional[Any]:
|
|
374
|
+
"""
|
|
375
|
+
Get an input or output handle index from a path
|
|
376
|
+
usually generated by link_read or link_write
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
| handle: the handle containing the index
|
|
380
|
+
| path: path as generated by link_read or link_write
|
|
381
|
+
"""
|
|
382
|
+
tmp = None
|
|
383
|
+
if "output" in handle:
|
|
384
|
+
for output in handle["output"]:
|
|
385
|
+
if handle["output"][output]["path"] == path:
|
|
386
|
+
tmp = output
|
|
387
|
+
if "input" in handle:
|
|
388
|
+
for input in handle["input"]:
|
|
389
|
+
if handle["input"][input]["path"] == path:
|
|
390
|
+
tmp = input
|
|
391
|
+
return tmp
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# flake8: noqa C901
|
|
395
|
+
def register_issues(token: str, handle: dict) -> dict: # sourcery no-metrics
|
|
396
|
+
"""
|
|
397
|
+
Internal function, should only be called from finalise.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
api_url = handle["yaml"]["run_metadata"]["local_data_registry_url"]
|
|
401
|
+
issues = handle["issues"]
|
|
402
|
+
groups = {handle["issues"][i]["group"] for i in handle["issues"]}
|
|
403
|
+
api_version = handle["yaml"]["run_metadata"]["api_version"]
|
|
404
|
+
|
|
405
|
+
for group in groups:
|
|
406
|
+
component_list = []
|
|
407
|
+
issue = None
|
|
408
|
+
severity = None
|
|
409
|
+
for i in issues:
|
|
410
|
+
if issues[i]["group"] == group:
|
|
411
|
+
issue_type = issues[i]["type"]
|
|
412
|
+
issue = issues[i]["issue"]
|
|
413
|
+
severity = issues[i]["severity"]
|
|
414
|
+
index = issues[i]["index"]
|
|
415
|
+
data_product = issues[i]["use_data_product"]
|
|
416
|
+
component = issues[i]["use_component"]
|
|
417
|
+
version = issues[i]["version"]
|
|
418
|
+
namespace = issues[i]["use_namespace"]
|
|
419
|
+
|
|
420
|
+
component_url = None
|
|
421
|
+
object_id = None
|
|
422
|
+
if issue_type == "config":
|
|
423
|
+
object_id = handle["model_config"]
|
|
424
|
+
elif issue_type == "github_repo":
|
|
425
|
+
object_id = handle["code_repo"]
|
|
426
|
+
|
|
427
|
+
elif issue_type == "submission_script":
|
|
428
|
+
object_id = handle["submission_script"]
|
|
429
|
+
if object_id:
|
|
430
|
+
component_url = get_entry(
|
|
431
|
+
url=api_url,
|
|
432
|
+
endpoint="object_component",
|
|
433
|
+
query={
|
|
434
|
+
"object": extract_id(object_id),
|
|
435
|
+
"whole_object": True,
|
|
436
|
+
},
|
|
437
|
+
api_version=api_version,
|
|
438
|
+
)[0]["url"]
|
|
439
|
+
|
|
440
|
+
if index:
|
|
441
|
+
if "output" in handle:
|
|
442
|
+
for ii in handle["output"]:
|
|
443
|
+
if handle["output"][ii] == index:
|
|
444
|
+
if (
|
|
445
|
+
"component_url"
|
|
446
|
+
in handle["output"][ii].keys()
|
|
447
|
+
):
|
|
448
|
+
component_url = handle["output"][ii][
|
|
449
|
+
"component_url"
|
|
450
|
+
]
|
|
451
|
+
else:
|
|
452
|
+
logging.warning("No Component Found")
|
|
453
|
+
if "input" in handle:
|
|
454
|
+
for ii in handle["input"]:
|
|
455
|
+
if handle["input"][ii] == index:
|
|
456
|
+
if (
|
|
457
|
+
"component_url"
|
|
458
|
+
in handle["input"][ii].keys()
|
|
459
|
+
):
|
|
460
|
+
component_url = handle["input"][ii][
|
|
461
|
+
"component_url"
|
|
462
|
+
]
|
|
463
|
+
else:
|
|
464
|
+
logging.warning("No Component Found")
|
|
465
|
+
|
|
466
|
+
if data_product:
|
|
467
|
+
current_namespace = get_entry(
|
|
468
|
+
url=api_url,
|
|
469
|
+
endpoint="namespace",
|
|
470
|
+
query={"name": namespace},
|
|
471
|
+
api_version=api_version,
|
|
472
|
+
)[0]["url"]
|
|
473
|
+
|
|
474
|
+
object_entry = get_entry(
|
|
475
|
+
url=api_url,
|
|
476
|
+
endpoint="data_product",
|
|
477
|
+
query={
|
|
478
|
+
"name": data_product,
|
|
479
|
+
"version": version,
|
|
480
|
+
"namespace": extract_id(current_namespace),
|
|
481
|
+
},
|
|
482
|
+
api_version=api_version,
|
|
483
|
+
)[0]["object"]
|
|
484
|
+
object_id = extract_id(object_entry)
|
|
485
|
+
if component:
|
|
486
|
+
component_url = get_entry(
|
|
487
|
+
url=api_url,
|
|
488
|
+
endpoint="object_component",
|
|
489
|
+
query={"name": component, "object": object_id},
|
|
490
|
+
api_version=api_version,
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
component_obj = get_entry(
|
|
494
|
+
url=api_url,
|
|
495
|
+
endpoint="object_component",
|
|
496
|
+
query={"object": object_id, "whole_object": True},
|
|
497
|
+
api_version=api_version,
|
|
498
|
+
)
|
|
499
|
+
component_url = component_obj[0]["url"]
|
|
500
|
+
|
|
501
|
+
if component_url:
|
|
502
|
+
component_list.append(component_url)
|
|
503
|
+
|
|
504
|
+
# Register the issue:
|
|
505
|
+
logging.info("Registering issue: {}".format(group))
|
|
506
|
+
current_issue = post_entry(
|
|
507
|
+
url=api_url,
|
|
508
|
+
endpoint="issue",
|
|
509
|
+
data={
|
|
510
|
+
"severity": severity,
|
|
511
|
+
"description": issue,
|
|
512
|
+
"component_issues": component_list,
|
|
513
|
+
},
|
|
514
|
+
token=token,
|
|
515
|
+
api_version=api_version,
|
|
516
|
+
)
|
|
517
|
+
return current_issue
|