runnable 0.12.3__py3-none-any.whl → 0.14.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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):
|