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.
@@ -0,0 +1,611 @@
1
+ import datetime
2
+ import logging
3
+ import os
4
+
5
+ import yaml
6
+
7
+ from data_pipeline_api import fdp_utils
8
+
9
+ WRITING_STR = "Writing {} to local registry"
10
+
11
+ def initialise(token: str, config: str, script: str) -> dict:
12
+ """Reads in token, config file and script, creates necessary registry items
13
+ and creates new code run.
14
+
15
+ Args:
16
+ | token: registry token
17
+ | config: Path to config file
18
+ | script: Path to script file
19
+
20
+ Returns:
21
+ | dict: a dictionary containing the following keys:
22
+ | 'yaml': config_yaml path,
23
+ | 'fdp_config_dir': config dir path,
24
+ | 'model_config': model config url,
25
+ | 'submission_script': submission script object url,
26
+ | 'code_repo': code repo object url,
27
+ | 'code_run': coderun url,
28
+ | 'code_run_uuid': coderun uuid,
29
+ | 'author': author url
30
+ """
31
+
32
+ # Validate Yamls
33
+ if not fdp_utils.is_valid_yaml(config):
34
+ raise ValueError("Config is not a valid YAML file")
35
+ if not fdp_utils.is_file(script):
36
+ raise ValueError("Script does not exist")
37
+
38
+ # Read config file and extract run metadata
39
+ with open(config, "r") as data:
40
+ config_yaml = yaml.safe_load(data)
41
+ run_metadata = config_yaml["run_metadata"]
42
+ registry_url = run_metadata["local_data_registry_url"]
43
+ if registry_url[-1] != "/":
44
+ registry_url += "/"
45
+ filename = os.path.basename(config)
46
+
47
+ # @todo to be set from config
48
+ if "api_version" not in config_yaml["run_metadata"].keys():
49
+ config_yaml["run_metadata"]["api_version"] = "1.0.0"
50
+
51
+ api_version = config_yaml["run_metadata"]["api_version"]
52
+
53
+ logging.info("Reading {} from local filestore".format(filename))
54
+
55
+ # Configure storage root for config
56
+ config_storageroot_response = fdp_utils.post_storage_root(
57
+ token=token,
58
+ url=registry_url,
59
+ data={"root": run_metadata["write_data_store"], "local": True},
60
+ api_version=api_version,
61
+ )
62
+
63
+ config_storageroot_url = config_storageroot_response["url"]
64
+ config_storageroot_id = fdp_utils.extract_id(config_storageroot_url)
65
+ config_hash = fdp_utils.get_file_hash(config)
66
+
67
+ # Configure Storage Location for config
68
+ config_storage_data = {
69
+ "path": config.replace(run_metadata["write_data_store"], ""),
70
+ "hash": config_hash,
71
+ "public": True,
72
+ "storage_root": config_storageroot_url,
73
+ }
74
+
75
+ config_location_response = fdp_utils.post_entry(
76
+ token=token,
77
+ url=registry_url,
78
+ endpoint="storage_location",
79
+ data=config_storage_data,
80
+ api_version=api_version,
81
+ )
82
+
83
+ config_location_url = config_location_response["url"]
84
+
85
+ # Configure Yaml File Type
86
+ config_filetype_response = fdp_utils.post_file_type(
87
+ token=token,
88
+ url=registry_url,
89
+ data={"name": "YAML Document", "extension": "yaml"},
90
+ api_version=api_version,
91
+ )
92
+ config_filetype_url = config_filetype_response["url"]
93
+
94
+ # Get user for registry admin account
95
+ results = fdp_utils.get_entry(
96
+ url=registry_url,
97
+ endpoint="users",
98
+ query={"username": "admin"},
99
+ token=token,
100
+ api_version=api_version,
101
+ )
102
+
103
+ if not results:
104
+ raise IndexError(f"list {results} empty")
105
+ else:
106
+ user = fdp_utils.get_first_entry(results)
107
+ # Check users exists
108
+ if not user:
109
+ raise ValueError(
110
+ "Error: Admin user not found\
111
+ \nDid you run fair init?"
112
+ )
113
+
114
+ user_url = user["url"]
115
+ user_id = fdp_utils.extract_id(user_url)
116
+ # Get author(s)
117
+ results = fdp_utils.get_entry(
118
+ url=registry_url,
119
+ endpoint="user_author",
120
+ query={"user": user_id},
121
+ api_version=api_version,
122
+ )
123
+ if not results:
124
+ raise IndexError(f"list {results} empty")
125
+ else:
126
+ author = fdp_utils.get_first_entry(results)
127
+ # Check user author exists
128
+ if not author:
129
+ raise ValueError(
130
+ "Error: user_author not found\
131
+ \nDid you run fair init?"
132
+ )
133
+
134
+ author_url = author["author"]
135
+
136
+ # Create new object for config file
137
+
138
+ config_object = fdp_utils.post_entry(
139
+ token=token,
140
+ url=registry_url,
141
+ endpoint="object",
142
+ data={
143
+ "description": "Working config.yaml location in datastore",
144
+ "storage_location": config_location_url,
145
+ "authors": [author_url],
146
+ "file_type": config_filetype_url,
147
+ },
148
+ api_version=api_version,
149
+ )
150
+
151
+ config_object_url = config_object["url"]
152
+
153
+ logging.info(WRITING_STR.format(filename))
154
+
155
+ # Check if script exists in storage_location
156
+ script_storageroot_url = config_storageroot_url
157
+ _ = config_storageroot_id
158
+ script_hash = fdp_utils.get_file_hash(script)
159
+
160
+ script_location_exists = fdp_utils.get_entry(
161
+ registry_url,
162
+ "storage_location",
163
+ query={
164
+ "hash": script_hash
165
+ }
166
+ )
167
+ if script_location_exists:
168
+ script_location_url = script_location_exists[0]["url"]
169
+ else:
170
+ # Create Script Storage Location
171
+ script_storage_data = {
172
+ "path": script.replace(run_metadata["write_data_store"], ""),
173
+ "hash": script_hash,
174
+ "public": True,
175
+ "storage_root": script_storageroot_url,
176
+ }
177
+
178
+ script_location_response = fdp_utils.post_entry(
179
+ token=token,
180
+ url=registry_url,
181
+ endpoint="storage_location",
182
+ data=script_storage_data,
183
+ api_version=api_version,
184
+ )
185
+
186
+ script_location_url = script_location_response["url"]
187
+ script_file_type = os.path.basename(script).split(".")[-1]
188
+
189
+ # Create Script File Type
190
+ script_filetype_response = fdp_utils.post_file_type(
191
+ token=token,
192
+ url=registry_url,
193
+ data={"name": "python submission script", "extension": script_file_type},
194
+ api_version=api_version,
195
+ )
196
+
197
+ script_filetype_url = script_filetype_response["url"]
198
+
199
+ # Create new registry object for script
200
+ script_object = fdp_utils.post_entry(
201
+ token=token,
202
+ url=registry_url,
203
+ endpoint="object",
204
+ data={
205
+ "description": "Working script location in datastore",
206
+ "storage_location": script_location_url,
207
+ "authors": [author_url],
208
+ "file_type": script_filetype_url,
209
+ },
210
+ api_version=api_version,
211
+ )
212
+
213
+ script_object_url = script_object["url"]
214
+
215
+ logging.info(WRITING_STR.format(script))
216
+
217
+ # Create new remote storage root
218
+ repo_storageroot_url = fdp_utils.post_storage_root(
219
+ token=token,
220
+ url=registry_url,
221
+ data={"root": "https://github.com", "local": False},
222
+ api_version=api_version,
223
+ )["url"]
224
+
225
+ repo_storageroot_id = fdp_utils.extract_id(repo_storageroot_url)
226
+
227
+ sha = run_metadata["latest_commit"]
228
+ repo_name = run_metadata["remote_repo"]
229
+
230
+ # Check if code repo entry exists for given hash
231
+
232
+ _ = fdp_utils.get_entry(
233
+ url=registry_url,
234
+ endpoint="storage_location",
235
+ query={
236
+ "hash": sha,
237
+ "public": True,
238
+ "storage_root": repo_storageroot_id,
239
+ },
240
+ api_version=api_version,
241
+ )
242
+
243
+ # Configure Code Repo Location
244
+ coderepo_location_response = fdp_utils.post_entry(
245
+ token=token,
246
+ url=registry_url,
247
+ endpoint="storage_location",
248
+ data={
249
+ "path": repo_name,
250
+ "hash": sha,
251
+ "public": True,
252
+ "storage_root": repo_storageroot_url,
253
+ },
254
+ api_version=api_version,
255
+ )
256
+
257
+ coderepo_location_url = coderepo_location_response["url"]
258
+
259
+ # Configure Code Repo Object
260
+ coderepo_object_response = fdp_utils.post_entry(
261
+ token=token,
262
+ url=registry_url,
263
+ endpoint="object",
264
+ data={
265
+ "description": "Analysis / processing script location",
266
+ "storage_location": coderepo_location_url,
267
+ "authors": [author_url],
268
+ },
269
+ api_version=api_version,
270
+ )
271
+
272
+ coderepo_object_url = coderepo_object_response["url"]
273
+
274
+ logging.info(WRITING_STR.format(repo_name))
275
+
276
+ # Register new code run
277
+
278
+ coderun_response = fdp_utils.post_entry(
279
+ token=token,
280
+ url=registry_url,
281
+ endpoint="code_run",
282
+ data={
283
+ "run_date": str(datetime.datetime.now()),
284
+ "description": run_metadata["description"],
285
+ "code_repo": coderepo_object_url,
286
+ "model_config": config_object_url,
287
+ "submission_script": script_object_url,
288
+ "input_urls": [],
289
+ "output_urls": [],
290
+ },
291
+ api_version=api_version,
292
+ )
293
+
294
+ coderun_url = coderun_response["url"]
295
+ coderun_uuid = coderun_response["uuid"]
296
+
297
+ logging.info("Writing new code_run to local registry")
298
+
299
+ # Write code run and object info to handle
300
+
301
+ return {
302
+ "yaml": config_yaml,
303
+ "fdp_config_dir": os.path.dirname(config),
304
+ "model_config": config_object_url,
305
+ "submission_script": script_object_url,
306
+ "code_repo": coderepo_object_url,
307
+ "code_run": coderun_url,
308
+ "code_run_uuid": coderun_uuid,
309
+ "author": author_url,
310
+ }
311
+
312
+
313
+ # flake8: noqa C901
314
+ def finalise(token: str, handle: dict) -> None:
315
+ """
316
+ Renames files with their hash, updates data_product names and records
317
+ metadata in the registry
318
+
319
+ Args:
320
+ | token: registry token
321
+ | config: Path to config file
322
+ | script: Path to script file
323
+
324
+ Returns:
325
+ | dict: a dictionary containing the following keys:
326
+ | 'yaml': config_yaml path
327
+ | 'fdp_config_dir': config dir path
328
+ | 'model_config': model config url
329
+ | 'submission_script': submission script object url
330
+ | 'code_repo': code repo object url
331
+ | 'code_run': coderun url
332
+ | 'code_run_uuid': coderun uuid
333
+ | 'author': author url
334
+ | 'inputs':
335
+ | 'data_product': data product
336
+ | 'use_data_product': data product
337
+ | 'use_component': component
338
+ | 'use_version': version
339
+ | 'use_namespace': namespace
340
+ | 'path': path
341
+ | 'component_url': component url
342
+ | 'outputs':
343
+ | component_url: component url
344
+ | data_product_url: data product url
345
+ """
346
+ registry_url = handle["yaml"]["run_metadata"]["local_data_registry_url"]
347
+ datastore = handle["yaml"]["run_metadata"]["write_data_store"]
348
+ api_version = handle["yaml"]["run_metadata"]["api_version"]
349
+
350
+ datastore = fdp_utils.remove_local_from_root(datastore)
351
+ datastore_root = fdp_utils.get_entry(
352
+ url=registry_url,
353
+ endpoint="storage_root",
354
+ query={"root": datastore},
355
+ api_version=api_version,
356
+ )
357
+ datastore_root_url = None
358
+
359
+ # Check datastore is in registry
360
+ if datastore_root:
361
+ datastore_root_dict = fdp_utils.get_first_entry(datastore_root)
362
+ datastore_root_url = datastore_root_dict["url"]
363
+ else:
364
+ datastore_root_url = fdp_utils.post_storage_root(
365
+ token=token,
366
+ url=registry_url,
367
+ data={"root": datastore, "local": True},
368
+ api_version=api_version,
369
+ )["url"]
370
+
371
+ datastore_root_id = fdp_utils.extract_id(datastore_root_url)
372
+
373
+ if "output" in handle:
374
+ for output in handle["output"]:
375
+
376
+ if "${RUN_ID}" in handle["output"][output]["use_data_product"]:
377
+ handle["output"][output]["use_data_product"] = handle[
378
+ "output"
379
+ ][output]["use_data_product"].replace(
380
+ "${RUN_ID}", handle["code_run_uuid"]
381
+ )
382
+ write_namespace = fdp_utils.get_entry(
383
+ url=registry_url,
384
+ endpoint="namespace",
385
+ query={"name": handle["output"][output]["use_namespace"]},
386
+ api_version=api_version,
387
+ )
388
+ write_namespace_url = None
389
+ if write_namespace:
390
+ entry = fdp_utils.get_first_entry(write_namespace)
391
+ write_namespace_url = entry["url"]
392
+ else:
393
+ write_namespace_url = fdp_utils.post_entry(
394
+ token=token,
395
+ url=registry_url,
396
+ endpoint="namespace",
397
+ data={"name": handle["output"][output]["use_namespace"]},
398
+ api_version=api_version,
399
+ )["url"]
400
+
401
+ file_hash = fdp_utils.get_file_hash(handle["output"][output]["path"])
402
+
403
+ storage_exists = fdp_utils.get_entry(
404
+ url=registry_url,
405
+ endpoint="storage_location",
406
+ query={
407
+ "hash": file_hash,
408
+ "public": str(handle["output"][output]["public"]).lower(),
409
+ "storage_root": datastore_root_id,
410
+ },
411
+ api_version=api_version,
412
+ )
413
+
414
+ storage_location_url = None
415
+
416
+ if storage_exists:
417
+ storage_exists_dict = fdp_utils.get_first_entry(storage_exists)
418
+ storage_location_url = storage_exists_dict["url"]
419
+
420
+ os.remove(handle["output"][output]["path"])
421
+
422
+ directory = os.path.dirname(handle["output"][output]["path"])
423
+ i = 0
424
+ while os.path.normpath(directory) != os.path.normpath(
425
+ datastore
426
+ ):
427
+ try:
428
+ os.rmdir(directory)
429
+ except Exception:
430
+ logging.warning(
431
+ "Ignoring Directory: {} as it is not empty".format(
432
+ directory
433
+ )
434
+ )
435
+ directory = os.path.split(directory)[0]
436
+ i += 1
437
+ if i > 4:
438
+ break
439
+
440
+ existing_path = storage_exists_dict["path"]
441
+
442
+ existing_root = fdp_utils.get_entity(
443
+ url=registry_url,
444
+ endpoint="storage_root",
445
+ id=int(
446
+ fdp_utils.extract_id(
447
+ storage_exists_dict["storage_root"]
448
+ )
449
+ ),
450
+ api_version=api_version,
451
+ )["root"]
452
+
453
+ existing_root = fdp_utils.remove_local_from_root(existing_root)
454
+
455
+ new_path = os.path.join(existing_root, existing_path)
456
+
457
+ else:
458
+ tmp_filename = os.path.basename(
459
+ handle["output"][output]["path"]
460
+ )
461
+ extension = tmp_filename.split(sep=".")[-1]
462
+ new_filename = ".".join([file_hash, extension])
463
+ data_product = handle["output"][output]["data_product"]
464
+ namespace = handle["output"][output]["use_namespace"]
465
+ new_path = os.path.join(
466
+ datastore, namespace, data_product, new_filename
467
+ ).replace("\\", "/")
468
+ os.rename(handle["output"][output]["path"], new_path)
469
+ new_storage_location = os.path.join(
470
+ namespace, data_product, new_filename
471
+ ).replace("\\", "/")
472
+
473
+ storage_location_url = fdp_utils.post_entry(
474
+ token=token,
475
+ url=registry_url,
476
+ endpoint="storage_location",
477
+ data={
478
+ "path": new_storage_location,
479
+ "hash": file_hash,
480
+ "public": str(handle["output"][output]["public"]).lower(),
481
+ "storage_root": datastore_root_url,
482
+ },
483
+ api_version=api_version,
484
+ )["url"]
485
+
486
+ file_type = os.path.basename(new_path).split(".")[-1]
487
+
488
+ file_type_url = fdp_utils.post_file_type(
489
+ token=token,
490
+ url=registry_url,
491
+ data={"name": file_type, "extension": file_type},
492
+ api_version=api_version,
493
+ )["url"]
494
+
495
+ data_product_exists = fdp_utils.get_entry(
496
+ url=registry_url,
497
+ endpoint="data_product",
498
+ query={
499
+ "name": handle["output"][output]["use_data_product"],
500
+ "version": handle["output"][output]["use_version"],
501
+ "namespace": write_namespace_url,
502
+ },
503
+ api_version=api_version,
504
+ )
505
+
506
+ if data_product_exists:
507
+ data_product_exists_dict = fdp_utils.get_first_entry(
508
+ data_product_exists
509
+ )
510
+ data_product_url = data_product_exists_dict["url"]
511
+ object_url = data_product_exists_dict["object"]
512
+ obj = fdp_utils.get_entity(
513
+ url=registry_url,
514
+ endpoint="object",
515
+ id=int(fdp_utils.extract_id(object_url)),
516
+ api_version=api_version,
517
+ )
518
+ component_url = obj["components"][0]
519
+
520
+ else:
521
+ object_url = fdp_utils.post_entry(
522
+ token=token,
523
+ url=registry_url,
524
+ endpoint="object",
525
+ data={
526
+ "description": handle["output"][output][
527
+ "data_product_description"
528
+ ],
529
+ "storage_location": storage_location_url,
530
+ "authors": [handle["author"]],
531
+ "file_type": file_type_url,
532
+ },
533
+ api_version=api_version,
534
+ )["url"]
535
+
536
+ component_url = None
537
+
538
+ if handle["output"][output]["use_component"]:
539
+ component_url = fdp_utils.post_entry(
540
+ token=token,
541
+ url=registry_url,
542
+ endpoint="object_component",
543
+ data={
544
+ "object": object_url,
545
+ "name": handle["output"][output]["use_component"],
546
+ },
547
+ api_version=api_version,
548
+ )["url"]
549
+ else:
550
+ component_url = fdp_utils.get_entry(
551
+ url=registry_url,
552
+ endpoint="object_component",
553
+ query={
554
+ "object": fdp_utils.extract_id(object_url),
555
+ },
556
+ api_version=api_version,
557
+ )[0]["url"]
558
+
559
+ data_product_url = fdp_utils.post_entry(
560
+ token=token,
561
+ url=registry_url,
562
+ endpoint="data_product",
563
+ data={
564
+ "name": handle["output"][output]["use_data_product"],
565
+ "version": handle["output"][output]["use_version"],
566
+ "object": object_url,
567
+ "namespace": write_namespace_url,
568
+ },
569
+ api_version=api_version,
570
+ )["url"]
571
+
572
+ handle["output"][output]["component_url"] = component_url
573
+ handle["output"][output]["data_product_url"] = data_product_url
574
+
575
+ logging.info(
576
+ WRITING_STR.format(
577
+ handle["output"][output]["use_data_product"]
578
+ )
579
+ )
580
+
581
+ output_components = []
582
+ input_components = []
583
+
584
+ if "output" in handle.keys():
585
+ for output in handle["output"]:
586
+ output_components.append(handle["output"][output]["component_url"])
587
+
588
+ if "input" in handle.keys():
589
+ for input in handle["input"]:
590
+ input_components.append(handle["input"][input]["component_url"])
591
+
592
+ if "issues" in handle.keys():
593
+ fdp_utils.register_issues(token, handle)
594
+
595
+ fdp_utils.patch_entry(
596
+ token=token,
597
+ url=handle["code_run"],
598
+ data={"inputs": input_components, "outputs": output_components},
599
+ api_version=api_version,
600
+ )
601
+
602
+ coderuns_path = os.path.join(
603
+ handle["fdp_config_dir"], "coderuns.txt"
604
+ ).replace("\\", "/")
605
+
606
+ with open(coderuns_path, "a+") as coderun_file:
607
+ coderun_file.seek(0)
608
+ data = coderun_file.read(100)
609
+ if len(data) > 0:
610
+ coderun_file.write("\n")
611
+ coderun_file.write(handle["code_run_uuid"])