tracdap-runtime 0.8.0rc2__py3-none-any.whl → 0.9.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 (64) hide show
  1. tracdap/rt/_impl/core/config_parser.py +29 -3
  2. tracdap/rt/_impl/core/data.py +627 -40
  3. tracdap/rt/_impl/core/repos.py +17 -8
  4. tracdap/rt/_impl/core/storage.py +25 -13
  5. tracdap/rt/_impl/core/struct.py +254 -60
  6. tracdap/rt/_impl/core/util.py +125 -11
  7. tracdap/rt/_impl/exec/context.py +35 -8
  8. tracdap/rt/_impl/exec/dev_mode.py +169 -127
  9. tracdap/rt/_impl/exec/engine.py +203 -140
  10. tracdap/rt/_impl/exec/functions.py +228 -263
  11. tracdap/rt/_impl/exec/graph.py +141 -126
  12. tracdap/rt/_impl/exec/graph_builder.py +428 -449
  13. tracdap/rt/_impl/grpc/codec.py +8 -13
  14. tracdap/rt/_impl/grpc/server.py +7 -7
  15. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +25 -18
  16. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.pyi +27 -9
  17. tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
  18. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +1 -1
  19. tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
  20. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +37 -35
  21. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +37 -43
  22. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +1 -1
  23. tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
  24. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +67 -63
  25. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +11 -2
  26. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +1 -1
  27. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +1 -1
  28. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +1 -1
  29. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +1 -1
  30. tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
  31. tracdap/rt/_impl/grpc/tracdap/metadata/storage_pb2.py +11 -9
  32. tracdap/rt/_impl/grpc/tracdap/metadata/storage_pb2.pyi +11 -2
  33. tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
  34. tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
  35. tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +23 -19
  36. tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.pyi +15 -2
  37. tracdap/rt/_impl/runtime.py +3 -9
  38. tracdap/rt/_impl/static_api.py +5 -6
  39. tracdap/rt/_plugins/format_csv.py +2 -2
  40. tracdap/rt/_plugins/repo_git.py +56 -11
  41. tracdap/rt/_plugins/storage_aws.py +165 -150
  42. tracdap/rt/_plugins/storage_azure.py +17 -11
  43. tracdap/rt/_plugins/storage_gcp.py +35 -18
  44. tracdap/rt/_version.py +1 -1
  45. tracdap/rt/api/model_api.py +45 -0
  46. tracdap/rt/config/__init__.py +7 -9
  47. tracdap/rt/config/common.py +3 -14
  48. tracdap/rt/config/job.py +17 -3
  49. tracdap/rt/config/platform.py +9 -32
  50. tracdap/rt/config/result.py +8 -4
  51. tracdap/rt/config/runtime.py +5 -10
  52. tracdap/rt/config/tenant.py +28 -0
  53. tracdap/rt/launch/cli.py +0 -8
  54. tracdap/rt/launch/launch.py +1 -3
  55. tracdap/rt/metadata/__init__.py +35 -35
  56. tracdap/rt/metadata/data.py +19 -31
  57. tracdap/rt/metadata/job.py +3 -1
  58. tracdap/rt/metadata/storage.py +9 -0
  59. tracdap/rt/metadata/type.py +9 -5
  60. {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b2.dist-info}/METADATA +5 -3
  61. {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b2.dist-info}/RECORD +64 -63
  62. {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b2.dist-info}/WHEEL +1 -1
  63. {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b2.dist-info}/licenses/LICENSE +0 -0
  64. {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b2.dist-info}/top_level.txt +0 -0
@@ -16,10 +16,12 @@
16
16
  import datetime as dt
17
17
  import pathlib
18
18
  import platform
19
+ import re
19
20
 
20
21
  import typing as tp
21
22
  import uuid
22
23
 
24
+ import tracdap.rt.api as api
23
25
  import tracdap.rt.exceptions as ex
24
26
  import tracdap.rt.metadata as meta
25
27
  import tracdap.rt.config as cfg
