runnable 0.35.0__py3-none-any.whl → 0.36.0__py3-none-any.whl

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