tracdap-runtime 0.8.0rc1__py3-none-any.whl → 0.9.0b1__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 (56) hide show
  1. tracdap/rt/_impl/core/data.py +578 -33
  2. tracdap/rt/_impl/core/repos.py +7 -0
  3. tracdap/rt/_impl/core/storage.py +10 -3
  4. tracdap/rt/_impl/core/util.py +54 -11
  5. tracdap/rt/_impl/exec/dev_mode.py +122 -100
  6. tracdap/rt/_impl/exec/engine.py +178 -109
  7. tracdap/rt/_impl/exec/functions.py +218 -257
  8. tracdap/rt/_impl/exec/graph.py +140 -125
  9. tracdap/rt/_impl/exec/graph_builder.py +411 -449
  10. tracdap/rt/_impl/grpc/codec.py +4 -2
  11. tracdap/rt/_impl/grpc/server.py +7 -7
  12. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +25 -18
  13. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.pyi +27 -9
  14. tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
  15. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +40 -0
  16. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.pyi +62 -0
  17. tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
  18. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +1 -1
  19. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +1 -1
  20. tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
  21. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +67 -63
  22. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +11 -2
  23. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +1 -1
  24. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +3 -3
  25. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +4 -0
  26. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +8 -6
  27. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +8 -2
  28. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +18 -5
  29. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +42 -2
  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 +1 -1
  36. tracdap/rt/_impl/runtime.py +8 -0
  37. tracdap/rt/_plugins/repo_git.py +56 -11
  38. tracdap/rt/_version.py +1 -1
  39. tracdap/rt/config/__init__.py +6 -4
  40. tracdap/rt/config/common.py +5 -0
  41. tracdap/rt/config/dynamic.py +28 -0
  42. tracdap/rt/config/job.py +13 -3
  43. tracdap/rt/config/result.py +8 -4
  44. tracdap/rt/config/runtime.py +2 -0
  45. tracdap/rt/metadata/__init__.py +37 -30
  46. tracdap/rt/metadata/config.py +95 -0
  47. tracdap/rt/metadata/job.py +2 -0
  48. tracdap/rt/metadata/object.py +6 -0
  49. tracdap/rt/metadata/object_id.py +4 -0
  50. tracdap/rt/metadata/resource.py +41 -1
  51. tracdap/rt/metadata/storage.py +9 -0
  52. {tracdap_runtime-0.8.0rc1.dist-info → tracdap_runtime-0.9.0b1.dist-info}/METADATA +5 -2
  53. {tracdap_runtime-0.8.0rc1.dist-info → tracdap_runtime-0.9.0b1.dist-info}/RECORD +56 -52
  54. {tracdap_runtime-0.8.0rc1.dist-info → tracdap_runtime-0.9.0b1.dist-info}/WHEEL +1 -1
  55. {tracdap_runtime-0.8.0rc1.dist-info → tracdap_runtime-0.9.0b1.dist-info/licenses}/LICENSE +0 -0
  56. {tracdap_runtime-0.8.0rc1.dist-info → tracdap_runtime-0.9.0b1.dist-info}/top_level.txt +0 -0
@@ -13,18 +13,18 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import typing as tp
17
- import dataclasses as dc
16
+ import typing as _tp
17
+ import dataclasses as _dc
18
18
 
19
19
  import tracdap.rt._impl.core.data as _data
20
- import tracdap.rt.metadata as meta
21
- import tracdap.rt.config as cfg
20
+ import tracdap.rt.metadata as _meta
21
+ import tracdap.rt.config as _cfg
22
22
 
23
23
 
24
- _T = tp.TypeVar('_T')
24
+ _T = _tp.TypeVar('_T')
25
25
 
26
26
 
27
- @dc.dataclass(frozen=True)
27
+ @_dc.dataclass(frozen=True)
28
28
  class NodeNamespace:
29
29
 
30
30
  __ROOT = None
@@ -36,7 +36,7 @@ class NodeNamespace:
36
36
  return cls.__ROOT
37
37
 
38
38
  name: str
