tracdap-runtime 0.6.2__tar.gz → 0.6.3__tar.gz

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 (132) hide show
  1. {tracdap_runtime-0.6.2/tracdap_runtime.egg-info → tracdap_runtime-0.6.3}/PKG-INFO +4 -4
  2. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/setup.cfg +4 -6
  3. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/actors.py +87 -10
  4. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/dev_mode.py +9 -17
  5. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/engine.py +79 -14
  6. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/runtime.py +83 -40
  7. tracdap_runtime-0.6.3/src/tracdap/rt/_exec/server.py +345 -0
  8. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/config_parser.py +219 -49
  9. tracdap_runtime-0.6.3/src/tracdap/rt/_impl/grpc/codec.py +99 -0
  10. tracdap_runtime-0.6.3/src/tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +51 -0
  11. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.pyi +11 -9
  12. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +25 -25
  13. tracdap_runtime-0.6.3/src/tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +63 -0
  14. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +33 -6
  15. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +8 -3
  16. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +13 -2
  17. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/guard_rails.py +21 -0
  18. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/models.py +25 -0
  19. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/static_api.py +23 -9
  20. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/type_system.py +17 -0
  21. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/validation.py +10 -0
  22. tracdap_runtime-0.6.3/src/tracdap/rt/_plugins/config_local.py +49 -0
  23. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_version.py +1 -1
  24. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/api/hook.py +6 -3
  25. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/api/static_api.py +71 -21
  26. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/__init__.py +4 -4
  27. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/common.py +10 -0
  28. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/platform.py +0 -10
  29. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/runtime.py +2 -0
  30. tracdap_runtime-0.6.3/src/tracdap/rt/ext/config.py +34 -0
  31. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/ext/embed.py +1 -3
  32. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/ext/plugins.py +47 -6
  33. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/launch/cli.py +4 -0
  34. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/launch/launch.py +34 -9
  35. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/model.py +6 -0
  36. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/object.py +3 -0
  37. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3/tracdap_runtime.egg-info}/PKG-INFO +4 -4
  38. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/tracdap_runtime.egg-info/SOURCES.txt +2 -11
  39. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/tracdap_runtime.egg-info/requires.txt +3 -3
  40. tracdap_runtime-0.6.2/src/tracdap/rt/_exec/server.py +0 -68
  41. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/codec.py +0 -44
  42. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +0 -51
  43. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/common_pb2.py +0 -55
  44. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/common_pb2.pyi +0 -103
  45. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/job_pb2.py +0 -42
  46. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/job_pb2.pyi +0 -44
  47. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/platform_pb2.py +0 -71
  48. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/platform_pb2.pyi +0 -197
  49. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/result_pb2.py +0 -37
  50. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/result_pb2.pyi +0 -35
  51. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/runtime_pb2.py +0 -42
  52. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/config/runtime_pb2.pyi +0 -46
  53. tracdap_runtime-0.6.2/src/tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +0 -51
  54. tracdap_runtime-0.6.2/src/tracdap/rt/ext/_guard.py +0 -37
  55. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/LICENSE +0 -0
  56. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/README.md +0 -0
  57. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/pyproject.toml +0 -0
  58. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/__init__.py +0 -0
  59. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/__init__.py +0 -0
  60. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/context.py +0 -0
  61. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/functions.py +0 -0
  62. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/graph.py +0 -0
  63. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_exec/graph_builder.py +0 -0
  64. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/__init__.py +0 -0
  65. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/data.py +0 -0
  66. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/__init__.py +0 -0
  67. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +0 -0
  68. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.pyi +0 -0
  69. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +0 -0
  70. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.pyi +0 -0
  71. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +0 -0
  72. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +0 -0
  73. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +0 -0
  74. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +0 -0
  75. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +0 -0
  76. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.pyi +0 -0
  77. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +0 -0
  78. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +0 -0
  79. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +0 -0
  80. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +0 -0
  81. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +0 -0
  82. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.pyi +0 -0
  83. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/stoarge_pb2.py +0 -0
  84. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/stoarge_pb2.pyi +0 -0
  85. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +0 -0
  86. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.pyi +0 -0
  87. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +0 -0
  88. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.pyi +0 -0
  89. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +0 -0
  90. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.pyi +0 -0
  91. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/repos.py +0 -0
  92. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/schemas.py +0 -0
  93. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/shim.py +0 -0
  94. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/storage.py +0 -0
  95. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_impl/util.py +0 -0
  96. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/__init__.py +0 -0
  97. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/_helpers.py +0 -0
  98. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/format_arrow.py +0 -0
  99. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/format_csv.py +0 -0
  100. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/format_parquet.py +0 -0
  101. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/repo_git.py +0 -0
  102. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/repo_local.py +0 -0
  103. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/repo_pypi.py +0 -0
  104. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/storage_aws.py +0 -0
  105. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/storage_azure.py +0 -0
  106. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/storage_gcp.py +0 -0
  107. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/_plugins/storage_local.py +0 -0
  108. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/api/__init__.py +0 -0
  109. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/api/model_api.py +0 -0
  110. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/job.py +0 -0
  111. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/config/result.py +0 -0
  112. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/exceptions.py +0 -0
  113. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/ext/__init__.py +0 -0
  114. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/ext/repos.py +0 -0
  115. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/ext/storage.py +0 -0
  116. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/launch/__init__.py +0 -0
  117. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/launch/__main__.py +0 -0
  118. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/__init__.py +17 -17
  119. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/common.py +0 -0
  120. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/custom.py +0 -0
  121. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/data.py +0 -0
  122. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/file.py +0 -0
  123. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/flow.py +0 -0
  124. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/job.py +0 -0
  125. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/object_id.py +0 -0
  126. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/search.py +0 -0
  127. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/stoarge.py +0 -0
  128. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/tag.py +0 -0
  129. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/tag_update.py +0 -0
  130. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/src/tracdap/rt/metadata/type.py +0 -0
  131. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/tracdap_runtime.egg-info/dependency_links.txt +0 -0
  132. {tracdap_runtime-0.6.2 → tracdap_runtime-0.6.3}/tracdap_runtime.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tracdap-runtime
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Runtime package for building models on the TRAC Data & Analytics Platform
5
5
  Home-page: https://tracdap.finos.org/
