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.
Files changed (88) hide show
  1. tracdap/rt/_impl/core/__init__.py +14 -0
  2. tracdap/rt/_impl/{config_parser.py → core/config_parser.py} +36 -19
  3. tracdap/rt/_impl/{data.py → core/data.py} +136 -32
  4. tracdap/rt/_impl/core/logging.py +195 -0
  5. tracdap/rt/_impl/{models.py → core/models.py} +15 -12
  6. tracdap/rt/_impl/{repos.py → core/repos.py} +12 -3
  7. tracdap/rt/_impl/{schemas.py → core/schemas.py} +5 -5
  8. tracdap/rt/_impl/{shim.py → core/shim.py} +5 -4
  9. tracdap/rt/_impl/{storage.py → core/storage.py} +21 -10
  10. tracdap/rt/_impl/core/struct.py +547 -0
  11. tracdap/rt/_impl/{util.py → core/util.py} +1 -111
  12. tracdap/rt/_impl/{validation.py → core/validation.py} +99 -31
  13. tracdap/rt/_impl/exec/__init__.py +14 -0
  14. tracdap/rt/{_exec → _impl/exec}/actors.py +12 -14
  15. tracdap/rt/{_exec → _impl/exec}/context.py +228 -82
  16. tracdap/rt/{_exec → _impl/exec}/dev_mode.py +163 -81
  17. tracdap/rt/{_exec → _impl/exec}/engine.py +230 -105
  18. tracdap/rt/{_exec → _impl/exec}/functions.py +191 -100
  19. tracdap/rt/{_exec → _impl/exec}/graph.py +24 -36
  20. tracdap/rt/{_exec → _impl/exec}/graph_builder.py +252 -115
  21. tracdap/rt/_impl/grpc/codec.py +1 -1
  22. tracdap/rt/{_exec → _impl/grpc}/server.py +7 -6
  23. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +3 -3
  24. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +1 -1
  25. tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
  26. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +40 -0
  27. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.pyi +62 -0
  28. tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
  29. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +32 -20
  30. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +48 -2
  31. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +4 -2
  32. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
  33. tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
  34. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +65 -63
  35. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
  36. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +28 -26
  37. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
  38. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +4 -4
  39. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +6 -0
  40. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +9 -7
  41. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +12 -4
  42. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +18 -5
  43. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +42 -2
  44. tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
  45. tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.py → storage_pb2.py} +4 -4
  46. tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
  47. tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
  48. tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +1 -1
  49. tracdap/rt/{_exec → _impl}/runtime.py +32 -18
  50. tracdap/rt/_impl/static_api.py +65 -37
  51. tracdap/rt/_plugins/format_csv.py +1 -1
  52. tracdap/rt/_plugins/repo_git.py +56 -11
  53. tracdap/rt/_plugins/storage_sql.py +1 -1
  54. tracdap/rt/_version.py +1 -1
  55. tracdap/rt/api/__init__.py +5 -24
  56. tracdap/rt/api/constants.py +57 -0
  57. tracdap/rt/api/experimental.py +32 -0
  58. tracdap/rt/api/hook.py +26 -7
  59. tracdap/rt/api/model_api.py +16 -0
  60. tracdap/rt/api/static_api.py +265 -127
  61. tracdap/rt/config/__init__.py +11 -11
  62. tracdap/rt/config/common.py +2 -26
  63. tracdap/rt/config/dynamic.py +28 -0
  64. tracdap/rt/config/platform.py +17 -31
  65. tracdap/rt/config/runtime.py +2 -0
  66. tracdap/rt/ext/embed.py +2 -2
  67. tracdap/rt/ext/plugins.py +3 -3
  68. tracdap/rt/launch/launch.py +12 -14
  69. tracdap/rt/metadata/__init__.py +28 -18
  70. tracdap/rt/metadata/config.py +95 -0
  71. tracdap/rt/metadata/data.py +40 -0
  72. tracdap/rt/metadata/file.py +10 -0
  73. tracdap/rt/metadata/job.py +16 -0
  74. tracdap/rt/metadata/model.py +12 -2
  75. tracdap/rt/metadata/object.py +9 -1
  76. tracdap/rt/metadata/object_id.py +6 -0
  77. tracdap/rt/metadata/resource.py +41 -1
  78. {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/METADATA +23 -17
  79. tracdap_runtime-0.8.0.dist-info/RECORD +129 -0
  80. {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/WHEEL +1 -1
  81. tracdap/rt/_exec/__init__.py +0 -0
  82. tracdap_runtime-0.7.1.dist-info/RECORD +0 -121
  83. /tracdap/rt/_impl/{guard_rails.py → core/guard_rails.py} +0 -0
  84. /tracdap/rt/_impl/{type_system.py → core/type_system.py} +0 -0
  85. /tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.pyi → storage_pb2.pyi} +0 -0
  86. /tracdap/rt/metadata/{stoarge.py → storage.py} +0 -0
  87. {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info/licenses}/LICENSE +0 -0
  88. {tracdap_runtime-0.7.1.dist-info → tracdap_runtime-0.8.0.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,8 @@ import pathlib
22
22
 
23
23
  import tracdap.rt.metadata as meta
24
24
  import tracdap.rt.exceptions as ex
25
- import tracdap.rt._impl.util as util
25
+ import tracdap.rt._impl.core.logging as log
26
+ import tracdap.rt._impl.core.util as util
26
27
 
27
28
  # _Named placeholder type from API hook is needed for API type checking
28
29
  from tracdap.rt.api.hook import _Named # noqa
@@ -66,15 +67,23 @@ class SkipValidation(tp.Generic[T_SKIP_VAL]):
66
67
 
67
68
  class _TypeValidator:
68
69
 
69
- # The metaclass for generic types varies between versions of the typing library
70
- # To work around this, detect the correct metaclass by inspecting a generic type variable
71
- __generic_metaclass = type(tp.List[object])
70
+ # Support both new and old styles for generic, union and optional types
71
+ # Old-style annotations are still valid, even when the new style is fully supported
72
+ __generic_metaclass = [
73
+ types.GenericAlias,
74
+ type(tp.List[object]),
75
+ type(tp.Optional[object])
76
+ ]
77
+
78
+ # UnionType was added to the types module in Python 3.10, we support 3.9 (Jan 2025)
79
+ if hasattr(types, "UnionType"):
80
+ __generic_metaclass.append(types.UnionType)
72
81
 
73
82
  # Cache method signatures to avoid inspection on every call
74
83
  # Inspecting a function signature can take ~ half a second in Python 3.7
75
84
  __method_cache: tp.Dict[str, tp.Tuple[inspect.Signature, tp.Any]] = dict()
76
85
 
77
- _log: logging.Logger = util.logger_for_namespace(__name__)
86
+ _log: logging.Logger = log.logger_for_namespace(__name__)
78
87
 
79
88
  @classmethod
80
89
  def validate_signature(cls, method: tp.Callable, *args, **kwargs):
@@ -203,7 +212,7 @@ class _TypeValidator:
203
212
  if value.skip_type == expected_type:
204
213
  return True
205
214
 
206
- if isinstance(expected_type, cls.__generic_metaclass):
215
+ if any(map(lambda _t: isinstance(expected_type, _t), cls.__generic_metaclass)):
207
216
 
208
217
  origin = util.get_origin(expected_type)
209
218
  args = util.get_args(expected_type)
@@ -239,6 +248,33 @@ class _TypeValidator:
239
248
  all(map(lambda k: cls._validate_type(key_type, k), value.keys())) and \
240
249
  all(map(lambda v: cls._validate_type(value_type, v), value.values()))
241
250
 
251
+ if origin is type:
252
+
253
+ if not isinstance(value, type):
254
+ return False
255
+
256
+ type_arg = args[0]
257
+
258
+ if type_arg == tp.Any:
259
+ return True
260
+
261
+ if isinstance(type_arg, tp.TypeVar):
262
+
263
+ constraints = util.get_constraints(type_arg)
264
+ bound = util.get_bound(type_arg)
265
+
266
+ if constraints:
267
+ if not any(map(lambda c: expected_type == c, constraints)):
268
+ return False
269
+
270
+ if bound:
271
+ if not issubclass(expected_type, bound):
272
+ return False
273
+
274
+ # So long as constraints / bound are ok, any type matches a generic type var
275
+ return True
276
+
277
+
242
278
  if origin.__module__.startswith("tracdap.rt.api."):
243
279
  return isinstance(value, origin)
244
280
 
@@ -273,7 +309,7 @@ class _TypeValidator:
273
309
  @classmethod
274
310
  def _type_name(cls, type_var: tp.Type, qualified: bool = False) -> str:
275
311
 
276
- if isinstance(type_var, cls.__generic_metaclass):
312
+ if any(map(lambda _t: isinstance(type_var, _t), cls.__generic_metaclass)):
277
313
 
278
314
  origin = util.get_origin(type_var)
279
315
  args = util.get_args(type_var)
@@ -290,7 +326,11 @@ class _TypeValidator:
290
326
 
291
327
  if origin is list:
292
328
  list_type = cls._type_name(args[0])
293
- return f"List[{list_type}]"
329
+ return f"list[{list_type}]"
330
+
331
+ if origin is type:
332
+ type_arg = cls._type_name(args[0])
333
+ return f"type[{type_arg}]"
294
334
 
295
335
  raise ex.ETracInternal(f"Validation of [{origin.__name__}] generic parameters is not supported yet")
296
336
 
@@ -306,6 +346,9 @@ class StaticValidator:
306
346
  __reserved_identifier_pattern = re.compile("\\A(_|trac_)", re.ASCII)
307
347
  __label_length_limit = 4096
308
348
 
349
+ __file_extension_pattern = re.compile('\\A[a-zA-Z0-9]+\\Z')
350
+ __mime_type_pattern = re.compile('\\A\\w+/[-.\\w]+(?:\\+[-.\\w]+)?\\Z')
351
+
309
352
  __PRIMITIVE_TYPES = [
310
353
  meta.BasicType.BOOLEAN,
311
354
  meta.BasicType.INTEGER,
@@ -321,7 +364,7 @@ class StaticValidator:
321
364
  meta.BasicType.INTEGER,
322
365
  meta.BasicType.DATE]
323
366
 
324
- _log: logging.Logger = util.logger_for_namespace(__name__)
367
+ _log: logging.Logger = log.logger_for_namespace(__name__)
325
368
 
326
369
  @classmethod
327
370
  def is_primitive_type(cls, basic_type: meta.BasicType) -> bool:
@@ -418,25 +461,49 @@ class StaticValidator:
418
461
  cls._valid_identifiers(param.paramProps.keys(), "entry in param props")
419
462
 
420
463
  @classmethod
421
- def _check_inputs_or_outputs(cls, inputs_or_outputs):
464
+ def _check_inputs_or_outputs(cls, sockets):
422
465
 
423
- for input_name, input_schema in inputs_or_outputs.items():
466
+ for socket_name, socket in sockets.items():
424
467
 
425
- cls._log.info(f"Checking {input_name}")
468
+ if socket.objectType == meta.ObjectType.DATA:
469
+ cls._check_socket_schema(socket_name, socket)
470
+ elif socket.objectType == meta.ObjectType.FILE:
471
+ cls._check_socket_file_type(socket_name, socket)
472
+ else:
473
+ raise ex.EModelValidation(f"Invalid object type [{socket.objectType.name}] for [{socket_name}]")
426
474
 
427
- if input_schema.dynamic:
428
- if input_schema.schema and input_schema.schema.table:
429
- error = "Dynamic schemas must have schema.table = None"
430
- cls._fail(f"Invalid schema for [{input_name}]: {error}")
431
- else:
432
- continue
475
+ label = socket.label
476
+ cls._check_label(label, socket_name)
477
+
478
+ if isinstance(socket, meta.ModelInputSchema):
479
+ if socket.inputProps is not None:
480
+ cls._valid_identifiers(socket.inputProps.keys(), "entry in input props")
481
+ else:
482
+ if socket.outputProps is not None:
483
+ cls._valid_identifiers(socket.outputProps.keys(), "entry in output props")
433
484
 
434
- fields = input_schema.schema.table.fields
485
+ @classmethod
486
+ def _check_socket_schema(cls, socket_name, socket):
487
+
488
+ if socket.schema is None:
489
+ cls._fail(f"Missing schema requirement for [{socket_name}]")
490
+ return
491
+
492
+ if socket.dynamic:
493
+ if socket.schema and socket.schema.table:
494
+ error = "Dynamic schemas must have schema.table = None"
495
+ cls._fail(f"Invalid schema for [{socket_name}]: {error}")
496
+ else:
497
+ return
498
+
499
+ if socket.schema.schemaType == meta.SchemaType.TABLE:
500
+
501
+ fields = socket.schema.table.fields
435
502
  field_names = list(map(lambda f: f.fieldName, fields))
436
- property_type = f"field in [{input_name}]"
503
+ property_type = f"field in [{socket_name}]"
437
504
 
438
505
  if len(fields) == 0:
439
- cls._fail(f"Invalid schema for [{input_name}]: No fields defined")
506
+ cls._fail(f"Invalid schema for [{socket_name}]: No fields defined")
440
507
 
441
508
  cls._valid_identifiers(field_names, property_type)
442
509
  cls._case_insensitive_duplicates(field_names, property_type)
@@ -444,23 +511,24 @@ class StaticValidator:
444
511
  for field in fields:
445
512
  cls._check_single_field(field, property_type)
446
513
 
447
- label = input_schema.label
448
- cls._check_label(label, input_name)
514
+ @classmethod
515
+ def _check_socket_file_type(cls, socket_name, socket):
449
516
 
450
- if isinstance(input_schema, meta.ModelInputSchema):
451
- if input_schema.inputProps is not None:
452
- cls._valid_identifiers(input_schema.inputProps.keys(), "entry in input props")
453
- else:
454
- if input_schema.outputProps is not None:
455
- cls._valid_identifiers(input_schema.outputProps.keys(), "entry in output props")
517
+ if socket.fileType is None:
518
+ cls._fail(f"Missing file type requirement for [{socket_name}]")
519
+ return
520
+
521
+ if not cls.__file_extension_pattern.match(socket.fileType.extension):
522
+ cls._fail(f"Invalid extension [{socket.fileType.extension}] for [{socket_name}]")
523
+
524
+ if not cls.__mime_type_pattern.match(socket.fileType.mimeType):
525
+ cls._fail(f"Invalid mime type [{socket.fileType.mimeType}] for [{socket_name}]")
456
526
 
457
527
  @classmethod
458
528
  def _check_single_field(cls, field: meta.FieldSchema, property_type):
459
529
 
460
530
  # Valid identifier and not trac reserved checked separately
461
531
 
462
- cls._log.info(field.fieldName)
463
-
464
532
  if field.fieldOrder < 0:
465
533
  cls._fail(f"Invalid {property_type}: [{field.fieldName}] fieldOrder < 0")
466
534
 
@@ -0,0 +1,14 @@
1
+ # Licensed to the Fintech Open Source Foundation (FINOS) under one or
2
+ # more contributor license agreements. See the NOTICE file distributed
3
+ # with this work for additional information regarding copyright ownership.
4
+ # FINOS licenses this file to you under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from __future__ import annotations
17
-
18
16
  import logging
19
17
  import threading
20
18
  import functools as func
@@ -25,8 +23,8 @@ import inspect
25
23
  import queue
26
24
  import time
27
25
 
28
- import tracdap.rt._impl.util as util # noqa
29
- import tracdap.rt._impl.validation as _val # noqa
26
+ import tracdap.rt._impl.core.logging as _logging
27
+ import tracdap.rt._impl.core.validation as _val
30
28
  import tracdap.rt.exceptions as _ex
31
29
 
32
30
 
@@ -118,7 +116,7 @@ class Actor:
118
116
  def error(self) -> tp.Optional[Exception]:
119
117
  return self.__ctx.get_error()
120
118
 
121
- def actors(self) -> ActorContext:
119
+ def actors(self) -> "ActorContext":
122
120
  return self.__ctx
123
121
 
124
122
  def on_start(self):
@@ -134,7 +132,7 @@ class Actor:
134
132
  class ActorContext:
135
133
 
136
134
  def __init__(
137
- self, node: ActorNode, message: str,
135
+ self, node: "ActorNode", message: str,
138
136
  current_actor: ActorId, parent: ActorId, sender: tp.Optional[ActorId]):
139
137
 
140
138
  self.__node = node
@@ -188,13 +186,13 @@ class ThreadsafeActor(Actor):
188
186
  super().__init__()
189
187
  self.__threadsafe: tp.Optional[ThreadsafeContext] = None
190
188
 
191
- def threadsafe(self) -> ThreadsafeContext:
189
+ def threadsafe(self) -> "ThreadsafeContext":
192
190
  return self.__threadsafe
193
191
 
194
192
 
195
193
  class ThreadsafeContext:
196
194
 
197
- def __init__(self, node: ActorNode):
195
+ def __init__(self, node: "ActorNode"):
198
196
  self.__node = node
199
197
  self.__id = node.actor_id
200
198
  self.__parent = node.parent.actor_id if node.parent is not None else None
@@ -235,7 +233,7 @@ class EventLoop:
235
233
  self.__shutdown = False
236
234
  self.__shutdown_now = False
237
235
  self.__done = False
238
- self.__log = util.logger_for_object(self)
236
+ self.__log = _logging.logger_for_object(self)
239
237
 
240
238
  def post_message(self, msg: _T_MSG, processor: tp.Callable[[_T_MSG], None]) -> bool:
241
239
  with self.__msg_lock:
@@ -365,12 +363,12 @@ class FunctionCache:
365
363
 
366
364
  class ActorNode:
367
365
 
368
- _log = util.logger_for_class(Actor)
366
+ _log = _logging.logger_for_class(Actor)
369
367
 
370
368
  def __init__(
371
369
  self, actor_id: ActorId, actor: Actor,
372
- parent: tp.Optional[ActorNode],
373
- system: ActorSystem,
370
+ parent: "tp.Optional[ActorNode]",
371
+ system: "ActorSystem",
374
372
  event_loop: EventLoop):
375
373
 
376
374
  self.actor_id = actor_id
@@ -483,7 +481,7 @@ class ActorNode:
483
481
 
484
482
  target_node._accept(msg)
485
483
 
486
- def _lookup_node(self, target_id: ActorId) -> tp.Optional[ActorNode]:
484
+ def _lookup_node(self, target_id: ActorId) -> "tp.Optional[ActorNode]":
487
485
 
488
486
  # Check self first
489
487
 
@@ -904,7 +902,7 @@ class ActorSystem:
904
902
 
905
903
  super().__init__()
906
904
 
907
- self._log = util.logger_for_object(self)
905
+ self._log = _logging.logger_for_object(self)
908
906
 
909
907
  # self.__actors: tp.Dict[ActorId, ActorNode] = {self.__ROOT_ID: ActorNode("", self.__ROOT_ID, None)}
910
908
  # self.__message_queue: tp.List[Msg] = list()