runnable 0.12.3__py3-none-any.whl → 0.14.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.
- runnable/__init__.py +0 -11
- runnable/catalog.py +27 -5
- runnable/cli.py +122 -26
- runnable/datastore.py +71 -35
- runnable/defaults.py +0 -1
- runnable/entrypoints.py +107 -32
- runnable/exceptions.py +6 -2
- runnable/executor.py +28 -9
- runnable/graph.py +37 -12
- runnable/integration.py +7 -2
- runnable/nodes.py +15 -17
- runnable/parameters.py +27 -8
- runnable/pickler.py +1 -1
- runnable/sdk.py +101 -33
- runnable/secrets.py +3 -1
- runnable/tasks.py +246 -34
- runnable/utils.py +41 -13
- {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/METADATA +25 -31
- runnable-0.14.0.dist-info/RECORD +24 -0
- {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/WHEEL +1 -1
- runnable-0.14.0.dist-info/entry_points.txt +40 -0
- runnable/extensions/__init__.py +0 -0
- runnable/extensions/catalog/__init__.py +0 -21
- runnable/extensions/catalog/file_system/__init__.py +0 -0
- runnable/extensions/catalog/file_system/implementation.py +0 -234
- runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
- runnable/extensions/catalog/k8s_pvc/implementation.py +0 -16
- runnable/extensions/catalog/k8s_pvc/integration.py +0 -59
- runnable/extensions/executor/__init__.py +0 -649
- runnable/extensions/executor/argo/__init__.py +0 -0
- runnable/extensions/executor/argo/implementation.py +0 -1194
- runnable/extensions/executor/argo/specification.yaml +0 -51
- runnable/extensions/executor/k8s_job/__init__.py +0 -0
- runnable/extensions/executor/k8s_job/implementation_FF.py +0 -259
- runnable/extensions/executor/k8s_job/integration_FF.py +0 -69
- runnable/extensions/executor/local/__init__.py +0 -0
- runnable/extensions/executor/local/implementation.py +0 -71
- runnable/extensions/executor/local_container/__init__.py +0 -0
- runnable/extensions/executor/local_container/implementation.py +0 -446
- runnable/extensions/executor/mocked/__init__.py +0 -0
- runnable/extensions/executor/mocked/implementation.py +0 -154
- runnable/extensions/executor/retry/__init__.py +0 -0
- runnable/extensions/executor/retry/implementation.py +0 -168
- runnable/extensions/nodes.py +0 -855
- runnable/extensions/run_log_store/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
- runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
- runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
- runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
- runnable/extensions/run_log_store/db/integration_FF.py +0 -0
- runnable/extensions/run_log_store/file_system/__init__.py +0 -0
- runnable/extensions/run_log_store/file_system/implementation.py +0 -140
- runnable/extensions/run_log_store/generic_chunked.py +0 -557
- runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
- runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
- runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
- runnable/extensions/secrets/__init__.py +0 -0
- runnable/extensions/secrets/dotenv/__init__.py +0 -0
- runnable/extensions/secrets/dotenv/implementation.py +0 -100
- runnable-0.12.3.dist-info/RECORD +0 -64
- runnable-0.12.3.dist-info/entry_points.txt +0 -41
- {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info/licenses}/LICENSE +0 -0
runnable/entrypoints.py
CHANGED
@@ -35,6 +35,7 @@ def prepare_configurations(
|
|
35
35
|
force_local_executor: bool = False,
|
36
36
|
) -> context.Context:
|
37
37
|
"""
|
38
|
+
Sets up everything needed
|
38
39
|
Replace the placeholders in the dag/config against the variables file.
|
39
40
|
|
40
41
|
Attach the secrets_handler, run_log_store, catalog_handler to the executor and return it.
|
@@ -53,11 +54,17 @@ def prepare_configurations(
|
|
53
54
|
variables = utils.gather_variables()
|
54
55
|
|
55
56
|
templated_configuration = {}
|
56
|
-
configuration_file = os.environ.get(
|
57
|
+
configuration_file = os.environ.get(
|
58
|
+
"RUNNABLE_CONFIGURATION_FILE", configuration_file
|
59
|
+
)
|
57
60
|
|
58
61
|
if configuration_file:
|
59
62
|
templated_configuration = utils.load_yaml(configuration_file) or {}
|
60
63
|
|
64
|
+
# Since all the services (run_log_store, catalog, secrets, executor) are
|
65
|
+
# dynamically loaded via stevedore, we cannot validate the configuration
|
66
|
+
# before they are passed to the service.
|
67
|
+
|
61
68
|
configuration: RunnableConfig = cast(RunnableConfig, templated_configuration)
|
62
69
|
|
63
70
|
logger.info(f"Resolved configurations: {configuration}")
|
@@ -65,23 +72,32 @@ def prepare_configurations(
|
|
65
72
|
# Run log settings, configuration over-rides everything
|
66
73
|
run_log_config: Optional[ServiceConfig] = configuration.get("run_log_store", None)
|
67
74
|
if not run_log_config:
|
68
|
-
run_log_config = cast(
|
75
|
+
run_log_config = cast(
|
76
|
+
ServiceConfig,
|
77
|
+
runnable_defaults.get("run_log_store", defaults.DEFAULT_RUN_LOG_STORE),
|
78
|
+
)
|
69
79
|
run_log_store = utils.get_provider_by_name_and_type("run_log_store", run_log_config)
|
70
80
|
|
71
81
|
# Catalog handler settings, configuration over-rides everything
|
72
82
|
catalog_config: Optional[ServiceConfig] = configuration.get("catalog", None)
|
73
83
|
if not catalog_config:
|
74
|
-
catalog_config = cast(
|
84
|
+
catalog_config = cast(
|
85
|
+
ServiceConfig, runnable_defaults.get("catalog", defaults.DEFAULT_CATALOG)
|
86
|
+
)
|
75
87
|
catalog_handler = utils.get_provider_by_name_and_type("catalog", catalog_config)
|
76
88
|
|
77
89
|
# Secret handler settings, configuration over-rides everything
|
78
90
|
secrets_config: Optional[ServiceConfig] = configuration.get("secrets", None)
|
79
91
|
if not secrets_config:
|
80
|
-
secrets_config = cast(
|
92
|
+
secrets_config = cast(
|
93
|
+
ServiceConfig, runnable_defaults.get("secrets", defaults.DEFAULT_SECRETS)
|
94
|
+
)
|
81
95
|
secrets_handler = utils.get_provider_by_name_and_type("secrets", secrets_config)
|
82
96
|
|
83
97
|
# pickler
|
84
|
-
pickler_config = cast(
|
98
|
+
pickler_config = cast(
|
99
|
+
ServiceConfig, runnable_defaults.get("pickler", defaults.DEFAULT_PICKLER)
|
100
|
+
)
|
85
101
|
pickler_handler = utils.get_provider_by_name_and_type("pickler", pickler_config)
|
86
102
|
|
87
103
|
# executor configurations, configuration over rides everything
|
@@ -90,8 +106,12 @@ def prepare_configurations(
|
|
90
106
|
executor_config = ServiceConfig(type="local", config={})
|
91
107
|
|
92
108
|
if not executor_config:
|
93
|
-
executor_config = cast(
|
94
|
-
|
109
|
+
executor_config = cast(
|
110
|
+
ServiceConfig, runnable_defaults.get("executor", defaults.DEFAULT_EXECUTOR)
|
111
|
+
)
|
112
|
+
configured_executor = utils.get_provider_by_name_and_type(
|
113
|
+
"executor", executor_config
|
114
|
+
)
|
95
115
|
|
96
116
|
# Construct the context
|
97
117
|
run_context = context.Context(
|
@@ -174,20 +194,26 @@ def execute(
|
|
174
194
|
|
175
195
|
run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
|
176
196
|
|
177
|
-
utils.set_runnable_environment_variables(
|
197
|
+
utils.set_runnable_environment_variables(
|
198
|
+
run_id=run_id, configuration_file=configuration_file, tag=tag
|
199
|
+
)
|
178
200
|
|
179
201
|
# Prepare for graph execution
|
180
202
|
executor.prepare_for_graph_execution()
|
181
203
|
|
182
204
|
logger.info(f"Executing the graph: {run_context.dag}")
|
183
205
|
with Progress(
|
184
|
-
TextColumn(
|
206
|
+
TextColumn(
|
207
|
+
"[progress.description]{task.description}", table_column=Column(ratio=2)
|
208
|
+
),
|
185
209
|
BarColumn(table_column=Column(ratio=1), style="dark_orange"),
|
186
210
|
TimeElapsedColumn(table_column=Column(ratio=1)),
|
187
211
|
console=console,
|
188
212
|
expand=True,
|
189
213
|
) as progress:
|
190
|
-
pipeline_execution_task = progress.add_task(
|
214
|
+
pipeline_execution_task = progress.add_task(
|
215
|
+
"[dark_orange] Starting execution .. ", total=1
|
216
|
+
)
|
191
217
|
try:
|
192
218
|
run_context.progress = progress
|
193
219
|
executor.execute_graph(dag=run_context.dag) # type: ignore
|
@@ -197,16 +223,30 @@ def execute(
|
|
197
223
|
executor.send_return_code(stage="traversal")
|
198
224
|
return
|
199
225
|
|
200
|
-
run_log = run_context.run_log_store.get_run_log_by_id(
|
226
|
+
run_log = run_context.run_log_store.get_run_log_by_id(
|
227
|
+
run_id=run_context.run_id, full=False
|
228
|
+
)
|
201
229
|
|
202
230
|
if run_log.status == defaults.SUCCESS:
|
203
|
-
progress.update(
|
231
|
+
progress.update(
|
232
|
+
pipeline_execution_task,
|
233
|
+
description="[green] Success",
|
234
|
+
completed=True,
|
235
|
+
)
|
204
236
|
else:
|
205
|
-
progress.update(
|
237
|
+
progress.update(
|
238
|
+
pipeline_execution_task, description="[red] Failed", completed=True
|
239
|
+
)
|
206
240
|
except Exception as e: # noqa: E722
|
207
241
|
console.print(e, style=defaults.error_style)
|
208
|
-
progress.update(
|
209
|
-
|
242
|
+
progress.update(
|
243
|
+
pipeline_execution_task,
|
244
|
+
description="[red] Errored execution",
|
245
|
+
completed=True,
|
246
|
+
)
|
247
|
+
run_log = run_context.run_log_store.get_run_log_by_id(
|
248
|
+
run_id=run_context.run_id, full=False
|
249
|
+
)
|
210
250
|
run_log.status = defaults.FAIL
|
211
251
|
run_context.run_log_store.add_branch_log(run_log, run_context.run_id)
|
212
252
|
raise e
|
@@ -240,9 +280,13 @@ def execute_single_node(
|
|
240
280
|
"""
|
241
281
|
from runnable import nodes
|
242
282
|
|
243
|
-
task_console.print(
|
283
|
+
task_console.print(
|
284
|
+
f"Executing the single node: {step_name} with map variable: {map_variable}"
|
285
|
+
)
|
244
286
|
|
245
|
-
configuration_file = os.environ.get(
|
287
|
+
configuration_file = os.environ.get(
|
288
|
+
"RUNNABLE_CONFIGURATION_FILE", configuration_file
|
289
|
+
)
|
246
290
|
|
247
291
|
run_context = prepare_configurations(
|
248
292
|
configuration_file=configuration_file,
|
@@ -257,7 +301,9 @@ def execute_single_node(
|
|
257
301
|
|
258
302
|
executor = run_context.executor
|
259
303
|
run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
|
260
|
-
utils.set_runnable_environment_variables(
|
304
|
+
utils.set_runnable_environment_variables(
|
305
|
+
run_id=run_id, configuration_file=configuration_file, tag=tag
|
306
|
+
)
|
261
307
|
|
262
308
|
executor.prepare_for_node_execution()
|
263
309
|
|
@@ -271,7 +317,9 @@ def execute_single_node(
|
|
271
317
|
map_variable_dict = utils.json_to_ordered_dict(map_variable)
|
272
318
|
|
273
319
|
step_internal_name = nodes.BaseNode._get_internal_name_from_command_name(step_name)
|
274
|
-
node_to_execute, _ = graph.search_node_by_internal_name(
|
320
|
+
node_to_execute, _ = graph.search_node_by_internal_name(
|
321
|
+
run_context.dag, step_internal_name
|
322
|
+
)
|
275
323
|
|
276
324
|
logger.info("Executing the single node of : %s", node_to_execute)
|
277
325
|
## This step is where we save the log file
|
@@ -316,7 +364,9 @@ def execute_notebook(
|
|
316
364
|
|
317
365
|
executor = run_context.executor
|
318
366
|
run_context.execution_plan = defaults.EXECUTION_PLAN.UNCHAINED.value
|
319
|
-
utils.set_runnable_environment_variables(
|
367
|
+
utils.set_runnable_environment_variables(
|
368
|
+
run_id=run_id, configuration_file=configuration_file, tag=tag
|
369
|
+
)
|
320
370
|
|
321
371
|
console.print("Working with context:")
|
322
372
|
console.print(run_context)
|
@@ -336,17 +386,25 @@ def execute_notebook(
|
|
336
386
|
# Prepare for graph execution
|
337
387
|
executor.prepare_for_graph_execution()
|
338
388
|
|
339
|
-
logger.info(
|
389
|
+
logger.info(
|
390
|
+
"Executing the job from the user. We are still in the caller's compute environment"
|
391
|
+
)
|
340
392
|
executor.execute_job(node=node)
|
341
393
|
|
342
394
|
elif entrypoint == defaults.ENTRYPOINT.SYSTEM.value:
|
343
395
|
executor.prepare_for_node_execution()
|
344
|
-
logger.info(
|
396
|
+
logger.info(
|
397
|
+
"Executing the job from the system. We are in the config's compute environment"
|
398
|
+
)
|
345
399
|
executor.execute_node(node=node)
|
346
400
|
|
347
401
|
# Update the status of the run log
|
348
|
-
step_log = run_context.run_log_store.get_step_log(
|
349
|
-
|
402
|
+
step_log = run_context.run_log_store.get_step_log(
|
403
|
+
node._get_step_log_name(), run_id
|
404
|
+
)
|
405
|
+
run_context.run_log_store.update_run_log_status(
|
406
|
+
run_id=run_id, status=step_log.status
|
407
|
+
)
|
350
408
|
|
351
409
|
else:
|
352
410
|
raise ValueError(f"Invalid entrypoint {entrypoint}")
|
@@ -379,7 +437,9 @@ def execute_function(
|
|
379
437
|
executor = run_context.executor
|
380
438
|
|
381
439
|
run_context.execution_plan = defaults.EXECUTION_PLAN.UNCHAINED.value
|
382
|
-
utils.set_runnable_environment_variables(
|
440
|
+
utils.set_runnable_environment_variables(
|
441
|
+
run_id=run_id, configuration_file=configuration_file, tag=tag
|
442
|
+
)
|
383
443
|
|
384
444
|
console.print("Working with context:")
|
385
445
|
console.print(run_context)
|
@@ -399,17 +459,25 @@ def execute_function(
|
|
399
459
|
# Prepare for graph execution
|
400
460
|
executor.prepare_for_graph_execution()
|
401
461
|
|
402
|
-
logger.info(
|
462
|
+
logger.info(
|
463
|
+
"Executing the job from the user. We are still in the caller's compute environment"
|
464
|
+
)
|
403
465
|
executor.execute_job(node=node)
|
404
466
|
|
405
467
|
elif entrypoint == defaults.ENTRYPOINT.SYSTEM.value:
|
406
468
|
executor.prepare_for_node_execution()
|
407
|
-
logger.info(
|
469
|
+
logger.info(
|
470
|
+
"Executing the job from the system. We are in the config's compute environment"
|
471
|
+
)
|
408
472
|
executor.execute_node(node=node)
|
409
473
|
|
410
474
|
# Update the status of the run log
|
411
|
-
step_log = run_context.run_log_store.get_step_log(
|
412
|
-
|
475
|
+
step_log = run_context.run_log_store.get_step_log(
|
476
|
+
node._get_step_log_name(), run_id
|
477
|
+
)
|
478
|
+
run_context.run_log_store.update_run_log_status(
|
479
|
+
run_id=run_id, status=step_log.status
|
480
|
+
)
|
413
481
|
|
414
482
|
else:
|
415
483
|
raise ValueError(f"Invalid entrypoint {entrypoint}")
|
@@ -444,7 +512,9 @@ def fan(
|
|
444
512
|
"""
|
445
513
|
from runnable import nodes
|
446
514
|
|
447
|
-
configuration_file = os.environ.get(
|
515
|
+
configuration_file = os.environ.get(
|
516
|
+
"RUNNABLE_CONFIGURATION_FILE", configuration_file
|
517
|
+
)
|
448
518
|
|
449
519
|
run_context = prepare_configurations(
|
450
520
|
configuration_file=configuration_file,
|
@@ -459,12 +529,17 @@ def fan(
|
|
459
529
|
|
460
530
|
executor = run_context.executor
|
461
531
|
run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
|
462
|
-
utils.set_runnable_environment_variables(
|
532
|
+
utils.set_runnable_environment_variables(
|
533
|
+
run_id=run_id, configuration_file=configuration_file, tag=tag
|
534
|
+
)
|
463
535
|
|
464
536
|
executor.prepare_for_node_execution()
|
465
537
|
|
466
538
|
step_internal_name = nodes.BaseNode._get_internal_name_from_command_name(step_name)
|
467
|
-
node_to_execute, _ = graph.search_node_by_internal_name(
|
539
|
+
node_to_execute, _ = graph.search_node_by_internal_name(
|
540
|
+
run_context.dag, # type: ignore
|
541
|
+
step_internal_name,
|
542
|
+
)
|
468
543
|
|
469
544
|
map_variable_dict = utils.json_to_ordered_dict(map_variable)
|
470
545
|
|
runnable/exceptions.py
CHANGED
@@ -31,7 +31,9 @@ class StepLogNotFoundError(Exception): # pragma: no cover
|
|
31
31
|
|
32
32
|
def __init__(self, run_id, name):
|
33
33
|
super().__init__()
|
34
|
-
self.message =
|
34
|
+
self.message = (
|
35
|
+
f"Step log for {name} is not found in the datastore for Run id: {run_id}"
|
36
|
+
)
|
35
37
|
|
36
38
|
|
37
39
|
class BranchLogNotFoundError(Exception): # pragma: no cover
|
@@ -43,7 +45,9 @@ class BranchLogNotFoundError(Exception): # pragma: no cover
|
|
43
45
|
|
44
46
|
def __init__(self, run_id, name):
|
45
47
|
super().__init__()
|
46
|
-
self.message =
|
48
|
+
self.message = (
|
49
|
+
f"Branch log for {name} is not found in the datastore for Run id: {run_id}"
|
50
|
+
)
|
47
51
|
|
48
52
|
|
49
53
|
class NodeNotFoundError(Exception): # pragma: no cover
|
runnable/executor.py
CHANGED
@@ -14,7 +14,7 @@ from runnable.defaults import TypeMapVariable
|
|
14
14
|
from runnable.graph import Graph
|
15
15
|
|
16
16
|
if TYPE_CHECKING: # pragma: no cover
|
17
|
-
from
|
17
|
+
from extensions.nodes.nodes import TaskNode
|
18
18
|
from runnable.nodes import BaseNode
|
19
19
|
|
20
20
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
@@ -36,9 +36,12 @@ class BaseExecutor(ABC, BaseModel):
|
|
36
36
|
|
37
37
|
overrides: dict = {}
|
38
38
|
|
39
|
-
_local: bool =
|
39
|
+
_local: bool = (
|
40
|
+
False # This is a flag to indicate whether the executor is local or not.
|
41
|
+
)
|
40
42
|
|
41
|
-
|
43
|
+
# TODO: Change this to _is_local
|
44
|
+
_context_node: Optional[BaseNode] = None
|
42
45
|
model_config = ConfigDict(extra="forbid")
|
43
46
|
|
44
47
|
@property
|
@@ -90,7 +93,9 @@ class BaseExecutor(ABC, BaseModel):
|
|
90
93
|
...
|
91
94
|
|
92
95
|
@abstractmethod
|
93
|
-
def _sync_catalog(
|
96
|
+
def _sync_catalog(
|
97
|
+
self, stage: str, synced_catalogs=None
|
98
|
+
) -> Optional[List[DataCatalog]]:
|
94
99
|
"""
|
95
100
|
1). Identify the catalog settings by over-riding node settings with the global settings.
|
96
101
|
2). For stage = get:
|
@@ -141,7 +146,13 @@ class BaseExecutor(ABC, BaseModel):
|
|
141
146
|
return int(os.environ.get(defaults.ATTEMPT_NUMBER, 1))
|
142
147
|
|
143
148
|
@abstractmethod
|
144
|
-
def _execute_node(
|
149
|
+
def _execute_node(
|
150
|
+
self,
|
151
|
+
node: BaseNode,
|
152
|
+
map_variable: TypeMapVariable = None,
|
153
|
+
mock: bool = False,
|
154
|
+
**kwargs,
|
155
|
+
):
|
145
156
|
"""
|
146
157
|
This is the entry point when we do the actual execution of the function.
|
147
158
|
|
@@ -163,7 +174,9 @@ class BaseExecutor(ABC, BaseModel):
|
|
163
174
|
...
|
164
175
|
|
165
176
|
@abstractmethod
|
166
|
-
def execute_node(
|
177
|
+
def execute_node(
|
178
|
+
self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
|
179
|
+
):
|
167
180
|
"""
|
168
181
|
The entry point for all executors apart from local.
|
169
182
|
We have already prepared for node execution.
|
@@ -191,7 +204,9 @@ class BaseExecutor(ABC, BaseModel):
|
|
191
204
|
...
|
192
205
|
|
193
206
|
@abstractmethod
|
194
|
-
def execute_from_graph(
|
207
|
+
def execute_from_graph(
|
208
|
+
self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
|
209
|
+
):
|
195
210
|
"""
|
196
211
|
This is the entry point to from the graph execution.
|
197
212
|
|
@@ -219,7 +234,9 @@ class BaseExecutor(ABC, BaseModel):
|
|
219
234
|
...
|
220
235
|
|
221
236
|
@abstractmethod
|
222
|
-
def trigger_job(
|
237
|
+
def trigger_job(
|
238
|
+
self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
|
239
|
+
):
|
223
240
|
"""
|
224
241
|
Executor specific way of triggering jobs when runnable does both traversal and execution
|
225
242
|
|
@@ -236,7 +253,9 @@ class BaseExecutor(ABC, BaseModel):
|
|
236
253
|
...
|
237
254
|
|
238
255
|
@abstractmethod
|
239
|
-
def _get_status_and_next_node_name(
|
256
|
+
def _get_status_and_next_node_name(
|
257
|
+
self, current_node: BaseNode, dag: Graph, map_variable: TypeMapVariable = None
|
258
|
+
):
|
240
259
|
"""
|
241
260
|
Given the current node and the graph, returns the name of the next node to execute.
|
242
261
|
|
runnable/graph.py
CHANGED
@@ -24,7 +24,9 @@ class Graph(BaseModel):
|
|
24
24
|
name: str = ""
|
25
25
|
description: Optional[str] = ""
|
26
26
|
internal_branch_name: str = Field(default="", exclude=True)
|
27
|
-
nodes: SerializeAsAny[Dict[str, "BaseNode"]] = Field(
|
27
|
+
nodes: SerializeAsAny[Dict[str, "BaseNode"]] = Field(
|
28
|
+
default_factory=dict, serialization_alias="steps"
|
29
|
+
)
|
28
30
|
|
29
31
|
def get_summary(self) -> Dict[str, Any]:
|
30
32
|
"""
|
@@ -229,7 +231,9 @@ class Graph(BaseModel):
|
|
229
231
|
return False
|
230
232
|
return True
|
231
233
|
|
232
|
-
def is_cyclic_util(
|
234
|
+
def is_cyclic_util(
|
235
|
+
self, node: "BaseNode", visited: Dict[str, bool], recstack: Dict[str, bool]
|
236
|
+
) -> bool:
|
233
237
|
"""
|
234
238
|
Recursive utility that determines if a node and neighbors has a cycle. Is used in is_dag method.
|
235
239
|
|
@@ -327,7 +331,9 @@ def create_graph(dag_config: Dict[str, Any], internal_branch_name: str = "") ->
|
|
327
331
|
Graph: The created graph object
|
328
332
|
"""
|
329
333
|
description: str = dag_config.get("description", None)
|
330
|
-
start_at: str = cast(
|
334
|
+
start_at: str = cast(
|
335
|
+
str, dag_config.get("start_at")
|
336
|
+
) # Let the start_at be relative to the graph
|
331
337
|
|
332
338
|
graph = Graph(
|
333
339
|
start_at=start_at,
|
@@ -339,7 +345,9 @@ def create_graph(dag_config: Dict[str, Any], internal_branch_name: str = "") ->
|
|
339
345
|
for name, step_config in dag_config.get("steps", {}).items():
|
340
346
|
logger.info(f"Adding node {name} with :{step_config}")
|
341
347
|
|
342
|
-
node = create_node(
|
348
|
+
node = create_node(
|
349
|
+
name, step_config=step_config, internal_branch_name=internal_branch_name
|
350
|
+
)
|
343
351
|
graph.add_node(node)
|
344
352
|
|
345
353
|
graph.add_terminal_nodes(internal_branch_name=internal_branch_name)
|
@@ -369,8 +377,12 @@ def create_node(name: str, step_config: dict, internal_branch_name: Optional[str
|
|
369
377
|
internal_name = internal_branch_name + "." + name
|
370
378
|
|
371
379
|
try:
|
372
|
-
node_type = step_config.pop(
|
373
|
-
|
380
|
+
node_type = step_config.pop(
|
381
|
+
"type"
|
382
|
+
) # Remove the type as it is not used in node creation.
|
383
|
+
node_mgr: BaseNode = driver.DriverManager(
|
384
|
+
namespace="nodes", name=node_type
|
385
|
+
).driver
|
374
386
|
|
375
387
|
next_node = step_config.pop("next", None)
|
376
388
|
|
@@ -424,11 +436,18 @@ def search_node_by_internal_name(dag: Graph, internal_name: str):
|
|
424
436
|
for i in range(len(dot_path)):
|
425
437
|
if i % 2:
|
426
438
|
# Its odd, so we are in brach name
|
427
|
-
|
428
|
-
|
439
|
+
|
440
|
+
current_branch = current_node._get_branch_by_name( # type: ignore
|
441
|
+
".".join(dot_path[: i + 1])
|
442
|
+
)
|
443
|
+
logger.debug(
|
444
|
+
f"Finding step for {internal_name} in branch: {current_branch}"
|
445
|
+
)
|
429
446
|
else:
|
430
447
|
# Its even, so we are in Step, we start here!
|
431
|
-
current_node = current_branch.get_node_by_internal_name(
|
448
|
+
current_node = current_branch.get_node_by_internal_name(
|
449
|
+
".".join(dot_path[: i + 1])
|
450
|
+
)
|
432
451
|
logger.debug(f"Finding {internal_name} in node: {current_node}")
|
433
452
|
|
434
453
|
logger.debug(f"current branch : {current_branch}, current step {current_node}")
|
@@ -463,12 +482,18 @@ def search_branch_by_internal_name(dag: Graph, internal_name: str):
|
|
463
482
|
for i in range(len(dot_path)):
|
464
483
|
if i % 2:
|
465
484
|
# Its odd, so we are in brach name
|
466
|
-
current_branch = current_node._get_branch_by_name(
|
467
|
-
|
485
|
+
current_branch = current_node._get_branch_by_name( # type: ignore
|
486
|
+
".".join(dot_path[: i + 1])
|
487
|
+
)
|
488
|
+
logger.debug(
|
489
|
+
f"Finding step for {internal_name} in branch: {current_branch}"
|
490
|
+
)
|
468
491
|
|
469
492
|
else:
|
470
493
|
# Its even, so we are in Step, we start here!
|
471
|
-
current_node = current_branch.get_node_by_internal_name(
|
494
|
+
current_node = current_branch.get_node_by_internal_name(
|
495
|
+
".".join(dot_path[: i + 1])
|
496
|
+
)
|
472
497
|
logger.debug(f"Finding {internal_name} in node: {current_node}")
|
473
498
|
|
474
499
|
logger.debug(f"current branch : {current_branch}, current step {current_node}")
|
runnable/integration.py
CHANGED
@@ -49,7 +49,9 @@ class BaseIntegration:
|
|
49
49
|
# --8<-- [end:docs]
|
50
50
|
|
51
51
|
|
52
|
-
def get_integration_handler(
|
52
|
+
def get_integration_handler(
|
53
|
+
executor: "BaseExecutor", service: object
|
54
|
+
) -> BaseIntegration:
|
53
55
|
"""
|
54
56
|
Return the integration handler between executor and the service.
|
55
57
|
|
@@ -147,6 +149,7 @@ def configure_for_execution(executor: "BaseExecutor", service: object, **kwargs)
|
|
147
149
|
integration_handler.configure_for_execution(**kwargs)
|
148
150
|
|
149
151
|
|
152
|
+
# TODO: Move all of these to the proper locations
|
150
153
|
class BufferedRunLogStore(BaseIntegration):
|
151
154
|
"""
|
152
155
|
Integration between any executor and buffered run log store
|
@@ -157,7 +160,9 @@ class BufferedRunLogStore(BaseIntegration):
|
|
157
160
|
|
158
161
|
def validate(self, **kwargs):
|
159
162
|
if not self.executor.service_name == "local":
|
160
|
-
raise Exception(
|
163
|
+
raise Exception(
|
164
|
+
"Buffered run log store is only supported for local executor"
|
165
|
+
)
|
161
166
|
|
162
167
|
msg = (
|
163
168
|
"Run log generated by buffered run log store are not persisted. "
|
runnable/nodes.py
CHANGED
@@ -51,7 +51,9 @@ class BaseNode(ABC, BaseModel):
|
|
51
51
|
raise ValueError("Node names cannot have . or '%' in them")
|
52
52
|
return name
|
53
53
|
|
54
|
-
def _command_friendly_name(
|
54
|
+
def _command_friendly_name(
|
55
|
+
self, replace_with=defaults.COMMAND_FRIENDLY_CHARACTER
|
56
|
+
) -> str:
|
55
57
|
"""
|
56
58
|
Replace spaces with special character for spaces.
|
57
59
|
Spaces in the naming of the node is convenient for the user but causes issues when used programmatically.
|
@@ -76,7 +78,9 @@ class BaseNode(ABC, BaseModel):
|
|
76
78
|
return command_name.replace(defaults.COMMAND_FRIENDLY_CHARACTER, " ")
|
77
79
|
|
78
80
|
@classmethod
|
79
|
-
def _resolve_map_placeholders(
|
81
|
+
def _resolve_map_placeholders(
|
82
|
+
cls, name: str, map_variable: TypeMapVariable = None
|
83
|
+
) -> str:
|
80
84
|
"""
|
81
85
|
If there is no map step used, then we just return the name as we find it.
|
82
86
|
|
@@ -141,7 +145,9 @@ class BaseNode(ABC, BaseModel):
|
|
141
145
|
Returns:
|
142
146
|
str: The dot path name of the step log name
|
143
147
|
"""
|
144
|
-
return self._resolve_map_placeholders(
|
148
|
+
return self._resolve_map_placeholders(
|
149
|
+
self.internal_name, map_variable=map_variable
|
150
|
+
)
|
145
151
|
|
146
152
|
def _get_branch_log_name(self, map_variable: TypeMapVariable = None) -> str:
|
147
153
|
"""
|
@@ -158,7 +164,9 @@ class BaseNode(ABC, BaseModel):
|
|
158
164
|
Returns:
|
159
165
|
str: The dot path name of the branch log
|
160
166
|
"""
|
161
|
-
return self._resolve_map_placeholders(
|
167
|
+
return self._resolve_map_placeholders(
|
168
|
+
self.internal_branch_name, map_variable=map_variable
|
169
|
+
)
|
162
170
|
|
163
171
|
def __str__(self) -> str: # pragma: no cover
|
164
172
|
"""
|
@@ -180,7 +188,6 @@ class BaseNode(ABC, BaseModel):
|
|
180
188
|
str: The on_failure node defined by the dag or ''
|
181
189
|
This is a base implementation which the BaseNode does not satisfy
|
182
190
|
"""
|
183
|
-
...
|
184
191
|
|
185
192
|
@abstractmethod
|
186
193
|
def _get_next_node(self) -> str:
|
@@ -190,7 +197,6 @@ class BaseNode(ABC, BaseModel):
|
|
190
197
|
Returns:
|
191
198
|
str: The node name, relative to the dag, as defined by the config
|
192
199
|
"""
|
193
|
-
...
|
194
200
|
|
195
201
|
@abstractmethod
|
196
202
|
def _is_terminal_node(self) -> bool:
|
@@ -200,7 +206,6 @@ class BaseNode(ABC, BaseModel):
|
|
200
206
|
Returns:
|
201
207
|
bool: True or False of whether there is next node.
|
202
208
|
"""
|
203
|
-
...
|
204
209
|
|
205
210
|
@abstractmethod
|
206
211
|
def _get_catalog_settings(self) -> Dict[str, Any]:
|
@@ -210,7 +215,6 @@ class BaseNode(ABC, BaseModel):
|
|
210
215
|
Returns:
|
211
216
|
dict: catalog settings defined as per the node or None
|
212
217
|
"""
|
213
|
-
...
|
214
218
|
|
215
219
|
@abstractmethod
|
216
220
|
def _get_branch_by_name(self, branch_name: str):
|
@@ -225,7 +229,6 @@ class BaseNode(ABC, BaseModel):
|
|
225
229
|
Raises:
|
226
230
|
Exception: [description]
|
227
231
|
"""
|
228
|
-
...
|
229
232
|
|
230
233
|
def _get_neighbors(self) -> List[str]:
|
231
234
|
"""
|
@@ -261,7 +264,6 @@ class BaseNode(ABC, BaseModel):
|
|
261
264
|
Returns:
|
262
265
|
dict: The executor config, if defined or an empty dict
|
263
266
|
"""
|
264
|
-
...
|
265
267
|
|
266
268
|
@abstractmethod
|
267
269
|
def _get_max_attempts(self) -> int:
|
@@ -271,7 +273,6 @@ class BaseNode(ABC, BaseModel):
|
|
271
273
|
Returns:
|
272
274
|
int: The number of maximum retries as defined by the config or 1.
|
273
275
|
"""
|
274
|
-
...
|
275
276
|
|
276
277
|
@abstractmethod
|
277
278
|
def execute(
|
@@ -296,7 +297,6 @@ class BaseNode(ABC, BaseModel):
|
|
296
297
|
Raises:
|
297
298
|
NotImplementedError: Base class, hence not implemented.
|
298
299
|
"""
|
299
|
-
...
|
300
300
|
|
301
301
|
@abstractmethod
|
302
302
|
def execute_as_graph(self, map_variable: TypeMapVariable = None, **kwargs):
|
@@ -312,7 +312,6 @@ class BaseNode(ABC, BaseModel):
|
|
312
312
|
Raises:
|
313
313
|
NotImplementedError: Base class, hence not implemented.
|
314
314
|
"""
|
315
|
-
...
|
316
315
|
|
317
316
|
@abstractmethod
|
318
317
|
def fan_out(self, map_variable: TypeMapVariable = None, **kwargs):
|
@@ -329,7 +328,6 @@ class BaseNode(ABC, BaseModel):
|
|
329
328
|
Raises:
|
330
329
|
Exception: If the node is not a composite node.
|
331
330
|
"""
|
332
|
-
...
|
333
331
|
|
334
332
|
@abstractmethod
|
335
333
|
def fan_in(self, map_variable: TypeMapVariable = None, **kwargs):
|
@@ -346,7 +344,6 @@ class BaseNode(ABC, BaseModel):
|
|
346
344
|
Raises:
|
347
345
|
Exception: If the node is not a composite node.
|
348
346
|
"""
|
349
|
-
...
|
350
347
|
|
351
348
|
@classmethod
|
352
349
|
@abstractmethod
|
@@ -360,7 +357,6 @@ class BaseNode(ABC, BaseModel):
|
|
360
357
|
Returns:
|
361
358
|
BaseNode: The corresponding node.
|
362
359
|
"""
|
363
|
-
...
|
364
360
|
|
365
361
|
@abstractmethod
|
366
362
|
def get_summary(self) -> Dict[str, Any]:
|
@@ -471,7 +467,9 @@ class CompositeNode(TraversalNode):
|
|
471
467
|
attempt_number: int = 1,
|
472
468
|
**kwargs,
|
473
469
|
) -> StepLog:
|
474
|
-
raise Exception(
|
470
|
+
raise Exception(
|
471
|
+
"This is a composite node and does not have an execute function"
|
472
|
+
)
|
475
473
|
|
476
474
|
|
477
475
|
class TerminalNode(BaseNode):
|