6
6
  Author: Martin Traverse
@@ -16,9 +16,9 @@ Classifier: Operating System :: OS Independent
16
16
  Requires-Python: <3.13,>=3.8
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
- Requires-Dist: protobuf==5.27.4
20
- Requires-Dist: pyarrow==16.0.0
21
- Requires-Dist: pyyaml==6.0.1
19
+ Requires-Dist: protobuf==5.28.2
20
+ Requires-Dist: pyarrow==16.1.0
21
+ Requires-Dist: pyyaml==6.0.2
22
22
  Requires-Dist: dulwich==0.22.1
23
23
  Requires-Dist: requests==2.32.3
24
24
  Requires-Dist: pandas<2.3.0,>=1.2.0
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = tracdap-runtime
3
- version = 0.6.2
3
+ version = 0.6.3
4
4
  description = Runtime package for building models on the TRAC Data & Analytics Platform
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -31,8 +31,6 @@ packages =
31
31
  tracdap.rt._impl.grpc
32
32
  tracdap.rt._impl.grpc.tracdap
33
33
  tracdap.rt._impl.grpc.tracdap.metadata
34
- tracdap.rt._impl.grpc.tracdap.config
35
- tracdap.rt._impl.grpc.tracdap.api
36
34
  tracdap.rt._impl.grpc.tracdap.api.internal
37
35
  tracdap.rt._plugins
38
36
  package_dir =
@@ -40,9 +38,9 @@ package_dir =
40
38
  tracdap.rt = src/tracdap/rt
41
39
  python_requires = >= 3.8, < 3.13
42
40
  install_requires =
43
- protobuf == 5.27.4
44
- pyarrow == 16.0.0
45
- pyyaml == 6.0.1
41
+ protobuf == 5.28.2
42
+ pyarrow == 16.1.0
43
+ pyyaml == 6.0.2
46
44
  dulwich == 0.22.1
47
45
  requests == 2.32.3
48
46
 
