Skip to content

Commit 3b12a29

Browse files
committed
Implement Register Issue
1 parent 59cfae1 commit 3b12a29

File tree

6 files changed

+283
-29
lines changed

6 files changed

+283
-29
lines changed

.zenodo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
},
1111
{
1212
"affiliation": "The University of Sheffield",
13-
"name": "Reddyhoff, Dennis"
13+
"name": "Reddyhoff, Dennis",
14+
"orcid": "0000-0002-4971-2346"
1415
}
1516
],
1617
"access_right": "open",

src/org/fairdatapipeline/api/common/fdp_utils.py

Lines changed: 234 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from datetime import datetime
66
import hashlib
77
import random
8+
from requests import api
9+
from requests.models import Response
810
import yaml
911
import uuid
1012

@@ -14,7 +16,16 @@ def get_entry(
1416
query: dict,
1517
token: str = None,
1618
)-> list:
17-
19+
"""
20+
Internal function to retreive and item from the registy using a query
21+
Args:
22+
| url: str of the registry url
23+
| enpoint: enpoint (table)
24+
| query: dict forming a query
25+
| token: (optional) str of the registry token
26+
Returns:
27+
| dict: responce from registry
28+
"""
1829
headers = {}
1930

2031
if token is not None:
@@ -54,7 +65,16 @@ def get_entity(
5465
id: int,
5566
token: str = None,
5667
)-> list:
57-
68+
"""
69+
Internal function to get an item from the registry using it's id
70+
Args:
71+
| url: str of the registry url
72+
| enpoint: endpoint (table)
73+
| id: id of the item
74+
| token: (optional) str of the registry token
75+
Returns:
76+
| dict: responce from registry
77+
"""
5878
headers = {}
5979

