tracdap-runtime 0.7.0rc1__py3-none-any.whl → 0.8.0b2__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.
- tracdap/rt/_exec/actors.py +5 -4
- tracdap/rt/_exec/context.py +166 -74
- tracdap/rt/_exec/dev_mode.py +147 -71
- tracdap/rt/_exec/engine.py +224 -99
- tracdap/rt/_exec/functions.py +122 -80
- tracdap/rt/_exec/graph.py +23 -35
- tracdap/rt/_exec/graph_builder.py +250 -113
- tracdap/rt/_exec/runtime.py +24 -10
- tracdap/rt/_exec/server.py +4 -3
- tracdap/rt/_impl/config_parser.py +3 -2
- tracdap/rt/_impl/data.py +89 -16
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +3 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +64 -62
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +27 -25
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +3 -3
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +2 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +4 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +4 -2
- tracdap/rt/_impl/logging.py +195 -0
- tracdap/rt/_impl/models.py +11 -8
- tracdap/rt/_impl/repos.py +5 -3
- tracdap/rt/_impl/schemas.py +2 -2
- tracdap/rt/_impl/shim.py +3 -2
- tracdap/rt/_impl/static_api.py +53 -33
- tracdap/rt/_impl/storage.py +4 -3
- tracdap/rt/_impl/util.py +1 -111
- tracdap/rt/_impl/validation.py +57 -30
- tracdap/rt/_version.py +1 -1
- tracdap/rt/api/__init__.py +6 -3
- tracdap/rt/api/file_types.py +29 -0
- tracdap/rt/api/hook.py +15 -7
- tracdap/rt/api/model_api.py +16 -0
- tracdap/rt/api/static_api.py +211 -125
- tracdap/rt/config/__init__.py +6 -6
- tracdap/rt/config/common.py +11 -1
- tracdap/rt/config/platform.py +4 -6
- tracdap/rt/ext/plugins.py +2 -2
- tracdap/rt/launch/launch.py +9 -11
- tracdap/rt/metadata/__init__.py +11 -9
- tracdap/rt/metadata/file.py +8 -0
- tracdap/rt/metadata/job.py +16 -0
- tracdap/rt/metadata/model.py +12 -2
- tracdap/rt/metadata/object.py +2 -0
- tracdap/rt/metadata/object_id.py +2 -0
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/METADATA +15 -15
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/RECORD +52 -50
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/WHEEL +1 -1
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/LICENSE +0 -0
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,8 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
+
import datetime as _dt
|
17
|
+
|
16
18
|
import tracdap.rt.config as config
|
17
19
|
import tracdap.rt.exceptions as _ex
|
18
20
|
import tracdap.rt._impl.data as _data # noqa
|
@@ -33,10 +35,10 @@ class GraphBuilder:
|
|
33
35
|
|
34
36
|
__JOB_BUILD_FUNC = tp.Callable[[meta.JobDefinition, NodeId], GraphSection]
|
35
37
|
|
36
|
-
def __init__(self,
|
38
|
+
def __init__(self, sys_config: config.RuntimeConfig, job_config: config.JobConfig):
|
37
39
|
|
40
|
+
self._sys_config = sys_config
|
38
41
|
self._job_config = job_config
|
39
|
-
self._result_spec = result_spec
|
40
42
|
|
41
43
|
self._job_key = _util.object_key(job_config.jobId)
|
42
44
|
self._job_namespace = NodeNamespace(self._job_key)
|
@@ -45,7 +47,7 @@ class GraphBuilder:
|
|
45
47
|
|
46
48
|
def _child_builder(self, job_id: meta.TagHeader) -> "GraphBuilder":
|
47
49
|
|
48
|
-
builder = GraphBuilder(self.
|
50
|
+
builder = GraphBuilder(self._sys_config, self._job_config)
|
49
51
|
builder._job_key = _util.object_key(job_id)
|
50
52
|
builder._job_namespace = NodeNamespace(builder._job_key)
|
51
53
|
|
@@ -355,58 +357,76 @@ class GraphBuilder:
|
|
355
357
|
|
356
358
|
nodes = dict()
|
357
359
|
outputs = set()
|
358
|
-
must_run = list()
|
359
360
|
|
360
|
-
for input_name,
|
361
|
+
for input_name, input_def in required_inputs.items():
|
362
|
+
|
363
|
+
# Backwards compatibility with pre 0.8 versions
|
364
|
+
input_type = meta.ObjectType.DATA \
|
365
|
+
if input_def.objectType == meta.ObjectType.OBJECT_TYPE_NOT_SET \
|
366
|
+
else input_def.objectType
|
367
|
+
|
368
|
+
input_selector = supplied_inputs.get(input_name)
|
361
369
|
|
362
|
-
|
370
|
+
if input_selector is None:
|
363
371
|
|
364
|
-
|
365
|
-
if input_schema.optional:
|
372
|
+
if input_def.optional:
|
366
373
|
data_view_id = NodeId.of(input_name, self._job_namespace, _data.DataView)
|
367
|
-
|
374
|
+
data_view = _data.DataView.create_empty(input_type)
|
375
|
+
nodes[data_view_id] = StaticValueNode(data_view_id, data_view, explicit_deps=explicit_deps)
|
368
376
|
outputs.add(data_view_id)
|
369
|
-
continue
|
370
377
|
else:
|
371
378
|
self._error(_ex.EJobValidation(f"Missing required input: [{input_name}]"))
|
372
|
-
continue
|
373
379
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
380
|
+
elif input_type == meta.ObjectType.DATA:
|
381
|
+
self._build_data_input(input_name, input_selector, nodes, outputs, explicit_deps)
|
382
|
+
|
383
|
+
elif input_type == meta.ObjectType.FILE:
|
384
|
+
self._build_file_input(input_name, input_selector, nodes, outputs, explicit_deps)
|
378
385
|
|
379
|
-
if data_def.schemaId:
|
380
|
-
schema_def = _util.get_job_resource(data_def.schemaId, self._job_config).schema
|
381
386
|
else:
|
382
|
-
|
387
|
+
self._error(_ex.EJobValidation(f"Invalid input type [{input_type.name}] for input [{input_name}]"))
|
383
388
|
|
384
|
-
|
385
|
-
data_item = data_def.parts[root_part_opaque_key].snap.deltas[0].dataItem
|
386
|
-
data_spec = _data.DataSpec(data_item, data_def, storage_def, schema_def)
|
389
|
+
return GraphSection(nodes, outputs=outputs)
|
387
390
|
|
388
|
-
|
389
|
-
data_spec_id = NodeId.of(f"{input_name}:SPEC", self._job_namespace, _data.DataSpec)
|
390
|
-
data_spec_node = StaticValueNode(data_spec_id, data_spec, explicit_deps=explicit_deps)
|
391
|
+
def _build_data_input(self, input_name, input_selector, nodes, outputs, explicit_deps):
|
391
392
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
393
|
+
# Build a data spec using metadata from the job config
|
394
|
+
# For now we are always loading the root part, snap 0, delta 0
|
395
|
+
data_def = _util.get_job_resource(input_selector, self._job_config).data
|
396
|
+
storage_def = _util.get_job_resource(data_def.storageId, self._job_config).storage
|
396
397
|
|
397
|
-
|
398
|
-
|
399
|
-
|
398
|
+
if data_def.schemaId:
|
399
|
+
schema_def = _util.get_job_resource(data_def.schemaId, self._job_config).schema
|
400
|
+
else:
|
401
|
+
schema_def = data_def.schema
|
400
402
|
|
401
|
-
|
402
|
-
|
403
|
-
|
403
|
+
root_part_opaque_key = 'part-root' # TODO: Central part names / constants
|
404
|
+
data_item = data_def.parts[root_part_opaque_key].snap.deltas[0].dataItem
|
405
|
+
data_spec = _data.DataSpec.create_data_spec(data_item, data_def, storage_def, schema_def)
|
406
|
+
|
407
|
+
# Physical load of data items from disk
|
408
|
+
# Currently one item per input, since inputs are single part/delta
|
409
|
+
data_load_id = NodeId.of(f"{input_name}:LOAD", self._job_namespace, _data.DataItem)
|
410
|
+
nodes[data_load_id] = LoadDataNode(data_load_id, spec=data_spec, explicit_deps=explicit_deps)
|
411
|
+
|
412
|
+
# Input views assembled by mapping one root part to each view
|
413
|
+
data_view_id = NodeId.of(input_name, self._job_namespace, _data.DataView)
|
414
|
+
nodes[data_view_id] = DataViewNode(data_view_id, schema_def, data_load_id)
|
415
|
+
outputs.add(data_view_id)
|
416
|
+
|
417
|
+
def _build_file_input(self, input_name, input_selector, nodes, outputs, explicit_deps):
|
418
|
+
|
419
|
+
file_def = _util.get_job_resource(input_selector, self._job_config).file
|
420
|
+
storage_def = _util.get_job_resource(file_def.storageId, self._job_config).storage
|
404
421
|
|
405
|
-
|
406
|
-
|
407
|
-
|
422
|
+
file_spec = _data.DataSpec.create_file_spec(file_def.dataItem, file_def, storage_def)
|
423
|
+
file_load_id = NodeId.of(f"{input_name}:LOAD", self._job_namespace, _data.DataItem)
|
424
|
+
nodes[file_load_id] = LoadDataNode(file_load_id, spec=file_spec, explicit_deps=explicit_deps)
|
408
425
|
|
409
|
-
|
426
|
+
# Input views assembled by mapping one root part to each view
|
427
|
+
file_view_id = NodeId.of(input_name, self._job_namespace, _data.DataView)
|
428
|
+
nodes[file_view_id] = DataViewNode(file_view_id, None, file_load_id)
|
429
|
+
outputs.add(file_view_id)
|
410
430
|
|
411
431
|
def build_job_outputs(
|
412
432
|
self,
|
@@ -418,12 +438,21 @@ class GraphBuilder:
|
|
418
438
|
nodes = {}
|
419
439
|
inputs = set()
|
420
440
|
|
421
|
-
for output_name,
|
441
|
+
for output_name, output_def in required_outputs.items():
|
422
442
|
|
423
|
-
|
443
|
+
# Output data view must already exist in the namespace, it is an input to the save operation
|
444
|
+
data_view_id = NodeId.of(output_name, self._job_namespace, _data.DataView)
|
445
|
+
inputs.add(data_view_id)
|
446
|
+
|
447
|
+
# Backwards compatibility with pre 0.8 versions
|
448
|
+
output_type = meta.ObjectType.DATA \
|
449
|
+
if output_def.objectType == meta.ObjectType.OBJECT_TYPE_NOT_SET \
|
450
|
+
else output_def.objectType
|
424
451
|
|
425
|
-
|
426
|
-
|
452
|
+
output_selector = supplied_outputs.get(output_name)
|
453
|
+
|
454
|
+
if output_selector is None:
|
455
|
+
if output_def.optional:
|
427
456
|
optional_info = "(configuration is required for all optional outputs, in case they are produced)"
|
428
457
|
self._error(_ex.EJobValidation(f"Missing optional output: [{output_name}] {optional_info}"))
|
429
458
|
continue
|
@@ -431,75 +460,150 @@ class GraphBuilder:
|
|
431
460
|
self._error(_ex.EJobValidation(f"Missing required output: [{output_name}]"))
|
432
461
|
continue
|
433
462
|
|
434
|
-
|
435
|
-
|
436
|
-
|
463
|
+
elif output_type == meta.ObjectType.DATA:
|
464
|
+
self._build_data_output(output_name, output_selector, data_view_id, nodes, explicit_deps)
|
465
|
+
|
466
|
+
elif output_type == meta.ObjectType.FILE:
|
467
|
+
self._build_file_output(output_name, output_def, output_selector, data_view_id, nodes, explicit_deps)
|
437
468
|
|
438
|
-
|
469
|
+
else:
|
470
|
+
self._error(_ex.EJobValidation(f"Invalid output type [{output_type.name}] for input [{output_name}]"))
|
439
471
|
|
440
|
-
|
472
|
+
return GraphSection(nodes, inputs=inputs)
|
441
473
|
|
442
|
-
|
474
|
+
def _build_data_output(self, output_name, output_selector, data_view_id, nodes, explicit_deps):
|
443
475
|
|
444
|
-
|
445
|
-
|
476
|
+
# Map one data item from each view, since outputs are single part/delta
|
477
|
+
data_item_id = NodeId(f"{output_name}:ITEM", self._job_namespace, _data.DataItem)
|
478
|
+
nodes[data_item_id] = DataItemNode(data_item_id, data_view_id)
|
446
479
|
|
447
|
-
|
448
|
-
schema_def = _util.get_job_resource(data_def.schemaId, self._job_config).schema
|
449
|
-
else:
|
450
|
-
schema_def = data_def.schema
|
480
|
+
data_obj = _util.get_job_resource(output_selector, self._job_config, optional=True)
|
451
481
|
|
452
|
-
|
453
|
-
data_item = data_def.parts[root_part_opaque_key].snap.deltas[0].dataItem
|
454
|
-
data_spec = _data.DataSpec(data_item, data_def, storage_def, schema_def)
|
482
|
+
if data_obj is not None:
|
455
483
|
|
456
|
-
|
484
|
+
# If data def for the output has been built in advance, use a static data spec
|
457
485
|
|
458
|
-
|
459
|
-
|
486
|
+
data_def = data_obj.data
|
487
|
+
storage_def = _util.get_job_resource(data_def.storageId, self._job_config).storage
|
460
488
|
|
489
|
+
if data_def.schemaId:
|
490
|
+
schema_def = _util.get_job_resource(data_def.schemaId, self._job_config).schema
|
461
491
|
else:
|
492
|
+
schema_def = data_def.schema
|
462
493
|
|
463
|
-
|
464
|
-
|
494
|
+
root_part_opaque_key = 'part-root' # TODO: Central part names / constants
|
495
|
+
data_item = data_def.parts[root_part_opaque_key].snap.deltas[0].dataItem
|
496
|
+
data_spec = _data.DataSpec.create_data_spec(data_item, data_def, storage_def, schema_def)
|
465
497
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
storage_id = self._job_config.resultMapping[storage_key]
|
498
|
+
# Create a physical save operation for the data item
|
499
|
+
data_save_id = NodeId.of(f"{output_name}:SAVE", self._job_namespace, _data.DataSpec)
|
500
|
+
nodes[data_save_id] = SaveDataNode(data_save_id, data_item_id, spec=data_spec)
|
470
501
|
|
471
|
-
|
472
|
-
|
473
|
-
data_id, storage_id,
|
474
|
-
prior_data_spec=None,
|
475
|
-
explicit_deps=explicit_deps)
|
502
|
+
output_key = output_name
|
503
|
+
storage_key = output_name + ":STORAGE"
|
476
504
|
|
477
|
-
|
478
|
-
output_storage_key = _util.object_key(storage_id)
|
505
|
+
else:
|
479
506
|
|
480
|
-
#
|
481
|
-
|
482
|
-
|
507
|
+
# If output data def for an output was not supplied in the job, create a dynamic data spec
|
508
|
+
# Dynamic data def will always use an embedded schema (this is no ID for an external schema)
|
509
|
+
|
510
|
+
mapped_output_key = output_name
|
511
|
+
mapped_storage_key = output_name + ":STORAGE"
|
512
|
+
|
513
|
+
data_id = self._job_config.resultMapping[mapped_output_key]
|
514
|
+
storage_id = self._job_config.resultMapping[mapped_storage_key]
|
515
|
+
|
516
|
+
data_spec_id = NodeId.of(f"{output_name}:SPEC", self._job_namespace, _data.DataSpec)
|
517
|
+
nodes[data_spec_id] = DynamicDataSpecNode(
|
518
|
+
data_spec_id, data_view_id,
|
519
|
+
data_id, storage_id,
|
520
|
+
prior_data_spec=None,
|
521
|
+
explicit_deps=explicit_deps)
|
483
522
|
|
484
523
|
# Create a physical save operation for the data item
|
485
|
-
data_save_id = NodeId.of(f"{output_name}:SAVE", self._job_namespace,
|
486
|
-
|
524
|
+
data_save_id = NodeId.of(f"{output_name}:SAVE", self._job_namespace, _data.DataSpec)
|
525
|
+
nodes[data_save_id] = SaveDataNode(data_save_id, data_item_id, spec_id=data_spec_id)
|
487
526
|
|
488
|
-
|
489
|
-
|
490
|
-
data_result_id, output_name,
|
491
|
-
data_item_id, data_spec_id, data_save_id,
|
492
|
-
output_data_key, output_storage_key)
|
527
|
+
output_key = _util.object_key(data_id)
|
528
|
+
storage_key = _util.object_key(storage_id)
|
493
529
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
530
|
+
data_result_id = NodeId.of(f"{output_name}:RESULT", self._job_namespace, ObjectBundle)
|
531
|
+
nodes[data_result_id] = DataResultNode(
|
532
|
+
data_result_id, output_name, data_save_id,
|
533
|
+
data_key=output_key,
|
534
|
+
storage_key=storage_key)
|
498
535
|
|
499
|
-
|
500
|
-
inputs.add(data_view_id)
|
536
|
+
def _build_file_output(self, output_name, output_def, output_selector, file_view_id, nodes, explicit_deps):
|
501
537
|
|
502
|
-
|
538
|
+
mapped_output_key = output_name
|
539
|
+
mapped_storage_key = output_name + ":STORAGE"
|
540
|
+
|
541
|
+
file_obj = _util.get_job_resource(output_selector, self._job_config, optional=True)
|
542
|
+
|
543
|
+
if file_obj is not None:
|
544
|
+
|
545
|
+
# Definitions already exist (generated by dev mode translator)
|
546
|
+
|
547
|
+
file_def = _util.get_job_resource(output_selector, self._job_config).file
|
548
|
+
storage_def = _util.get_job_resource(file_def.storageId, self._job_config).storage
|
549
|
+
|
550
|
+
resolved_output_key = mapped_output_key
|
551
|
+
resolved_storage_key = mapped_storage_key
|
552
|
+
|
553
|
+
else:
|
554
|
+
|
555
|
+
# Create new definitions (default behavior for jobs sent from the platform)
|
556
|
+
|
557
|
+
output_id = self._job_config.resultMapping[mapped_output_key]
|
558
|
+
storage_id = self._job_config.resultMapping[mapped_storage_key]
|
559
|
+
|
560
|
+
file_type = output_def.fileType
|
561
|
+
timestamp = _dt.datetime.fromisoformat(output_id.objectTimestamp.isoDatetime)
|
562
|
+
data_item = f"file/{output_id.objectId}/version-{output_id.objectVersion}"
|
563
|
+
storage_key = self._sys_config.storage.defaultBucket
|
564
|
+
storage_path = f"file/FILE-{output_id.objectId}/version-{output_id.objectVersion}/{output_name}.{file_type.extension}"
|
565
|
+
|
566
|
+
file_def = self.build_file_def(output_name, file_type, storage_id, data_item)
|
567
|
+
storage_def = self.build_storage_def(data_item, storage_key, storage_path, file_type.mimeType, timestamp)
|
568
|
+
|
569
|
+
resolved_output_key = _util.object_key(output_id)
|
570
|
+
resolved_storage_key = _util.object_key(storage_id)
|
571
|
+
|
572
|
+
# Required object defs are available, now build the graph nodes
|
573
|
+
|
574
|
+
file_item_id = NodeId(f"{output_name}:ITEM", self._job_namespace, _data.DataItem)
|
575
|
+
nodes[file_item_id] = DataItemNode(file_item_id, file_view_id, explicit_deps=explicit_deps)
|
576
|
+
|
577
|
+
file_spec = _data.DataSpec.create_file_spec(file_def.dataItem, file_def, storage_def)
|
578
|
+
file_save_id = NodeId.of(f"{output_name}:SAVE", self._job_namespace, _data.DataSpec)
|
579
|
+
nodes[file_save_id] = SaveDataNode(file_save_id, file_item_id, spec=file_spec)
|
580
|
+
|
581
|
+
data_result_id = NodeId.of(f"{output_name}:RESULT", self._job_namespace, ObjectBundle)
|
582
|
+
nodes[data_result_id] = DataResultNode(
|
583
|
+
data_result_id, output_name, file_save_id,
|
584
|
+
file_key=resolved_output_key,
|
585
|
+
storage_key=resolved_storage_key)
|
586
|
+
|
587
|
+
@classmethod
|
588
|
+
def build_output_file_and_storage(cls, output_key, file_type: meta.FileType, sys_config: cfg.RuntimeConfig, job_config: cfg.JobConfig):
|
589
|
+
|
590
|
+
# TODO: Review and de-dupe building of output metadata
|
591
|
+
# Responsibility for assigning outputs could perhaps move from orchestrator to runtime
|
592
|
+
|
593
|
+
output_storage_key = f"{output_key}:STORAGE"
|
594
|
+
|
595
|
+
output_id = job_config.resultMapping[output_key]
|
596
|
+
output_storage_id = job_config.resultMapping[output_storage_key]
|
597
|
+
|
598
|
+
timestamp = _dt.datetime.fromisoformat(output_id.objectTimestamp.isoDatetime)
|
599
|
+
data_item = f"file/{output_id.objectId}/version-{output_id.objectVersion}"
|
600
|
+
storage_key = sys_config.storage.defaultBucket
|
601
|
+
storage_path = f"file/FILE-{output_id.objectId}/version-{output_id.objectVersion}/{output_key}.{file_type.extension}"
|
602
|
+
|
603
|
+
file_def = cls.build_file_def(output_key, file_type, output_storage_id, data_item)
|
604
|
+
storage_def = cls.build_storage_def(data_item, storage_key, storage_path, file_type.mimeType, timestamp)
|
605
|
+
|
606
|
+
return file_def, storage_def
|
503
607
|
|
504
608
|
@classmethod
|
505
609
|
def build_runtime_outputs(cls, output_names: tp.List[str], job_namespace: NodeNamespace):
|
@@ -519,9 +623,10 @@ class GraphBuilder:
|
|
519
623
|
data_view_id = NodeId.of(output_name, job_namespace, _data.DataView)
|
520
624
|
data_spec_id = NodeId.of(f"{output_name}:SPEC", job_namespace, _data.DataSpec)
|
521
625
|
|
522
|
-
|
626
|
+
mapped_output_key = output_name
|
627
|
+
mapped_storage_key = output_name + ":STORAGE"
|
628
|
+
|
523
629
|
data_id = _util.new_object_id(meta.ObjectType.DATA)
|
524
|
-
storage_key = output_name + ":STORAGE"
|
525
630
|
storage_id = _util.new_object_id(meta.ObjectType.STORAGE)
|
526
631
|
|
527
632
|
data_spec_node = DynamicDataSpecNode(
|
@@ -529,22 +634,21 @@ class GraphBuilder:
|
|
529
634
|
data_id, storage_id,
|
530
635
|
prior_data_spec=None)
|
531
636
|
|
532
|
-
|
533
|
-
|
637
|
+
output_key = _util.object_key(data_id)
|
638
|
+
storage_key = _util.object_key(storage_id)
|
534
639
|
|
535
640
|
# Map one data item from each view, since outputs are single part/delta
|
536
641
|
data_item_id = NodeId(f"{output_name}:ITEM", job_namespace, _data.DataItem)
|
537
642
|
data_item_node = DataItemNode(data_item_id, data_view_id)
|
538
643
|
|
539
644
|
# Create a physical save operation for the data item
|
540
|
-
data_save_id = NodeId.of(f"{output_name}:SAVE", job_namespace,
|
541
|
-
data_save_node = SaveDataNode(data_save_id,
|
645
|
+
data_save_id = NodeId.of(f"{output_name}:SAVE", job_namespace, _data.DataSpec)
|
646
|
+
data_save_node = SaveDataNode(data_save_id, data_item_id, spec_id=data_spec_id)
|
542
647
|
|
543
648
|
data_result_id = NodeId.of(f"{output_name}:RESULT", job_namespace, ObjectBundle)
|
544
649
|
data_result_node = DataResultNode(
|
545
|
-
data_result_id, output_name,
|
546
|
-
|
547
|
-
output_data_key, output_storage_key)
|
650
|
+
data_result_id, output_name, data_save_id,
|
651
|
+
output_key, storage_key)
|
548
652
|
|
549
653
|
nodes[data_spec_id] = data_spec_node
|
550
654
|
nodes[data_item_id] = data_item_node
|
@@ -563,6 +667,45 @@ class GraphBuilder:
|
|
563
667
|
|
564
668
|
return GraphSection(nodes, inputs=inputs, outputs={runtime_outputs_id})
|
565
669
|
|
670
|
+
@classmethod
|
671
|
+
def build_file_def(cls, file_name, file_type, storage_id, data_item):
|
672
|
+
|
673
|
+
file_def = meta.FileDefinition()
|
674
|
+
file_def.name = f"{file_name}.{file_type.extension}"
|
675
|
+
file_def.extension = file_type.extension
|
676
|
+
file_def.mimeType = file_type.mimeType
|
677
|
+
file_def.storageId = _util.selector_for_latest(storage_id)
|
678
|
+
file_def.dataItem = data_item
|
679
|
+
file_def.size = 0
|
680
|
+
|
681
|
+
return file_def
|
682
|
+
|
683
|
+
@classmethod
|
684
|
+
def build_storage_def(
|
685
|
+
cls, data_item: str,
|
686
|
+
storage_key, storage_path, storage_format,
|
687
|
+
timestamp: _dt.datetime):
|
688
|
+
|
689
|
+
first_incarnation = 0
|
690
|
+
|
691
|
+
storage_copy = meta.StorageCopy(
|
692
|
+
storage_key, storage_path, storage_format,
|
693
|
+
copyStatus=meta.CopyStatus.COPY_AVAILABLE,
|
694
|
+
copyTimestamp=meta.DatetimeValue(timestamp.isoformat()))
|
695
|
+
|
696
|
+
storage_incarnation = meta.StorageIncarnation(
|
697
|
+
[storage_copy],
|
698
|
+
incarnationIndex=first_incarnation,
|
699
|
+
incarnationTimestamp=meta.DatetimeValue(timestamp.isoformat()),
|
700
|
+
incarnationStatus=meta.IncarnationStatus.INCARNATION_AVAILABLE)
|
701
|
+
|
702
|
+
storage_item = meta.StorageItem([storage_incarnation])
|
703
|
+
|
704
|
+
storage_def = meta.StorageDefinition()
|
705
|
+
storage_def.dataItems[data_item] = storage_item
|
706
|
+
|
707
|
+
return storage_def
|
708
|
+
|
566
709
|
def build_job_results(
|
567
710
|
self,
|
568
711
|
objects: tp.Dict[str, NodeId[meta.ObjectDefinition]] = None,
|
@@ -570,15 +713,16 @@ class GraphBuilder:
|
|
570
713
|
explicit_deps: tp.Optional[tp.List[NodeId]] = None) \
|
571
714
|
-> GraphSection:
|
572
715
|
|
573
|
-
|
716
|
+
result_id = self._job_config.resultMapping.get("trac_job_result")
|
717
|
+
result_node_id = NodeId.of("trac_job_result", self._job_namespace, cfg.JobResult)
|
574
718
|
|
575
719
|
if objects is not None:
|
576
720
|
|
577
721
|
results_inputs = set(objects.values())
|
578
722
|
|
579
723
|
build_result_node = BuildJobResultNode(
|
580
|
-
|
581
|
-
outputs
|
724
|
+
result_node_id, result_id, self._job_config.jobId,
|
725
|
+
outputs=JobOutputs(objects=objects),
|
582
726
|
explicit_deps=explicit_deps)
|
583
727
|
|
584
728
|
elif bundles is not None:
|
@@ -586,23 +730,16 @@ class GraphBuilder:
|
|
586
730
|
results_inputs = set(bundles)
|
587
731
|
|
588
732
|
build_result_node = BuildJobResultNode(
|
589
|
-
|
590
|
-
outputs
|
733
|
+
result_node_id, result_id, self._job_config.jobId,
|
734
|
+
outputs=JobOutputs(bundles=bundles),
|
591
735
|
explicit_deps=explicit_deps)
|
592
736
|
|
593
737
|
else:
|
594
738
|
raise _ex.EUnexpected()
|
595
739
|
|
596
|
-
|
597
|
-
save_result_id = NodeId("trac_save_result", self._job_namespace)
|
598
|
-
save_result_node = SaveJobResultNode(save_result_id, build_result_id, self._result_spec)
|
599
|
-
result_nodes = {build_result_id: build_result_node, save_result_id: save_result_node}
|
600
|
-
job_result_id = save_result_id
|
601
|
-
else:
|
602
|
-
result_nodes = {build_result_id: build_result_node}
|
603
|
-
job_result_id = build_result_id
|
740
|
+
result_nodes = {result_node_id: build_result_node}
|
604
741
|
|
605
|
-
return GraphSection(result_nodes, inputs=results_inputs, must_run=[
|
742
|
+
return GraphSection(result_nodes, inputs=results_inputs, must_run=[result_node_id])
|
606
743
|
|
607
744
|
def build_model_or_flow_with_context(
|
608
745
|
self, namespace: NodeNamespace, model_or_flow_name: str,
|
tracdap/rt/_exec/runtime.py
CHANGED
@@ -33,11 +33,12 @@ import tracdap.rt._exec.actors as _actors
|
|
33
33
|
import tracdap.rt._exec.engine as _engine
|
34
34
|
import tracdap.rt._exec.dev_mode as _dev_mode
|
35
35
|
import tracdap.rt._impl.config_parser as _cparse # noqa
|
36
|
-
import tracdap.rt._impl.
|
36
|
+
import tracdap.rt._impl.guard_rails as _guard # noqa
|
37
|
+
import tracdap.rt._impl.logging as _log # noqa
|
37
38
|
import tracdap.rt._impl.models as _models # noqa
|
38
39
|
import tracdap.rt._impl.storage as _storage # noqa
|
39
40
|
import tracdap.rt._impl.static_api as _static_api # noqa
|
40
|
-
import tracdap.rt._impl.
|
41
|
+
import tracdap.rt._impl.util as _util # noqa
|
41
42
|
import tracdap.rt._version as _version
|
42
43
|
|
43
44
|
|
@@ -83,8 +84,8 @@ class TracRuntime:
|
|
83
84
|
if isinstance(scratch_dir, str):
|
84
85
|
scratch_dir = pathlib.Path(scratch_dir)
|
85
86
|
|
86
|
-
|
87
|
-
self._log =
|
87
|
+
_log.configure_logging()
|
88
|
+
self._log = _log.logger_for_object(self)
|
88
89
|
self._log.info(f"TRAC D.A.P. Python Runtime {trac_version}")
|
89
90
|
|
90
91
|
self._sys_config = sys_config if isinstance(sys_config, _cfg.RuntimeConfig) else None
|
@@ -96,6 +97,7 @@ class TracRuntime:
|
|
96
97
|
self._scratch_dir_persist = scratch_dir_persist
|
97
98
|
self._plugin_packages = plugin_packages or []
|
98
99
|
self._dev_mode = dev_mode
|
100
|
+
self._dev_mode_translator = None
|
99
101
|
|
100
102
|
# Runtime control
|
101
103
|
self._runtime_lock = threading.Lock()
|
@@ -141,10 +143,6 @@ class TracRuntime:
|
|
141
143
|
|
142
144
|
self._log.info(f"Beginning pre-start sequence...")
|
143
145
|
|
144
|
-
# Scratch dir is needed during pre-start (at least dev mode translation uses the model loader)
|
145
|
-
|
146
|
-
self._prepare_scratch_dir()
|
147
|
-
|
148
146
|
# Plugin manager, static API and guard rails are singletons
|
149
147
|
# Calling these methods multiple times is safe (e.g. for embedded or testing scenarios)
|
150
148
|
# However, plugins are never un-registered for the lifetime of the processes
|
@@ -198,9 +196,17 @@ class TracRuntime:
|
|
198
196
|
|
199
197
|
self._log.info("Starting the engine...")
|
200
198
|
|
199
|
+
self._prepare_scratch_dir()
|
200
|
+
|
201
201
|
self._models = _models.ModelLoader(self._sys_config, self._scratch_dir)
|
202
202
|
self._storage = _storage.StorageManager(self._sys_config)
|
203
203
|
|
204
|
+
if self._dev_mode:
|
205
|
+
|
206
|
+
self._dev_mode_translator = _dev_mode.DevModeTranslator(
|
207
|
+
self._sys_config, self._config_mgr, self._scratch_dir,
|
208
|
+
model_loader=self._models, storage_manager=self._storage)
|
209
|
+
|
204
210
|
# Enable protection after the initial setup of the runtime is complete
|
205
211
|
# Storage plugins in particular are likely to tigger protected imports
|
206
212
|
# Once the runtime is up, no more plugins should be loaded
|
@@ -323,6 +329,9 @@ class TracRuntime:
|
|
323
329
|
self, job_config: tp.Union[str, pathlib.Path, _cfg.JobConfig],
|
324
330
|
model_class: tp.Optional[_api.TracModel.__class__] = None):
|
325
331
|
|
332
|
+
if not self._engine or self._shutdown_requested:
|
333
|
+
raise _ex.ETracInternal("Engine is not started or shutdown has been requested")
|
334
|
+
|
326
335
|
if isinstance(job_config, _cfg.JobConfig):
|
327
336
|
self._log.info("Using embedded job config")
|
328
337
|
|
@@ -334,13 +343,15 @@ class TracRuntime:
|
|
334
343
|
config_file_name="job")
|
335
344
|
|
336
345
|
if self._dev_mode:
|
337
|
-
|
338
|
-
job_config = translator.translate_job_config(job_config, model_class)
|
346
|
+
job_config = self._dev_mode_translator.translate_job_config(job_config, model_class)
|
339
347
|
|
340
348
|
return job_config
|
341
349
|
|
342
350
|
def submit_job(self, job_config: _cfg.JobConfig):
|
343
351
|
|
352
|
+
if not self._engine or self._shutdown_requested:
|
353
|
+
raise _ex.ETracInternal("Engine is not started or shutdown has been requested")
|
354
|
+
|
344
355
|
job_key = _util.object_key(job_config.jobId)
|
345
356
|
self._jobs[job_key] = _RuntimeJobInfo()
|
346
357
|
|
@@ -351,6 +362,9 @@ class TracRuntime:
|
|
351
362
|
|
352
363
|
def wait_for_job(self, job_id: _api.TagHeader):
|
353
364
|
|
365
|
+
if not self._engine or self._shutdown_requested:
|
366
|
+
raise _ex.ETracInternal("Engine is not started or shutdown has been requested")
|
367
|
+
|
354
368
|
job_key = _util.object_key(job_id)
|
355
369
|
|
356
370
|
if job_key not in self._jobs:
|
tracdap/rt/_exec/server.py
CHANGED
@@ -21,6 +21,7 @@ import tracdap.rt.config as config
|
|
21
21
|
import tracdap.rt.exceptions as ex
|
22
22
|
import tracdap.rt._exec.actors as actors
|
23
23
|
import tracdap.rt._impl.grpc.codec as codec # noqa
|
24
|
+
import tracdap.rt._impl.logging as logging # noqa
|
24
25
|
import tracdap.rt._impl.util as util # noqa
|
25
26
|
|
26
27
|
# Check whether gRPC is installed before trying to load any of the generated modules
|
@@ -44,7 +45,7 @@ class RuntimeApiServer(runtime_grpc.TracRuntimeApiServicer):
|
|
44
45
|
|
45
46
|
def __init__(self, system: actors.ActorSystem, port: int):
|
46
47
|
|
47
|
-
self.__log =
|
48
|
+
self.__log = logging.logger_for_object(self)
|
48
49
|
|
49
50
|
self.__system = system
|
50
51
|
self.__engine_id = system.main_id()
|
@@ -158,7 +159,7 @@ class ApiAgent(actors.ThreadsafeActor):
|
|
158
159
|
|
159
160
|
def __init__(self):
|
160
161
|
super().__init__()
|
161
|
-
self._log =
|
162
|
+
self._log = logging.logger_for_object(self)
|
162
163
|
self._event_loop = asyncio.get_event_loop()
|
163
164
|
self.__start_signal = asyncio.Event()
|
164
165
|
|
@@ -258,7 +259,7 @@ class ApiRequest(actors.ThreadsafeActor, tp.Generic[_T_REQUEST, _T_RESPONSE]):
|
|
258
259
|
self.threadsafe().stop()
|
259
260
|
|
260
261
|
|
261
|
-
ApiRequest._log =
|
262
|
+
ApiRequest._log = logging.logger_for_class(ApiRequest)
|
262
263
|
|
263
264
|
|
264
265
|
class ListJobsRequest(ApiRequest[runtime_pb2.RuntimeListJobsRequest, runtime_pb2.RuntimeListJobsResponse]):
|
@@ -32,6 +32,7 @@ import tracdap.rt.config as _config
|
|
32
32
|
import tracdap.rt.exceptions as _ex
|
33
33
|
import tracdap.rt.ext.plugins as _plugins
|
34
34
|
import tracdap.rt.ext.config as _config_ext
|
35
|
+
import tracdap.rt._impl.logging as _logging
|
35
36
|
import tracdap.rt._impl.util as _util
|
36
37
|
|
37
38
|
import yaml
|
@@ -103,7 +104,7 @@ class ConfigManager:
|
|
103
104
|
return raw_url
|
104
105
|
|
105
106
|
def __init__(self, root_dir_url: _urlp.ParseResult, root_file_url: tp.Optional[_urlp.ParseResult]):
|
106
|
-
self._log =
|
107
|
+
self._log = _logging.logger_for_object(self)
|
107
108
|
self._root_dir_url = root_dir_url
|
108
109
|
self._root_file_url = root_file_url
|
109
110
|
|
@@ -294,7 +295,7 @@ class ConfigParser(tp.Generic[_T]):
|
|
294
295
|
}
|
295
296
|
|
296
297
|
def __init__(self, config_class: _T.__class__, dev_mode_locations: tp.List[str] = None):
|
297
|
-
self._log =
|
298
|
+
self._log = _logging.logger_for_object(self)
|
298
299
|
self._config_class = config_class
|
299
300
|
self._dev_mode_locations = dev_mode_locations or []
|
300
301
|
self._errors = []
|