runnable 0.35.0__py3-none-any.whl → 0.36.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.
- extensions/job_executor/__init__.py +3 -4
- extensions/job_executor/emulate.py +106 -0
- extensions/job_executor/k8s.py +8 -8
- extensions/job_executor/local_container.py +13 -14
- extensions/nodes/__init__.py +0 -0
- extensions/nodes/conditional.py +7 -5
- extensions/nodes/fail.py +72 -0
- extensions/nodes/map.py +350 -0
- extensions/nodes/parallel.py +159 -0
- extensions/nodes/stub.py +89 -0
- extensions/nodes/success.py +72 -0
- extensions/nodes/task.py +92 -0
- extensions/pipeline_executor/__init__.py +24 -26
- extensions/pipeline_executor/argo.py +18 -15
- extensions/pipeline_executor/emulate.py +112 -0
- extensions/pipeline_executor/local.py +4 -4
- extensions/pipeline_executor/local_container.py +19 -79
- extensions/pipeline_executor/mocked.py +4 -4
- extensions/pipeline_executor/retry.py +6 -10
- extensions/tasks/torch.py +1 -1
- runnable/__init__.py +0 -8
- runnable/catalog.py +1 -21
- runnable/cli.py +0 -59
- runnable/context.py +519 -28
- runnable/datastore.py +51 -54
- runnable/defaults.py +12 -34
- runnable/entrypoints.py +82 -440
- runnable/exceptions.py +35 -34
- runnable/executor.py +13 -20
- runnable/names.py +1 -1
- runnable/nodes.py +16 -15
- runnable/parameters.py +2 -2
- runnable/sdk.py +66 -163
- runnable/tasks.py +62 -21
- runnable/utils.py +6 -268
- {runnable-0.35.0.dist-info → runnable-0.36.0.dist-info}/METADATA +1 -1
- runnable-0.36.0.dist-info/RECORD +74 -0
- {runnable-0.35.0.dist-info → runnable-0.36.0.dist-info}/entry_points.txt +8 -7
- extensions/nodes/nodes.py +0 -778
- runnable-0.35.0.dist-info/RECORD +0 -66
- {runnable-0.35.0.dist-info → runnable-0.36.0.dist-info}/WHEEL +0 -0
- {runnable-0.35.0.dist-info → runnable-0.36.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,12 @@ import logging
|
|
2
2
|
from pathlib import Path
|
3
3
|
from typing import Dict
|
4
4
|
|
5
|
-
from pydantic import Field
|
5
|
+
from pydantic import Field, PrivateAttr
|
6
6
|
|
7
7
|
from extensions.pipeline_executor import GenericPipelineExecutor
|
8
|
-
from runnable import
|
8
|
+
from runnable import defaults, utils
|
9
9
|
from runnable.datastore import StepLog
|
10
|
-
from runnable.defaults import
|
10
|
+
from runnable.defaults import MapVariableType
|
11
11
|
from runnable.nodes import BaseNode
|
12
12
|
|
13
13
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
@@ -70,7 +70,7 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
70
70
|
auto_remove_container: bool = True
|
71
71
|
environment: Dict[str, str] = Field(default_factory=dict)
|
72
72
|
|
73
|
-
|
73
|
+
_should_setup_run_log_at_traversal: bool = PrivateAttr(default=True)
|
74
74
|
|
75
75
|
_container_log_location = "/tmp/run_logs/"
|
76
76
|
_container_catalog_location = "/tmp/catalog/"
|
@@ -104,7 +104,7 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
104
104
|
code_id.code_identifier_url = "local docker host"
|
105
105
|
step_log.code_identities.append(code_id)
|
106
106
|
|
107
|
-
def execute_node(self, node: BaseNode, map_variable:
|
107
|
+
def execute_node(self, node: BaseNode, map_variable: MapVariableType = None):
|
108
108
|
"""
|
109
109
|
We are already in the container, we just execute the node.
|
110
110
|
The node is already prepared for execution.
|
@@ -112,69 +112,8 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
112
112
|
self._use_volumes()
|
113
113
|
return self._execute_node(node, map_variable)
|
114
114
|
|
115
|
-
def execute_from_graph(
|
116
|
-
self,
|
117
|
-
node: BaseNode,
|
118
|
-
map_variable: TypeMapVariable = None,
|
119
|
-
):
|
120
|
-
"""
|
121
|
-
This is the entry point to from the graph execution.
|
122
|
-
|
123
|
-
While the self.execute_graph is responsible for traversing the graph, this function is responsible for
|
124
|
-
actual execution of the node.
|
125
|
-
|
126
|
-
If the node type is:
|
127
|
-
* task : We can delegate to _execute_node after checking the eligibility for re-run in cases of a re-run
|
128
|
-
* success: We can delegate to _execute_node
|
129
|
-
* fail: We can delegate to _execute_node
|
130
|
-
|
131
|
-
For nodes that are internally graphs:
|
132
|
-
* parallel: Delegate the responsibility of execution to the node.execute_as_graph()
|
133
|
-
* dag: Delegate the responsibility of execution to the node.execute_as_graph()
|
134
|
-
* map: Delegate the responsibility of execution to the node.execute_as_graph()
|
135
|
-
|
136
|
-
Transpilers will NEVER use this method and will NEVER call ths method.
|
137
|
-
This method should only be used by interactive executors.
|
138
|
-
|
139
|
-
Args:
|
140
|
-
node (Node): The node to execute
|
141
|
-
map_variable (dict, optional): If the node if of a map state, this corresponds to the value of iterable.
|
142
|
-
Defaults to None.
|
143
|
-
"""
|
144
|
-
step_log = self._context.run_log_store.create_step_log(
|
145
|
-
node.name, node._get_step_log_name(map_variable)
|
146
|
-
)
|
147
|
-
|
148
|
-
self.add_code_identities(node=node, step_log=step_log)
|
149
|
-
|
150
|
-
step_log.step_type = node.node_type
|
151
|
-
step_log.status = defaults.PROCESSING
|
152
|
-
|
153
|
-
self._context.run_log_store.add_step_log(step_log, self._context.run_id)
|
154
|
-
|
155
|
-
logger.info(f"Executing node: {node.get_summary()}")
|
156
|
-
|
157
|
-
# Add the step log to the database as per the situation.
|
158
|
-
# If its a terminal node, complete it now
|
159
|
-
if node.node_type in ["success", "fail"]:
|
160
|
-
self._execute_node(node, map_variable=map_variable)
|
161
|
-
return
|
162
|
-
|
163
|
-
# We call an internal function to iterate the sub graphs and execute them
|
164
|
-
if node.is_composite:
|
165
|
-
node.execute_as_graph(map_variable=map_variable)
|
166
|
-
return
|
167
|
-
|
168
|
-
task_console.export_text(clear=True)
|
169
|
-
|
170
|
-
task_name = node._resolve_map_placeholders(node.internal_name, map_variable)
|
171
|
-
console.print(
|
172
|
-
f":runner: Executing the node {task_name} ... ", style="bold color(208)"
|
173
|
-
)
|
174
|
-
self.trigger_node_execution(node=node, map_variable=map_variable)
|
175
|
-
|
176
115
|
def trigger_node_execution(
|
177
|
-
self, node: BaseNode, map_variable:
|
116
|
+
self, node: BaseNode, map_variable: MapVariableType = None
|
178
117
|
):
|
179
118
|
"""
|
180
119
|
We come into this step via execute from graph, use trigger job to spin up the container.
|
@@ -192,7 +131,9 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
192
131
|
logger.debug("Here is the resolved executor config")
|
193
132
|
logger.debug(executor_config)
|
194
133
|
|
195
|
-
command =
|
134
|
+
command = self._context.get_node_callable_command(
|
135
|
+
node, map_variable=map_variable
|
136
|
+
)
|
196
137
|
|
197
138
|
self._spin_container(
|
198
139
|
node=node,
|
@@ -218,7 +159,7 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
218
159
|
self,
|
219
160
|
node: BaseNode,
|
220
161
|
command: str,
|
221
|
-
map_variable:
|
162
|
+
map_variable: MapVariableType = None,
|
222
163
|
auto_remove_container: bool = True,
|
223
164
|
):
|
224
165
|
"""
|
@@ -294,6 +235,7 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
294
235
|
"""
|
295
236
|
Mount the volumes for the container
|
296
237
|
"""
|
238
|
+
# TODO: There should be an abstraction on top of service providers
|
297
239
|
match self._context.run_log_store.service_name:
|
298
240
|
case "file-system":
|
299
241
|
write_to = self._context.run_log_store.log_folder
|
@@ -308,17 +250,17 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
308
250
|
"mode": "rw",
|
309
251
|
}
|
310
252
|
|
311
|
-
match self._context.
|
253
|
+
match self._context.catalog.service_name:
|
312
254
|
case "file-system":
|
313
|
-
catalog_location = self._context.
|
255
|
+
catalog_location = self._context.catalog.catalog_location
|
314
256
|
self._volumes[str(Path(catalog_location).resolve())] = {
|
315
257
|
"bind": f"{self._container_catalog_location}",
|
316
258
|
"mode": "rw",
|
317
259
|
}
|
318
260
|
|
319
|
-
match self._context.
|
261
|
+
match self._context.secrets.service_name:
|
320
262
|
case "dotenv":
|
321
|
-
secrets_location = self._context.
|
263
|
+
secrets_location = self._context.secrets.location
|
322
264
|
self._volumes[str(Path(secrets_location).resolve())] = {
|
323
265
|
"bind": f"{self._container_secrets_location}",
|
324
266
|
"mode": "ro",
|
@@ -331,14 +273,12 @@ class LocalContainerExecutor(GenericPipelineExecutor):
|
|
331
273
|
case "chunked-fs":
|
332
274
|
self._context.run_log_store.log_folder = self._container_log_location
|
333
275
|
|
334
|
-
match self._context.
|
276
|
+
match self._context.catalog.service_name:
|
335
277
|
case "file-system":
|
336
|
-
self._context.
|
278
|
+
self._context.catalog.catalog_location = (
|
337
279
|
self._container_catalog_location
|
338
280
|
)
|
339
281
|
|
340
|
-
match self._context.
|
282
|
+
match self._context.secrets.service_name:
|
341
283
|
case "dotenv":
|
342
|
-
self._context.
|
343
|
-
self._container_secrets_location
|
344
|
-
)
|
284
|
+
self._context.secrets.location = self._container_secrets_location
|
@@ -4,10 +4,10 @@ from typing import Any, Dict, Type, cast
|
|
4
4
|
|
5
5
|
from pydantic import ConfigDict, Field
|
6
6
|
|
7
|
-
from extensions.nodes.
|
7
|
+
from extensions.nodes.task import TaskNode
|
8
8
|
from extensions.pipeline_executor import GenericPipelineExecutor
|
9
9
|
from runnable import context, defaults
|
10
|
-
from runnable.defaults import
|
10
|
+
from runnable.defaults import MapVariableType
|
11
11
|
from runnable.nodes import BaseNode
|
12
12
|
from runnable.tasks import BaseTaskType
|
13
13
|
|
@@ -36,7 +36,7 @@ class MockedExecutor(GenericPipelineExecutor):
|
|
36
36
|
def _context(self):
|
37
37
|
return context.run_context
|
38
38
|
|
39
|
-
def execute_from_graph(self, node: BaseNode, map_variable:
|
39
|
+
def execute_from_graph(self, node: BaseNode, map_variable: MapVariableType = None):
|
40
40
|
"""
|
41
41
|
This is the entry point to from the graph execution.
|
42
42
|
|
@@ -140,7 +140,7 @@ class MockedExecutor(GenericPipelineExecutor):
|
|
140
140
|
|
141
141
|
return effective_node_config
|
142
142
|
|
143
|
-
def execute_node(self, node: BaseNode, map_variable:
|
143
|
+
def execute_node(self, node: BaseNode, map_variable: MapVariableType = None):
|
144
144
|
"""
|
145
145
|
The entry point for all executors apart from local.
|
146
146
|
We have already prepared for node execution.
|
@@ -3,9 +3,9 @@ from functools import cached_property
|
|
3
3
|
from typing import Any, Dict, Optional
|
4
4
|
|
5
5
|
from extensions.pipeline_executor import GenericPipelineExecutor
|
6
|
-
from runnable import
|
6
|
+
from runnable import defaults, exceptions
|
7
7
|
from runnable.datastore import RunLog
|
8
|
-
from runnable.defaults import
|
8
|
+
from runnable.defaults import MapVariableType
|
9
9
|
from runnable.nodes import BaseNode
|
10
10
|
|
11
11
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
@@ -33,10 +33,6 @@ class RetryExecutor(GenericPipelineExecutor):
|
|
33
33
|
_original_run_log: Optional[RunLog] = None
|
34
34
|
_restart_initiated: bool = False
|
35
35
|
|
36
|
-
@property
|
37
|
-
def _context(self):
|
38
|
-
return context.run_context
|
39
|
-
|
40
36
|
@cached_property
|
41
37
|
def original_run_log(self):
|
42
38
|
return self._context.run_log_store.get_run_log_by_id(
|
@@ -46,7 +42,7 @@ class RetryExecutor(GenericPipelineExecutor):
|
|
46
42
|
|
47
43
|
def _set_up_for_re_run(self, params: Dict[str, Any]) -> None:
|
48
44
|
# Sync the previous run log catalog to this one.
|
49
|
-
self._context.
|
45
|
+
self._context.catalog.sync_between_runs(
|
50
46
|
previous_run_id=self.run_id, run_id=self._context.run_id
|
51
47
|
)
|
52
48
|
|
@@ -63,7 +59,7 @@ class RetryExecutor(GenericPipelineExecutor):
|
|
63
59
|
# Should the parameters be copied from previous execution
|
64
60
|
# self._set_up_for_re_run(params=params)
|
65
61
|
|
66
|
-
def execute_from_graph(self, node: BaseNode, map_variable:
|
62
|
+
def execute_from_graph(self, node: BaseNode, map_variable: MapVariableType = None):
|
67
63
|
"""
|
68
64
|
This is the entry point to from the graph execution.
|
69
65
|
|
@@ -124,7 +120,7 @@ class RetryExecutor(GenericPipelineExecutor):
|
|
124
120
|
self.execute_node(node=node, map_variable=map_variable)
|
125
121
|
|
126
122
|
def _is_step_eligible_for_rerun(
|
127
|
-
self, node: BaseNode, map_variable:
|
123
|
+
self, node: BaseNode, map_variable: MapVariableType = None
|
128
124
|
):
|
129
125
|
"""
|
130
126
|
In case of a re-run, this method checks to see if the previous run step status to determine if a re-run is
|
@@ -172,5 +168,5 @@ class RetryExecutor(GenericPipelineExecutor):
|
|
172
168
|
self._restart_initiated = True
|
173
169
|
return True
|
174
170
|
|
175
|
-
def execute_node(self, node: BaseNode, map_variable:
|
171
|
+
def execute_node(self, node: BaseNode, map_variable: MapVariableType = None):
|
176
172
|
self._execute_node(node, map_variable=map_variable)
|
extensions/tasks/torch.py
CHANGED
@@ -81,7 +81,7 @@ class TorchTaskType(BaseTaskType, TorchConfig):
|
|
81
81
|
|
82
82
|
def execute_command(
|
83
83
|
self,
|
84
|
-
map_variable: defaults.
|
84
|
+
map_variable: defaults.MapVariableType = None,
|
85
85
|
):
|
86
86
|
assert map_variable is None, "map_variable is not supported for torch"
|
87
87
|
|
runnable/__init__.py
CHANGED
@@ -1,17 +1,9 @@
|
|
1
1
|
# ruff: noqa
|
2
2
|
|
3
|
-
|
4
|
-
import logging
|
5
3
|
import os
|
6
|
-
from logging.config import dictConfig
|
7
4
|
|
8
5
|
from rich.console import Console
|
9
6
|
|
10
|
-
from runnable import defaults
|
11
|
-
|
12
|
-
dictConfig(defaults.LOGGING_CONFIG)
|
13
|
-
logger = logging.getLogger(defaults.LOGGER_NAME)
|
14
|
-
|
15
7
|
console = Console(record=True)
|
16
8
|
console.print(":runner: Lets go!!")
|
17
9
|
|
runnable/catalog.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from abc import ABC, abstractmethod
|
3
|
-
from typing import Any, Dict, List
|
3
|
+
from typing import Any, Dict, List
|
4
4
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
6
6
|
|
@@ -11,26 +11,6 @@ from runnable.datastore import DataCatalog
|
|
11
11
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
12
12
|
|
13
13
|
|
14
|
-
def is_catalog_out_of_sync(
|
15
|
-
catalog, synced_catalogs=Optional[List[DataCatalog]]
|
16
|
-
) -> bool:
|
17
|
-
"""
|
18
|
-
Check if the catalog items are out of sync from already cataloged objects.
|
19
|
-
If they are, return False.
|
20
|
-
If the object does not exist or synced catalog does not exist, return True
|
21
|
-
"""
|
22
|
-
if not synced_catalogs:
|
23
|
-
return True # If nothing has been synced in the past
|
24
|
-
|
25
|
-
for synced_catalog in synced_catalogs:
|
26
|
-
if synced_catalog.catalog_relative_path == catalog.catalog_relative_path:
|
27
|
-
if synced_catalog.data_hash == catalog.data_hash:
|
28
|
-
return False
|
29
|
-
return True
|
30
|
-
|
31
|
-
return True # The object does not exist, sync it
|
32
|
-
|
33
|
-
|
34
14
|
# --8<-- [start:docs]
|
35
15
|
|
36
16
|
|
runnable/cli.py
CHANGED
@@ -223,56 +223,6 @@ def fan(
|
|
223
223
|
)
|
224
224
|
|
225
225
|
|
226
|
-
@app.command()
|
227
|
-
def submit_job(
|
228
|
-
job_definition_file: Annotated[
|
229
|
-
str,
|
230
|
-
typer.Argument(
|
231
|
-
help=("The yaml file containing the job definition"),
|
232
|
-
),
|
233
|
-
],
|
234
|
-
config_file: Annotated[
|
235
|
-
str,
|
236
|
-
typer.Option(
|
237
|
-
"--config", "-c", help="The configuration file specifying the services"
|
238
|
-
),
|
239
|
-
] = "",
|
240
|
-
parameters_file: Annotated[
|
241
|
-
str,
|
242
|
-
typer.Option(
|
243
|
-
"--parameters",
|
244
|
-
"-p",
|
245
|
-
help="Parameters, in yaml, accessible by the application",
|
246
|
-
),
|
247
|
-
] = "",
|
248
|
-
log_level: Annotated[
|
249
|
-
LogLevel,
|
250
|
-
typer.Option(
|
251
|
-
"--log-level",
|
252
|
-
help="The log level",
|
253
|
-
show_default=True,
|
254
|
-
case_sensitive=False,
|
255
|
-
),
|
256
|
-
] = LogLevel.WARNING,
|
257
|
-
tag: Annotated[str, typer.Option(help="A tag attached to the run")] = "",
|
258
|
-
run_id: Annotated[
|
259
|
-
str,
|
260
|
-
typer.Option(
|
261
|
-
help="An optional run_id, one would be generated if its not provided"
|
262
|
-
),
|
263
|
-
] = "",
|
264
|
-
):
|
265
|
-
logger.setLevel(log_level.value)
|
266
|
-
|
267
|
-
entrypoints.execute_job_yaml_spec(
|
268
|
-
configuration_file=config_file,
|
269
|
-
job_definition_file=job_definition_file,
|
270
|
-
tag=tag,
|
271
|
-
run_id=run_id,
|
272
|
-
parameters_file=parameters_file,
|
273
|
-
)
|
274
|
-
|
275
|
-
|
276
226
|
@app.command(hidden=True)
|
277
227
|
def execute_job(
|
278
228
|
job_definition_file: Annotated[
|
@@ -302,14 +252,6 @@ def execute_job(
|
|
302
252
|
help="Parameters, in yaml, accessible by the application",
|
303
253
|
),
|
304
254
|
] = "",
|
305
|
-
mode: Annotated[
|
306
|
-
ExecutionMode,
|
307
|
-
typer.Option(
|
308
|
-
"--mode",
|
309
|
-
"-m",
|
310
|
-
help="spec in yaml or python sdk",
|
311
|
-
),
|
312
|
-
] = ExecutionMode.YAML,
|
313
255
|
log_level: Annotated[
|
314
256
|
LogLevel,
|
315
257
|
typer.Option(
|
@@ -326,7 +268,6 @@ def execute_job(
|
|
326
268
|
entrypoints.execute_job_non_local(
|
327
269
|
configuration_file=config_file,
|
328
270
|
job_definition_file=job_definition_file,
|
329
|
-
mode=mode,
|
330
271
|
tag=tag,
|
331
272
|
run_id=run_id,
|
332
273
|
parameters_file=parameters_file,
|