39
- parent: "tp.Optional[NodeNamespace]" = dc.field(default_factory=lambda: NodeNamespace.root())
39
+ parent: "_tp.Optional[NodeNamespace]" = _dc.field(default_factory=lambda: NodeNamespace.root())
40
40
 
41
41
  def __str__(self):
42
42
  if self is self.__ROOT:
@@ -56,17 +56,17 @@ class NodeNamespace:
56
56
  return [self.name] + self.parent.components()
57
57
 
58
58
 
59
- @dc.dataclass(frozen=True)
60
- class NodeId(tp.Generic[_T]):
59
+ @_dc.dataclass(frozen=True)
60
+ class NodeId(_tp.Generic[_T]):
61
61
 
62
62
  @staticmethod
63
- def of(name: str, namespace: NodeNamespace, result_type: tp.Type[_T]) -> "NodeId[_T]":
63
+ def of(name: str, namespace: NodeNamespace, result_type: _tp.Type[_T]) -> "NodeId[_T]":
64
64
  return NodeId(name, namespace, result_type)
65
65
 
66
66
  name: str
67
67
  namespace: NodeNamespace
68
68
 
69
- result_type: tp.Type[_T] = dc.field(default=type(None), init=True, compare=False, hash=False)
69
+ result_type: _tp.Type[_T] = _dc.field(default=type(None), init=True, compare=False, hash=False)
70
70
 
71
71
  def __str__(self):
72
72
  return f"{self.name} / {self.namespace}"
@@ -75,14 +75,14 @@ class NodeId(tp.Generic[_T]):
75
75
  return f"{self.name} / {repr(self.namespace)}"
76
76
 
77
77
 
78
- @dc.dataclass(frozen=True)
78
+ @_dc.dataclass(frozen=True)
79
79
  class DependencyType:
80
80
 
81
81
  immediate: bool = True
82
82
  tolerant: bool = False
83
83
 
84
- HARD: "tp.ClassVar[DependencyType]"
85
- TOLERANT: "tp.ClassVar[DependencyType]"
84
+ HARD: "_tp.ClassVar[DependencyType]"
85
+ TOLERANT: "_tp.ClassVar[DependencyType]"
86
86
 
87
87
 
88
88
  DependencyType.HARD = DependencyType(immediate=True, tolerant=False)
@@ -91,31 +91,31 @@ DependencyType.TOLERANT = DependencyType(immediate=True, tolerant=True)
91
91
  DependencyType.DELAYED = DependencyType(immediate=False, tolerant=False)
92
92
 
93
93
 
94
- @dc.dataclass(frozen=True)
94
+ @_dc.dataclass(frozen=True)
95
95
  class Dependency:
96
96
 
97
97
  node_id: NodeId
98
98
  dependency_type: DependencyType
99
99
 
100
100
 
101
- @dc.dataclass(frozen=True)
102
- class Node(tp.Generic[_T]):
101
+ @_dc.dataclass(frozen=True)
102
+ class Node(_tp.Generic[_T]):
103
103
 
104
104
  """A node in the TRAC execution graph"""
105
105
 
106
106
  id: NodeId[_T]
107
107
  """ID of this node"""
108
108
 
109
- dependencies: tp.Dict[NodeId, DependencyType] = dc.field(init=False, default_factory=dict)
109
+ dependencies: _tp.Dict[NodeId, DependencyType] = _dc.field(init=False, default_factory=dict)
110
110
  """Set of node IDs that are dependencies for this node"""
111
111
 
112
- bundle_result: bool = dc.field(init=False, default=False)
112
+ bundle_result: bool = _dc.field(init=False, default=False)
113
113
  """Flag indicating whether the result of this node is a bundle"""
114
114
 
115
- bundle_namespace: tp.Optional[NodeNamespace] = dc.field(init=False, default=None)
115
+ bundle_namespace: _tp.Optional[NodeNamespace] = _dc.field(init=False, default=None)
116
116
  """If the result is a bundle, indicates the namespace that the bundle will be unpacked into"""
117
117
 
