runnable 0.13.0__py3-none-any.whl → 0.16.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. runnable/__init__.py +1 -12
  2. runnable/catalog.py +29 -5
  3. runnable/cli.py +268 -215
  4. runnable/context.py +10 -3
  5. runnable/datastore.py +212 -53
  6. runnable/defaults.py +13 -55
  7. runnable/entrypoints.py +270 -183
  8. runnable/exceptions.py +28 -2
  9. runnable/executor.py +133 -86
  10. runnable/graph.py +37 -13
  11. runnable/nodes.py +50 -22
  12. runnable/parameters.py +27 -8
  13. runnable/pickler.py +1 -1
  14. runnable/sdk.py +230 -66
  15. runnable/secrets.py +3 -1
  16. runnable/tasks.py +99 -41
  17. runnable/utils.py +59 -39
  18. {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/METADATA +28 -31
  19. runnable-0.16.0.dist-info/RECORD +23 -0
  20. {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/WHEEL +1 -1
  21. runnable-0.16.0.dist-info/entry_points.txt +45 -0
  22. runnable/extensions/__init__.py +0 -0
  23. runnable/extensions/catalog/__init__.py +0 -21
  24. runnable/extensions/catalog/file_system/__init__.py +0 -0
  25. runnable/extensions/catalog/file_system/implementation.py +0 -234
  26. runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
  27. runnable/extensions/catalog/k8s_pvc/implementation.py +0 -16
  28. runnable/extensions/catalog/k8s_pvc/integration.py +0 -59
  29. runnable/extensions/executor/__init__.py +0 -649
  30. runnable/extensions/executor/argo/__init__.py +0 -0
  31. runnable/extensions/executor/argo/implementation.py +0 -1194
  32. runnable/extensions/executor/argo/specification.yaml +0 -51
  33. runnable/extensions/executor/k8s_job/__init__.py +0 -0
  34. runnable/extensions/executor/k8s_job/implementation_FF.py +0 -259
  35. runnable/extensions/executor/k8s_job/integration_FF.py +0 -69
  36. runnable/extensions/executor/local.py +0 -69
  37. runnable/extensions/executor/local_container/__init__.py +0 -0
  38. runnable/extensions/executor/local_container/implementation.py +0 -446
  39. runnable/extensions/executor/mocked/__init__.py +0 -0
  40. runnable/extensions/executor/mocked/implementation.py +0 -154
  41. runnable/extensions/executor/retry/__init__.py +0 -0
  42. runnable/extensions/executor/retry/implementation.py +0 -168
  43. runnable/extensions/nodes.py +0 -870
  44. runnable/extensions/run_log_store/__init__.py +0 -0
  45. runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
  46. runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
  47. runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
  48. runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
  49. runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
  50. runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
  51. runnable/extensions/run_log_store/db/integration_FF.py +0 -0
  52. runnable/extensions/run_log_store/file_system/__init__.py +0 -0
  53. runnable/extensions/run_log_store/file_system/implementation.py +0 -140
  54. runnable/extensions/run_log_store/generic_chunked.py +0 -557
  55. runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
  56. runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
  57. runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
  58. runnable/extensions/secrets/__init__.py +0 -0
  59. runnable/extensions/secrets/dotenv/__init__.py +0 -0
  60. runnable/extensions/secrets/dotenv/implementation.py +0 -100
  61. runnable/integration.py +0 -192
  62. runnable-0.13.0.dist-info/RECORD +0 -63
  63. runnable-0.13.0.dist-info/entry_points.txt +0 -41
  64. {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,649 +0,0 @@
1
- import copy
2
- import logging
3
- import os
4
- from abc import abstractmethod
5
- from typing import Dict, List, Optional, cast
6
-
7
- from runnable import (
8
- console,
9
- context,
10
- defaults,
11
- exceptions,
12
- integration,
13
- parameters,
14
- task_console,
15
- utils,
16
- )
17
- from runnable.datastore import DataCatalog, JsonParameter, RunLog, StepLog
18
- from runnable.defaults import TypeMapVariable
19
- from runnable.executor import BaseExecutor
20
- from runnable.extensions.nodes import TaskNode
21
- from runnable.graph import Graph
22
- from runnable.nodes import BaseNode
23
-
24
- logger = logging.getLogger(defaults.LOGGER_NAME)
25
-
26
-
27
- class GenericExecutor(BaseExecutor):
28
- """
29
- The skeleton of an executor class.
30
- Any implementation of an executor should inherit this class and over-ride accordingly.
31
-
32
- This is a loaded base class which has a lot of methods already implemented for "typical" executions.
33
- Look at the function docs to understand how to use them appropriately.
34
-
35
- For any implementation:
36
- 1). Who/when should the run log be set up?
37
- 2). Who/When should the step log be set up?
38
-
39
- """
40
-
41
- service_name: str = ""
42
- service_type: str = "executor"
43
-
44
- @property
45
- def _context(self):
46
- return context.run_context
47
-
48
- def _get_parameters(self) -> Dict[str, JsonParameter]:
49
- """
50
- Consolidate the parameters from the environment variables
51
- and the parameters file.
52
-
53
- The parameters defined in the environment variables take precedence over the parameters file.
54
-
55
- Returns:
56
- _type_: _description_
57
- """
58
- params: Dict[str, JsonParameter] = {}
59
- if self._context.parameters_file:
60
- user_defined = utils.load_yaml(self._context.parameters_file) or {}
61
-
62
- for key, value in user_defined.items():
63
- params[key] = JsonParameter(value=value, kind="json")
64
-
65
- # Update these with some from the environment variables
66
- params.update(parameters.get_user_set_parameters())
67
- logger.debug(f"parameters as seen by executor: {params}")
68
- return params
69
-
70
- def _set_up_run_log(self, exists_ok=False):
71
- """
72
- Create a run log and put that in the run log store
73
-
74
- If exists_ok, we allow the run log to be already present in the run log store.
75
- """
76
- try:
77
- attempt_run_log = self._context.run_log_store.get_run_log_by_id(run_id=self._context.run_id, full=False)
78
-
79
- logger.warning(f"The run log by id: {self._context.run_id} already exists, is this designed?")
80
- raise exceptions.RunLogExistsError(
81
- f"The run log by id: {self._context.run_id} already exists and is {attempt_run_log.status}"
82
- )
83
- except exceptions.RunLogNotFoundError:
84
- pass
85
- except exceptions.RunLogExistsError:
86
- if exists_ok:
87
- return
88
- raise
89
-
90
- # Consolidate and get the parameters
91
- params = self._get_parameters()
92
-
93
- self._context.run_log_store.create_run_log(
94
- run_id=self._context.run_id,
95
- tag=self._context.tag,
96
- status=defaults.PROCESSING,
97
- dag_hash=self._context.dag_hash,
98
- )
99
- # Any interaction with run log store attributes should happen via API if available.
100
- self._context.run_log_store.set_parameters(run_id=self._context.run_id, parameters=params)
101
-
102
- # Update run_config
103
- run_config = utils.get_run_config()
104
- logger.debug(f"run_config as seen by executor: {run_config}")
105
- self._context.run_log_store.set_run_config(run_id=self._context.run_id, run_config=run_config)
106
-
107
- def prepare_for_graph_execution(self):
108
- """
109
- This method should be called prior to calling execute_graph.
110
- Perform any steps required before doing the graph execution.
111
-
112
- The most common implementation is to prepare a run log for the run if the run uses local interactive compute.
113
-
114
- But in cases of actual rendering the job specs (eg: AWS step functions, K8's) we check if the services are OK.
115
- We do not set up a run log as its not relevant.
116
- """
117
-
118
- integration.validate(self, self._context.run_log_store)
119
- integration.configure_for_traversal(self, self._context.run_log_store)
120
-
121
- integration.validate(self, self._context.catalog_handler)
122
- integration.configure_for_traversal(self, self._context.catalog_handler)
123
-
124
- integration.validate(self, self._context.secrets_handler)
125
- integration.configure_for_traversal(self, self._context.secrets_handler)
126
-
127
- self._set_up_run_log()
128
-
129
- def prepare_for_node_execution(self):
130
- """
131
- Perform any modifications to the services prior to execution of the node.
132
-
133
- Args:
134
- node (Node): [description]
135
- map_variable (dict, optional): [description]. Defaults to None.
136
- """
137
- integration.validate(self, self._context.run_log_store)
138
- integration.configure_for_execution(self, self._context.run_log_store)
139
-
140
- integration.validate(self, self._context.catalog_handler)
141
- integration.configure_for_execution(self, self._context.catalog_handler)
142
-
143
- integration.validate(self, self._context.secrets_handler)
144
- integration.configure_for_execution(self, self._context.secrets_handler)
145
-
146
- def _sync_catalog(self, stage: str, synced_catalogs=None) -> Optional[List[DataCatalog]]:
147
- """
148
- 1). Identify the catalog settings by over-riding node settings with the global settings.
149
- 2). For stage = get:
150
- Identify the catalog items that are being asked to get from the catalog
151
- And copy them to the local compute data folder
152
- 3). For stage = put:
153
- Identify the catalog items that are being asked to put into the catalog
154
- Copy the items from local compute folder to the catalog
155
- 4). Add the items onto the step log according to the stage
156
-
157
- Args:
158
- node (Node): The current node being processed
159
- step_log (StepLog): The step log corresponding to that node
160
- stage (str): One of get or put
161
-
162
- Raises:
163
- Exception: If the stage is not in one of get/put
164
-
165
- """
166
- if stage not in ["get", "put"]:
167
- msg = (
168
- "Catalog service only accepts get/put possible actions as part of node execution."
169
- f"Sync catalog of the executor: {self.service_name} asks for {stage} which is not accepted"
170
- )
171
- logger.exception(msg)
172
- raise Exception(msg)
173
-
174
- try:
175
- node_catalog_settings = self._context_node._get_catalog_settings()
176
- except exceptions.TerminalNodeError:
177
- return None
178
-
179
- if not (node_catalog_settings and stage in node_catalog_settings):
180
- logger.info("No catalog settings found for stage: %s", stage)
181
- # Nothing to get/put from the catalog
182
- return None
183
-
184
- compute_data_folder = self.get_effective_compute_data_folder()
185
-
186
- data_catalogs = []
187
- for name_pattern in node_catalog_settings.get(stage) or []:
188
- if stage == "get":
189
- data_catalog = self._context.catalog_handler.get(
190
- name=name_pattern, run_id=self._context.run_id, compute_data_folder=compute_data_folder
191
- )
192
-
193
- elif stage == "put":
194
- data_catalog = self._context.catalog_handler.put(
195
- name=name_pattern,
196
- run_id=self._context.run_id,
197
- compute_data_folder=compute_data_folder,
198
- synced_catalogs=synced_catalogs,
199
- )
200
-
201
- logger.debug(f"Added data catalog: {data_catalog} to step log")
202
- data_catalogs.extend(data_catalog)
203
-
204
- return data_catalogs
205
-
206
- def get_effective_compute_data_folder(self) -> str:
207
- """
208
- Get the effective compute data folder for the given stage.
209
- If there is nothing to catalog, we return None.
210
-
211
- The default is the compute data folder of the catalog but this can be over-ridden by the node.
212
-
213
- Args:
214
- stage (str): The stage we are in the process of cataloging
215
-
216
-
217
- Returns:
218
- str: The compute data folder as defined by the node defaulting to catalog handler
219
- """
220
- compute_data_folder = self._context.catalog_handler.compute_data_folder
221
-
222
- catalog_settings = self._context_node._get_catalog_settings()
223
- effective_compute_data_folder = catalog_settings.get("compute_data_folder", "") or compute_data_folder
224
-
225
- return effective_compute_data_folder
226
-
227
- @property
228
- def step_attempt_number(self) -> int:
229
- """
230
- The attempt number of the current step.
231
- Orchestrators should use this step to submit multiple attempts of the job.
232
-
233
- Returns:
234
- int: The attempt number of the current step. Defaults to 1.
235
- """
236
- return int(os.environ.get(defaults.ATTEMPT_NUMBER, 1))
237
-
238
- def _execute_node(self, node: BaseNode, map_variable: TypeMapVariable = None, mock: bool = False, **kwargs):
239
- """
240
- This is the entry point when we do the actual execution of the function.
241
- DO NOT Over-ride this function.
242
-
243
- While in interactive execution, we just compute, in 3rd party interactive execution, we need to reach
244
- this function.
245
-
246
- In most cases,
247
- * We get the corresponding step_log of the node and the parameters.
248
- * We sync the catalog to GET any data sets that are in the catalog
249
- * We call the execute method of the node for the actual compute and retry it as many times as asked.
250
- * If the node succeeds, we get any of the user defined metrics provided by the user.
251
- * We sync the catalog to PUT any data sets that are in the catalog.
252
-
253
- Args:
254
- node (Node): The node to execute
255
- map_variable (dict, optional): If the node is of a map state, map_variable is the value of the iterable.
256
- Defaults to None.
257
- """
258
- logger.info(f"Trying to execute node: {node.internal_name}, attempt : {self.step_attempt_number}")
259
-
260
- self._context_node = node
261
-
262
- data_catalogs_get: Optional[List[DataCatalog]] = self._sync_catalog(stage="get")
263
- logger.debug(f"data_catalogs_get: {data_catalogs_get}")
264
-
265
- step_log = node.execute(
266
- map_variable=map_variable,
267
- attempt_number=self.step_attempt_number,
268
- mock=mock,
269
- **kwargs,
270
- )
271
-
272
- data_catalogs_put: Optional[List[DataCatalog]] = self._sync_catalog(stage="put")
273
- logger.debug(f"data_catalogs_put: {data_catalogs_put}")
274
-
275
- step_log.add_data_catalogs(data_catalogs_get or [])
276
- step_log.add_data_catalogs(data_catalogs_put or [])
277
-
278
- console.print(f"Summary of the step: {step_log.internal_name}")
279
- console.print(step_log.get_summary(), style=defaults.info_style)
280
-
281
- self._context_node = None # type: ignore
282
-
283
- self._context.run_log_store.add_step_log(step_log, self._context.run_id)
284
-
285
- def add_code_identities(self, node: BaseNode, step_log: StepLog, **kwargs):
286
- """
287
- Add code identities specific to the implementation.
288
-
289
- The Base class has an implementation of adding git code identities.
290
-
291
- Args:
292
- step_log (object): The step log object
293
- node (BaseNode): The node we are adding the step log for
294
- """
295
- step_log.code_identities.append(utils.get_git_code_identity())
296
-
297
- def execute_from_graph(self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs):
298
- """
299
- This is the entry point to from the graph execution.
300
-
301
- While the self.execute_graph is responsible for traversing the graph, this function is responsible for
302
- actual execution of the node.
303
-
304
- If the node type is:
305
- * task : We can delegate to _execute_node after checking the eligibility for re-run in cases of a re-run
306
- * success: We can delegate to _execute_node
307
- * fail: We can delegate to _execute_node
308
-
309
- For nodes that are internally graphs:
310
- * parallel: Delegate the responsibility of execution to the node.execute_as_graph()
311
- * dag: Delegate the responsibility of execution to the node.execute_as_graph()
312
- * map: Delegate the responsibility of execution to the node.execute_as_graph()
313
-
314
- Transpilers will NEVER use this method and will NEVER call ths method.
315
- This method should only be used by interactive executors.
316
-
317
- Args:
318
- node (Node): The node to execute
319
- map_variable (dict, optional): If the node if of a map state, this corresponds to the value of iterable.
320
- Defaults to None.
321
- """
322
- step_log = self._context.run_log_store.create_step_log(node.name, node._get_step_log_name(map_variable))
323
-
324
- self.add_code_identities(node=node, step_log=step_log)
325
-
326
- step_log.step_type = node.node_type
327
- step_log.status = defaults.PROCESSING
328
-
329
- self._context.run_log_store.add_step_log(step_log, self._context.run_id)
330
-
331
- logger.info(f"Executing node: {node.get_summary()}")
332
-
333
- # Add the step log to the database as per the situation.
334
- # If its a terminal node, complete it now
335
- if node.node_type in ["success", "fail"]:
336
- self._execute_node(node, map_variable=map_variable, **kwargs)
337
- return
338
-
339
- # We call an internal function to iterate the sub graphs and execute them
340
- if node.is_composite:
341
- node.execute_as_graph(map_variable=map_variable, **kwargs)
342
- return
343
-
344
- task_console.export_text(clear=True)
345
-
346
- task_name = node._resolve_map_placeholders(node.internal_name, map_variable)
347
- console.print(f":runner: Executing the node {task_name} ... ", style="bold color(208)")
348
- self.trigger_job(node=node, map_variable=map_variable, **kwargs)
349
-
350
- log_file_name = utils.make_log_file_name(node=node, map_variable=map_variable)
351
- task_console.save_text(log_file_name, clear=True)
352
-
353
- self._context.catalog_handler.put(name=log_file_name, run_id=self._context.run_id)
354
- os.remove(log_file_name)
355
-
356
- def trigger_job(self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs):
357
- """
358
- Call this method only if we are responsible for traversing the graph via
359
- execute_from_graph().
360
-
361
- We are not prepared to execute node as of now.
362
-
363
- Args:
364
- node (BaseNode): The node to execute
365
- map_variable (str, optional): If the node if of a map state, this corresponds to the value of iterable.
366
- Defaults to ''.
367
-
368
- NOTE: We do not raise an exception as this method is not required by many extensions
369
- """
370
- pass
371
-
372
- def _get_status_and_next_node_name(self, current_node: BaseNode, dag: Graph, map_variable: TypeMapVariable = None):
373
- """
374
- Given the current node and the graph, returns the name of the next node to execute.
375
-
376
- The name is always relative the graph that the node resides in.
377
-
378
- If the current node succeeded, we return the next node as per the graph.
379
- If the current node failed, we return the on failure node of the node (if provided) or the global one.
380
-
381
- This method is only used by interactive executors i.e local and local-container
382
-
383
- Args:
384
- current_node (BaseNode): The current node.
385
- dag (Graph): The dag we are traversing.
386
- map_variable (dict): If the node belongs to a map branch.
387
-
388
- """
389
-
390
- step_log = self._context.run_log_store.get_step_log(
391
- current_node._get_step_log_name(map_variable), self._context.run_id
392
- )
393
- logger.info(f"Finished executing the node {current_node} with status {step_log.status}")
394
-
395
- try:
396
- next_node_name = current_node._get_next_node()
397
- except exceptions.TerminalNodeError:
398
- next_node_name = ""
399
-
400
- if step_log.status == defaults.FAIL:
401
- next_node_name = dag.get_fail_node().name
402
- if current_node._get_on_failure_node():
403
- next_node_name = current_node._get_on_failure_node()
404
-
405
- return step_log.status, next_node_name
406
-
407
- def execute_graph(self, dag: Graph, map_variable: TypeMapVariable = None, **kwargs):
408
- """
409
- The parallelization is controlled by the nodes and not by this function.
410
-
411
- Transpilers should over ride this method to do the translation of dag to the platform specific way.
412
- Interactive methods should use this to traverse and execute the dag.
413
- - Use execute_from_graph to handle sub-graphs
414
-
415
- Logically the method should:
416
- * Start at the dag.start_at of the dag.
417
- * Call the self.execute_from_graph(node)
418
- * depending upon the status of the execution, either move to the success node or failure node.
419
-
420
- Args:
421
- dag (Graph): The directed acyclic graph to traverse and execute.
422
- map_variable (dict, optional): If the node if of a map state, this corresponds to the value of the iterable.
423
- Defaults to None.
424
- """
425
- current_node = dag.start_at
426
- previous_node = None
427
- logger.info(f"Running the execution with {current_node}")
428
-
429
- branch_execution_task = None
430
- branch_task_name: str = ""
431
- if dag.internal_branch_name:
432
- branch_task_name = BaseNode._resolve_map_placeholders(
433
- dag.internal_branch_name or "Graph",
434
- map_variable,
435
- )
436
- branch_execution_task = self._context.progress.add_task(
437
- f"[dark_orange]Executing {branch_task_name}",
438
- total=1,
439
- )
440
-
441
- while True:
442
- working_on = dag.get_node_by_name(current_node)
443
- task_name = working_on._resolve_map_placeholders(working_on.internal_name, map_variable)
444
-
445
- if previous_node == current_node:
446
- raise Exception("Potentially running in a infinite loop")
447
-
448
- previous_node = current_node
449
-
450
- logger.debug(f"Creating execution log for {working_on}")
451
-
452
- depth = " " * ((task_name.count(".")) or 1 - 1)
453
-
454
- task_execution = self._context.progress.add_task(f"{depth}Executing {task_name}", total=1)
455
-
456
- try:
457
- self.execute_from_graph(working_on, map_variable=map_variable, **kwargs)
458
- status, next_node_name = self._get_status_and_next_node_name(
459
- current_node=working_on, dag=dag, map_variable=map_variable
460
- )
461
-
462
- if status == defaults.SUCCESS:
463
- self._context.progress.update(
464
- task_execution,
465
- description=f"{depth}[green] {task_name} Completed",
466
- completed=True,
467
- overflow="fold",
468
- )
469
- else:
470
- self._context.progress.update(
471
- task_execution, description=f"{depth}[red] {task_name} Failed", completed=True
472
- ) # type ignore
473
- except Exception as e: # noqa: E722
474
- self._context.progress.update(
475
- task_execution,
476
- description=f"{depth}[red] {task_name} Errored",
477
- completed=True,
478
- )
479
- console.print(e, style=defaults.error_style)
480
- logger.exception(e)
481
- raise
482
-
483
- console.rule(style="[dark orange]")
484
-
485
- if working_on.node_type in ["success", "fail"]:
486
- break
487
-
488
- current_node = next_node_name
489
-
490
- if branch_execution_task:
491
- self._context.progress.update(
492
- branch_execution_task, description=f"[green3] {branch_task_name} completed", completed=True
493
- )
494
-
495
- run_log = self._context.run_log_store.get_branch_log(
496
- working_on._get_branch_log_name(map_variable), self._context.run_id
497
- )
498
-
499
- branch = "graph"
500
- if working_on.internal_branch_name:
501
- branch = working_on.internal_branch_name
502
-
503
- logger.info(f"Finished execution of the {branch} with status {run_log.status}")
504
-
505
- # We are in the root dag
506
- if dag == self._context.dag:
507
- run_log = cast(RunLog, run_log)
508
- console.print("Completed Execution, Summary:", style="bold color(208)")
509
- console.print(run_log.get_summary(), style=defaults.info_style)
510
-
511
- def send_return_code(self, stage="traversal"):
512
- """
513
- Convenience function used by pipeline to send return code to the caller of the cli
514
-
515
- Raises:
516
- Exception: If the pipeline execution failed
517
- """
518
- run_id = self._context.run_id
519
-
520
- run_log = self._context.run_log_store.get_run_log_by_id(run_id=run_id, full=False)
521
- if run_log.status == defaults.FAIL:
522
- raise exceptions.ExecutionFailedError(run_id=run_id)
523
-
524
- def _resolve_executor_config(self, node: BaseNode):
525
- """
526
- The overrides section can contain specific over-rides to an global executor config.
527
- To avoid too much clutter in the dag definition, we allow the configuration file to have overrides block.
528
- The nodes can over-ride the global config by referring to key in the overrides.
529
-
530
- This function also applies variables to the effective node config.
531
-
532
- For example:
533
- # configuration.yaml
534
- execution:
535
- type: cloud-implementation
536
- config:
537
- k1: v1
538
- k3: v3
539
- overrides:
540
- custom_config:
541
- k1: v11
542
- k2: v2 # Could be a mapping internally.
543
-
544
- # in pipeline definition.yaml
545
- dag:
546
- steps:
547
- step1:
548
- overrides:
549
- cloud-implementation: custom_config
550
-
551
- This method should resolve the node_config to {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'}
552
-
553
- Args:
554
- node (BaseNode): The current node being processed.
555
-
556
- """
557
- effective_node_config = copy.deepcopy(self.model_dump())
558
- try:
559
- ctx_node_config = node._get_executor_config(self.service_name)
560
- except exceptions.TerminalNodeError:
561
- # Some modes request for effective node config even for success or fail nodes
562
- return utils.apply_variables(effective_node_config, self._context.variables)
563
-
564
- if ctx_node_config:
565
- if ctx_node_config not in self.overrides:
566
- raise Exception(f"No override of key: {ctx_node_config} found in the overrides section")
567
-
568
- effective_node_config.update(self.overrides[ctx_node_config])
569
-
570
- effective_node_config = utils.apply_variables(effective_node_config, self._context.variables)
571
- logger.debug(f"Effective node config: {effective_node_config}")
572
-
573
- return effective_node_config
574
-
575
- @abstractmethod
576
- def execute_job(self, node: TaskNode):
577
- """
578
- Executor specific way of executing a job (python function or a notebook).
579
-
580
- Interactive executors should execute the job.
581
- Transpilers should write the instructions.
582
-
583
- Args:
584
- node (BaseNode): The job node to execute
585
-
586
- Raises:
587
- NotImplementedError: Executors should choose to extend this functionality or not.
588
- """
589
- raise NotImplementedError
590
-
591
- def fan_out(self, node: BaseNode, map_variable: TypeMapVariable = None):
592
- """
593
- This method is used to appropriately fan-out the execution of a composite node.
594
- This is only useful when we want to execute a composite node during 3rd party orchestrators.
595
-
596
- Reason: Transpilers typically try to run the leaf nodes but do not have any capacity to do anything for the
597
- step which is composite. By calling this fan-out before calling the leaf nodes, we have an opportunity to
598
- do the right set up (creating the step log, exposing the parameters, etc.) for the composite step.
599
-
600
- All 3rd party orchestrators should use this method to fan-out the execution of a composite node.
601
- This ensures:
602
- - The dot path notation is preserved, this method should create the step and call the node's fan out to
603
- create the branch logs and let the 3rd party do the actual step execution.
604
- - Gives 3rd party orchestrators an opportunity to set out the required for running a composite node.
605
-
606
- Args:
607
- node (BaseNode): The node to fan-out
608
- map_variable (dict, optional): If the node if of a map state,.Defaults to None.
609
-
610
- """
611
- step_log = self._context.run_log_store.create_step_log(
612
- node.name, node._get_step_log_name(map_variable=map_variable)
613
- )
614
-
615
- self.add_code_identities(node=node, step_log=step_log)
616
-
617
- step_log.step_type = node.node_type
618
- step_log.status = defaults.PROCESSING
619
- self._context.run_log_store.add_step_log(step_log, self._context.run_id)
620
-
621
- node.fan_out(executor=self, map_variable=map_variable)
622
-
623
- def fan_in(self, node: BaseNode, map_variable: TypeMapVariable = None):
624
- """
625
- This method is used to appropriately fan-in after the execution of a composite node.
626
- This is only useful when we want to execute a composite node during 3rd party orchestrators.
627
-
628
- Reason: Transpilers typically try to run the leaf nodes but do not have any capacity to do anything for the
629
- step which is composite. By calling this fan-in after calling the leaf nodes, we have an opportunity to
630
- act depending upon the status of the individual branches.
631
-
632
- All 3rd party orchestrators should use this method to fan-in the execution of a composite node.
633
- This ensures:
634
- - Gives the renderer's the control on where to go depending upon the state of the composite node.
635
- - The status of the step and its underlying branches are correctly updated.
636
-
637
- Args:
638
- node (BaseNode): The node to fan-in
639
- map_variable (dict, optional): If the node if of a map state,.Defaults to None.
640
-
641
- """
642
- node.fan_in(executor=self, map_variable=map_variable)
643
-
644
- step_log = self._context.run_log_store.get_step_log(
645
- node._get_step_log_name(map_variable=map_variable), self._context.run_id
646
- )
647
-
648
- if step_log.status == defaults.FAIL:
649
- raise Exception(f"Step {node.name} failed")
File without changes