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/tasks.py CHANGED
@@ -31,7 +31,7 @@ logger = logging.getLogger(defaults.LOGGER_NAME)
31
31
  logging.getLogger("stevedore").setLevel(logging.CRITICAL)
32
32
 
33
33
 
34
- # TODO: Can we add memory peak, cpu usage, etc. to the metrics?
34
+ # TODO: This has to be an extension
35
35
 
36
36
 
37
37
  class TaskReturns(BaseModel):
@@ -43,8 +43,12 @@ class BaseTaskType(BaseModel):
43
43
  """A base task class which does the execution of command defined by the user."""
44
44
 
45
45
  task_type: str = Field(serialization_alias="command_type")
46
- secrets: List[str] = Field(default_factory=list)
47
- returns: List[TaskReturns] = Field(default_factory=list, alias="returns")
46
+ secrets: List[str] = Field(
47
+ default_factory=list
48
+ ) # A list of secrets to expose by secrets manager
49
+ returns: List[TaskReturns] = Field(
50
+ default_factory=list, alias="returns"
51
+ ) # The return values of the task
48
52
 
49
53
  model_config = ConfigDict(extra="forbid")
50
54
 
@@ -70,11 +74,13 @@ class BaseTaskType(BaseModel):
70
74
  raise NotImplementedError()
71
75
 
72
76
  def set_secrets_as_env_variables(self):
77
+ # Preparing the environment for the task execution
73
78
  for key in self.secrets:
74
79
  secret_value = context.run_context.secrets_handler.get(key)
75
80
  os.environ[key] = secret_value
76
81
 
77
82
  def delete_secrets_from_env_variables(self):
83
+ # Cleaning up the environment after the task execution
78
84
  for key in self.secrets:
79
85
  if key in os.environ:
80
86
  del os.environ[key]
@@ -99,6 +105,7 @@ class BaseTaskType(BaseModel):
99
105
  def _diff_parameters(
100
106
  self, parameters_in: Dict[str, Parameter], context_params: Dict[str, Parameter]
101
107
  ) -> Dict[str, Parameter]:
108
+ # If the parameter is different from existing parameters, then it is updated
102
109
  diff: Dict[str, Parameter] = {}
103
110
  for param_name, param in context_params.items():
104
111
  if param_name in parameters_in:
@@ -112,12 +119,7 @@ class BaseTaskType(BaseModel):
112
119
 
113
120
  @contextlib.contextmanager
114
121
  def expose_secrets(self):
115
- """Context manager to expose secrets to the execution.
116
-
117
- Args:
118
- map_variable (dict, optional): If the command is part of map node, the value of map. Defaults to None.
119
-
120
- """
122
+ """Context manager to expose secrets to the execution."""
121
123
  self.set_secrets_as_env_variables()
122
124
  try:
123
125
  yield
@@ -126,31 +128,45 @@ class BaseTaskType(BaseModel):
126
128
  finally:
127
129
  self.delete_secrets_from_env_variables()
128
130
 
129
- @contextlib.contextmanager
130
- def execution_context(self, map_variable: TypeMapVariable = None, allow_complex: bool = True):
131
- params = self._context.run_log_store.get_parameters(run_id=self._context.run_id).copy()
132
- logger.info(f"Parameters available for the execution: {params}")
131
+ def resolve_unreduced_parameters(self, map_variable: TypeMapVariable = None):
132
+ """Resolve the unreduced parameters."""
133
+ params = self._context.run_log_store.get_parameters(
134
+ run_id=self._context.run_id
135
+ ).copy()
133
136
 
134
137
  for param_name, param in params.items():
135
- # Any access to unreduced param should be replaced.
136
- # The replacement is the context param
137
- # It is possible that the unreduced param is not created as no upstream step
138
- # has created it yet.
139
138
  if param.reduced is False:
139
+ assert (
140
+ map_variable is not None
141
+ ), "Parameters in non-map node should always be reduced"
142
+
140
143
  context_param = param_name
141
- for _, v in map_variable.items(): # type: ignore
144
+ for _, v in map_variable.items():
142
145
  context_param = f"{v}_{context_param}"
143
146
 
144
- if context_param in params:
147
+ if context_param in params: # Is this if required?
145
148
  params[param_name].value = params[context_param].value