118
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
118
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
119
119
  return {}
120
120
 
121
121
 
@@ -129,11 +129,11 @@ def _node_type(node_class):
129
129
 
130
130
  class NodeBuilder(Node):
131
131
 
132
- explicit_deps: dc.InitVar[tp.List[NodeId]] = None
132
+ explicit_deps: _dc.InitVar[_tp.List[NodeId]] = None
133
133
 
134
- bundle: dc.InitVar[NodeNamespace] = None
134
+ bundle: _dc.InitVar[NodeNamespace] = None
135
135
 
136
- def __post_init__(self, explicit_deps: tp.List[NodeId], bundle: NodeNamespace):
136
+ def __post_init__(self, explicit_deps: _tp.List[NodeId], bundle: NodeNamespace):
137
137
  dependencies = self._node_dependencies()
138
138
  if explicit_deps:
139
139
  dependencies.update({dep_id: DependencyType.HARD for dep_id in explicit_deps})
@@ -148,37 +148,57 @@ def _node_type(node_class):
148
148
 
149
149
  node_class.__annotations__.update(NodeBuilder.__annotations__)
150
150
 
151
- return dc.dataclass(frozen=True)(node_class)
151
+ return _dc.dataclass(frozen=True)(node_class)
152
152
 
153
153
 
154
- NodeMap = tp.Dict[NodeId, Node]
154
+ NodeMap = _tp.Dict[NodeId, Node]
155
155
 
156
156
 
157
- @dc.dataclass(frozen=True)
157
+ @_dc.dataclass(frozen=True)
158
+ class GraphContext:
159
+
160
+ job_id: _meta.TagHeader
161
+ job_namespace: NodeNamespace
162
+ ctx_namespace: NodeNamespace
163
+
164
+ storage_config: _cfg.StorageConfig
165
+
166
+
167
+ @_dc.dataclass(frozen=True)
158
168
  class Graph:
159
169
 
160
170
  nodes: NodeMap
161
171
  root_id: NodeId
162
172
 
163
173
 
164
- @dc.dataclass(frozen=False)
174
+ @_dc.dataclass(frozen=False)
165
175
  class GraphSection:
166
176
 
167
177
  nodes: NodeMap
168
- inputs: tp.Set[NodeId] = dc.field(default_factory=set)
169
- outputs: tp.Set[NodeId] = dc.field(default_factory=set)
170
- must_run: tp.List[NodeId] = dc.field(default_factory=list)
178
+ inputs: _tp.Set[NodeId] = _dc.field(default_factory=set)
179
+ outputs: _tp.Set[NodeId] = _dc.field(default_factory=set)
180
+ must_run: _tp.List[NodeId] = _dc.field(default_factory=list)
181
+
171
182
 
183
+ @_dc.dataclass(frozen=False)
184
+ class GraphUpdate:
172
185
 
173
- Bundle: tp.Generic[_T] = tp.Dict[str, _T]
174
- ObjectBundle = Bundle[meta.ObjectDefinition]
186
+ nodes: NodeMap = _dc.field(default_factory=dict)
187
+ dependencies: _tp.Dict[NodeId, _tp.List[Dependency]] = _dc.field(default_factory=dict)
175
188
 
176
189
 
177
- @dc.dataclass(frozen=True)
178
- class JobOutputs:
190
+ @_dc.dataclass
191
+ class GraphOutput:
179
192
 
180
- objects: tp.Dict[str, NodeId[meta.ObjectDefinition]] = dc.field(default_factory=dict)
181
- bundles: tp.List[NodeId[ObjectBundle]] = dc.field(default_factory=list)
193
+ objectId: _meta.TagHeader
194
+ definition: _meta.ObjectDefinition
195
+ attrs: _tp.List[_meta.TagUpdate] = _dc.field(default_factory=list)
196
+
197
+
198
+ Bundle = _tp.Dict[str, _T]
199
+ ObjectBundle = Bundle[_meta.ObjectDefinition]
200
+
201
+ JOB_OUTPUT_TYPE = _tp.Union[GraphOutput, _data.DataSpec]
182
202
 