@@ -25,6 +25,7 @@ import queue
25
25
  import time
26
26
 
27
27
  import tracdap.rt._impl.util as util # noqa
28
+ import tracdap.rt._impl.validation as _val # noqa
28
29
  import tracdap.rt.exceptions as _ex
29
30
 
30
31
 
@@ -180,6 +181,49 @@ class ActorContext:
180
181
  return self.__error or self.__node.error
181
182
 
182
183
 
184
+ class ThreadsafeActor(Actor):
185
+
186
+ def __init__(self):
187
+ super().__init__()
188
+ self.__threadsafe: tp.Optional[ThreadsafeContext] = None
189
+
190
+ def threadsafe(self) -> ThreadsafeContext:
191
+ return self.__threadsafe
192
+
193
+
194
+ class ThreadsafeContext:
195
+
196
+ def __init__(self, node: ActorNode):
197
+ self.__node = node
198
+ self.__id = node.actor_id
199
+ self.__parent = node.parent.actor_id if node.parent is not None else None
200
+
201
+ def spawn(self, actor: Actor):
202
+ self.__node.event_loop.post_message(
203
+ None, lambda _:
204
+ self.__node.spawn(actor) and None)
205
+
206
+ def send(self, target_id: ActorId, message: str, *args, **kwargs):
207
+ self.__node.event_loop.post_message(
208
+ None, lambda _:
209
+ self.__node.send_message(self.__id, target_id, message, args, kwargs))
210
+
211
+ def send_parent(self, message: str, *args, **kwargs):
212
+ self.__node.event_loop.post_message(
213
+ None, lambda _:
214
+ self.__node.send_message(self.__id, self.__parent, message, args, kwargs))
215
+
216
+ def stop(self):
217
+ self.__node.event_loop.post_message(
218
+ None, lambda _:
219
+ self.__node.send_signal(self.__id, self.__id, SignalNames.STOP))
220
+
221
+ def fail(self, error: Exception):
222
+ self.__node.event_loop.post_message(
223
+ None, lambda _:
224
+ self.__node.send_signal(self.__id, self.__id, SignalNames.STOP, error))
225
+
226
+
183
227
  class EventLoop:
184
228
 
185
229
  _T_MSG = tp.TypeVar("_T_MSG")
@@ -340,7 +384,7 @@ class ActorNode:
340
384
  self.state: ActorState = ActorState.NOT_STARTED
341
385
  self.error: tp.Optional[Exception] = None
342
386
 
343
- def spawn(self, child_actor: Actor):
387
+ def spawn(self, child_actor: Actor) -> ActorId:
344
388
 
345
389
  if self._log.isEnabledFor(logging.DEBUG):
346
390
  self._log.debug(f"spawn [{self.actor_id}]: [{type(child_actor)}]")
@@ -355,6 +399,11 @@ class ActorNode:
355
399
  child_node = ActorNode(child_id, child_actor, self, self.system, event_loop)
356
400
  self.children[child_id] = child_node
357
401
 
402
+ # If this is a threadsafe actor, set up the threadsafe context
403
+ if isinstance(child_actor, ThreadsafeActor):
404
+ threadsafe = ThreadsafeContext(child_node)
405
+ child_actor._ThreadsafeActor__threadsafe = threadsafe
406
+
358
407
  child_node.send_signal(self.actor_id, child_id, SignalNames.START)
359
408
 
360
409
  return child_id
@@ -542,6 +591,12 @@ class ActorNode:
542
591
  if not self._check_message_target(signal):
543
592
  return
544
593
 
594
+ # Do not process signals after the actor has stopped
595
+ # This is common with e.g. STOP signals that propagate up and down the tree
596
+
597
+ if self.state in [ActorState.STOPPED, ActorState.FAILED]:
598
+ return
599
+
545
600
  # Call the signal receiver function
546
601
  # This gives the actor a chance to respond to the signal
547
602
 
@@ -768,10 +823,12 @@ class ActorNode:
768
823
  # Positional arg types
769
824
  for pos_param, pos_arg in zip(pos_params, args):
770
825
 