146
149
 
150
+ return params
151
+
152
+ @contextlib.contextmanager
153
+ def execution_context(
154
+ self, map_variable: TypeMapVariable = None, allow_complex: bool = True
155
+ ):
156
+ params = self.resolve_unreduced_parameters(map_variable=map_variable)
157
+ logger.info(f"Parameters available for the execution: {params}")
158
+
147
159
  task_console.log("Parameters available for the execution:")
148
160
  task_console.log(params)
149
161
 
150
162
  logger.debug(f"Resolved parameters: {params}")
151
163
 
152
164
  if not allow_complex:
153
- params = {key: value for key, value in params.items() if isinstance(value, JsonParameter)}
165
+ params = {
166
+ key: value
167
+ for key, value in params.items()
168
+ if isinstance(value, JsonParameter)
169
+ }
154
170
 
155
171
  parameters_in = copy.deepcopy(params)
156
172
  try:
@@ -161,8 +177,12 @@ class BaseTaskType(BaseModel):
161
177
  finally:
162
178
  # Update parameters
163
179
  # This should only update the parameters that are changed at the root level.
164
- diff_parameters = self._diff_parameters(parameters_in=parameters_in, context_params=params)
165
- self._context.run_log_store.set_parameters(parameters=diff_parameters, run_id=self._context.run_id)
180
+ diff_parameters = self._diff_parameters(
181
+ parameters_in=parameters_in, context_params=params
182
+ )
183
+ self._context.run_log_store.set_parameters(
184
+ parameters=diff_parameters, run_id=self._context.run_id
185
+ )
166
186
 
167
187
 
168
188
  def task_return_to_parameter(task_return: TaskReturns, value: Any) -> Parameter:
@@ -258,7 +278,10 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
258
278
  """Execute the notebook as defined by the command."""
259
279
  attempt_log = StepAttempt(status=defaults.FAIL, start_time=str(datetime.now()))
260
280
 
261
- with self.execution_context(map_variable=map_variable) as params, self.expose_secrets() as _:
281
+ with (
282
+ self.execution_context(map_variable=map_variable) as params,
283
+ self.expose_secrets() as _,
284
+ ):
262
285
  module, func = utils.get_module_and_attr_names(self.command)
263
286
  sys.path.insert(0, os.getcwd()) # Need to add the current directory to path
264
287
  imported_module = importlib.import_module(module)
@@ -266,21 +289,32 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
266
289
 
267
290
  try:
268
291
  try:
269
- filtered_parameters = parameters.filter_arguments_for_func(f, params.copy(), map_variable)
270
- logger.info(f"Calling {func} from {module} with {filtered_parameters}")
292
+ filtered_parameters = parameters.filter_arguments_for_func(
293
+ f, params.copy(), map_variable
294
+ )
295
+ logger.info(
296
+ f"Calling {func} from {module} with {filtered_parameters}"
297
+ )
271
298
 
272
299
  out_file = io.StringIO()
273
300
  with contextlib.redirect_stdout(out_file):
274
- user_set_parameters = f(**filtered_parameters) # This is a tuple or single value
301
+ user_set_parameters = f(
302
+ **filtered_parameters
303
+ ) # This is a tuple or single value
275
304
  task_console.print(out_file.getvalue())
276
305
  except Exception as e:
277
- raise exceptions.CommandCallError(f"Function call: {self.command} did not succeed.\n") from e
306
+ raise exceptions.CommandCallError(
307
+ f"Function call: {self.command} did not succeed.\n"
308
+ ) from e
278
309
 
279
310
  attempt_log.input_parameters = params.copy()
280
311
 
281
312
  if map_variable:
282
313
  attempt_log.input_parameters.update(
283
- {k: JsonParameter(value=v, kind="json") for k, v in map_variable.items()}
314
+ {
315
+ k: JsonParameter(value=v, kind="json")
316
+ for k, v in map_variable.items()
317
+ }
284
318
  )
285
319
 
286
320
  if self.returns:
@@ -288,7 +322,9 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
288
322
  user_set_parameters = (user_set_parameters,)
289
323
 
290
324
  if len(user_set_parameters) != len(self.returns):