@@ -30,6 +32,7 @@ import traceback as tb
30
32
  __IS_WINDOWS = platform.system() == "Windows"
31
33
  __FIRST_MODEL_FRAME_NAME = "run_model"
32
34
  __FIRST_MODEL_FRAME_TEST_NAME = "_callTestMethod"
35
+ __OBJ_KEY_PATTERN = re.compile(r"([A-Z]+)-(.*)-v(\d+)")
33
36
 
34
37
 
35
38
  def is_windows():
@@ -60,7 +63,7 @@ def format_file_size(size: int) -> str:
60
63
 
61
64
  def new_object_id(object_type: meta.ObjectType) -> meta.TagHeader:
62
65
 
63
- timestamp = dt.datetime.utcnow()
66
+ timestamp = dt.datetime.now(dt.timezone.utc)
64
67
 
65
68
  return meta.TagHeader(
66
69
  objectType=object_type,
@@ -71,6 +74,19 @@ def new_object_id(object_type: meta.ObjectType) -> meta.TagHeader:
71
74
  tagTimestamp=meta.DatetimeValue(timestamp.isoformat()))
72
75
 
73
76
 
77
+ def new_object_version(prior_id: meta.TagHeader) -> meta.TagHeader:
78
+
79
+ timestamp = dt.datetime.now(dt.timezone.utc)
80
+
81
+ return meta.TagHeader(
82
+ objectType=prior_id.objectType,
83
+ objectId=prior_id.objectId,
84
+ objectVersion=prior_id.objectVersion + 1,
85
+ objectTimestamp=meta.DatetimeValue(timestamp.isoformat()),
86
+ tagVersion=1,
87
+ tagTimestamp=meta.DatetimeValue(timestamp.isoformat()))
88
+
89
+
74
90
  def object_key(object_id: tp.Union[meta.TagHeader, meta.TagSelector]) -> str:
75
91
 
76
92
  if isinstance(object_id, meta.TagHeader):
@@ -106,29 +122,105 @@ def selector_for_latest(object_id: meta.TagHeader) -> meta.TagSelector:
106
122
  latestTag=True)
107
123
 
108
124
 
109
- def get_job_resource(
125
+ def get_job_mapping(
126
+ selector: tp.Union[meta.TagHeader, meta.TagSelector],
127
+ job_config: cfg.JobConfig) \
128
+ -> meta.TagHeader:
129
+
130
+ obj_key = object_key(selector)
131
+ obj_id = job_config.objectMapping.get(obj_key)
132
+
133
+ if obj_id is not None:
134
+ return obj_id
135
+
136
+ obj_key_match = __OBJ_KEY_PATTERN.match(obj_key)
137
+
138
+ if not obj_key_match:
139
+ err = f"Missing required {selector.objectType.name} ID for [{object_key(selector)}]"
140
+ raise ex.ERuntimeValidation(err)
141
+
142
+ obj_type = obj_key_match.group(1)
143
+ obj_id = obj_key_match.group(2)
144
+ obj_ver = obj_key_match.group(3)
145
+ obj_ts = job_config.jobId.objectTimestamp
146
+
147
+ return meta.TagHeader(
148
+ meta.ObjectType.__members__[obj_type], obj_id,
149
+ int(obj_ver), obj_ts, 1, obj_ts)
150
+
151
+
152
+ def get_job_metadata(
110
153
  selector: tp.Union[meta.TagHeader, meta.TagSelector],
111
154
  job_config: cfg.JobConfig,
112
- optional: bool = False):
155
+ optional: bool = False) \
156
+ -> tp.Optional[meta.ObjectDefinition]:
157
+
158
+ return __get_job_metadata_item(selector, job_config, job_config.objects, "object", optional)
159
+
160
+
161
+ def get_job_metadata_tag(
162
+ selector: tp.Union[meta.TagHeader, meta.TagSelector],
163
+ job_config: cfg.JobConfig,
164
+ optional: bool = False) \
165
+ -> tp.Optional[meta.Tag]:
166
+
167
+ return __get_job_metadata_item(selector, job_config, job_config.tags, "tag", optional)
113
168
 
