runnable 0.16.0__py3-none-any.whl → 0.17.1__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.
- runnable/context.py +3 -1
- runnable/datastore.py +14 -3
- runnable/executor.py +1 -3
- runnable/sdk.py +40 -99
- {runnable-0.16.0.dist-info → runnable-0.17.1.dist-info}/METADATA +1 -1
- {runnable-0.16.0.dist-info → runnable-0.17.1.dist-info}/RECORD +9 -9
- {runnable-0.16.0.dist-info → runnable-0.17.1.dist-info}/WHEEL +0 -0
- {runnable-0.16.0.dist-info → runnable-0.17.1.dist-info}/entry_points.txt +0 -0
- {runnable-0.16.0.dist-info → runnable-0.17.1.dist-info}/licenses/LICENSE +0 -0
runnable/context.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Dict, List, Optional
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
2
|
|
3
3
|
from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny
|
4
4
|
from rich.progress import Progress
|
@@ -29,6 +29,8 @@ class Context(BaseModel):
|
|
29
29
|
from_sdk: bool = False
|
30
30
|
|
31
31
|
run_id: str = ""
|
32
|
+
object_serialisation: bool = True
|
33
|
+
return_objects: Dict[str, Any] = {}
|
32
34
|
|
33
35
|
tag: str = ""
|
34
36
|
variables: Dict[str, str] = {}
|
runnable/datastore.py
CHANGED
@@ -98,22 +98,33 @@ class ObjectParameter(BaseModel):
|
|
98
98
|
@computed_field # type: ignore
|
99
99
|
@property
|
100
100
|
def description(self) -> str:
|
101
|
-
|
101
|
+
if context.run_context.object_serialisation:
|
102
|
+
return f"Pickled object stored in catalog as: {self.value}"
|
103
|
+
|
104
|
+
return f"Object stored in memory as: {self.value}"
|
102
105
|
|
103
106
|
@property
|
104
107
|
def file_name(self) -> str:
|
105
108
|
return f"{self.value}{context.run_context.pickler.extension}"
|
106
109
|
|
107
110
|
def get_value(self) -> Any:
|
108
|
-
#
|
109
|
-
|
111
|
+
# If there was no serialisation, return the object from the return objects
|
112
|
+
if not context.run_context.object_serialisation:
|
113
|
+
return context.run_context.return_objects[self.value]
|
110
114
|
|
115
|
+
# If the object was serialised, get it from the catalog
|
116
|
+
catalog_handler = context.run_context.catalog_handler
|
111
117
|
catalog_handler.get(name=self.file_name, run_id=context.run_context.run_id)
|
112
118
|
obj = context.run_context.pickler.load(path=self.file_name)
|
113
119
|
os.remove(self.file_name) # Remove after loading
|
114
120
|
return obj
|
115
121
|
|
116
122
|
def put_object(self, data: Any) -> None:
|
123
|
+
if not context.run_context.object_serialisation:
|
124
|
+
context.run_context.return_objects[self.value] = data
|
125
|
+
return
|
126
|
+
|
127
|
+
# If the object was serialised, put it in the catalog
|
117
128
|
context.run_context.pickler.dump(data=data, path=self.file_name)
|
118
129
|
|
119
130
|
catalog_handler = context.run_context.catalog_handler
|
runnable/executor.py
CHANGED
@@ -34,9 +34,7 @@ class BaseExecutor(ABC, BaseModel):
|
|
34
34
|
service_name: str = ""
|
35
35
|
service_type: str = "executor"
|
36
36
|
|
37
|
-
_is_local: bool = (
|
38
|
-
False # This is a flag to indicate whether the executor is local or not.
|
39
|
-
)
|
37
|
+
_is_local: bool = PrivateAttr(default=False)
|
40
38
|
|
41
39
|
model_config = ConfigDict(extra="forbid")
|
42
40
|
|
runnable/sdk.py
CHANGED
@@ -84,7 +84,7 @@ class BaseTraversal(ABC, BaseModel):
|
|
84
84
|
next_node: str = Field(default="", serialization_alias="next_node")
|
85
85
|
terminate_with_success: bool = Field(default=False, exclude=True)
|
86
86
|
terminate_with_failure: bool = Field(default=False, exclude=True)
|
87
|
-
on_failure:
|
87
|
+
on_failure: Optional[Pipeline] = Field(default=None)
|
88
88
|
|
89
89
|
model_config = ConfigDict(extra="forbid")
|
90
90
|
|
@@ -117,18 +117,6 @@ class BaseTraversal(ABC, BaseModel):
|
|
117
117
|
|
118
118
|
return other
|
119
119
|
|
120
|
-
def depends_on(self, node: StepType) -> Self:
|
121
|
-
assert not isinstance(node, Success)
|
122
|
-
assert not isinstance(node, Fail)
|
123
|
-
|
124
|
-
if node.next_node:
|
125
|
-
raise Exception(
|
126
|
-
f"The {node} node already has a next node: {node.next_node}"
|
127
|
-
)
|
128
|
-
|
129
|
-
node.next_node = self.name
|
130
|
-
return self
|
131
|
-
|
132
120
|
@model_validator(mode="after")
|
133
121
|
def validate_terminations(self) -> Self:
|
134
122
|
if self.terminate_with_failure and self.terminate_with_success:
|
@@ -175,7 +163,6 @@ class BaseTask(BaseTraversal):
|
|
175
163
|
if isinstance(x, str):
|
176
164
|
task_returns.append(TaskReturns(name=x, kind="json"))
|
177
165
|
continue
|
178
|
-
|
179
166
|
# Its already task returns
|
180
167
|
task_returns.append(x)
|
181
168
|
|
@@ -188,6 +175,9 @@ class BaseTask(BaseTraversal):
|
|
188
175
|
"A node not being terminated must have a user defined next node"
|
189
176
|
)
|
190
177
|
|
178
|
+
if self.on_failure:
|
179
|
+
self.on_failure = self.on_failure.steps[0].name # type: ignore
|
180
|
+
|
191
181
|
return TaskNode.parse_from_config(
|
192
182
|
self.model_dump(exclude_none=True, by_alias=True)
|
193
183
|
)
|
@@ -605,8 +595,6 @@ class Pipeline(BaseModel):
|
|
605
595
|
The order of steps is important as it determines the order of execution.
|
606
596
|
Any on failure behavior should the first step in ```on_failure``` pipelines.
|
607
597
|
|
608
|
-
|
609
|
-
|
610
598
|
on_failure (List[List[Pipeline], optional): A list of Pipelines to execute in case of failure.
|
611
599
|
|
612
600
|
For example, for the below pipeline:
|
@@ -624,7 +612,7 @@ class Pipeline(BaseModel):
|
|
624
612
|
|
625
613
|
"""
|
626
614
|
|
627
|
-
steps: List[
|
615
|
+
steps: List[StepType]
|
628
616
|
name: str = ""
|
629
617
|
description: str = ""
|
630
618
|
|
@@ -637,114 +625,67 @@ class Pipeline(BaseModel):
|
|
637
625
|
_dag: graph.Graph = PrivateAttr()
|
638
626
|
model_config = ConfigDict(extra="forbid")
|
639
627
|
|
640
|
-
def _validate_path(self, path: List[StepType], failure_path: bool = False) -> None:
|
641
|
-
# TODO: Drastically simplify this
|
642
|
-
# Check if one and only one step terminates with success
|
643
|
-
# Check no more than one step terminates with failure
|
644
|
-
|
645
|
-
reached_success = False
|
646
|
-
reached_failure = False
|
647
|
-
|
648
|
-
for step in path:
|
649
|
-
if step.terminate_with_success:
|
650
|
-
if reached_success:
|
651
|
-
raise Exception(
|
652
|
-
"A pipeline cannot have more than one step that terminates with success"
|
653
|
-
)
|
654
|
-
reached_success = True
|
655
|
-
continue
|
656
|
-
if step.terminate_with_failure:
|
657
|
-
if reached_failure:
|
658
|
-
raise Exception(
|
659
|
-
"A pipeline cannot have more than one step that terminates with failure"
|
660
|
-
)
|
661
|
-
reached_failure = True
|
662
|
-
|
663
|
-
if not reached_success and not reached_failure:
|
664
|
-
raise Exception(
|
665
|
-
"A pipeline must have at least one step that terminates with success"
|
666
|
-
)
|
667
|
-
|
668
|
-
def _construct_path(self, path: List[StepType]) -> None:
|
669
|
-
prev_step = path[0]
|
670
|
-
|
671
|
-
for step in path:
|
672
|
-
if step == prev_step:
|
673
|
-
continue
|
674
|
-
|
675
|
-
if prev_step.terminate_with_success or prev_step.terminate_with_failure:
|
676
|
-
raise Exception(
|
677
|
-
f"A step that terminates with success/failure cannot have a next step: {prev_step}"
|
678
|
-
)
|
679
|
-
|
680
|
-
if prev_step.next_node and prev_step.next_node not in ["success", "fail"]:
|
681
|
-
raise Exception(f"Step already has a next node: {prev_step} ")
|
682
|
-
|
683
|
-
prev_step.next_node = step.name
|
684
|
-
prev_step = step
|
685
|
-
|
686
628
|
def model_post_init(self, __context: Any) -> None:
|
687
629
|
"""
|
688
630
|
The sequence of steps can either be:
|
689
|
-
[step1, step2,..., stepN
|
631
|
+
[step1, step2,..., stepN]
|
690
632
|
indicates:
|
691
633
|
- step1 > step2 > ... > stepN
|
692
634
|
- We expect terminate with success or fail to be explicitly stated on a step.
|
693
635
|
- If it is stated, the step cannot have a next step defined apart from "success" and "fail".
|
694
|
-
|
695
|
-
The inner list of steps is only to accommodate on-failure behaviors.
|
696
|
-
- For sake of simplicity, lets assume that it has the same behavior as the happy pipeline.
|
697
|
-
- A task which was already seen should not be part of this.
|
698
|
-
- There should be at least one step which terminates with success
|
699
|
-
|
700
636
|
Any definition of pipeline should have one node that terminates with success.
|
701
637
|
"""
|
702
|
-
#
|
703
|
-
#
|
638
|
+
# The last step of the pipeline is defaulted to be a success step
|
639
|
+
# unless it is explicitly stated to terminate with failure.
|
640
|
+
terminal_step: StepType = self.steps[-1]
|
641
|
+
if not terminal_step.terminate_with_failure:
|
642
|
+
terminal_step.terminate_with_success = True
|
704
643
|
|
705
|
-
|
706
|
-
|
644
|
+
# assert that there is only one termination node with success or failure
|
645
|
+
# Assert that there are no duplicate step names
|
646
|
+
observed: Dict[str, str] = {}
|
647
|
+
count_termination: int = 0
|
707
648
|
|
708
649
|
for step in self.steps:
|
709
650
|
if isinstance(
|
710
651
|
step, (Stub, PythonTask, NotebookTask, ShellTask, Parallel, Map)
|
711
652
|
):
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
# Check all paths are valid and construct the path
|
720
|
-
paths = [success_path] + on_failure_paths
|
721
|
-
failure_path = False
|
722
|
-
for path in paths:
|
723
|
-
self._validate_path(path, failure_path)
|
724
|
-
self._construct_path(path)
|
725
|
-
|
726
|
-
failure_path = True
|
653
|
+
if step.terminate_with_success or step.terminate_with_failure:
|
654
|
+
count_termination += 1
|
655
|
+
if step.name in observed:
|
656
|
+
raise Exception(
|
657
|
+
f"Step names should be unique. Found duplicate: {step.name}"
|
658
|
+
)
|
659
|
+
observed[step.name] = step.name
|
727
660
|
|
728
|
-
|
661
|
+
if count_termination > 1:
|
662
|
+
raise AssertionError(
|
663
|
+
"A pipeline can only have one termination node with success or failure"
|
664
|
+
)
|
729
665
|
|
730
|
-
|
731
|
-
|
732
|
-
|
666
|
+
# link the steps by assigning the next_node name to be that name of the node
|
667
|
+
# immediately after it.
|
668
|
+
for i in range(len(self.steps) - 1):
|
669
|
+
self.steps[i] >> self.steps[i + 1]
|
733
670
|
|
734
|
-
|
735
|
-
|
671
|
+
# Add any on_failure pipelines to the steps
|
672
|
+
gathered_on_failure: List[StepType] = []
|
673
|
+
for step in self.steps:
|
674
|
+
if step.on_failure:
|
675
|
+
gathered_on_failure.extend(step.on_failure.steps)
|
736
676
|
|
737
677
|
self._dag = graph.Graph(
|
738
|
-
start_at=
|
678
|
+
start_at=self.steps[0].name,
|
739
679
|
description=self.description,
|
740
680
|
internal_branch_name=self.internal_branch_name,
|
741
681
|
)
|
742
682
|
|
743
|
-
|
683
|
+
self.steps.extend(gathered_on_failure)
|
684
|
+
|
685
|
+
for step in self.steps:
|
744
686
|
self._dag.add_node(step.create_node())
|
745
687
|
|
746
|
-
|
747
|
-
self._dag.add_terminal_nodes()
|
688
|
+
self._dag.add_terminal_nodes()
|
748
689
|
|
749
690
|
self._dag.check_graph()
|
750
691
|
|
@@ -1,23 +1,23 @@
|
|
1
1
|
runnable/__init__.py,sha256=KqpLDTD1CfdEj2aDyEkSn2KW-_83qyrRrrWLc5lZVM4,624
|
2
2
|
runnable/catalog.py,sha256=MiEmb-18liAKmgeMdDF41VVn0ZEAVLP8hR33oacQ1zs,4930
|
3
3
|
runnable/cli.py,sha256=01zmzOdynEmLI4vWDtSHQ6y1od_Jlc8G1RF69fi2L8g,8446
|
4
|
-
runnable/context.py,sha256=
|
5
|
-
runnable/datastore.py,sha256=
|
4
|
+
runnable/context.py,sha256=by5uepmuCP0dmM9BmsliXihSes5QEFejwAsmekcqylE,1388
|
5
|
+
runnable/datastore.py,sha256=9y5enzn6AXLHLdwvgkdjGPrBkVlrcjfbaAHsst-lJzg,32466
|
6
6
|
runnable/defaults.py,sha256=3o9IVGryyCE6PoQTOoaIaHHTbJGEzmdXMcwzOhwAYoI,3518
|
7
7
|
runnable/entrypoints.py,sha256=67gPBiIIS4Kd9g6LdoGCraRJPda8K1i7Lp7XcD2iY5k,18913
|
8
8
|
runnable/exceptions.py,sha256=LFbp0-Qxg2PAMLEVt7w2whhBxSG-5pzUEv5qN-Rc4_c,3003
|
9
|
-
runnable/executor.py,sha256=
|
9
|
+
runnable/executor.py,sha256=Rafu9EECrNq1LBkJmS6KYCekchP5ufrR04mHWG-JzqQ,15543
|
10
10
|
runnable/graph.py,sha256=jVjikRLR-so3b2ufmNKpEQ_Ny68qN4bcGDAdXBRKiCY,16574
|
11
11
|
runnable/names.py,sha256=vn92Kv9ANROYSZX6Z4z1v_WA3WiEdIYmG6KEStBFZug,8134
|
12
12
|
runnable/nodes.py,sha256=YU9u7r1ESzui1uVtJ1dgwdv1ozyJnF2k-MCFieT8CLI,17519
|
13
13
|
runnable/parameters.py,sha256=g_bJurLjuppFDiDpfFqy6BRF36o_EY0OC5APl7HJFok,5450
|
14
14
|
runnable/pickler.py,sha256=ydJ_eti_U1F4l-YacFp7BWm6g5vTn04UXye25S1HVok,2684
|
15
|
-
runnable/sdk.py,sha256=
|
15
|
+
runnable/sdk.py,sha256=xN5F4XX8r5wCN131kgN2xG7MkNm0bSGJ3Ukw8prHYJ8,31444
|
16
16
|
runnable/secrets.py,sha256=PXcEJw-4WPzeWRLfsatcPPyr1zkqgHzdRWRcS9vvpvM,2354
|
17
17
|
runnable/tasks.py,sha256=JnIIYQf3YUidHXIN6hiUIfDnegc7_rJMNXuHW4WS9ig,29378
|
18
18
|
runnable/utils.py,sha256=wqyN7lMW56cBqyE59iDE6_i2HXPkvEUCQ-66UQnIwTA,19993
|
19
|
-
runnable-0.
|
20
|
-
runnable-0.
|
21
|
-
runnable-0.
|
22
|
-
runnable-0.
|
23
|
-
runnable-0.
|
19
|
+
runnable-0.17.1.dist-info/METADATA,sha256=ST_BmhGguYwYrDH0DlUEhR9QCJz9QM09BraYWO9TJbU,10102
|
20
|
+
runnable-0.17.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
21
|
+
runnable-0.17.1.dist-info/entry_points.txt,sha256=I92DYldRrCb9HCsoum8GjC2UsQrWpuw2kawXTZpkIz4,1559
|
22
|
+
runnable-0.17.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
23
|
+
runnable-0.17.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|