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.
- runnable/__init__.py +1 -12
- runnable/catalog.py +29 -5
- runnable/cli.py +268 -215
- runnable/context.py +10 -3
- runnable/datastore.py +212 -53
- runnable/defaults.py +13 -55
- runnable/entrypoints.py +270 -183
- runnable/exceptions.py +28 -2
- runnable/executor.py +133 -86
- runnable/graph.py +37 -13
- runnable/nodes.py +50 -22
- runnable/parameters.py +27 -8
- runnable/pickler.py +1 -1
- runnable/sdk.py +230 -66
- runnable/secrets.py +3 -1
- runnable/tasks.py +99 -41
- runnable/utils.py +59 -39
- {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/METADATA +28 -31
- runnable-0.16.0.dist-info/RECORD +23 -0
- {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/WHEEL +1 -1
- runnable-0.16.0.dist-info/entry_points.txt +45 -0
- runnable/extensions/__init__.py +0 -0
- runnable/extensions/catalog/__init__.py +0 -21
- runnable/extensions/catalog/file_system/__init__.py +0 -0
- runnable/extensions/catalog/file_system/implementation.py +0 -234
- runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
- runnable/extensions/catalog/k8s_pvc/implementation.py +0 -16
- runnable/extensions/catalog/k8s_pvc/integration.py +0 -59
- runnable/extensions/executor/__init__.py +0 -649
- runnable/extensions/executor/argo/__init__.py +0 -0
- runnable/extensions/executor/argo/implementation.py +0 -1194
- runnable/extensions/executor/argo/specification.yaml +0 -51
- runnable/extensions/executor/k8s_job/__init__.py +0 -0
- runnable/extensions/executor/k8s_job/implementation_FF.py +0 -259
- runnable/extensions/executor/k8s_job/integration_FF.py +0 -69
- runnable/extensions/executor/local.py +0 -69
- runnable/extensions/executor/local_container/__init__.py +0 -0
- runnable/extensions/executor/local_container/implementation.py +0 -446
- runnable/extensions/executor/mocked/__init__.py +0 -0
- runnable/extensions/executor/mocked/implementation.py +0 -154
- runnable/extensions/executor/retry/__init__.py +0 -0
- runnable/extensions/executor/retry/implementation.py +0 -168
- runnable/extensions/nodes.py +0 -870
- runnable/extensions/run_log_store/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
- runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
- runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
- runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
- runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
- runnable/extensions/run_log_store/db/integration_FF.py +0 -0
- runnable/extensions/run_log_store/file_system/__init__.py +0 -0
- runnable/extensions/run_log_store/file_system/implementation.py +0 -140
- runnable/extensions/run_log_store/generic_chunked.py +0 -557
- runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
- runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
- runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
- runnable/extensions/secrets/__init__.py +0 -0
- runnable/extensions/secrets/dotenv/__init__.py +0 -0
- runnable/extensions/secrets/dotenv/implementation.py +0 -100
- runnable/integration.py +0 -192
- runnable-0.13.0.dist-info/RECORD +0 -63
- runnable-0.13.0.dist-info/entry_points.txt +0 -41
- {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:
|
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(
|
47
|
-
|
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
|
-
|
130
|
-
|
131
|
-
params = self._context.run_log_store.get_parameters(
|
132
|
-
|
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():
|
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 = {
|
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(
|
165
|
-
|
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
|
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(
|
270
|
-
|
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(
|
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(
|
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
|
-
{
|
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(
|
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
|
-
|
395
|
-
|
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", {
|
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
|
427
|
-
|
428
|
-
|
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 = [
|
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(
|
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(
|
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(
|
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(
|
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 =
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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 =
|
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.
|
421
|
-
action = action +
|
436
|
+
if context.run_context.from_sdk:
|
437
|
+
action = action + "--mode python "
|
422
438
|
|
423
439
|
if map_variable:
|
424
|
-
action = action + f"
|
440
|
+
action = action + f"--map-variable '{json.dumps(map_variable)}' "
|
425
441
|
|
426
442
|
if context.run_context.configuration_file:
|
427
|
-
action = action + f"
|
443
|
+
action = action + f"--config {context.run_context.configuration_file} "
|
428
444
|
|
429
445
|
if context.run_context.parameters_file:
|
430
|
-
action = action + f"
|
446
|
+
action = action + f"--parameters-file {context.run_context.parameters_file} "
|
431
447
|
|
432
448
|
if context.run_context.tag:
|
433
|
-
action = action + f"
|
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
|
-
|
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
|
-
|
503
|
-
|
504
|
-
|
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
|
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
|
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(
|
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(
|
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(
|
556
|
-
|
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(
|
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() ->
|
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
|
+
Metadata-Version: 2.4
|
2
2
|
Name: runnable
|
3
|
-
Version: 0.
|
4
|
-
Summary:
|
5
|
-
|
6
|
-
License:
|
7
|
-
|
8
|
-
|
9
|
-
Requires-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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:
|
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,,
|
@@ -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
|