291
- raise ValueError("Returns task signature does not match the function returns")
325
+ raise ValueError(
326
+ "Returns task signature does not match the function returns"
327
+ )
292
328
 
293
329
  output_parameters: Dict[str, Parameter] = {}
294
330
  metrics: Dict[str, Parameter] = {}
@@ -391,8 +427,12 @@ class NotebookTaskType(BaseTaskType):
391
427
 
392
428
  @property
393
429
  def notebook_output_path(self) -> str:
394
- node_name = self._context.executor._context_node.internal_name
395
- sane_name = "".join(x for x in node_name if x.isalnum())
430
+ # This is to accommodate jobs which does not have a context_node
431
+ if self._context.executor._context_node:
432
+ node_name = self._context.executor._context_node.internal_name
433
+ sane_name = "".join(x for x in node_name if x.isalnum())
434
+ else:
435
+ sane_name = ""
396
436
 
397
437
  output_path = Path(".", self.command)
398
438
  file_name = output_path.parent / (output_path.stem + f"{sane_name}_out.ipynb")
@@ -400,7 +440,10 @@ class NotebookTaskType(BaseTaskType):
400
440
  return str(file_name)
401
441
 
402
442
  def get_cli_options(self) -> Tuple[str, dict]:
403
- return "notebook", {"command": self.command, "notebook-output-path": self.notebook_output_path}
443
+ return "notebook", {
444
+ "command": self.command,
445
+ "notebook-output-path": self.notebook_output_path,
446
+ }
404
447
 
