runnable 0.13.0__py3-none-any.whl → 0.16.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.
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
runnable/exceptions.py CHANGED
@@ -10,6 +10,18 @@ class RunLogExistsError(Exception): # pragma: no cover
10
10
  self.message = f"Run id for {run_id} is already found in the datastore"
11
11
 
12
12
 
13
+ class JobLogNotFoundError(Exception):
14
+ """
15
+ Exception class
16
+ Args:
17
+ Exception ([type]): [description]
18
+ """
19
+
20
+ def __init__(self, run_id):
21
+ super().__init__()
22
+ self.message = f"Job for {run_id} is not found in the datastore"
23
+
24
+
13
25
  class RunLogNotFoundError(Exception): # pragma: no cover
14
26
  """
15
27
  Exception class
@@ -31,7 +43,9 @@ class StepLogNotFoundError(Exception): # pragma: no cover
31
43
 
32
44
  def __init__(self, run_id, name):
33
45
  super().__init__()
34
- self.message = f"Step log for {name} is not found in the datastore for Run id: {run_id}"
46
+ self.message = (
47
+ f"Step log for {name} is not found in the datastore for Run id: {run_id}"
48
+ )
35
49
 
36
50
 
37
51
  class BranchLogNotFoundError(Exception): # pragma: no cover
@@ -43,7 +57,9 @@ class BranchLogNotFoundError(Exception): # pragma: no cover
43
57
 
44
58
  def __init__(self, run_id, name):
45
59
  super().__init__()
46
- self.message = f"Branch log for {name} is not found in the datastore for Run id: {run_id}"
60
+ self.message = (
61
+ f"Branch log for {name} is not found in the datastore for Run id: {run_id}"
62
+ )
47
63
 
48
64
 
49
65
  class NodeNotFoundError(Exception): # pragma: no cover
@@ -70,6 +86,16 @@ class BranchNotFoundError(Exception): # pragma: no cover
70
86
  self.message = f"Branch of name {name} is not found the graph"
71
87
 
72
88
 
89
+ class NodeMethodCallError(Exception):
90
+ """
91
+ Exception class
92
+ """
93
+
94
+ def __init__(self, message):
95
+ super().__init__()
96
+ self.message = message
97
+
98
+
73
99
  class TerminalNodeError(Exception): # pragma: no cover
74
100
  def __init__(self):
75
101
  super().__init__()
runnable/executor.py CHANGED
@@ -5,17 +5,17 @@ import os
5
5
  from abc import ABC, abstractmethod
6
6
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
7
7
 
8
- from pydantic import BaseModel, ConfigDict
8
+ from pydantic import BaseModel, ConfigDict, PrivateAttr
9
9
 
10
10
  import runnable.context as context
11
11
  from runnable import defaults
12
- from runnable.datastore import DataCatalog, StepLog
12
+ from runnable.datastore import DataCatalog, JobLog, StepLog
13
13
  from runnable.defaults import TypeMapVariable
14
14
  from runnable.graph import Graph
15
15
 
16
16
  if TYPE_CHECKING: # pragma: no cover
17
- from runnable.extensions.nodes import TaskNode
18
17
  from runnable.nodes import BaseNode
18
+ from runnable.tasks import BaseTaskType
19
19
 
20
20
  logger = logging.getLogger(defaults.LOGGER_NAME)
21
21
 
@@ -34,11 +34,10 @@ class BaseExecutor(ABC, BaseModel):
34
34
  service_name: str = ""
35
35
  service_type: str = "executor"
36
36
 
37
- overrides: dict = {}
38
-
39
- _local: bool = False # This is a flag to indicate whether the executor is local or not.
37
+ _is_local: bool = (
38
+ False # This is a flag to indicate whether the executor is local or not.
39
+ )
40
40
 
41
- _context_node = None # type: BaseNode
42
41
  model_config = ConfigDict(extra="forbid")
43
42
 
44
43
  @property
@@ -65,32 +64,63 @@ class BaseExecutor(ABC, BaseModel):
65
64
  """
66
65
  ...
67
66
 