183
203
 
184
204
  # ----------------------------------------------------------------------------------------------------------------------
@@ -186,6 +206,8 @@ class JobOutputs:
186
206
  # ----------------------------------------------------------------------------------------------------------------------
187
207
 
188
208
 
209
+ # STATIC VALUES
210
+
189
211
  @_node_type
190
212
  class NoopNode(Node):
191
213
  pass
@@ -196,28 +218,53 @@ class StaticValueNode(Node[_T]):
196
218
  value: _T
197
219
 
198
220
 
221
+ # MAPPING OPERATIONS
222
+
199
223
  class MappingNode(Node[_T]):
200
224
  pass
201
225
 
202
226
 
227
+ @_node_type
228
+ class IdentityNode(MappingNode[_T]):
229
+
230
+ """Map one graph node directly from another (identity function)"""
231
+
232
+ src_id: NodeId[_T]
233
+
234
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
235
+ return {self.src_id: DependencyType.HARD}
236
+
237
+
238
+ @_node_type
239
+ class KeyedItemNode(MappingNode[_T]):
240
+
241
+ """Map a graph node from a keyed item in an existing node (dictionary lookup)"""
242
+
243
+ src_id: NodeId[Bundle[_T]]
244
+ src_item: str
245
+
246
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
247
+ return {self.src_id: DependencyType.HARD}
248
+
249
+
203
250
  @_node_type
204
251
  class BundleItemNode(MappingNode[_T]):
205
252
 
206
253
  bundle_id: NodeId[Bundle[_T]]
207
254
  bundle_item: str
208
255
 
209
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
256
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
210
257
  return {self.bundle_id: DependencyType.HARD}
211
258
 
212
259
 
213
260
  @_node_type
214
- class ContextPushNode(Node[Bundle[tp.Any]]):
261
+ class ContextPushNode(Node[Bundle[_tp.Any]]):
215
262
 
216
263
  """Push a new execution context onto the stack"""
217
264
 
218
265
  namespace: NodeNamespace
219
266
 
220
- mapping: tp.Dict[NodeId, NodeId] = dc.field(default_factory=dict)
267
+ mapping: _tp.Dict[NodeId, NodeId] = _dc.field(default_factory=dict)
221
268
  """Mapping of node IDs from the inner to the outer context (i.e. keys are in the context being pushed)"""
222
269
 
223
270
  def _node_dependencies(self):
@@ -226,11 +273,11 @@ class ContextPushNode(Node[Bundle[tp.Any]]):
226
273
 
227
274
 
228
275
  @_node_type
229
- class ContextPopNode(Node[Bundle[tp.Any]]):
276
+ class ContextPopNode(Node[Bundle[_tp.Any]]):
230
277
 
231
278
  namespace: NodeNamespace
232
279
 
233
- mapping: tp.Dict[NodeId, NodeId] = dc.field(default_factory=dict)
280
+ mapping: _tp.Dict[NodeId, NodeId] = _dc.field(default_factory=dict)
234
281
  """Mapping of node IDs from the inner to the outer context (i.e. keys are in the context being popped)"""
235
282
 
236
283
  def _node_dependencies(self):
@@ -238,50 +285,33 @@ class ContextPopNode(Node[Bundle[tp.Any]]):
238
285
  return {nid: DependencyType.HARD for nid in self.mapping.keys()}
239
286
 
240
287
 
241
- @_node_type
242
- class IdentityNode(MappingNode[_T]):
243
-
244
- """Map one graph node directly from another (identity function)"""
245
-
246
- src_id: NodeId[_T]
247
-
248
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
249
- return {self.src_id: DependencyType.HARD}
288
+ # DATA HANDLING
250
289
 
251
290
 
252
291
  @_node_type
