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
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