67
+ # TODO: Make this attempt number
68
+ @property
69
+ def step_attempt_number(self) -> int:
70
+ """
71
+ The attempt number of the current step.
72
+ Orchestrators should use this step to submit multiple attempts of the job.
73
+
74
+ Returns:
75
+ int: The attempt number of the current step. Defaults to 1.
76
+ """
77
+ return int(os.environ.get(defaults.ATTEMPT_NUMBER, 1))
78
+
68
79
  @abstractmethod
69
- def prepare_for_graph_execution(self):
80
+ def send_return_code(self, stage="traversal"):
81
+ """
82
+ Convenience function used by pipeline to send return code to the caller of the cli
83
+
84
+ Raises:
85
+ Exception: If the pipeline execution failed
70
86
  """
71
- This method should be called prior to calling execute_graph.
72
- Perform any steps required before doing the graph execution.
87
+ ...
88
+
73
89
 
74
- The most common implementation is to prepare a run log for the run if the run uses local interactive compute.
90
+ class BaseJobExecutor(BaseExecutor):
91
+ service_type: str = "job_executor"
75
92
 
76
- But in cases of actual rendering the job specs (eg: AWS step functions, K8's) we check if the services are OK.
77
- We do not set up a run log as its not relevant.
93
+ @abstractmethod
94
+ def submit_job(self, job: BaseTaskType, catalog_settings: Optional[List[str]]):
95
+ """
96
+ Local executors should
97
+ - create the run log
98
+ - and call an execute_job
99
+
100
+ Non local executors should
101
+ - transpile the job to the platform specific job spec
102
+ - submit the job to call execute_job
78
103
  """
79
104
  ...
80
105
 
81
106
  @abstractmethod
82
- def prepare_for_node_execution(self):
107
+ def add_code_identities(self, job_log: JobLog, **kwargs):
83
108
  """
84
- Perform any modifications to the services prior to execution of the node.
109
+ Add code identities specific to the implementation.
110
+
111
+ The Base class has an implementation of adding git code identities.
85
112
 
86
113
  Args:
87
- node (Node): [description]
88
- map_variable (dict, optional): [description]. Defaults to None.
114
+ step_log (object): The step log object
115
+ node (BaseNode): The node we are adding the step log for
89
116
  """
90
117
  ...
91
118
 
92
119
  @abstractmethod
93
- def _sync_catalog(self, stage: str, synced_catalogs=None) -> Optional[List[DataCatalog]]:
120
+ def _sync_catalog(
121
+ self,
122
+ catalog_settings: Optional[List[str]],
123
+ ) -> Optional[List[DataCatalog]]:
94
124
  """
95
125
  1). Identify the catalog settings by over-riding node settings with the global settings.
96
126
  2). For stage = get:
@@ -112,6 +142,34 @@ class BaseExecutor(ABC, BaseModel):
112
142
  """
113
143
  ...
114
144
 
145
+ @abstractmethod
146
+ def execute_job(self, job: BaseTaskType, catalog_settings: Optional[List[str]]):
147
+ """
148
+ Focusses only on execution of the job.
149
+ """
150
+ ...
151
+
152
+
153
+ # TODO: Consolidate execute_node, trigger_node_execution, _execute_node
154
+ class BasePipelineExecutor(BaseExecutor):
155
+ service_type: str = "pipeline_executor"
156
+ overrides: dict = {}
157
+
158
+ _context_node: Optional[BaseNode] = PrivateAttr(default=None)
159
+
160
+ @abstractmethod
161
+ def add_code_identities(self, node: BaseNode, step_log: StepLog, **kwargs):
162
+ """
163
+ Add code identities specific to the implementation.
164
+
165
+ The Base class has an implementation of adding git code identities.
166
+
167
+ Args:
168
+ step_log (object): The step log object
169
+ node (BaseNode): The node we are adding the step log for
170
+ """
171
+ ...
172
+
115
173
  @abstractmethod
116
174
  def get_effective_compute_data_folder(self) -> Optional[str]:
117
175
  """
@@ -129,19 +187,39 @@ class BaseExecutor(ABC, BaseModel):
129
187
  """
130
188
  ...
131
189
 
132
- @property
133
- def step_attempt_number(self) -> int:
190
+ @abstractmethod
191
+ def _sync_catalog(
192
+ self, stage: str, synced_catalogs=None
193
+ ) -> Optional[List[DataCatalog]]:
134
194
  """
135
- The attempt number of the current step.
136
- Orchestrators should use this step to submit multiple attempts of the job.
195
+ 1). Identify the catalog settings by over-riding node settings with the global settings.
196
+ 2). For stage = get:
197
+ Identify the catalog items that are being asked to get from the catalog
198
+ And copy them to the local compute data folder
199
+ 3). For stage = put:
200
+ Identify the catalog items that are being asked to put into the catalog
201
+ Copy the items from local compute folder to the catalog
202
+ 4). Add the items onto the step log according to the stage
203
+
204
+ Args:
205
+ node (Node): The current node being processed
206
+ step_log (StepLog): The step log corresponding to that node
207
+ stage (str): One of get or put
208
+
209
+ Raises:
210
+ Exception: If the stage is not in one of get/put
137
211
 
138
- Returns:
139
- int: The attempt number of the current step. Defaults to 1.
140
212
  """
141
- return int(os.environ.get(defaults.ATTEMPT_NUMBER, 1))
213
+ ...
142
214
 
143
215
  @abstractmethod
144
- def _execute_node(self, node: BaseNode, map_variable: TypeMapVariable = None, mock: bool = False, **kwargs):
216
+ def _execute_node(
217
+ self,
218
+ node: BaseNode,
219
+ map_variable: TypeMapVariable = None,
220
+ mock: bool = False,
221
+ **kwargs,
222
+ ):
145
223
  """
146
224
  This is the entry point when we do the actual execution of the function.
147
225
 
@@ -163,7 +241,9 @@ class BaseExecutor(ABC, BaseModel):
163
241
  ...
164
242
 
165
243
  @abstractmethod
166
- def execute_node(self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs):
244
+ def execute_node(
245
+ self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
246
+ ):
167
247
  """
168
248
  The entry point for all executors apart from local.
169
249
  We have already prepared for node execution.
@@ -178,20 +258,9 @@ class BaseExecutor(ABC, BaseModel):
178
258
  ...
179
259
 
180
260
  @abstractmethod
181
- def add_code_identities(self, node: BaseNode, step_log: StepLog, **kwargs):
182
- """
183
- Add code identities specific to the implementation.
184
-
185
- The Base class has an implementation of adding git code identities.
186
-
187
- Args:
188
- step_log (object): The step log object
189
- node (BaseNode): The node we are adding the step log for
190
- """
191
- ...
192
-
193
- @abstractmethod
194
- def execute_from_graph(self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs):
261
+ def execute_from_graph(
262
+ self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
263
+ ):
195
264
  """
196
265
  This is the entry point to from the graph execution.
197
266
 
@@ -219,24 +288,9 @@ class BaseExecutor(ABC, BaseModel):
219
288
  ...
220
289
 
221
290
  @abstractmethod
222
- def trigger_job(self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs):
223
- """
224
- Executor specific way of triggering jobs when runnable does both traversal and execution
225
-
226
- Transpilers will NEVER use this method and will NEVER call them.
227
- Only interactive executors who need execute_from_graph will ever implement it.
228
-
229
- Args:
230
- node (BaseNode): The node to execute
231
- map_variable (str, optional): If the node if of a map state, this corresponds to the value of iterable.
232
- Defaults to ''.
233
-
234
- NOTE: We do not raise an exception as this method is not required by many extensions
235
- """
236
- ...
237
-
238
- @abstractmethod
239
- def _get_status_and_next_node_name(self, current_node: BaseNode, dag: Graph, map_variable: TypeMapVariable = None):
291
+ def _get_status_and_next_node_name(
292
+ self, current_node: BaseNode, dag: Graph, map_variable: TypeMapVariable = None
293
+ ) -> tuple[str, str]:
240
294
  """
241
295
  Given the current node and the graph, returns the name of the next node to execute.
242
296
 
@@ -275,17 +329,7 @@ class BaseExecutor(ABC, BaseModel):
275
329
  ...
276
330
 
277
331
  @abstractmethod
278
- def send_return_code(self, stage="traversal"):
279
- """
280
- Convenience function used by pipeline to send return code to the caller of the cli
281
-
282
- Raises:
283
- Exception: If the pipeline execution failed
284
- """
285
- ...
286
-
287
- @abstractmethod
288
- def _resolve_executor_config(self, node: BaseNode):
332
+ def _resolve_executor_config(self, node: BaseNode) -> Dict[str, Any]:
289
333
  """
290
334
  The overrides section can contain specific over-rides to an global executor config.
291
335
  To avoid too much clutter in the dag definition, we allow the configuration file to have overrides block.
@@ -318,22 +362,6 @@ class BaseExecutor(ABC, BaseModel):
318
362
  """
319
363
  ...
320
364
 
321
- @abstractmethod
322
- def execute_job(self, node: TaskNode):
323
- """
324
- Executor specific way of executing a job (python function or a notebook).
325
-
326
- Interactive executors should execute the job.
327
- Transpilers should write the instructions.
328
-
329
- Args:
330
- node (BaseNode): The job node to execute
331
-
332
- Raises:
333
- NotImplementedError: Executors should choose to extend this functionality or not.
334
- """
335
- ...
336
-
337
365
  @abstractmethod
338
366
  def fan_out(self, node: BaseNode, map_variable: TypeMapVariable = None):
339
367
  """
@@ -378,3 +406,22 @@ class BaseExecutor(ABC, BaseModel):
378
406
 
379
407
  """
380
408
  ...
409
+
410
+ @abstractmethod
411
+ def trigger_node_execution(
412
+ self, node: BaseNode, map_variable: TypeMapVariable = None, **kwargs
413
+ ):
414
+ """
415
+ Executor specific way of triggering jobs when runnable does both traversal and execution
416
+
417
+ Transpilers will NEVER use this method and will NEVER call them.
418
+ Only interactive executors who need execute_from_graph will ever implement it.
419
+
420
+ Args:
421
+ node (BaseNode): The node to execute
422
+ map_variable (str, optional): If the node if of a map state, this corresponds to the value of iterable.
423
+ Defaults to ''.
424
+
425
+ NOTE: We do not raise an exception as this method is not required by many extensions
426
+ """
427
+ ...
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(default_factory=dict, serialization_alias="steps")
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(self, node: "BaseNode", visited: Dict[str, bool], recstack: Dict[str, bool]) -> bool:
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(str, dag_config.get("start_at")) # Let the start_at be relative to the graph
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(name, step_config=step_config, internal_branch_name=internal_branch_name)
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("type") # Remove the type as it is not used in node creation.
373
- node_mgr: BaseNode = driver.DriverManager(namespace="nodes", name=node_type).driver
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
 
@@ -386,7 +398,6 @@ def create_node(name: str, step_config: dict, internal_branch_name: Optional[str
386
398
  node = node_mgr.parse_from_config(config=invoke_kwds)
387
399
  return node
388
400
  except KeyError:
389
- # type is missing!!
390
401
  msg = "The node configuration does not contain the required key 'type'."
391
402
  logger.exception(step_config)
392
403
  raise Exception(msg)
@@ -424,11 +435,18 @@ def search_node_by_internal_name(dag: Graph, internal_name: str):
424
435
  for i in range(len(dot_path)):
425
436
  if i % 2:
426
437
  # Its odd, so we are in brach name
427
- current_branch = current_node._get_branch_by_name(".".join(dot_path[: i + 1])) # type: ignore
428
- logger.debug(f"Finding step for {internal_name} in branch: {current_branch}")
438
+
439
+ current_branch = current_node._get_branch_by_name( # type: ignore
440
+ ".".join(dot_path[: i + 1])
441
+ )
442
+ logger.debug(
443
+ f"Finding step for {internal_name} in branch: {current_branch}"
444
+ )
429
445
  else:
430
446
  # Its even, so we are in Step, we start here!
431
- current_node = current_branch.get_node_by_internal_name(".".join(dot_path[: i + 1]))
447
+ current_node = current_branch.get_node_by_internal_name(
448
+ ".".join(dot_path[: i + 1])
449
+ )
432
450
  logger.debug(f"Finding {internal_name} in node: {current_node}")
433
451
 
434
452
  logger.debug(f"current branch : {current_branch}, current step {current_node}")
@@ -463,12 +481,18 @@ def search_branch_by_internal_name(dag: Graph, internal_name: str):
463
481
  for i in range(len(dot_path)):
464
482
  if i % 2:
465
483
  # Its odd, so we are in brach name
466
- current_branch = current_node._get_branch_by_name(".".join(dot_path[: i + 1])) # type: ignore
467
- logger.debug(f"Finding step for {internal_name} in branch: {current_branch}")
484
+ current_branch = current_node._get_branch_by_name( # type: ignore
485
+ ".".join(dot_path[: i + 1])
486
+ )
487
+ logger.debug(
488
+ f"Finding step for {internal_name} in branch: {current_branch}"
489
+ )
468
490
 
469
491
  else:
470
492
  # Its even, so we are in Step, we start here!
471
- current_node = current_branch.get_node_by_internal_name(".".join(dot_path[: i + 1]))
493
+ current_node = current_branch.get_node_by_internal_name(
494
+ ".".join(dot_path[: i + 1])
495
+ )
472
496
  logger.debug(f"Finding {internal_name} in node: {current_node}")
473
497
 
474
498
  logger.debug(f"current branch : {current_branch}, current step {current_node}")
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(self, replace_with=defaults.COMMAND_FRIENDLY_CHARACTER) -> str:
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(cls, name: str, map_variable: TypeMapVariable = None) -> str:
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(self.internal_name, map_variable=map_variable)
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(self.internal_branch_name, map_variable=map_variable)
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]:
@@ -439,16 +435,34 @@ class ExecutableNode(TraversalNode):
439
435
  return self.max_attempts
440
436
 
441
437
  def _get_branch_by_name(self, branch_name: str):
442
- raise Exception("This is an executable node and does not have branches")
438
+ raise exceptions.NodeMethodCallError(
439
+ "This is an executable node and does not have branches"
440
+ )
443
441
 
444
442
  def execute_as_graph(self, map_variable: TypeMapVariable = None, **kwargs):
445
- raise Exception("This is an executable node and does not have a graph")
443
+ raise exceptions.NodeMethodCallError(
444
+ "This is an executable node and does not have a graph"
445
+ )
446
446
 
447
447
  def fan_in(self, map_variable: TypeMapVariable = None, **kwargs):
448
- raise Exception("This is an executable node and does not have a fan in")
448
+ raise exceptions.NodeMethodCallError(
449
+ "This is an executable node and does not have a fan in"
450
+ )
449
451
 
450
452
  def fan_out(self, map_variable: TypeMapVariable = None, **kwargs):
451
- raise Exception("This is an executable node and does not have a fan out")
453
+ raise exceptions.NodeMethodCallError(
454
+ "This is an executable node and does not have a fan out"
455
+ )
456
+
457
+ def prepare_for_job_execution(self):
458
+ raise exceptions.NodeMethodCallError(
459
+ "This is an executable node and does not have a prepare_for_job_execution"
460
+ )
461
+
462
+ def tear_down_after_job_execution(self):
463
+ raise exceptions.NodeMethodCallError(
464
+ "This is an executable node and does not have a tear_down_after_job_execution",
465
+ )
452
466
 
453
467
 
454
468
  class CompositeNode(TraversalNode):
@@ -459,7 +473,9 @@ class CompositeNode(TraversalNode):
459
473
  Returns:
460
474
  dict: catalog settings defined as per the node or None
461
475
  """
462
- raise Exception("This is a composite node and does not have a catalog settings")
476
+ raise exceptions.NodeMethodCallError(
477
+ "This is a composite node and does not have a catalog settings"
478
+ )
463
479
 
464
480
  def _get_max_attempts(self) -> int:
465
481
  raise Exception("This is a composite node and does not have a max_attempts")
@@ -471,7 +487,19 @@ class CompositeNode(TraversalNode):
471
487
  attempt_number: int = 1,
472
488
  **kwargs,
473
489
  ) -> StepLog:
474
- raise Exception("This is a composite node and does not have an execute function")
490
+ raise exceptions.NodeMethodCallError(
491
+ "This is a composite node and does not have an execute function"
492
+ )
493
+
494
+ def prepare_for_job_execution(self):
495
+ raise exceptions.NodeMethodCallError(
496
+ "This is an executable node and does not have a prepare_for_job_execution"
497
+ )
498
+
499
+ def tear_down_after_job_execution(self):
500
+ raise exceptions.NodeMethodCallError(
501
+ "This is an executable node and does not have a tear_down_after_job_execution"
502
+ )
475
503
 
476
504
 
477
505
  class TerminalNode(BaseNode):