253
- class KeyedItemNode(MappingNode[_T]):
254
-
255
- """Map a graph node from a keyed item in an existing node (dictionary lookup)"""
256
-
257
- src_id: NodeId[Bundle[_T]]
258
- src_item: str
259
-
260
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
261
- return {self.src_id: DependencyType.HARD}
262
-
263
-
264
- @_node_type
265
- class DynamicDataSpecNode(Node[_data.DataSpec]):
292
+ class DataSpecNode(Node[_data.DataSpec]):
266
293
 
267
294
  data_view_id: NodeId[_data.DataView]
268
295
 
269
- data_obj_id: meta.TagHeader
270
- storage_obj_id: meta.TagHeader
296
+ data_obj_id: _meta.TagHeader
297
+ storage_obj_id: _meta.TagHeader
298
+ context_key: str
299
+
300
+ storage_config: _cfg.StorageConfig
271
301
 
272
- prior_data_spec: tp.Optional[_data.DataSpec]
302
+ prior_data_spec: _tp.Optional[_data.DataSpec]
273
303
 
274
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
304
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
275
305
  return {self.data_view_id: DependencyType.HARD}
276
306
 
277
307
 
278
308
  @_node_type
279
309
  class DataViewNode(Node[_data.DataView]):
280
310
 
281
- schema: meta.SchemaDefinition
311
+ schema: _meta.SchemaDefinition
282
312
  root_item: NodeId[_data.DataItem]
283
313
 
284
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
314
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
285
315
  return {self.root_item: DependencyType.HARD}
286
316
 
287
317
 
@@ -292,27 +322,10 @@ class DataItemNode(MappingNode[_data.DataItem]):
292
322
 
293
323
  data_view_id: NodeId[_data.DataView]
294
324
 
295
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
325
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
296
326
  return {self.data_view_id: DependencyType.HARD}
297
327
 
298
328
 
299
- @_node_type
300
- class DataResultNode(Node[ObjectBundle]):
301
-
302
- # TODO: Remove this node type
303
- # Either produce metadata in SaveDataNode, or handle DataSpec outputs in result processing nodes
304
-
305
- output_name: str
306
- data_save_id: NodeId[_data.DataSpec]
307
-
308
- data_key: str = None
309
- file_key: str = None
310
- storage_key: str = None
311
-
312
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
313
- return {self.data_save_id: DependencyType.HARD}
314
-
315
-
316
329
  @_node_type
317
330
  class LoadDataNode(Node[_data.DataItem]):
318
331
 
@@ -321,10 +334,10 @@ class LoadDataNode(Node[_data.DataItem]):
321
334
  The latest incarnation of the item will be loaded from any available copy