114
- resource_key = object_key(selector)
115
- resource_id = job_config.resourceMapping.get(resource_key)
116
169
 
117
- if resource_id is not None:
118
- resource_key = object_key(resource_id)
170
+ __METADATA_TYPE = tp.TypeVar("__METADATA_TYPE")
119
171
 
120
- resource = job_config.resources.get(resource_key)
121
172
 
122
- if resource is not None:
123
- return resource
173
+ def __get_job_metadata_item(
174
+ selector: tp.Union[meta.TagHeader, meta.TagSelector], job_config: cfg.JobConfig,
175
+ metadata: tp.Dict[str, __METADATA_TYPE], metadata_type: str,
176
+ optional: bool = False) \
177
+ -> tp.Optional[__METADATA_TYPE]:
178
+
179
+ obj_key = object_key(selector)
180
+ obj_id = job_config.objectMapping.get(obj_key)
181
+
182
+ if obj_id is not None:
183
+ obj_key = object_key(obj_id)
184
+
185
+ item = metadata.get(obj_key)
186
+
187
+ if item is not None:
188
+ return item
124
189
 
125
190
  if optional:
126
191
  return None
127
192
 
128
- err = f"Missing required {selector.objectType.name} resource [{object_key(selector)}]"
193
+ err = f"Missing required {selector.objectType.name} {metadata_type} for [{object_key(selector)}]"
129
194
  raise ex.ERuntimeValidation(err)
130
195
 
131
196
 
197
+ def attach_runtime_metadata(obj: tp.Any, metadata: api.RuntimeMetadata):
198
+
199
+ if hasattr(obj, "with_metadata"):
200
+ attach_func = getattr(obj, "with_metadata")
201
+ if isinstance(attach_func, tp.Callable):
202
+ return attach_func(metadata)
203
+
204
+ setattr(obj, "_metadata", metadata)
205
+
206
+ return obj
207
+
208
+
209
+ def retrieve_runtime_metadata(obj: tp.Any) -> tp.Optional[api.RuntimeMetadata]:
210
+
211
+ if hasattr(obj, "metadata"):
212
+ metadata = getattr(obj, "metadata")
213
+ if isinstance(metadata, api.RuntimeMetadata):
214
+ return metadata
215
+
216
+ if hasattr(obj, "_metadata"):
217
+ metadata = getattr(obj, "_metadata")
218
+ if isinstance(metadata, api.RuntimeMetadata):
219
+ return metadata
220
+
221
+ return None
222
+
223
+
132
224
  def get_origin(metaclass: type):
133
225
 
134
226
  # Minimum supported Python is 3.7, which does not provide get_origin and get_args
