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