826
+ # If no type hint is available, allow anything through
827
+ # Otherwise, reuse the validator logic to type check individual args
771
828
  type_hint = type_hints.get(pos_param.name)
829
+ type_check = type_hint is None or _val.check_type(type_hint, pos_arg)
772
830
 
773
- # If no type hint is available, allow anything through
774
- if type_hint is not None and not isinstance(pos_arg, type_hint):
831
+ if not type_check:
775
832
  error = f"Invalid message: [{message}] -> {target_id} (wrong parameter type for '{pos_param.name}')"
776
833
  self._log.error(error)
777
834
  raise EBadActor(error)
@@ -780,20 +837,20 @@ class ActorNode:
780
837
  for kw_param in kw_params:
781
838
 
782
839
  kw_arg = kwargs.get(kw_param.name)
783
- type_hint = type_hints.get(kw_param.name)
784
840
 
785
841
  # If param has taken a default value, no type check is needed
786
842
  if kw_arg is None:
787
843
  continue
788
844
 
789
- # If no type hint is available, allow anything through
790
- if type_hint is not None and not isinstance(kw_arg, type_hint):
845
+ # Otherwise use the same type-validation logic as positional args
846
+ type_hint = type_hints.get(kw_param.name)
847
+ type_check = type_hint is None or _val.check_type(type_hint, kw_arg)
848
+
849
+ if not type_check:
791
850
  error = f"Invalid message: [{message}] -> {target_id} (wrong parameter type for '{kw_param.name}')"
792
851
  self._log.error(error)
793
852
  raise EBadActor(error)
794
853
 
795
- # TODO: Verify generics for both args and kwargs
796
-
797
854
 
798
855
  class RootActor(Actor):
799
856
 
@@ -864,11 +921,17 @@ class ActorSystem:
864
921
 
865
922
  self.__root_started = threading.Event()
866
923
  self.__root_stopped = threading.Event()
924
+
867
925
  self.__root_actor = RootActor(main_actor, self.__root_started, self.__root_stopped)
868
926
  self.__root_node = ActorNode(self.ROOT_ID, self.__root_actor, None, self, self.__system_event_loop)
869
927
 
870
928
  # Public API
871
929
 
930
+ def main_id(self) -> ActorId:
931
+ if not self.__root_started.is_set():
932
+ raise EBadActor("System has not started yet")
933
+ return self.__root_actor.main_id
934
+
872
935
  def start(self, wait=True):
873
936
 
874
937
  self.__system_thread.start()
@@ -913,12 +976,26 @@ class ActorSystem:
913
976
 
914
977
  return self.__root_node.error
915
978
 
916
- def send(self, message: str, *args, **kwargs):
979
+ def spawn_agent(self, agent: Actor) -> ActorId:
980
+
981
+ if not self.__root_started.is_set():
982
+ raise EBadActor("System has not started yet")
983
+
984
+ return self.__root_node.spawn(agent)
985
+
986
+ def send_main(self, message: str, *args, **kwargs):
917
987
 
918
988
  if self.__root_actor.main_id is None:
919
989
  raise EBadActor("System has not started yet")
920
990
 
921
- self.__root_node.send_message("/external", self.__root_actor.main_id, message, args, kwargs)
991
+ self.__root_node.send_message("/external", self.__root_actor.main_id, message, args, kwargs) # TODO
992
+
993
+ def send(self, actor_id: ActorId, message: str, *args, **kwargs):
994
+
995
+ if not self.__root_started.is_set():
996
+ raise EBadActor("System has not started yet")
997
+
998
+ self.__root_node.send_message("/external", actor_id, message, args, kwargs)
922
999
 
923
1000
  def _setup_event_loops(self, thread_pools: tp.Dict[str, int]):
924
1001
 
@@ -46,7 +46,7 @@ class DevModeTranslator:
46
46
  _log: tp.Optional[_util.logging.Logger] = None
47
47
 
48
48
  @classmethod
49
- def translate_sys_config(cls, sys_config: _cfg.RuntimeConfig, config_dir: tp.Optional[pathlib.Path]):
49
+ def translate_sys_config(cls, sys_config: _cfg.RuntimeConfig, config_mgr: _cfg_p.ConfigManager):
50
50
 
