tracdap-runtime 0.7.1__py3-none-any.whl → 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.
- tracdap/rt/_impl/core/__init__.py +14 -0
- tracdap/rt/_impl/{config_parser.py → core/config_parser.py} +36 -19
- tracdap/rt/_impl/{data.py → core/data.py} +136 -32
- tracdap/rt/_impl/core/logging.py +195 -0
- tracdap/rt/_impl/{models.py → core/models.py} +15 -12
- tracdap/rt/_impl/{repos.py → core/repos.py} +12 -3
- tracdap/rt/_impl/{schemas.py → core/schemas.py} +5 -5
- tracdap/rt/_impl/{shim.py → core/shim.py} +5 -4
- tracdap/rt/_impl/{storage.py → core/storage.py} +21 -10
- tracdap/rt/_impl/core/struct.py +547 -0
- tracdap/rt/_impl/{util.py → core/util.py} +1 -111
- tracdap/rt/_impl/{validation.py → core/validation.py} +99 -31
- tracdap/rt/_impl/exec/__init__.py +14 -0
- tracdap/rt/{_exec → _impl/exec}/actors.py +12 -14
- tracdap/rt/{_exec → _impl/exec}/context.py +228 -82
- tracdap/rt/{_exec → _impl/exec}/dev_mode.py +163 -81
- tracdap/rt/{_exec → _impl/exec}/engine.py +230 -105
- tracdap/rt/{_exec → _impl/exec}/functions.py +191 -100
- tracdap/rt/{_exec → _impl/exec}/graph.py +24 -36
- tracdap/rt/{_exec → _impl/exec}/graph_builder.py +252 -115
- tracdap/rt/_impl/grpc/codec.py +1 -1
- tracdap/rt/{_exec → _impl/grpc}/server.py +7 -6
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +3 -3
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +40 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.pyi +62 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +32 -20
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +48 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +4 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +65 -63
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +28 -26
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +4 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +6 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +9 -7
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +12 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +18 -5
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +42 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.py → storage_pb2.py} +4 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +1 -1
- tracdap/rt/{_exec → _impl}/runtime.py +32 -18
- tracdap/rt/_impl/static_api.py +65 -37
- tracdap/rt/_plugins/format_csv.py +1 -1
- tracdap/rt/_plugins/repo_git.py +56 -11
- tracdap/rt/_plugins/storage_sql.py +1 -1
- tracdap/rt/_version.py +1 -1
- tracdap/rt/api/__init__.py +5 -24
- tracdap/rt/api/constants.py +57 -0
- tracdap/rt/api/experimental.py +32 -0
- tracdap/rt/api/hook.py +26 -7
- tracdap/rt/api/model_api.py +16 -0
- tracdap/rt/api/static_api.py +265 -127
- tracdap/rt/config/__init__.py +11 -11
- tracdap/rt/config/common.py +2 -26
- tracdap/rt/config/dynamic.py +28 -0
- tracdap/rt/config/platform.py +17 -31
- tracdap/rt/config/runtime.py +2 -0
- tracdap/rt/ext/embed.py +2 -2
- tracdap/rt/ext/plugins.py +3 -3
- tracdap/rt/launch/launch.py +12 -14
- tracdap/rt/metadata/__init__.py +28 -18
- tracdap/rt/metadata/config.py +95 -0
- tracdap/rt/metadata/data.py +40 -0
- tracdap/rt/metadata/file.py +10 -0
- tracdap/rt/metadata/job.py +16 -0
- tracdap/rt/metadata/model.py +12 -2
- tracdap/rt/metadata/object.py +9 -1
- tracdap/rt/metadata/object_id.py +6 -0
- tracdap/rt/metadata/resource.py +41 -1
- {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/METADATA +23 -17
- tracdap_runtime-0.8.0.dist-info/RECORD +129 -0
- {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/WHEEL +1 -1
- tracdap/rt/_exec/__init__.py +0 -0
- tracdap_runtime-0.7.1.dist-info/RECORD +0 -121
- /tracdap/rt/_impl/{guard_rails.py → core/guard_rails.py} +0 -0
- /tracdap/rt/_impl/{type_system.py → core/type_system.py} +0 -0
- /tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.pyi → storage_pb2.pyi} +0 -0
- /tracdap/rt/metadata/{stoarge.py → storage.py} +0 -0
- {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info/licenses}/LICENSE +0 -0
- {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/top_level.txt +0 -0
@@ -13,10 +13,12 @@
|
|
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
|
-
import tracdap.rt._impl.data as _data
|
19
|
-
import tracdap.rt._impl.util as _util
|
20
|
+
import tracdap.rt._impl.core.data as _data
|
21
|
+
import tracdap.rt._impl.core.util as _util
|
20
22
|
|
21
23
|
from .graph import *
|
22
24
|
|
@@ -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/_impl/grpc/codec.py
CHANGED
@@ -24,7 +24,7 @@ import tracdap.rt._impl.grpc.tracdap.metadata.object_id_pb2 as object_id_pb2
|
|
24
24
|
import tracdap.rt._impl.grpc.tracdap.metadata.object_pb2 as object_pb2
|
25
25
|
from tracdap.rt._impl.grpc.tracdap.metadata import model_pb2
|
26
26
|
import tracdap.rt._impl.grpc.tracdap.metadata.data_pb2 as data_pb2
|
27
|
-
import tracdap.rt._impl.grpc.tracdap.metadata.
|
27
|
+
import tracdap.rt._impl.grpc.tracdap.metadata.storage_pb2 as storage_pb2
|
28
28
|
|
29
29
|
from google.protobuf import message as _message
|
30
30
|
|
@@ -19,9 +19,10 @@ import typing as tp
|
|
19
19
|
|
20
20
|
import tracdap.rt.config as config
|
21
21
|
import tracdap.rt.exceptions as ex
|
22
|
-
import tracdap.rt.
|
23
|
-
import tracdap.rt._impl.grpc.codec as codec
|
24
|
-
import tracdap.rt._impl.
|
22
|
+
import tracdap.rt._impl.exec.actors as actors
|
23
|
+
import tracdap.rt._impl.grpc.codec as codec
|
24
|
+
import tracdap.rt._impl.core.logging as logging
|
25
|
+
import tracdap.rt._impl.core.util as util
|
25
26
|
|
26
27
|
# Check whether gRPC is installed before trying to load any of the generated modules
|
27
28
|
try:
|
@@ -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]):
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
3
3
|
# source: tracdap/rt/_impl/grpc/tracdap/api/internal/runtime.proto
|
4
|
-
# Protobuf Python Version: 4.25.
|
4
|
+
# Protobuf Python Version: 4.25.5
|
5
5
|
"""Generated protocol buffer code."""
|
6
6
|
from google.protobuf import descriptor as _descriptor
|
7
7
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
@@ -18,14 +18,14 @@ from tracdap.rt._impl.grpc.tracdap.metadata import object_pb2 as tracdap_dot_rt_
|
|
18
18
|
from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
|
19
19
|
|
20
20
|
|
21
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n8tracdap/rt/_impl/grpc/tracdap/api/internal/runtime.proto\x12\x14tracdap.api.internal\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\x1a\x30tracdap/rt/_impl/grpc/tracdap/metadata/job.proto\x1a\x33tracdap/rt/_impl/grpc/tracdap/metadata/object.proto\x1a\x1cgoogle/api/annotations.proto\"6\n\x16RuntimeListJobsRequest\x12\x12\n\x05limit\x18\x01 \x01(\rH\x00\x88\x01\x01\x42\x08\n\x06_limit\"O\n\x17RuntimeListJobsResponse\x12\x34\n\x04jobs\x18\x01 \x03(\x0b\x32&.tracdap.api.internal.RuntimeJobStatus\"f\n\x15RuntimeJobInfoRequest\x12\x34\n\x0bjobSelector\x18\x01 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelectorH\x00\x12\x10\n\x06jobKey\x18\x02 \x01(\tH\x00\x42\x05\n\x03job\"\x9f\x01\n\x10RuntimeJobStatus\x12*\n\x05jobId\x18\x01 \x01(\x0b\x32\x1b.tracdap.metadata.TagHeader\x12\x33\n\nstatusCode\x18\x02 \x01(\x0e\x32\x1f.tracdap.metadata.JobStatusCode\x12\x15\n\rstatusMessage\x18\x03 \x01(\t\x12\x13\n\x0b\x65rrorDetail\x18\x04 \x01(\t\"\xa4\x02\n\x10RuntimeJobResult\x12*\n\x05jobId\x18\x01 \x01(\x0b\x32\x1b.tracdap.metadata.TagHeader\x12\x33\n\nstatusCode\x18\x02 \x01(\x0e\x32\x1f.tracdap.metadata.JobStatusCode\x12\x15\n\rstatusMessage\x18\x03 \x01(\t\x12\x44\n\x07results\x18\x04 \x03(\x0b\x32\x33.tracdap.api.internal.RuntimeJobResult.ResultsEntry\x1aR\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x31\n\x05value\x18\x02 \x01(\x0b\x32\".tracdap.metadata.ObjectDefinition:\x02\x38\x01\x32\x95\x03\n\x0eTracRuntimeApi\x12{\n\x08listJobs\x12,.tracdap.api.internal.RuntimeListJobsRequest\x1a-.tracdap.api.internal.RuntimeListJobsResponse\"\x12\x82\xd3\xe4\x93\x02\x0c\x12\n/list-jobs\x12\x81\x01\n\x0cgetJobStatus\x12+.tracdap.api.internal.RuntimeJobInfoRequest\x1a&.tracdap.api.internal.RuntimeJobStatus\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/job-status/{jobKey}\x12\x81\x01\n\x0cgetJobResult\x12+.tracdap.api.internal.RuntimeJobInfoRequest\x1a&.tracdap.api.internal.RuntimeJobResult\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/job-result/{jobKey}
|
21
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n8tracdap/rt/_impl/grpc/tracdap/api/internal/runtime.proto\x12\x14tracdap.api.internal\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\x1a\x30tracdap/rt/_impl/grpc/tracdap/metadata/job.proto\x1a\x33tracdap/rt/_impl/grpc/tracdap/metadata/object.proto\x1a\x1cgoogle/api/annotations.proto\"6\n\x16RuntimeListJobsRequest\x12\x12\n\x05limit\x18\x01 \x01(\rH\x00\x88\x01\x01\x42\x08\n\x06_limit\"O\n\x17RuntimeListJobsResponse\x12\x34\n\x04jobs\x18\x01 \x03(\x0b\x32&.tracdap.api.internal.RuntimeJobStatus\"f\n\x15RuntimeJobInfoRequest\x12\x34\n\x0bjobSelector\x18\x01 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelectorH\x00\x12\x10\n\x06jobKey\x18\x02 \x01(\tH\x00\x42\x05\n\x03job\"\x9f\x01\n\x10RuntimeJobStatus\x12*\n\x05jobId\x18\x01 \x01(\x0b\x32\x1b.tracdap.metadata.TagHeader\x12\x33\n\nstatusCode\x18\x02 \x01(\x0e\x32\x1f.tracdap.metadata.JobStatusCode\x12\x15\n\rstatusMessage\x18\x03 \x01(\t\x12\x13\n\x0b\x65rrorDetail\x18\x04 \x01(\t\"\xa4\x02\n\x10RuntimeJobResult\x12*\n\x05jobId\x18\x01 \x01(\x0b\x32\x1b.tracdap.metadata.TagHeader\x12\x33\n\nstatusCode\x18\x02 \x01(\x0e\x32\x1f.tracdap.metadata.JobStatusCode\x12\x15\n\rstatusMessage\x18\x03 \x01(\t\x12\x44\n\x07results\x18\x04 \x03(\x0b\x32\x33.tracdap.api.internal.RuntimeJobResult.ResultsEntry\x1aR\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x31\n\x05value\x18\x02 \x01(\x0b\x32\".tracdap.metadata.ObjectDefinition:\x02\x38\x01\x32\x95\x03\n\x0eTracRuntimeApi\x12{\n\x08listJobs\x12,.tracdap.api.internal.RuntimeListJobsRequest\x1a-.tracdap.api.internal.RuntimeListJobsResponse\"\x12\x82\xd3\xe4\x93\x02\x0c\x12\n/list-jobs\x12\x81\x01\n\x0cgetJobStatus\x12+.tracdap.api.internal.RuntimeJobInfoRequest\x1a&.tracdap.api.internal.RuntimeJobStatus\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/job-status/{jobKey}\x12\x81\x01\n\x0cgetJobResult\x12+.tracdap.api.internal.RuntimeJobInfoRequest\x1a&.tracdap.api.internal.RuntimeJobResult\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/job-result/{jobKey}B7\n\x1eorg.finos.tracdap.api.internalB\x13RuntimeServiceProtoP\x01\x62\x06proto3')
|
22
22
|
|
23
23
|
_globals = globals()
|
24
24
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
25
25
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'tracdap.rt._impl.grpc.tracdap.api.internal.runtime_pb2', _globals)
|
26
26
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
27
27
|
_globals['DESCRIPTOR']._options = None
|
28
|
-
_globals['DESCRIPTOR']._serialized_options = b'\n\036org.finos.tracdap.api.
|
28
|
+
_globals['DESCRIPTOR']._serialized_options = b'\n\036org.finos.tracdap.api.internalB\023RuntimeServiceProtoP\001'
|
29
29
|
_globals['_RUNTIMEJOBRESULT_RESULTSENTRY']._options = None
|
30
30
|
_globals['_RUNTIMEJOBRESULT_RESULTSENTRY']._serialized_options = b'8\001'
|
31
31
|
_globals['_TRACRUNTIMEAPI'].methods_by_name['listJobs']._options = None
|
@@ -5,7 +5,7 @@ import warnings
|
|
5
5
|
|
6
6
|
from tracdap.rt._impl.grpc.tracdap.api.internal import runtime_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_api_dot_internal_dot_runtime__pb2
|
7
7
|
|
8
|
-
GRPC_GENERATED_VERSION = '1.
|
8
|
+
GRPC_GENERATED_VERSION = '1.70.0'
|
9
9
|
GRPC_VERSION = grpc.__version__
|
10
10
|
_version_not_supported = False
|
11
11
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
3
3
|
# source: tracdap/rt/_impl/grpc/tracdap/metadata/common.proto
|
4
|
-
# Protobuf Python Version: 4.25.
|
4
|
+
# Protobuf Python Version: 4.25.5
|
5
5
|
"""Generated protocol buffer code."""
|
6
6
|
from google.protobuf import descriptor as _descriptor
|
7
7
|
from google.protobuf import descriptor_pool as _descriptor_pool
|