6080
if token is not None:
@@ -72,7 +92,13 @@ def get_entity(
7292
return response.json()
7393

7494
def extract_id(url: str)-> str:
75-
95+
"""
96+
Internal function to return the id from an api url
97+
Args:
98+
| url: str of the api url
99+
Returns:
100+
| str: id derrived from the url
101+
"""
76102
parse = urllib.parse.urlsplit(url).path
77103
extract = list(filter(None, parse.split('/')))[-1]
78104

@@ -84,7 +110,16 @@ def post_entry(
84110
data: dict,
85111
token: str,
86112
)-> dict:
87-
113+
"""
114+
Internal function to post and entry on the registry
115+
Args:
116+
| url: str of the registry url
117+
| enpoint: str of the endpoint (table)
118+
| data: a dictionary containing the data to be posted
119+
| token: str of the registry token
120+
Returns:
121+
| dict: responce from registry
122+
"""
88123
headers = {
89124
'Authorization': 'token ' + token,
90125
'Content-type': 'application/json'
@@ -110,7 +145,15 @@ def patch_entry(
110145
data: dict,
111146
token: str
112147
)-> dict:
113-
148+
"""
149+
Internal function to patch and entry on the registry
150+
Args:
151+
| url: str of the url of what to be patched
152+
| data: a dictionary containing the data to be patched
153+
| token: str of the registry token
154+
Returns:
155+
| dict: responce from registry
156+
"""
114157
headers = {
115158
'Authorization': 'token ' + token,
116159
'Content-type': 'application/json'
@@ -125,6 +168,14 @@ def patch_entry(
125168
return response.json()
126169

127170
def post_storage_root(url, data, token):
171+
"""
172+
Internal function to post a storage root to the registry
173+
the function first adds file:// if the root is local
174+
Args:
175+
| url: str the url for the storage root e.g. https://github.com/
176+
Returns:
177+
| dict: repsonse from the local registy
178+
"""
128179
if 'local' in data.keys():
129180
if data['local']:
130181
data['root'] = 'file://' + data['root']
@@ -133,22 +184,40 @@ def post_storage_root(url, data, token):
133184
return post_entry(url, 'storage_root', data, token)
134185

135186
def remove_local_from_root(root: str):
187+
"""
188+
Internal function to remove prepending file:// from a given root
189+
Args:
190+
| root: the root
191+
Returns:
192+
| str: the root without file://
193+
"""
136194
if 'file://' in root:
137195
root = root.replace('file://', '')
138196

139197
return root
140198

141199

142200
def random_hash()-> str:
201+
"""
202+
Internal function to generate a random unique hash
143203
204+
Returns:
205+
| str: 40 character randomly generated hash.
206+
"""
144207
seed = datetime.now().timestamp() * random.uniform(1, 1000000)
145208
seed = str(seed).encode('utf-8')
146209
hashed = hashlib.sha1(seed)
147210

148211
return hashed.hexdigest()
149212

150213
def get_file_hash(path: str)-> str:
151-
214+
"""
215+
Internal function to return a files sha1 hash
216+
Args:
217+
| path: str file path
218+
Returns:
219+
| str: sha1 hash
220+
"""
152221
with open(path, 'rb') as data:
153222
data = data.read()
154223
#data = data.encode('utf-8')
@@ -157,27 +226,68 @@ def get_file_hash(path: str)-> str:
157226
return hashed.hexdigest()
158227

159228
def read_token(token_path: str):
229+
"""
230+
Internal function read a token from a given file
231+
Args:
232+
| token_path: path to token
233+
Returns:
234+
| str: token
235+
"""
160236
with open(token_path) as token:
161237
token = token.readline().strip()
162238
return token
163239

164240
def get_token(token_path: str):
241+
"""
242+
Internal function alias for read_token()
243+
Args:
244+
| token_path: path to token
245+
Returns:
246+
| str: token
247+
"""
165248
return read_token(token_path)
166249

167250
def is_file(filename: str):
251+
"""
252+
Internal function to check whether a file exists
253+
Args:
254+
| filename: file to check
255+
Returns:
256+
| boolean: whether the file exists
257+
"""
168258
return os.path.isfile(filename)
169259

170260
def is_yaml(filename: str):
261+
"""
262+
Internal function to check whether a file can be opened as a YAML file
263+
! warning returns True if the file can be coerced into yaml format
264+
Args:
265+
| filename: path to the yaml file
266+
Returns:
267+
| boolean: can the file be coerced into a yaml format?
268+
"""
171269
try:
172270
with open(filename, 'r') as data:
173271
yaml.safe_load(data)
174272
except: return False
175273
return True
176274

177275
def is_valid_yaml(filename: str):
276+
"""
277+
Internal function validate whether a file exists and can be coerced into a yaml format
278+
Args:
279+
| filename: path to the yaml file
280+
Returns:
281+
| boolean: does the file exist and can it be coerced into a yaml format
282+
"""
178283
return is_file(filename) & is_yaml(filename)
179284

180285
def generate_uuid():
286+
"""
287+
Internal function similar to random hash
288+
Returns:
289+
| str: a random unique identifier
290+
"""
181291
return datetime.now().strftime('%Y%m-%d%H-%M%S-') + str(uuid.uuid4())
182292

183293
def get_handle_index_from_path(handle: dict, path: str):
@@ -198,4 +308,121 @@ def get_handle_index_from_path(handle: dict, path: str):
198308
for input in handle['input']:
199309
if handle['input'][input]['path'] == path:
200310
tmp = input
201-
return tmp
311+
return tmp
312+
313+
def register_issues(token: str, handle: dict):
314+
"""
315+
Internal function, should only be called from finalise.
316+
"""
317+
318+
api_url = handle['yaml']['run_metadata']['local_data_registry_url']
319+
print(handle)
320+
issues = handle['issues']
321+
groups = set(handle['issues'][i]['group'] for i in handle['issues'])
322+
323+
for group in groups:
324+
component_list = []
325+
issue = None
326+
severity = None
327+
for i in issues:
328+
if issues[i]['group'] == group:
329+
type = issues[i]['type']
330+
issue = issues[i]['issue']
331+
severity = issues[i]['severity']
332+
index = issues[i]['index']
333+
data_product = issues[i]['use_data_product']
334+
component= issues[i]['use_component']
335+
version = issues[i]['version']
336+
namespace = issues[i]['use_namespace']
337+
338+
component_url = None
339+
object_id = None
340+
if type == 'config':
341+
object_id = handle['model_config']
342+
elif type == 'submission_script':
343+
object_id = handle['submission_script']
344+
elif type == 'github_repo':
345+
object_id = handle['code_repo']
346+
347+
if object_id:
348+
component_url = get_entry(
349+
url= api_url,
350+
endpoint= 'object_component',
351+
query= {
352+
'object' : extract_id(object_id),
353+
'whole_object': True
354+
}
355+
)[0]['url']
356+
357+
if index:
358+
if 'output' in handle.keys():
359+
for ii in handle['output']:
360+
if handle['output'][ii] == index:
361+
if 'component_url' in handle['output'][ii].keys():
362+
component_url = handle['output'][ii]['component_url']
363+
else:
364+
'No Component Found Please run finalise()'
365+
if 'input' in handle.keys():
366+
for ii in handle['input']:
367+
if handle['input'][ii] == index:
368+
if 'component_url' in handle['input'][ii].keys():
369+
component_url = handle['input'][ii]['component_url']
370+
else:
371+
'No Component Found Please run finalise()'
372+
373+
if data_product:
374+
current_namespace = get_entry(
375+
url = api_url,
376+
endpoint= 'namespace',
377+
query= {
378+
'name': namespace
379+
}
380+
)[0]['url']
381+
382+
object = get_entry(
383+
url= api_url,
384+
endpoint= 'data_product',
385+
query= {
386+
'name': data_product,
387+
'version': version,
388+
'namespace': extract_id(current_namespace)
389+
}
390+
)[0]['object']
391+
object_id = extract_id(object)
392+
if component:
393+
component_url = get_entry(
394+
url = api_url,
395+
endpoint= 'object_component',
396+
query= {
397+
'name': component,
398+
'object': object_id
399+
}
400+
)
401+
else:
402+
component_obj = get_entry(
403+
url = api_url,
404+
endpoint= 'object_component',
405+
query= {
406+
'object': object_id,
407+
'whole_object': True
408+
}
409+
)
410+
component_url = component_obj[0]['url']
411+
412+
if component_url:
413+
component_list.append(component_url)
414+
415+
# Register the issue:
416+
print('Registering issue:' + group)
417+
current_issue = post_entry(
418+
url = api_url,
419+
endpoint= 'issue',
420+
data = {
421+
'severity': severity,
422+
'description': issue,
423+
'component_issues': component_list
424+
},
425+
token = token
426+
)
427+
428+

src/org/fairdatapipeline/api/common/finalise_pipeline.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ def finalise(token: str, handle: dict):
260260
for input in handle['input']:
261261
input_components.append(handle['input'][input]['component_url'])
262262

263+
fdp_utils.register_issues(token, handle)
264+
263265
fdp_utils.patch_entry(
264266
token = token,
265267
url = handle['code_run'],

0 commit comments

Comments
 (0)