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.
Files changed (52) hide show
  1. tracdap/rt/_exec/actors.py +5 -4
  2. tracdap/rt/_exec/context.py +166 -74
  3. tracdap/rt/_exec/dev_mode.py +147 -71
  4. tracdap/rt/_exec/engine.py +224 -99
  5. tracdap/rt/_exec/functions.py +122 -80
  6. tracdap/rt/_exec/graph.py +23 -35
  7. tracdap/rt/_exec/graph_builder.py +250 -113
  8. tracdap/rt/_exec/runtime.py +24 -10
  9. tracdap/rt/_exec/server.py +4 -3
  10. tracdap/rt/_impl/config_parser.py +3 -2
  11. tracdap/rt/_impl/data.py +89 -16
  12. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +3 -1
  13. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
  14. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +64 -62
  15. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
  16. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +27 -25
  17. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
  18. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +3 -3
  19. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +2 -0
  20. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +4 -4
  21. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +4 -2
  22. tracdap/rt/_impl/logging.py +195 -0
  23. tracdap/rt/_impl/models.py +11 -8
  24. tracdap/rt/_impl/repos.py +5 -3
  25. tracdap/rt/_impl/schemas.py +2 -2
  26. tracdap/rt/_impl/shim.py +3 -2
  27. tracdap/rt/_impl/static_api.py +53 -33
  28. tracdap/rt/_impl/storage.py +4 -3
  29. tracdap/rt/_impl/util.py +1 -111
  30. tracdap/rt/_impl/validation.py +57 -30
  31. tracdap/rt/_version.py +1 -1
  32. tracdap/rt/api/__init__.py +6 -3
  33. tracdap/rt/api/file_types.py +29 -0
  34. tracdap/rt/api/hook.py +15 -7
  35. tracdap/rt/api/model_api.py +16 -0
  36. tracdap/rt/api/static_api.py +211 -125
  37. tracdap/rt/config/__init__.py +6 -6
  38. tracdap/rt/config/common.py +11 -1
  39. tracdap/rt/config/platform.py +4 -6
  40. tracdap/rt/ext/plugins.py +2 -2
  41. tracdap/rt/launch/launch.py +9 -11
  42. tracdap/rt/metadata/__init__.py +11 -9
  43. tracdap/rt/metadata/file.py +8 -0
  44. tracdap/rt/metadata/job.py +16 -0
  45. tracdap/rt/metadata/model.py +12 -2
  46. tracdap/rt/metadata/object.py +2 -0
  47. tracdap/rt/metadata/object_id.py +2 -0
  48. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/METADATA +15 -15
  49. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/RECORD +52 -50
  50. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/WHEEL +1 -1
  51. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/LICENSE +0 -0
  52. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0b2.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,7 @@ import tracdap.rt.config as _cfg
23
23
  import tracdap.rt.metadata as _meta
24
24
  import tracdap.rt.exceptions as _ex
25
25
  import tracdap.rt._impl.config_parser as _cfg_p # noqa
26
+ import tracdap.rt._impl.logging as _logging # noqa
26
27
  import tracdap.rt._impl.models as _models # noqa
27
28
  import tracdap.rt._impl.storage as _storage # noqa
28
29
  import tracdap.rt._impl.type_system as _types # noqa
@@ -50,7 +51,7 @@ DEV_MODE_SYS_CONFIG = []
50
51
 
51
52
  class DevModeTranslator:
52
53
 
53
- _log: tp.Optional[_util.logging.Logger] = None
54
+ _log: tp.Optional[_logging.Logger] = None
54
55
 
55
56
  @classmethod
56
57
  def translate_sys_config(cls, sys_config: _cfg.RuntimeConfig, config_mgr: _cfg_p.ConfigManager):
@@ -137,11 +138,14 @@ class DevModeTranslator:
137
138
  raise _ex.EConfigParse(msg)
138
139
 
139
140
 