51
51
  cls._log.info(f"Applying dev mode config translation to system config")
52
52
 
@@ -56,7 +56,7 @@ class DevModeTranslator:
56
56
  sys_config.storage = _cfg.StorageConfig()
57
57
 
58
58
  sys_config = cls._add_integrated_repo(sys_config)
59
- sys_config = cls._resolve_relative_storage_root(sys_config, config_dir)
59
+ sys_config = cls._resolve_relative_storage_root(sys_config, config_mgr)
60
60
 
61
61
  return sys_config
62
62
 
@@ -66,7 +66,7 @@ class DevModeTranslator:
66
66
  sys_config: _cfg.RuntimeConfig,
67
67
  job_config: _cfg.JobConfig,
68
68
  scratch_dir: pathlib.Path,
69
- config_dir: tp.Optional[pathlib.Path],
69
+ config_mgr: _cfg_p.ConfigManager,
70
70
  model_class: tp.Optional[_api.TracModel.__class__]) \
71
71
  -> _cfg.JobConfig:
72
72
 
@@ -84,7 +84,7 @@ class DevModeTranslator:
84
84
 
85
85
  # Fow flows, load external flow definitions then perform auto-wiring and type inference
86
86
  if job_config.job.jobType == _meta.JobType.RUN_FLOW:
87
- job_config = cls._process_flow_definition(job_config, config_dir)
87
+ job_config = cls._process_flow_definition(job_config, config_mgr)
88
88
 
89
89
  # For run (model|flow) jobs, apply processing to the parameters, inputs and outputs
90
90
  if job_config.job.jobType in [_meta.JobType.RUN_MODEL, _meta.JobType.RUN_FLOW]:
@@ -109,7 +109,7 @@ class DevModeTranslator:
109
109
  @classmethod
110
110
  def _resolve_relative_storage_root(
111
111
  cls, sys_config: _cfg.RuntimeConfig,
112
- sys_config_path: tp.Optional[pathlib.Path]):
112
+ config_mgr: _cfg_p.ConfigManager):
113
113
 
114
114
  storage_config = copy.deepcopy(sys_config.storage)
115
115
 
@@ -128,6 +128,7 @@ class DevModeTranslator:
128
128
 
129
129
  cls._log.info(f"Resolving relative path for [{bucket_key}] local storage...")
130
130
 
131
+ sys_config_path = config_mgr.config_dir_path()
131
132
  if sys_config_path is not None:
132
133
  absolute_path = sys_config_path.joinpath(root_path).resolve()
133
134
  if absolute_path.exists():
@@ -291,7 +292,7 @@ class DevModeTranslator:
291
292
  return model_id, model_object
292
293
 
293
294
  @classmethod
294
- def _process_flow_definition(cls, job_config: _cfg.JobConfig, config_dir: pathlib.Path) -> _cfg.JobConfig:
295
+ def _process_flow_definition(cls, job_config: _cfg.JobConfig, config_mgr: _cfg_p.ConfigManager) -> _cfg.JobConfig:
295
296
 
296
297
  flow_details = job_config.job.runFlow.flow
297
298
 
@@ -305,21 +306,12 @@ class DevModeTranslator:
305
306
  cls._log.error(err)
306
307
  raise _ex.EConfigParse(err)
307
308
 
308
- flow_path = config_dir.joinpath(flow_details) if config_dir is not None else pathlib.Path(flow_details)
309
-
310
- if not flow_path.exists():
311
- err = f"Flow definition not available for [{flow_details}]: File not found ({flow_path})"
312
- cls._log.error(err)
313
- raise _ex.EConfigParse(err)
314
-
315
309
  flow_id = _util.new_object_id(_meta.ObjectType.FLOW)
316
310
  flow_key = _util.object_key(flow_id)
317
311
 
318
- cls._log.info(f"Generating flow definition for [{flow_details}] with ID = [{flow_key}]")
312
+ cls._log.info(f"Generating flow definition from [{flow_details}] with ID = [{flow_key}]")
319
313
 