322
335
  """
323
336
 
324
- spec_id: tp.Optional[NodeId[_data.DataSpec]] = None
325
- spec: tp.Optional[_data.DataSpec] = None
337
+ spec_id: _tp.Optional[NodeId[_data.DataSpec]] = None
338
+ spec: _tp.Optional[_data.DataSpec] = None
326
339
 
327
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
340
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
328
341
  deps = dict()
329
342
  if self.spec_id is not None:
330
343
  deps[self.spec_id] = DependencyType.HARD
@@ -340,75 +353,77 @@ class SaveDataNode(Node[_data.DataSpec]):
340
353
 
341
354
  data_item_id: NodeId[_data.DataItem]
342
355
 
343
- spec_id: tp.Optional[NodeId[_data.DataSpec]] = None
344
- spec: tp.Optional[_data.DataSpec] = None
356
+ spec_id: _tp.Optional[NodeId[_data.DataSpec]] = None
357
+ spec: _tp.Optional[_data.DataSpec] = None
345
358
 
346
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
359
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
347
360
  deps = {self.data_item_id: DependencyType.HARD}
348
361
  if self.spec_id is not None:
349
362
  deps[self.spec_id] = DependencyType.HARD
350
363
  return deps
351
364
 
352
365
 
366
+ # MODEL EXECUTION
367
+
353
368
  @_node_type
354
- class ImportModelNode(Node[meta.ObjectDefinition]):
369
+ class ImportModelNode(Node[GraphOutput]):
355
370
 
356
- model_scope: str
357
- import_details: meta.ImportModelJob
371
+ model_id: _meta.TagHeader
372
+
373
+ import_details: _meta.ImportModelJob
374
+ import_scope: str
358
375
 
359
376
 
360
377
  @_node_type
361
378
  class RunModelNode(Node[Bundle[_data.DataView]]):
362
379
 
380
+ model_def: _meta.ModelDefinition
363
381
  model_scope: str
364
- model_def: meta.ModelDefinition
365
- parameter_ids: tp.FrozenSet[NodeId]
366
- input_ids: tp.FrozenSet[NodeId]
367
- storage_access: tp.Optional[tp.List[str]] = None
368
382
 
369
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
370
- return {dep_id: DependencyType.HARD for dep_id in [*self.parameter_ids, *self.input_ids]}
383
+ parameter_ids: _tp.FrozenSet[NodeId]
384
+ input_ids: _tp.FrozenSet[NodeId]
371
385
 
386
+ storage_access: _tp.Optional[_tp.List[str]] = None
372
387
 
373
- @_node_type
374
- class RunModelResultNode(Node[None]):
388
+ graph_context: _tp.Optional[GraphContext] = None
375
389
 
376
- model_id: NodeId
390
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
391
+ return {dep_id: DependencyType.HARD for dep_id in [*self.parameter_ids, *self.input_ids]}
377
392
 
378
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
379
- return {self.model_id: DependencyType.HARD}
380
393
 
394
+ # RESULTS PROCESSING
381
395
 
382
396
  @_node_type
383
- class RuntimeOutputsNode(Node[JobOutputs]):
397
+ class JobResultNode(Node[_cfg.JobResult]):
398
+
399
+ job_id: _meta.TagHeader
400
+ result_id: _meta.TagHeader
384
401
 
385
- outputs: JobOutputs
402
+ named_outputs: _tp.Dict[str, JOB_OUTPUT_TYPE] = _dc.field(default_factory=dict)
403
+ unnamed_outputs: _tp.List[NodeId[JOB_OUTPUT_TYPE]] = _dc.field(default_factory=list)
386
404
 
387
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
388
- dep_ids = [*self.outputs.bundles, *self.outputs.objects.values()]
405
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
406
+ dep_ids = [*self.named_outputs.values(), *self.unnamed_outputs]
389
407
  return {node_id: DependencyType.HARD for node_id in dep_ids}
390
408
 
391
409
 
392
410
  @_node_type
393
- class BuildJobResultNode(Node[cfg.JobResult]):
411
+ class DynamicOutputsNode(Node["DynamicOutputsNode"]):
394
412
 
395
- result_id: meta.TagHeader
396
- job_id: meta.TagHeader
413
+ named_outputs: _tp.Dict[str, JOB_OUTPUT_TYPE] = _dc.field(default_factory=dict)
414
+ unnamed_outputs: _tp.List[NodeId[JOB_OUTPUT_TYPE]] = _dc.field(default_factory=list)
397
415
 
398
- outputs: JobOutputs
399
- runtime_outputs: tp.Optional[NodeId[JobOutputs]] = None
400
-
401
- def _node_dependencies(self) -> tp.Dict[NodeId, DependencyType]:
402
- dep_ids = [*self.outputs.bundles, *self.outputs.objects.values()]
403
- if self.runtime_outputs is not None:
404
- dep_ids.append(self.runtime_outputs)
416
+ def _node_dependencies(self) -> _tp.Dict[NodeId, DependencyType]:
417
+ dep_ids = [*self.named_outputs.values(), *self.unnamed_outputs]
405
418
  return {node_id: DependencyType.HARD for node_id in dep_ids}
406
419
 
407
420
 
421
+ # MISC NODE TYPES
422
+
408
423
  @_node_type
409
- class ChildJobNode(Node[cfg.JobResult]):
424
+ class ChildJobNode(Node[_cfg.JobResult]):
410
425
 
411
- job_id: meta.TagHeader
412
- job_def: meta.JobDefinition
426
+ job_id: _meta.TagHeader
427
+ job_def: _meta.JobDefinition
413
428
 
414
429
  graph: Graph