140
- def __init__(self, sys_config: _cfg.RuntimeConfig, config_mgr: _cfg_p.ConfigManager, scratch_dir: pathlib.Path):
141
+ def __init__(
142
+ self, sys_config: _cfg.RuntimeConfig, config_mgr: _cfg_p.ConfigManager, scratch_dir: pathlib.Path = None,
143
+ model_loader: _models.ModelLoader = None, storage_manager: _storage.StorageManager = None):
144
+
141
145
  self._sys_config = sys_config
142
146
  self._config_mgr = config_mgr
143
- self._scratch_dir = scratch_dir
144
- self._model_loader: tp.Optional[_models.ModelLoader] = None
147
+ self._model_loader = model_loader or _models.ModelLoader(self._sys_config, scratch_dir)
148
+ self._storage_manager = storage_manager or _storage.StorageManager(self._sys_config)
145
149
 
146
150
  def translate_job_config(
147
151
  self, job_config: _cfg.JobConfig,
@@ -150,8 +154,6 @@ class DevModeTranslator:
150
154
 
151
155
  try:
152
156
  self._log.info(f"Applying dev mode config translation to job config")
153
-
154
- self._model_loader = _models.ModelLoader(self._sys_config, self._scratch_dir)
155
157
  self._model_loader.create_scope("DEV_MODE_TRANSLATION")
156
158
 
157
159
  job_config = copy.deepcopy(job_config)
@@ -168,7 +170,6 @@ class DevModeTranslator:
168
170
 
169
171
  finally:
170
172
  self._model_loader.destroy_scope("DEV_MODE_TRANSLATION")
171
- self._model_loader = None
172
173
 
173
174
  def translate_job_def(
174
175
  self, job_config: _cfg.JobConfig, job_def: _meta.JobDefinition,
@@ -694,7 +695,7 @@ class DevModeTranslator:
694
695
 
695
696
  model_selector = job_def.runFlow.models.get(source.node)
696
697
  model_obj = _util.get_job_resource(model_selector, job_config)
697
- model_input = model_obj.model.inputs.get(source.socket)
698
+ model_input = model_obj.model.outputs.get(source.socket)
698
699
  model_outputs.append(model_input)
699
700
 
700
701
  if len(model_outputs) == 0:
@@ -764,7 +765,7 @@ class DevModeTranslator:
764
765
  else:
765
766
  p_spec = param_specs[p_name]
766
767
 
767
- cls._log.info(f"Encoding parameter [{p_name}] as {p_spec.paramType.basicType}")
768
+ cls._log.info(f"Encoding parameter [{p_name}] as {p_spec.paramType.basicType.name}")
768
769
 
769
770
  encoded_value = _types.MetadataCodec.convert_value(p_value, p_spec.paramType)
770
771
  encoded_values[p_name] = encoded_value
@@ -798,38 +799,46 @@ class DevModeTranslator:
798
799
  if not (isinstance(input_value, str) and input_value in job_resources):
799
800
 
800
801
  model_input = required_inputs[input_key]
801
- input_schema = model_input.schema if model_input and not model_input.dynamic else None
802
802
 
803
- input_id = self._process_input_or_output(
804
- input_key, input_value, job_resources,
805
- new_unique_file=False, schema=input_schema)
803
+ if model_input.objectType == _meta.ObjectType.DATA:
804
+ schema = model_input.schema if model_input and not model_input.dynamic else None
805
+ input_id = self._process_data_socket(input_key, input_value, schema, job_resources, new_unique_file=False)
806
+ elif model_input.objectType == _meta.ObjectType.FILE:
807
+ file_type = model_input.fileType
808
+ input_id = self._process_file_socket(input_key, input_value, file_type, job_resources, new_unique_file=False)
809
+ else:
810
+ raise _ex.EUnexpected()
806
811
 
807
812
  job_inputs[input_key] = _util.selector_for(input_id)
808
813
 
809
814
  for output_key, output_value in job_outputs.items():
810
815
  if not (isinstance(output_value, str) and output_value in job_resources):
811
816
 
812
- model_output= required_outputs[output_key]
813
- output_schema = model_output.schema if model_output and not model_output.dynamic else None
817
+ model_output = required_outputs[output_key]
814
818
 
815
- output_id = self._process_input_or_output(
816
- output_key, output_value, job_resources,
817
- new_unique_file=True, schema=output_schema)
819
+ if model_output.objectType == _meta.ObjectType.DATA:
820
+ schema = model_output.schema if model_output and not model_output.dynamic else None
821
+ output_id = self._process_data_socket(output_key, output_value, schema, job_resources, new_unique_file=True)
822
+ elif model_output.objectType == _meta.ObjectType.FILE:
823
+ file_type = model_output.fileType
824
+ output_id = self._process_file_socket(output_key, output_value, file_type, job_resources, new_unique_file=True)
825
+ else:
826
+ raise _ex.EUnexpected()
818
827
 
819
828
  job_outputs[output_key] = _util.selector_for(output_id)
820
829
 
821
830
  return job_config, job_def
822
831
 
823
- def _process_input_or_output(
824
- self, data_key, data_value,
825
- resources: tp.Dict[str, _meta.ObjectDefinition],
826
- new_unique_file=False,
827
- schema: tp.Optional[_meta.SchemaDefinition] = None) \
832
+ def _process_data_socket(
833
+ self, data_key, data_value, schema: tp.Optional[_meta.SchemaDefinition],
834
+ resources: tp.Dict[str, _meta.ObjectDefinition], new_unique_file=False) \
828
835
  -> _meta.TagHeader:
829
836
 
830
837
  data_id = _util.new_object_id(_meta.ObjectType.DATA)
831
838
  storage_id = _util.new_object_id(_meta.ObjectType.STORAGE)
832
839
 
840
+ self._log.info(f"Generating data definition for [{data_key}] with ID = [{_util.object_key(data_id)}]")
841
+
833
842
  if isinstance(data_value, str):
834
843
  storage_path = data_value
835
844
  storage_key = self._sys_config.storage.defaultBucket
@@ -850,43 +859,85 @@ class DevModeTranslator:
850
859
  else:
851
860
  raise _ex.EConfigParse(f"Invalid configuration for input '{data_key}'")
852
861
 
853
- self._log.info(f"Generating data definition for [{data_key}] with ID = [{_util.object_key(data_id)}]")
854
-
855
862
  # For unique outputs, increment the snap number to find a new unique snap
856
863
  # These are not incarnations, bc likely in dev mode model code and inputs are changing
857
864
  # Incarnations are for recreation of a dataset using the exact same code path and inputs
858
865
 
859
866
  if new_unique_file:
867
+ storage_path, snap_version = self._new_unique_file(data_key, storage_key, storage_path, snap_version)
860
868
 
861
- x_storage_mgr = _storage.StorageManager(self._sys_config)
862
- x_storage = x_storage_mgr.get_file_storage(storage_key)
863
- x_orig_path = pathlib.PurePath(storage_path)
864
- x_name = x_orig_path.name
865
-
866
- if x_storage.exists(str(x_orig_path.parent)):
867
- listing = x_storage.ls(str(x_orig_path.parent))
868
- existing_files = list(map(lambda stat: stat.file_name, listing))
869
- else:
870
- existing_files = []
871
-
872
- while x_name in existing_files:
869
+ part_key = _meta.PartKey(opaqueKey="part-root", partType=_meta.PartType.PART_ROOT)
870
+ delta_index = 1
871
+ incarnation_index = 1
873
872
 
874
- snap_version += 1
875
- x_name = f"{x_orig_path.stem}-{snap_version}"
876
- storage_path = str(x_orig_path.parent.joinpath(x_name))
873
+ # This is also defined in functions.DynamicDataSpecFunc, maybe centralize?
874
+ data_item = f"data/table/{data_id.objectId}/{part_key.opaqueKey}/snap-{snap_version}/delta-{delta_index}"
877
875
 
878
- self._log.info(f"Output for [{data_key}] will be snap version {snap_version}")
876
+ data_obj = self._generate_data_definition(
877
+ part_key, snap_version, delta_index, data_item,
878
+ schema, storage_id)
879
879
 
880
- data_obj, storage_obj = self._generate_input_definition(
881
- data_id, storage_id, storage_key, storage_path, storage_format,
882
- snap_index=snap_version, delta_index=1, incarnation_index=1,
883
- schema=schema)
880
+ storage_obj = self._generate_storage_definition(
881
+ storage_id, storage_key, storage_path, storage_format,
882
+ data_item, incarnation_index)
884
883
 
885
884
  resources[_util.object_key(data_id)] = data_obj
886
885
  resources[_util.object_key(storage_id)] = storage_obj
887
886
 
888
887
  return data_id
889
888
 
889
+ def _process_file_socket(
890
+ self, file_key, file_value, file_type: _meta.FileType,
891
+ resources: tp.Dict[str, _meta.ObjectDefinition], new_unique_file=False) \
892
+ -> _meta.TagHeader:
893
+
894
+ file_id = _util.new_object_id(_meta.ObjectType.FILE)
895
+ storage_id = _util.new_object_id(_meta.ObjectType.STORAGE)
896
+
897
+ self._log.info(f"Generating file definition for [{file_key}] with ID = [{_util.object_key(file_id)}]")
898
+
899
+ if isinstance(file_value, str):
900
+
901
+ storage_key = self._sys_config.storage.defaultBucket
902
+ storage_path = file_value
903
+
904
+ elif isinstance(file_value, dict):
905
+
906
+ storage_key = file_value.get("storageKey") or self._sys_config.storage.defaultBucket
907
+ storage_path = file_value.get("path")
908
+
909
+ if not storage_path:
910
+ raise _ex.EConfigParse(f"Invalid configuration for input [{file_key}] (missing required value 'path'")
911
+
912
+ else:
913
+ raise _ex.EConfigParse(f"Invalid configuration for input '{file_key}'")
914
+
915
+ storage_format = "application/x-binary"
916
+ file_version = 1
917
+
918
+ if new_unique_file:
919
+ storage_path, file_version = self._new_unique_file(file_key, storage_key, storage_path, file_version)
920
+ file_size = 0
921
+ else:
922
+ storage = self._storage_manager.get_file_storage(storage_key)
923
+ file_size = storage.size(storage_path)
924
+
925
+ data_item = f"file/{file_id.objectId}/version-{file_version}"
926
+ file_name = f"{file_key}.{file_type.extension}"
927
+
928
+ file_obj = self._generate_file_definition(
929
+ file_name, file_type, file_size,
930
+ storage_id, data_item)
931
+
932
+ storage_obj = self._generate_storage_definition(
933
+ storage_id, storage_key, storage_path, storage_format,
934
+ data_item, incarnation_index=1)
935
+
936
+ resources[_util.object_key(file_id)] = file_obj
937
+ resources[_util.object_key(storage_id)] = storage_obj
938
+
939
+ return file_id
940
+
890
941
  @staticmethod
891
942
  def infer_format(storage_path: str, storage_config: _cfg.StorageConfig):
892
943
 
@@ -898,20 +949,33 @@ class DevModeTranslator:
898
949
  else:
899
950
  return storage_config.defaultFormat
900
951
 
901
- @classmethod
902
- def _generate_input_definition(
903
- cls, data_id: _meta.TagHeader, storage_id: _meta.TagHeader,
904
- storage_key: str, storage_path: str, storage_format: str,
905
- snap_index: int, delta_index: int, incarnation_index: int,
906
- schema: tp.Optional[_meta.SchemaDefinition] = None) \
907
- -> (_meta.ObjectDefinition, _meta.ObjectDefinition):
952
+ def _new_unique_file(self, socket_name, storage_key, storage_path, version):
908
953
 
909
- part_key = _meta.PartKey(
910
- opaqueKey="part-root",
911
- partType=_meta.PartType.PART_ROOT)
954
+ x_storage = self._storage_manager.get_file_storage(storage_key)
955
+ x_orig_path = pathlib.PurePath(storage_path)
956
+ x_name = x_orig_path.name
912
957
 
913
- # This is also defined in functions.DynamicDataSpecFunc, maybe centralize?
914
- data_item = f"data/table/{data_id.objectId}/{part_key.opaqueKey}/snap-{snap_index}/delta-{delta_index}"
958
+ if x_storage.exists(str(x_orig_path.parent)):
959
+ listing = x_storage.ls(str(x_orig_path.parent))
960
+ existing_files = list(map(lambda stat: stat.file_name, listing))
961
+ else:
962
+ existing_files = []
963
+
964
+ while x_name in existing_files:
965
+
966
+ version += 1
967
+ x_name = f"{x_orig_path.stem}-{version}{x_orig_path.suffix}"
968
+ storage_path = str(x_orig_path.parent.joinpath(x_name))
969
+
970
+ self._log.info(f"Output for [{socket_name}] will be version {version}")
971
+
972
+ return storage_path, version
973
+
974
+ @classmethod
975
+ def _generate_data_definition(
976
+ cls, part_key: _meta.PartKey, snap_index: int, delta_index: int, data_item: str,
977
+ schema: tp.Optional[_meta.SchemaDefinition], storage_id: _meta.TagHeader) \
978
+ -> (_meta.ObjectDefinition, _meta.ObjectDefinition):
915
979
 
916
980
  delta = _meta.DataDefinition.Delta(
917
981
  deltaIndex=delta_index,
@@ -925,17 +989,31 @@ class DevModeTranslator:
925
989
  partKey=part_key,
926
990
  snap=snap)
927
991
 
928
- data_def = _meta.DataDefinition(parts={})
992
+ data_def = _meta.DataDefinition()
929
993
  data_def.parts[part_key.opaqueKey] = part
994
+ data_def.schema = schema
995
+ data_def.storageId = _util.selector_for(storage_id)
930
996
 
931
- if schema is not None:
932
- data_def.schema = schema
933
- else:
934
- data_def.schema = None
997
+ return _meta.ObjectDefinition(objectType=_meta.ObjectType.DATA, data=data_def)
935
998
 
936
- data_def.storageId = _meta.TagSelector(
937
- _meta.ObjectType.STORAGE, storage_id.objectId,
938
- objectVersion=storage_id.objectVersion, latestTag=True)
999
+ @classmethod
1000
+ def _generate_file_definition(
1001
+ cls, file_name: str, file_type: _meta.FileType, file_size: int,
1002
+ storage_id: _meta.TagHeader, data_item: str) \
1003
+ -> _meta.ObjectDefinition:
1004
+
1005
+ file_def = _meta.FileDefinition(
1006
+ name=file_name, extension=file_type.extension, mimeType=file_type.mimeType,
1007
+ storageId=_util.selector_for(storage_id), dataItem=data_item, size=file_size)
1008
+
1009
+ return _meta.ObjectDefinition(objectType=_meta.ObjectType.FILE, file=file_def)
1010
+
1011
+ @classmethod
1012
+ def _generate_storage_definition(
1013
+ cls, storage_id: _meta.TagHeader,
1014
+ storage_key: str, storage_path: str, storage_format: str,
1015
+ data_item: str, incarnation_index: int) \
1016
+ -> _meta.ObjectDefinition:
939
1017
 
940
1018
  storage_copy = _meta.StorageCopy(
941
1019
  storageKey=storage_key,
@@ -952,16 +1030,14 @@ class DevModeTranslator:
952
1030
  storage_item = _meta.StorageItem(
953
1031
  incarnations=[storage_incarnation])
954
1032
 
955
- storage_def = _meta.StorageDefinition(dataItems={})
956
- storage_def.dataItems[delta.dataItem] = storage_item
1033
+ storage_def = _meta.StorageDefinition()
1034
+ storage_def.dataItems[data_item] = storage_item
957
1035
 
958
1036
  if storage_format.lower() == "csv":
959
1037
  storage_def.storageOptions["lenient_csv_parser"] = _types.MetadataCodec.encode_value(True)
960
1038
 
961
- data_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.DATA, data=data_def)
962
- storage_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.STORAGE, storage=storage_def)
1039
+ return _meta.ObjectDefinition(objectType=_meta.ObjectType.STORAGE, storage=storage_def)
963
1040
 
964
- return data_obj, storage_obj
965
1041
 
966
1042
 
967
- DevModeTranslator._log = _util.logger_for_class(DevModeTranslator)
1043
+ DevModeTranslator._log = _logging.logger_for_class(DevModeTranslator)