320
- flow_parser = _cfg_p.ConfigParser(_meta.FlowDefinition)
321
- flow_raw_data = flow_parser.load_raw_config(flow_path, flow_path.name)
322
- flow_def = flow_parser.parse(flow_raw_data, flow_path.name)
314
+ flow_def = config_mgr.load_config_object(flow_details, _meta.FlowDefinition)
323
315
 
324
316
  # Auto-wiring and inference only applied to externally loaded flows for now
325
317
  flow_def = cls._autowire_flow(flow_def, job_config)
@@ -19,6 +19,7 @@ import dataclasses as dc
19
19
  import enum
20
20
  import typing as tp
21
21
 
22
+ import tracdap.rt.metadata as _meta
22
23
  import tracdap.rt.config as _cfg
23
24
  import tracdap.rt.exceptions as _ex
24
25
  import tracdap.rt._exec.actors as _actors
@@ -28,7 +29,6 @@ import tracdap.rt._impl.models as _models # noqa
28
29
  import tracdap.rt._impl.data as _data # noqa
29
30
  import tracdap.rt._impl.storage as _storage # noqa
30
31
  import tracdap.rt._impl.util as _util # noqa
31
- from .actors import Signal
32
32
 
33
33
  from .graph import NodeId
34
34
 
@@ -66,6 +66,18 @@ class _EngineContext:
66
66
  failed_nodes: tp.Set[NodeId] = dc.field(default_factory=set)
67
67
 
68
68
 
69
+ @dc.dataclass
70
+ class _JobState:
71
+
72
+ job_id: _meta.TagHeader
73
+ job_config: _cfg.JobConfig
74
+
75
+ actor_id: _actors.ActorId = None
76
+
77
+ job_result: _cfg.JobResult = None
78
+ job_error: Exception = None
79
+
80
+
69
81
  class TracEngine(_actors.Actor):
70
82
 