@@ -253,3 +345,25 @@ def filter_model_stack_trace(full_stack: tb.StackSummary, checkout_directory: pa
253
345
  last_model_frame = first_model_frame + frame_index
254
346
 
255
347
  return full_stack[first_model_frame:last_model_frame+1]
348
+
349
+
350
+ __T = tp.TypeVar("__T")
351
+
352
+ def read_property(properties: tp.Dict[str, str], key: str, default: tp.Optional[__T] = None, convert: tp.Optional[tp.Type[__T]] = str) -> __T:
353
+
354
+ value = properties.get(key)
355
+
356
+ if value is None:
357
+ if default is not None:
358
+ value = default
359
+ else:
360
+ raise ex.EConfigParse(f"Missing required property: [{key}]")
361
+
362
+ try:
363
+ if convert is bool and isinstance(value, str):
364
+ return True if value.lower() == "true" else False
365
+ else:
366
+ return convert(value)
367
+
368
+ except (ValueError, TypeError):
369
+ raise ex.EConfigParse(f"Wrong property type: [{key}] = [{value}], expected type is [{convert}]")
@@ -118,6 +118,19 @@ class TracContextImpl(_api.TracContext):
118
118
 
119
119
  return not data_view.is_empty()
120
120
 
121
+ def get_metadata(self, item_name: str) -> tp.Optional[_api.RuntimeMetadata]:
122
+
123
+ _val.validate_signature(self.get_metadata, item_name)
124
+
125
+ self.__val.check_item_valid_identifier(item_name, TracContextValidator.ITEM)
126
+ self.__val.check_item_defined_in_model(item_name, TracContextValidator.ITEM)
127
+ self.__val.check_item_available_in_context(item_name, TracContextValidator.ITEM)
128
+
129
+ obj = self.__local_ctx.get(item_name)
130
+
131
+ # Can be none if no metadata is attached
132
+ return _util.retrieve_runtime_metadata(obj)
133
+
121
134
  def get_schema(self, dataset_name: str) -> _meta.SchemaDefinition:
122
135
 
123
136
  _val.validate_signature(self.get_schema, dataset_name)
@@ -201,8 +214,12 @@ class TracContextImpl(_api.TracContext):
201
214
  self.__val.check_context_data_view_type(struct_name, data_view, _meta.ObjectType.DATA)
202
215
  self.__val.check_dataset_schema_defined(struct_name, data_view)
203
216
 
204
- struct_data: dict = data_view.parts[part_key][0].content
205
- return _struct.StructProcessor.parse_struct(struct_data, None, python_class)
217
+ struct_data = data_view.parts[part_key][0].content
218
+
219
+ if isinstance(struct_data, python_class):
220
+ return struct_data
221
+ else:
222
+ return _struct.StructProcessor.parse_struct(struct_data, None, python_class)
206
223
 
207
224
  def get_file(self, file_name: str) -> bytes:
208
225
 
@@ -371,16 +388,25 @@ class TracContextImpl(_api.TracContext):
371
388
  self.__val.check_item_valid_identifier(file_name, TracContextValidator.FILE)
372
389
  self.__val.check_item_is_model_output(file_name, TracContextValidator.FILE)
373
390
 
391
+ class DelayedClose(io.BytesIO):
392
+
393
+ def __init__(self):
394
+ super().__init__()
395
+
396
+ def close(self):
397
+ super().flush()
398
+
374
399
  @contextlib.contextmanager
375
400
  def memory_stream(stream: io.BytesIO):
376
401
  try:
377
402
  yield stream
378
- buffer = stream.getbuffer().tobytes()
379
- self.put_file(file_name, buffer)
380
403
  finally:
381
- stream.close()
404
+ with stream.getbuffer() as buffer:
405
+ self.put_file(file_name, bytes(buffer))
406
+ if not stream.closed:
407
+ io.BytesIO.close(stream)
382
408
 
383
- return memory_stream(io.BytesIO())
409
+ return memory_stream(DelayedClose())
384
410
 
385
411
  def log(self) -> logging.Logger:
386
412
 
@@ -816,6 +842,7 @@ class TracContextErrorReporter:
816
842
 
817
843
  class TracContextValidator(TracContextErrorReporter):
818
844
 
845
+ ITEM = "Item"
819
846
  PARAMETER = "Parameter"
820
847
  DATASET = "Dataset"
821
848
  FILE = "File"
@@ -891,10 +918,10 @@ class TracContextValidator(TracContextErrorReporter):
891
918
  if schema is None:
892
919
  self._report_error(f"Schema not defined for dataset {dataset_name} in the current context")
893
920
 
894
- if schema.schemaType == _meta.SchemaType.TABLE and (schema.table is None or not schema.table.fields):
921
+ if schema.schemaType == _meta.SchemaType.TABLE_SCHEMA and (schema.table is None or not schema.table.fields):
895
922
  self._report_error(f"Schema not defined for dataset {dataset_name} in the current context")
896
923
 
897
- if schema.schemaType == _meta.SchemaType.STRUCT and (schema.struct is None or not schema.struct.fields):
924
+ if schema.schemaType == _meta.SchemaType.STRUCT_SCHEMA and not schema.fields:
898
925
  self._report_error(f"Schema not defined for dataset {dataset_name} in the current context")
899
926
 
900
927
  def check_dataset_schema_not_defined(self, dataset_name: str, data_view: _data.DataView):