405
448
  def execute_command(
406
449
  self,
@@ -423,9 +466,12 @@ class NotebookTaskType(BaseTaskType):
423
466
 
424
467
  notebook_output_path = self.notebook_output_path
425
468
 
426
- with self.execution_context(
427
- map_variable=map_variable, allow_complex=False
428
- ) as params, self.expose_secrets() as _:
469
+ with (
470
+ self.execution_context(
471
+ map_variable=map_variable, allow_complex=False
472
+ ) as params,
473
+ self.expose_secrets() as _,
474
+ ):
429
475
  copy_params = copy.deepcopy(params)
430
476
 
431
477
  if map_variable:
@@ -434,7 +480,9 @@ class NotebookTaskType(BaseTaskType):
434
480
  copy_params[key] = JsonParameter(kind="json", value=value)
435
481
 
436
482
  # Remove any {v}_unreduced parameters from the parameters
437
- unprocessed_params = [k for k, v in copy_params.items() if not v.reduced]
483
+ unprocessed_params = [
484
+ k for k, v in copy_params.items() if not v.reduced
485
+ ]
438
486
 
439
487
  for key in list(copy_params.keys()):
440
488
  if any(key.endswith(f"_{k}") for k in unprocessed_params):
@@ -458,7 +506,9 @@ class NotebookTaskType(BaseTaskType):
458
506
  pm.execute_notebook(**kwds)
459
507
  task_console.print(out_file.getvalue())
460
508
 
461
- context.run_context.catalog_handler.put(name=notebook_output_path, run_id=context.run_context.run_id)
509
+ context.run_context.catalog_handler.put(
510
+ name=notebook_output_path, run_id=context.run_context.run_id
511
+ )
462
512
 
463
513
  client = PloomberClient.from_path(path=notebook_output_path)
464
514
  namespace = client.get_namespace()
@@ -466,7 +516,9 @@ class NotebookTaskType(BaseTaskType):
466
516
  output_parameters: Dict[str, Parameter] = {}
467
517
  try:
468
518
  for task_return in self.returns:
469
- param_name = Template(task_return.name).safe_substitute(map_variable) # type: ignore
519
+ param_name = Template(task_return.name).safe_substitute(
520
+ map_variable # type: ignore
521
+ )
470
522
 
471
523
  if map_variable:
472
524
  for _, v in map_variable.items():
@@ -566,7 +618,9 @@ class ShellTaskType(BaseTaskType):
566
618
  def returns_should_be_json(cls, returns: List[TaskReturns]):
567
619
  for task_return in returns:
568
620
  if task_return.kind == "object" or task_return.kind == "pydantic":
569
- raise ValueError("Pydantic models or Objects are not allowed in returns")
621
+ raise ValueError(
622
+ "Pydantic models or Objects are not allowed in returns"
623
+ )
570
624
 
571
625
  return returns
572
626
 
@@ -601,7 +655,9 @@ class ShellTaskType(BaseTaskType):
601
655
  subprocess_env[key] = secret_value
602
656
 
603
657
  try:
604
- with self.execution_context(map_variable=map_variable, allow_complex=False) as params:
658
+ with self.execution_context(
659
+ map_variable=map_variable, allow_complex=False
660
+ ) as params:
605
661
  subprocess_env.update({k: v.get_value() for k, v in params.items()})
606
662
 
607
663
  # Json dumps all runnable environment variables
@@ -612,7 +668,9 @@ class ShellTaskType(BaseTaskType):
612
668
 
613
669
  collect_delimiter = "=== COLLECT ==="
614
670
 
615
- command = self.command.strip() + f" && echo '{collect_delimiter}' && env"
671
+ command = (
672
+ self.command.strip() + f" && echo '{collect_delimiter}' && env"
673
+ )
616
674
  logger.info(f"Executing shell command: {command}")
617
675
 
618
676
  capture = False
runnable/utils.py CHANGED
@@ -21,7 +21,6 @@ from runnable import defaults, names
21
21
  from runnable.defaults import TypeMapVariable
22
22
 
23
23
  if TYPE_CHECKING: # pragma: no cover
24
- from runnable.extensions.nodes import TaskNode
25
24
  from runnable.nodes import BaseNode
26
25
 
27
26
 
@@ -86,7 +85,9 @@ def generate_run_id(run_id: str = "") -> str:
86
85
  return run_id
87
86
 
88
87
 
89
- def apply_variables(apply_to: Dict[str, Any], variables: Dict[str, str]) -> Dict[str, Any]:
88
+ def apply_variables(
89
+ apply_to: Dict[str, Any], variables: Dict[str, str]
90
+ ) -> Dict[str, Any]:
90
91
  """Safely applies the variables to a config.
91
92
 
92
93
  For example: For config:
@@ -272,7 +273,9 @@ def get_local_docker_image_id(image_name: str) -> str:
272
273
  image = client.images.get(image_name)
273
274
  return image.attrs["Id"]
274
275
  except ImportError: # pragma: no cover
275
- logger.warning("Did not find docker installed, some functionality might be affected")
276
+ logger.warning(
277
+ "Did not find docker installed, some functionality might be affected"
278
+ )
276
279
  except BaseException:
277
280
  logger.exception(f"Could not find the image by name {image_name}")
278
281
 
@@ -295,7 +298,9 @@ def get_git_code_identity():
295
298
  code_identity.code_identifier_dependable, changed = is_git_clean()
296
299
  code_identity.code_identifier_url = get_git_remote()
297
300
  if changed:
298
- code_identity.code_identifier_message = "changes found in " + ", ".join(changed.split("\n"))
301
+ code_identity.code_identifier_message = "changes found in " + ", ".join(
302
+ changed.split("\n")
303
+ )
299
304
  except BaseException:
300
305
  logger.exception("Git code versioning problems")
301
306
 
@@ -331,7 +336,9 @@ def get_tracked_data() -> Dict[str, str]:
331
336
  try:
332
337
  tracked_data[key.lower()] = json.loads(value)
333
338
  except json.decoder.JSONDecodeError:
334
- logger.warning(f"Tracker {key} could not be JSON decoded, adding the literal value")
339
+ logger.warning(
340
+ f"Tracker {key} could not be JSON decoded, adding the literal value"
341
+ )
335
342
  tracked_data[key.lower()] = value
336
343
 
337
344
  del os.environ[env_var]
@@ -389,9 +396,13 @@ def get_data_hash(file_name: str):
389
396
  str: The SHA ID of the file contents
390
397
  """
391
398
  # https://stackoverflow.com/questions/3431825/generating-an-md5-checksum-of-a-file
392
- return hash_bytestr_iter(file_as_blockiter(open(file_name, "rb")), hashlib.sha256()) # pragma: no cover
399
+ # TODO: For a big file, we should only hash the first few bytes
400
+ return hash_bytestr_iter(
401
+ file_as_blockiter(open(file_name, "rb")), hashlib.sha256()
402
+ ) # pragma: no cover
393
403
 
394
404
 
405
+ # TODO: This is not the right place for this.
395
406
  def get_node_execution_command(
396
407
  node: BaseNode,
397
408
  map_variable: TypeMapVariable = None,
@@ -415,26 +426,32 @@ def get_node_execution_command(
415
426
 
416
427
  log_level = log_level or logging.getLevelName(logger.getEffectiveLevel())
417
428
 
418
- action = f"runnable execute_single_node {run_id} " f"{node._command_friendly_name()}" f" --log-level {log_level}"
429
+ action = (
430
+ f"runnable execute-single-node {run_id} "
431
+ f"{context.run_context.pipeline_file} "
432
+ f"{node._command_friendly_name()} "
433
+ f"--log-level {log_level} "
434
+ )
419
435
 
420
- if context.run_context.pipeline_file:
421
- action = action + f" --file {context.run_context.pipeline_file}"
436
+ if context.run_context.from_sdk:
437
+ action = action + "--mode python "
422
438
 
423
439
  if map_variable:
424
- action = action + f" --map-variable '{json.dumps(map_variable)}'"
440
+ action = action + f"--map-variable '{json.dumps(map_variable)}' "
425
441
 
426
442
  if context.run_context.configuration_file:
427
- action = action + f" --config-file {context.run_context.configuration_file}"
443
+ action = action + f"--config {context.run_context.configuration_file} "
428
444
 
429
445
  if context.run_context.parameters_file:
430
- action = action + f" --parameters-file {context.run_context.parameters_file}"
446
+ action = action + f"--parameters-file {context.run_context.parameters_file} "
431
447
 
432
448
  if context.run_context.tag:
433
- action = action + f" --tag {context.run_context.tag}"
449
+ action = action + f"--tag {context.run_context.tag}"
434
450
 
435
451
  return action
436
452
 
437
453
 
454
+ # TODO: This is not the right place for this.
438
455
  def get_fan_command(
439
456
  mode: str,
440
457
  node: BaseNode,
@@ -459,8 +476,8 @@ def get_fan_command(
459
476
  action = (
460
477
  f"runnable fan {run_id} "
461
478
  f"{node._command_friendly_name()} "
479
+ f"{context.run_context.pipeline_file} "
462
480
  f"--mode {mode} "
463
- f"--file {context.run_context.pipeline_file} "
464
481
  f"--log-level {log_level} "
465
482
  )
466
483
  if context.run_context.configuration_file:
@@ -478,18 +495,11 @@ def get_fan_command(
478
495
  return action
479
496
 
480
497
 
481
- def get_job_execution_command(node: TaskNode, over_write_run_id: str = "") -> str:
498
+ # TODO: This is not the right place for this.
499
+ def get_job_execution_command(over_write_run_id: str = "") -> str:
482
500
  """Get the execution command to run a job via command line.
483
501
 
484
502
  This function should be used by all executors to submit jobs in remote environment
485
-
486
- Args:
487
- executor (BaseExecutor): The executor class.
488
- node (BaseNode): The node being executed.
489
- over_write_run_id (str, optional): If the node is part of a map step. Defaults to ''.
490
-
491
- Returns:
492
- str: The execution command to run a job via command line.
493
503
  """
494
504
 
495
505
  run_id = context.run_context.run_id
@@ -499,28 +509,29 @@ def get_job_execution_command(node: TaskNode, over_write_run_id: str = "") -> st
499
509
 
500
510
  log_level = logging.getLevelName(logger.getEffectiveLevel())
501
511
 
502
- cli_command, cli_options = node.executable.get_cli_options()
503
-
504
- action = f"runnable execute_{cli_command} {run_id} " f" --log-level {log_level}"
505
-
506
- action = action + f" --entrypoint {defaults.ENTRYPOINT.SYSTEM.value}"
512
+ action = (
513
+ f"runnable execute-job /app/{context.run_context.job_definition_file} {run_id} "
514
+ f" --log-level {log_level}"
515
+ )
507
516
 
508
517
  if context.run_context.configuration_file:
509
- action = action + f" --config-file {context.run_context.configuration_file}"
518
+ action = action + f" --config {context.run_context.configuration_file}"
510
519
 
511
520
  if context.run_context.parameters_file:
512
- action = action + f" --parameters-file {context.run_context.parameters_file}"
521
+ action = action + f" --parameters {context.run_context.parameters_file}"
522
+
523
+ if context.run_context.from_sdk:
524
+ action = action + " --mode python "
513
525
 
514
526
  if context.run_context.tag:
515
527
  action = action + f" --tag {context.run_context.tag}"
516
528
 
517
- for key, value in cli_options.items():
518
- action = action + f" --{key} {value}"
519
-
520
529
  return action
521
530
 
522
531
 
523
- def get_provider_by_name_and_type(service_type: str, service_details: defaults.ServiceConfig):
532
+ def get_provider_by_name_and_type(
533
+ service_type: str, service_details: defaults.ServiceConfig
534
+ ):
524
535
  """Given a service type, one of executor, run_log_store, catalog, secrets and the config
525
536
  return the exact child class implementing the service.
526
537
  We use stevedore to do the work for us.
@@ -535,6 +546,7 @@ def get_provider_by_name_and_type(service_type: str, service_details: defaults.S
535
546
  Returns:
536
547
  object: A service object
537
548
  """
549
+
538
550
  namespace = service_type
539
551
 
540
552
  service_name = service_details["type"]
@@ -542,7 +554,9 @@ def get_provider_by_name_and_type(service_type: str, service_details: defaults.S
542
554
  if "config" in service_details:
543
555
  service_config = service_details.get("config", {})
544
556
 
545
- logger.debug(f"Trying to get a service of {service_type} of the name {service_name} with config: {service_config}")
557
+ logger.debug(
558
+ f"Trying to get a service of {service_type} of the name {service_name} with config: {service_config}"
559
+ )
546
560
  try:
547
561
  mgr = driver.DriverManager(
548
562
  namespace=namespace,
@@ -552,8 +566,12 @@ def get_provider_by_name_and_type(service_type: str, service_details: defaults.S
552
566
  )
553
567
  return mgr.driver
554
568
  except Exception as _e:
555
- logger.exception(f"Could not find the service of type: {service_type} with config: {service_details}")
556
- raise Exception(f"Could not find the service of type: {service_type} with config: {service_details}") from _e
569
+ logger.exception(
570
+ f"Could not find the service of type: {service_type} with config: {service_details}"
571
+ )
572
+ raise Exception(
573
+ f"Could not find the service of type: {service_type} with config: {service_details}"
574
+ ) from _e
557
575
 
558
576
 
559
577
  def get_run_config() -> dict:
@@ -585,7 +603,9 @@ def json_to_ordered_dict(json_str: str) -> TypeMapVariable:
585
603
  return OrderedDict()
586
604
 
587
605
 
588
- def set_runnable_environment_variables(run_id: str = "", configuration_file: str = "", tag: str = "") -> None:
606
+ def set_runnable_environment_variables(
607
+ run_id: str = "", configuration_file: str = "", tag: str = ""
608
+ ) -> None:
589
609
  """Set the environment variables used by runnable. This function should be called during the prepare configurations
590
610
  by all executors.
591
611
 
@@ -604,7 +624,7 @@ def set_runnable_environment_variables(run_id: str = "", configuration_file: str
604
624
  os.environ[defaults.RUNNABLE_RUN_TAG] = tag
605
625
 
606
626
 
607
- def gather_variables() -> dict:
627
+ def gather_variables() -> Dict[str, str]:
608
628
  """Gather all the environment variables used by runnable. All the variables start with runnable_VAR_.
609
629
 
610
630
  Returns:
@@ -1,36 +1,34 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: runnable
3
- Version: 0.13.0
4
- Summary: A Compute agnostic pipelining software
5
- Home-page: https://github.com/vijayvammi/runnable
6
- License: Apache-2.0
7
- Author: Vijay Vammi
8
- Author-email: mesanthu@gmail.com
9
- Requires-Python: >=3.9,<3.13
10
- Classifier: License :: OSI Approved :: Apache Software License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Provides-Extra: database
3
+ Version: 0.16.0
4
+ Summary: Add your description here
5
+ Author-email: "Vammi, Vijay" <vijay.vammi@astrazeneca.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: catalog
9
+ Requires-Dist: click-plugins>=1.1.1
10
+ Requires-Dist: click<=8.1.3
11
+ Requires-Dist: dill>=0.3.9
12
+ Requires-Dist: job-executor
13
+ Requires-Dist: nodes
14
+ Requires-Dist: pipeline-executor
15
+ Requires-Dist: pydantic>=2.10.3
16
+ Requires-Dist: python-dotenv>=1.0.1
17
+ Requires-Dist: rich>=13.9.4
18
+ Requires-Dist: ruamel-yaml>=0.18.6
19
+ Requires-Dist: run-log-store
20
+ Requires-Dist: secrets
21
+ Requires-Dist: setuptools>=75.6.0
22
+ Requires-Dist: stevedore>=5.4.0
23
+ Requires-Dist: typer>=0.15.1
17
24
  Provides-Extra: docker
25
+ Requires-Dist: docker>=7.1.0; extra == 'docker'
26
+ Provides-Extra: examples
27
+ Requires-Dist: pandas>=2.2.3; extra == 'examples'
28
+ Provides-Extra: k8s
29
+ Requires-Dist: kubernetes>=31.0.0; extra == 'k8s'
18
30
  Provides-Extra: notebook
19
- Requires-Dist: click
20
- Requires-Dist: click-plugins (>=1.1.1,<2.0.0)
21
- Requires-Dist: dill (>=0.3.8,<0.4.0)
22
- Requires-Dist: docker ; extra == "docker"
23
- Requires-Dist: mlflow-skinny
24
- Requires-Dist: ploomber-engine (>=0.0.31,<0.0.32) ; extra == "notebook"
25
- Requires-Dist: pydantic (>=2.5,<3.0)
26
- Requires-Dist: rich (>=13.5.2,<14.0.0)
27
- Requires-Dist: ruamel.yaml
28
- Requires-Dist: ruamel.yaml.clib
29
- Requires-Dist: sqlalchemy ; extra == "database"
30
- Requires-Dist: stevedore (>=3.5.0,<4.0.0)
31
- Requires-Dist: typing-extensions ; python_version < "3.8"
32
- Project-URL: Documentation, https://github.com/vijayvammi/runnable
33
- Project-URL: Repository, https://github.com/vijayvammi/runnable
31
+ Requires-Dist: ploomber-engine>=0.0.33; extra == 'notebook'
34
32
  Description-Content-Type: text/markdown
35
33
 
36
34
 
@@ -267,4 +265,3 @@ Execute a pipeline over an iterable parameter.
267
265
 
268
266
  ### [Arbitrary nesting](https://astrazeneca.github.io/runnable-core/concepts/nesting/)
269
267
  Any nesting of parallel within map and so on.
270
-
@@ -0,0 +1,23 @@
1
+ runnable/__init__.py,sha256=KqpLDTD1CfdEj2aDyEkSn2KW-_83qyrRrrWLc5lZVM4,624
2
+ runnable/catalog.py,sha256=MiEmb-18liAKmgeMdDF41VVn0ZEAVLP8hR33oacQ1zs,4930
3
+ runnable/cli.py,sha256=01zmzOdynEmLI4vWDtSHQ6y1od_Jlc8G1RF69fi2L8g,8446
4
+ runnable/context.py,sha256=pLw_n_5U-FM8-9-41YnkzETX94KrBAZWjxPgjm0O7hk,1305
5
+ runnable/datastore.py,sha256=a1pT_P8TNcqQB-di2_uga7y-zS3TqUCb7sFhdxmVKGY,31907
6
+ runnable/defaults.py,sha256=3o9IVGryyCE6PoQTOoaIaHHTbJGEzmdXMcwzOhwAYoI,3518
7
+ runnable/entrypoints.py,sha256=67gPBiIIS4Kd9g6LdoGCraRJPda8K1i7Lp7XcD2iY5k,18913
8
+ runnable/exceptions.py,sha256=LFbp0-Qxg2PAMLEVt7w2whhBxSG-5pzUEv5qN-Rc4_c,3003
9
+ runnable/executor.py,sha256=cS30EC2Pfz8OzzEVcUYrVIyvGboKVUw5jKG2l72-UfM,15606
10
+ runnable/graph.py,sha256=jVjikRLR-so3b2ufmNKpEQ_Ny68qN4bcGDAdXBRKiCY,16574
11
+ runnable/names.py,sha256=vn92Kv9ANROYSZX6Z4z1v_WA3WiEdIYmG6KEStBFZug,8134
12
+ runnable/nodes.py,sha256=YU9u7r1ESzui1uVtJ1dgwdv1ozyJnF2k-MCFieT8CLI,17519
13
+ runnable/parameters.py,sha256=g_bJurLjuppFDiDpfFqy6BRF36o_EY0OC5APl7HJFok,5450
14
+ runnable/pickler.py,sha256=ydJ_eti_U1F4l-YacFp7BWm6g5vTn04UXye25S1HVok,2684
15
+ runnable/sdk.py,sha256=hwdk2dLmJsOTs2GnOlayw8WfliyeZFpA6Tcnp3tgblg,33370
16
+ runnable/secrets.py,sha256=PXcEJw-4WPzeWRLfsatcPPyr1zkqgHzdRWRcS9vvpvM,2354
17
+ runnable/tasks.py,sha256=JnIIYQf3YUidHXIN6hiUIfDnegc7_rJMNXuHW4WS9ig,29378
18
+ runnable/utils.py,sha256=wqyN7lMW56cBqyE59iDE6_i2HXPkvEUCQ-66UQnIwTA,19993
19
+ runnable-0.16.0.dist-info/METADATA,sha256=JMu8mSsxMWr_wF246saRXV80D8_8cXMOPZrK6Pr9X6k,10102
20
+ runnable-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ runnable-0.16.0.dist-info/entry_points.txt,sha256=I92DYldRrCb9HCsoum8GjC2UsQrWpuw2kawXTZpkIz4,1559
22
+ runnable-0.16.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
23
+ runnable-0.16.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,45 @@
1
+ [console_scripts]
2
+ runnable = runnable.cli:app
3
+
4
+ [catalog]
5
+ do-nothing = runnable.catalog:DoNothingCatalog
6
+ file-system = extensions.catalog.file_system:FileSystemCatalog
7
+
8
+ [job_executor]
9
+ k8s-job = extensions.job_executor.k8s:K8sJobExecutor
10
+ local = extensions.job_executor.local:LocalJobExecutor
11
+ local-container = extensions.job_executor.local_container:LocalContainerJobExecutor
12
+
13
+ [nodes]
14
+ dag = extensions.nodes.nodes:DagNode
15
+ fail = extensions.nodes.nodes:FailNode
16
+ map = extensions.nodes.nodes:MapNode
17
+ parallel = extensions.nodes.nodes:ParallelNode
18
+ stub = extensions.nodes.nodes:StubNode
19
+ success = extensions.nodes.nodes:SuccessNode
20
+ task = extensions.nodes.nodes:TaskNode
21
+
22
+ [pickler]
23
+ pickle = runnable.pickler:NativePickler
24
+
25
+ [pipeline_executor]
26
+ argo = extensions.pipeline_executor.argo:ArgoExecutor
27
+ local = extensions.pipeline_executor.local:LocalExecutor
28
+ local-container = extensions.pipeline_executor.local_container:LocalContainerExecutor
29
+ mocked = extensions.pipeline_executor.mocked:MockedExecutor
30
+ retry = extensions.pipeline_executor.retry:RetryExecutor
31
+
32
+ [run_log_store]
33
+ buffered = runnable.datastore:BufferRunLogstore
34
+ chunked-fs = extensions.run_log_store.chunked_fs:ChunkedFileSystemRunLogStore
35
+ file-system = extensions.run_log_store.file_system:FileSystemRunLogstore
36
+
37
+ [secrets]
38
+ do-nothing = runnable.secrets:DoNothingSecretManager
39
+ dotenv = extensions.secrets.dotenv:DotEnvSecrets
40
+ env-secrets = runnable.secrets:EnvSecretsManager
41
+
42
+ [tasks]
43
+ notebook = runnable.tasks:NotebookTaskType
44
+ python = runnable.tasks:PythonTaskType
45
+ shell = runnable.tasks:ShellTaskType