71
83
  """
@@ -88,7 +100,7 @@ class TracEngine(_actors.Actor):
88
100
  self._storage = storage
89
101
  self._notify_callback = notify_callback
90
102
 
91
- self._job_actors = dict()
103
+ self._jobs: tp.Dict[str, _JobState] = dict()
92
104
 
93
105
  def on_start(self):
94
106
 
@@ -98,7 +110,7 @@ class TracEngine(_actors.Actor):
98
110
 
99
111
  self._log.info("Engine shutdown complete")
100
112
 
101
- def on_signal(self, signal: Signal) -> tp.Optional[bool]:
113
+ def on_signal(self, signal: _actors.Signal) -> tp.Optional[bool]:
102
114
 
103
115
  # Failed signals can propagate from leaf nodes up the actor tree for a job
104
116
  # If the failure goes all the way up the tree without being handled, it will reach the engine node
@@ -110,8 +122,8 @@ class TracEngine(_actors.Actor):
110
122
  failed_job_key = None
111
123
 
112
124
  # Look for the job key corresponding to the failed actor
113
- for job_key, job_actor in self._job_actors.items():
114
- if job_actor == signal.sender:
125
+ for job_key, job_state in self._jobs.items():
126
+ if job_state.actor_id == signal.sender:
115
127
  failed_job_key = job_key
116
128
 
117
129
  # If the job is still live, call job_failed explicitly
@@ -147,19 +159,34 @@ class TracEngine(_actors.Actor):
147
159
  job_processor = JobProcessor(job_key, job_config, result_spec,self._models, self._storage)
148
160
  job_actor_id = self.actors().spawn(job_processor)
149
161
 
150
- job_actors = {**self._job_actors, job_key: job_actor_id}
151
- self._job_actors = job_actors
162
+ job_state = _JobState(job_config.jobId, job_config)
163
+ job_state.actor_id = job_actor_id
164
+
165
+ self._jobs[job_key] = job_state
166
+
167
+ @_actors.Message
168
+ def get_job_list(self):
169
+
170
+ job_list = list(map(self._get_job_info, self._jobs.keys()))
171
+ self.actors().reply("job_list", job_list)
172
+
173
+ @_actors.Message
174
+ def get_job_details(self, job_key: str, details: bool):
175
+
176
+ details = self._get_job_info(job_key, details)
177
+ self.actors().reply("job_details", details)
152
178
 
153
179
  @_actors.Message
154
180
  def job_succeeded(self, job_key: str, job_result: _cfg.JobResult):
155
181
 
156
182
  # Ignore duplicate messages from the job processor (can happen in unusual error cases)
157
- if job_key not in self._job_actors:
183
+ if job_key not in self._jobs:
158
184
  self._log.warning(f"Ignoring [job_succeeded] message, job [{job_key}] has already completed")
159
185
  return
160
186
 
161
187
  self._log.info(f"Recording job as successful: {job_key}")
162
188
 
189
+ self._jobs[job_key].job_result = job_result
163
190
  self._finalize_job(job_key)
164
191
 
165
192
  if self._notify_callback is not None:
@@ -169,12 +196,13 @@ class TracEngine(_actors.Actor):
169
196
  def job_failed(self, job_key: str, error: Exception):
170
197
 
171
198
  # Ignore duplicate messages from the job processor (can happen in unusual error cases)
172
- if job_key not in self._job_actors:
199
+ if job_key not in self._jobs:
173
200
  self._log.warning(f"Ignoring [job_failed] message, job [{job_key}] has already completed")
174
201
  return
175
202
 
176
203
  self._log.error(f"Recording job as failed: {job_key}")
177
204
 
205
+ self._jobs[job_key].job_error = error
178
206
  self._finalize_job(job_key)
179
207
 
180
208
  if self._notify_callback is not None:
@@ -182,10 +210,47 @@ class TracEngine(_actors.Actor):
182
210
 
183
211
  def _finalize_job(self, job_key: str):
184
212
 
185
- job_actors = self._job_actors
186
- job_actor_id = job_actors.pop(job_key)
187
- self.actors().stop(job_actor_id)
188
- self._job_actors = job_actors
213
+ # Stop the actor but keep the job state available for status / results queries
214
+
215
+ # In the future, job state will need to be expunged after some period of time
216
+ # For now each instance of the runtime only processes one job so no need to worry
217
+
218
+ job_state = self._jobs.get(job_key)
219
+ job_actor_id = job_state.actor_id if job_state is not None else None
220
+
221
+ if job_actor_id is not None:
222
+ self.actors().stop(job_actor_id)
223
+ job_state.actor_id = None
224
+
225
+ def _get_job_info(self, job_key: str, details: bool = False) -> tp.Optional[_cfg.JobResult]:
226
+
227
+ job_state = self._jobs.get(job_key)
228
+
229
+ if job_state is None:
230
+ return None
231
+
232
+ job_result = _cfg.JobResult()
233
+ job_result.jobId = job_state.job_id
234
+
235
+ if job_state.actor_id is not None:
236
+ job_result.statusCode = _meta.JobStatusCode.RUNNING
237
+
238
+ elif job_state.job_result is not None:
239
+ job_result.statusCode = job_state.job_result.statusCode
240
+ job_result.statusMessage = job_state.job_result.statusMessage
241
+ if details:
242
+ job_result.results = job_state.job_result.results or dict()
243
+
244
+ elif job_state.job_error is not None:
245
+ job_result.statusCode = _meta.JobStatusCode.FAILED
246
+ job_result.statusMessage = str(job_state.job_error.args[0])
247
+
248
+ else:
249
+ # Alternatively return UNKNOWN status or throw an error here
250
+ job_result.statusCode = _meta.JobStatusCode.FAILED
251
+ job_result.statusMessage = "No details available"
252
+
253
+ return job_result
189
254
 
190
255
 
191
256
  class JobProcessor(_actors.Actor):
@@ -218,7 +283,7 @@ class JobProcessor(_actors.Actor):
218
283
  self._log.info(f"Cleaning up job [{self.job_key}]")
219
284
  self._models.destroy_scope(self.job_key)
220
285
 
221
- def on_signal(self, signal: Signal) -> tp.Optional[bool]:
286
+ def on_signal(self, signal: _actors.Signal) -> tp.Optional[bool]:
222
287
 
223
288
  if signal.message == _actors.SignalNames.FAILED and isinstance(signal, _actors.ErrorSignal):
224
289