digitalhub-runtime-python 0.5.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.
- digitalhub_runtime_python/__init__.py +44 -0
- digitalhub_runtime_python/entities/__init__.py +0 -0
- digitalhub_runtime_python/entities/functions/__init__.py +0 -0
- digitalhub_runtime_python/entities/functions/spec.py +144 -0
- digitalhub_runtime_python/entities/functions/status.py +9 -0
- digitalhub_runtime_python/entities/runs/__init__.py +0 -0
- digitalhub_runtime_python/entities/runs/spec.py +81 -0
- digitalhub_runtime_python/entities/runs/status.py +9 -0
- digitalhub_runtime_python/entities/tasks/__init__.py +0 -0
- digitalhub_runtime_python/entities/tasks/models.py +31 -0
- digitalhub_runtime_python/entities/tasks/spec.py +87 -0
- digitalhub_runtime_python/entities/tasks/status.py +15 -0
- digitalhub_runtime_python/runtimes/__init__.py +0 -0
- digitalhub_runtime_python/runtimes/kind_registry.py +15 -0
- digitalhub_runtime_python/runtimes/runtime.py +159 -0
- digitalhub_runtime_python/utils/configuration.py +251 -0
- digitalhub_runtime_python/utils/inputs.py +149 -0
- digitalhub_runtime_python/utils/outputs.py +204 -0
- digitalhub_runtime_python/utils/utils.py +73 -0
- digitalhub_runtime_python-0.5.0.dist-info/LICENSE.txt +216 -0
- digitalhub_runtime_python-0.5.0.dist-info/METADATA +233 -0
- digitalhub_runtime_python-0.5.0.dist-info/RECORD +24 -0
- digitalhub_runtime_python-0.5.0.dist-info/WHEEL +5 -0
- digitalhub_runtime_python-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util as imputil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from digitalhub_core.utils.generic_utils import (
|
|
8
|
+
decode_string,
|
|
9
|
+
extract_archive,
|
|
10
|
+
get_bucket_and_key,
|
|
11
|
+
get_s3_source,
|
|
12
|
+
requests_chunk_download,
|
|
13
|
+
)
|
|
14
|
+
from digitalhub_core.utils.git_utils import clone_repository
|
|
15
|
+
from digitalhub_core.utils.logger import LOGGER
|
|
16
|
+
from digitalhub_core.utils.uri_utils import map_uri_scheme
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_function_from_source(path: Path, source_spec: dict) -> Callable:
|
|
20
|
+
"""
|
|
21
|
+
Get function from source.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
path : Path
|
|
26
|
+
Path where to save the function source.
|
|
27
|
+
source_spec : dict
|
|
28
|
+
Funcrion source spec.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
Callable
|
|
33
|
+
Function.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
function_code = save_function_source(path, source_spec)
|
|
37
|
+
handler_path, function_name = parse_handler(source_spec["handler"])
|
|
38
|
+
function_path = (function_code / handler_path).with_suffix(".py")
|
|
39
|
+
return import_function(function_path, function_name)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
msg = f"Some error occurred while getting function. Exception: {e.__class__}. Error: {e.args}"
|
|
42
|
+
LOGGER.exception(msg)
|
|
43
|
+
raise RuntimeError(msg) from e
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_handler(handler: str) -> tuple:
|
|
47
|
+
"""
|
|
48
|
+
Parse handler.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
handler : str
|
|
53
|
+
Function handler
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
str
|
|
58
|
+
Function handler.
|
|
59
|
+
"""
|
|
60
|
+
parsed = handler.split(":")
|
|
61
|
+
if len(parsed) == 1:
|
|
62
|
+
return Path(""), parsed[0]
|
|
63
|
+
return Path(*parsed[0].split(".")), parsed[1]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_function_source(path: Path, source_spec: dict) -> Path:
|
|
67
|
+
"""
|
|
68
|
+
Save function source.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
path : Path
|
|
73
|
+
Path where to save the function source.
|
|
74
|
+
source_spec : dict
|
|
75
|
+
Function source spec.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Path
|
|
80
|
+
Path to the function source.
|
|
81
|
+
"""
|
|
82
|
+
# Prepare path
|
|
83
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
# Get relevant information
|
|
86
|
+
base64 = source_spec.get("base64")
|
|
87
|
+
source = source_spec.get("source")
|
|
88
|
+
|
|
89
|
+
scheme = None
|
|
90
|
+
if source is not None:
|
|
91
|
+
scheme = map_uri_scheme(source)
|
|
92
|
+
|
|
93
|
+
# Base64
|
|
94
|
+
if base64 is not None:
|
|
95
|
+
filename = "main.py"
|
|
96
|
+
if scheme == "local":
|
|
97
|
+
filename = Path(source).name
|
|
98
|
+
|
|
99
|
+
base64_path = path / filename
|
|
100
|
+
base64_path.write_text(decode_base64(base64))
|
|
101
|
+
|
|
102
|
+
if scheme is None or scheme == "local":
|
|
103
|
+
return base64_path
|
|
104
|
+
|
|
105
|
+
# Git repo
|
|
106
|
+
if scheme == "git":
|
|
107
|
+
get_repository(path, source)
|
|
108
|
+
|
|
109
|
+
# Http(s) or s3 presigned urls
|
|
110
|
+
elif scheme == "remote":
|
|
111
|
+
filename = path / "archive.zip"
|
|
112
|
+
get_remote_source(source, filename)
|
|
113
|
+
unzip(path, filename)
|
|
114
|
+
|
|
115
|
+
# S3 path
|
|
116
|
+
elif scheme == "s3":
|
|
117
|
+
filename = path / "archive.zip"
|
|
118
|
+
bucket, key = get_bucket_and_key(source)
|
|
119
|
+
get_s3_source(bucket, key, filename)
|
|
120
|
+
unzip(path, filename)
|
|
121
|
+
|
|
122
|
+
# Unsupported scheme
|
|
123
|
+
else:
|
|
124
|
+
raise RuntimeError(f"Unsupported scheme: {scheme}")
|
|
125
|
+
|
|
126
|
+
return path
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_remote_source(source: str, filename: Path) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Get remote source.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
source : str
|
|
136
|
+
HTTP(S) or S3 presigned URL.
|
|
137
|
+
filename : Path
|
|
138
|
+
Path where to save the function source.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
str
|
|
143
|
+
Function code.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
requests_chunk_download(source, filename)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
msg = f"Some error occurred while downloading function source. Exception: {e.__class__}. Error: {e.args}"
|
|
149
|
+
LOGGER.exception(msg)
|
|
150
|
+
raise RuntimeError(msg) from e
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def unzip(path: Path, filename: Path) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Extract an archive.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
path : Path
|
|
160
|
+
Path where to extract the archive.
|
|
161
|
+
filename : Path
|
|
162
|
+
Path to the archive.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
None
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
extract_archive(path, filename)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
msg = f"Source must be a valid zipfile. Exception: {e.__class__}. Error: {e.args}"
|
|
173
|
+
LOGGER.exception(msg)
|
|
174
|
+
raise RuntimeError(msg) from e
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_repository(path: Path, source: str) -> str:
|
|
178
|
+
"""
|
|
179
|
+
Get repository.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
path : Path
|
|
184
|
+
Path where to save the function source.
|
|
185
|
+
source : str
|
|
186
|
+
Git repository URL in format git://<url>.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
None
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
clone_repository(path, source)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
msg = f"Some error occurred while downloading function repo source. Exception: {e.__class__}. Error: {e.args}"
|
|
196
|
+
LOGGER.exception(msg)
|
|
197
|
+
raise RuntimeError(msg) from e
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def decode_base64(base64: str) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Decode base64 encoded code.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
base64 : str
|
|
207
|
+
The encoded code.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
str
|
|
212
|
+
The decoded code.
|
|
213
|
+
|
|
214
|
+
Raises
|
|
215
|
+
------
|
|
216
|
+
RuntimeError
|
|
217
|
+
Error while decoding code.
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
return decode_string(base64)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
msg = f"Some error occurred while decoding function source. Exception: {e.__class__}. Error: {e.args}"
|
|
223
|
+
LOGGER.exception(msg)
|
|
224
|
+
raise RuntimeError(msg) from e
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def import_function(path: Path, handler: str) -> Callable:
|
|
228
|
+
"""
|
|
229
|
+
Import a function from a module.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
path : Path
|
|
234
|
+
Path where the function source is located.
|
|
235
|
+
handler : str
|
|
236
|
+
Function name.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
Callable
|
|
241
|
+
Function.
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
spec = imputil.spec_from_file_location(path.stem, path)
|
|
245
|
+
mod = imputil.module_from_spec(spec)
|
|
246
|
+
spec.loader.exec_module(mod)
|
|
247
|
+
return getattr(mod, handler)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
msg = f"Some error occurred while importing function. Exception: {e.__class__}. Error: {e.args}"
|
|
250
|
+
LOGGER.exception(msg)
|
|
251
|
+
raise RuntimeError(msg) from e
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import typing
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from digitalhub_core.context.builder import get_context
|
|
8
|
+
from digitalhub_core.entities.artifacts.crud import artifact_from_dict
|
|
9
|
+
from digitalhub_core.utils.generic_utils import parse_entity_key
|
|
10
|
+
from digitalhub_core.utils.logger import LOGGER
|
|
11
|
+
from digitalhub_data.entities.dataitems.crud import dataitem_from_dict
|
|
12
|
+
from digitalhub_ml.entities.entity_types import EntityTypes
|
|
13
|
+
from digitalhub_ml.entities.models.crud import model_from_dict
|
|
14
|
+
from digitalhub_ml.entities.projects.crud import get_project
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from digitalhub_core.entities._base.entity import Entity
|
|
18
|
+
from digitalhub_core.entities.projects.entity import Project
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_project_(project_name: str) -> Project:
|
|
22
|
+
"""
|
|
23
|
+
Get project.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
project_name : str
|
|
28
|
+
Project name.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
Project
|
|
33
|
+
Project.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
ctx = get_context(project_name)
|
|
37
|
+
return get_project(project_name, local=ctx.local)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
msg = f"Error during project collection. Exception: {e.__class__}. Error: {e.args}"
|
|
40
|
+
LOGGER.exception(msg)
|
|
41
|
+
raise RuntimeError(msg)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_entity_inputs(inputs: dict) -> dict[str, Entity]:
|
|
45
|
+
"""
|
|
46
|
+
Set inputs.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
inputs : dict
|
|
51
|
+
Run inputs.
|
|
52
|
+
parameters : dict
|
|
53
|
+
Run parameters.
|
|
54
|
+
tmp_dir : Path
|
|
55
|
+
Temporary directory for storing dataitms and artifacts.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
dict
|
|
60
|
+
Mlrun inputs.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
inputs_objects = {}
|
|
64
|
+
for k, v in inputs.items():
|
|
65
|
+
_, entity_type, _, _, _ = parse_entity_key(v.get("key"))
|
|
66
|
+
if entity_type == EntityTypes.DATAITEMS.value:
|
|
67
|
+
inputs_objects[k] = dataitem_from_dict(v)
|
|
68
|
+
elif entity_type == EntityTypes.ARTIFACTS.value:
|
|
69
|
+
inputs_objects[k] = artifact_from_dict(v)
|
|
70
|
+
elif entity_type == EntityTypes.MODELS.value:
|
|
71
|
+
inputs_objects[k] = model_from_dict(v)
|
|
72
|
+
return inputs_objects
|
|
73
|
+
except Exception as e:
|
|
74
|
+
msg = f"Error during inputs collection. Exception: {e.__class__}. Error: {e.args}"
|
|
75
|
+
LOGGER.exception(msg)
|
|
76
|
+
raise RuntimeError(msg) from e
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def compose_inputs(
|
|
80
|
+
inputs: dict,
|
|
81
|
+
parameters: dict,
|
|
82
|
+
local_execution: bool,
|
|
83
|
+
func: Callable,
|
|
84
|
+
project: str | Project,
|
|
85
|
+
context: Any | None = None,
|
|
86
|
+
event: Any | None = None,
|
|
87
|
+
) -> dict:
|
|
88
|
+
"""
|
|
89
|
+
Compose inputs.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
inputs : dict
|
|
94
|
+
Run inputs.
|
|
95
|
+
parameters : dict
|
|
96
|
+
Run parameters.
|
|
97
|
+
local_execution : bool
|
|
98
|
+
Local execution.
|
|
99
|
+
func : Callable
|
|
100
|
+
Function to execute.
|
|
101
|
+
project : str
|
|
102
|
+
Project name.
|
|
103
|
+
context : nuclio_sdk.Context
|
|
104
|
+
Nuclio context.
|
|
105
|
+
event : nuclio_sdk.Event
|
|
106
|
+
Nuclio event.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
dict
|
|
111
|
+
Function inputs.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
entity_inputs = get_entity_inputs(inputs)
|
|
115
|
+
fnc_args = {**parameters, **entity_inputs}
|
|
116
|
+
|
|
117
|
+
fnc_parameters = inspect.signature(func).parameters
|
|
118
|
+
LOGGER.info(f"Function parameters: {'project' in fnc_parameters}")
|
|
119
|
+
|
|
120
|
+
_has_project = "project" in fnc_parameters
|
|
121
|
+
_has_context = "context" in fnc_parameters
|
|
122
|
+
_has_event = "event" in fnc_parameters
|
|
123
|
+
|
|
124
|
+
if _has_project:
|
|
125
|
+
if _has_context:
|
|
126
|
+
fnc_args["project"] = context.project
|
|
127
|
+
elif isinstance(project, str):
|
|
128
|
+
fnc_args["project"] = get_project_(project)
|
|
129
|
+
else:
|
|
130
|
+
fnc_args["project"] = project
|
|
131
|
+
|
|
132
|
+
if _has_context:
|
|
133
|
+
if context is not None and not local_execution:
|
|
134
|
+
fnc_args["context"] = context
|
|
135
|
+
else:
|
|
136
|
+
raise RuntimeError("Context is not available on local execution.")
|
|
137
|
+
|
|
138
|
+
if _has_event:
|
|
139
|
+
if event is not None and not local_execution:
|
|
140
|
+
fnc_args["event"] = event
|
|
141
|
+
else:
|
|
142
|
+
raise RuntimeError("Event is not available on local execution.")
|
|
143
|
+
|
|
144
|
+
return fnc_args
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
msg = f"Error during function arguments compostion. Exception: {e.__class__}. Error: {e.args}"
|
|
148
|
+
LOGGER.exception(msg)
|
|
149
|
+
raise RuntimeError(msg) from e
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from digitalhub_core.entities._base.status import State
|
|
7
|
+
from digitalhub_core.entities.artifacts.crud import new_artifact
|
|
8
|
+
from digitalhub_core.entities.artifacts.entity import Artifact
|
|
9
|
+
from digitalhub_core.utils.logger import LOGGER
|
|
10
|
+
from digitalhub_data.entities.dataitems.crud import new_dataitem
|
|
11
|
+
from digitalhub_data.entities.dataitems.entity.table import DataitemTable
|
|
12
|
+
from digitalhub_data.readers.registry import DATAFRAME_TYPES
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def collect_outputs(results: Any, outputs: list[str], project_name: str) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Collect outputs. Use the produced results directly.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
results : Any
|
|
22
|
+
Function outputs.
|
|
23
|
+
project : Project
|
|
24
|
+
Project object.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
dict
|
|
29
|
+
Function outputs.
|
|
30
|
+
"""
|
|
31
|
+
objects = {}
|
|
32
|
+
results = listify_results(results)
|
|
33
|
+
|
|
34
|
+
for idx, item in enumerate(results):
|
|
35
|
+
try:
|
|
36
|
+
name = outputs[idx]
|
|
37
|
+
except IndexError:
|
|
38
|
+
name = f"output_{idx}"
|
|
39
|
+
|
|
40
|
+
if isinstance(item, (str, int, float, bool, bytes)):
|
|
41
|
+
objects[name] = item
|
|
42
|
+
|
|
43
|
+
elif f"{item.__class__.__module__}.{item.__class__.__name__}" in DATAFRAME_TYPES:
|
|
44
|
+
objects[name] = build_and_load_dataitem(name, project_name, item)
|
|
45
|
+
|
|
46
|
+
else:
|
|
47
|
+
objects[name] = build_and_load_artifact(name, project_name, item)
|
|
48
|
+
|
|
49
|
+
return objects
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parse_outputs(results: Any, run_outputs: list, project_name: str) -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Parse outputs.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
results : Any
|
|
59
|
+
Function outputs.
|
|
60
|
+
project : Project
|
|
61
|
+
Project object.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
dict
|
|
66
|
+
Function outputs.
|
|
67
|
+
"""
|
|
68
|
+
results_list = listify_results(results)
|
|
69
|
+
out_list = []
|
|
70
|
+
for idx, _ in enumerate(results_list):
|
|
71
|
+
try:
|
|
72
|
+
out_list.append(run_outputs.pop(0))
|
|
73
|
+
except IndexError:
|
|
74
|
+
out_list.append(f"output_{idx}")
|
|
75
|
+
return collect_outputs(results, out_list, project_name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def listify_results(results: Any) -> list:
|
|
79
|
+
"""
|
|
80
|
+
Listify results.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
results : Any
|
|
85
|
+
Function outputs.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
list
|
|
90
|
+
Function outputs.
|
|
91
|
+
"""
|
|
92
|
+
if results is None:
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
if not isinstance(results, (tuple, list)):
|
|
96
|
+
results = [results]
|
|
97
|
+
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_and_load_dataitem(name: str, project_name: str, data: Any) -> DataitemTable:
|
|
102
|
+
"""
|
|
103
|
+
Build and load dataitem.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
name : str
|
|
108
|
+
Dataitem name.
|
|
109
|
+
project_name : str
|
|
110
|
+
Project name.
|
|
111
|
+
data : Any
|
|
112
|
+
Data.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
str
|
|
117
|
+
Dataitem key.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
path = f"s3://datalake/{project_name}/dataitems/table/{name}.parquet"
|
|
121
|
+
di: DataitemTable = new_dataitem(project=project_name, name=name, kind="table", path=path)
|
|
122
|
+
di.write_df(df=data)
|
|
123
|
+
return di
|
|
124
|
+
except Exception as e:
|
|
125
|
+
msg = f"Some error occurred while building and loading dataitem. Exception: {e.__class__}. Error: {e.args}"
|
|
126
|
+
LOGGER.exception(msg)
|
|
127
|
+
raise RuntimeError(msg)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_and_load_artifact(name: str, project_name: str, data: Any) -> Artifact:
|
|
131
|
+
"""
|
|
132
|
+
Build and load artifact.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
name : str
|
|
137
|
+
Artifact name.
|
|
138
|
+
project_name : str
|
|
139
|
+
Project name.
|
|
140
|
+
data : Any
|
|
141
|
+
Data.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
str
|
|
146
|
+
Artifact key.
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
path = f"s3://datalake/{project_name}/artifacts/artifact/{name}.pickle"
|
|
150
|
+
|
|
151
|
+
# Dump item to pickle
|
|
152
|
+
with open(f"{name}.pickle", "wb") as f:
|
|
153
|
+
f.write(pickle.dumps(data))
|
|
154
|
+
|
|
155
|
+
art = new_artifact(project=project_name, name=name, kind="artifact", path=path)
|
|
156
|
+
art.upload(source=f"{name}.pickle")
|
|
157
|
+
return art
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
msg = f"Some error occurred while building and loading artifact. Exception: {e.__class__}. Error: {e.args}"
|
|
161
|
+
LOGGER.exception(msg)
|
|
162
|
+
raise RuntimeError(msg)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_status(
|
|
166
|
+
parsed_execution: dict,
|
|
167
|
+
mapped_outputs: dict | None = None,
|
|
168
|
+
) -> dict:
|
|
169
|
+
"""
|
|
170
|
+
Collect outputs.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
parsed_execution : dict
|
|
175
|
+
Parsed execution dict.
|
|
176
|
+
mapped_outputs : dict
|
|
177
|
+
Mapped outputs.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
dict
|
|
182
|
+
Status dict.
|
|
183
|
+
"""
|
|
184
|
+
results = {}
|
|
185
|
+
outputs = {}
|
|
186
|
+
if mapped_outputs is None:
|
|
187
|
+
mapped_outputs = {}
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
for key, _ in mapped_outputs.items():
|
|
191
|
+
if key in parsed_execution:
|
|
192
|
+
if isinstance(parsed_execution[key], (DataitemTable, Artifact)):
|
|
193
|
+
outputs[key] = parsed_execution[key].key
|
|
194
|
+
else:
|
|
195
|
+
results[key] = parsed_execution[key]
|
|
196
|
+
return {
|
|
197
|
+
"state": State.COMPLETED.value,
|
|
198
|
+
"outputs": outputs,
|
|
199
|
+
"results": results,
|
|
200
|
+
}
|
|
201
|
+
except Exception as e:
|
|
202
|
+
msg = f"Something got wrong during run status building. Exception: {e.__class__}. Error: {e.args}"
|
|
203
|
+
LOGGER.exception(msg)
|
|
204
|
+
raise RuntimeError(msg) from e
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from digitalhub_runtime_python.utils.outputs import collect_outputs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handler(outputs: list[str] | None = None) -> Callable:
|
|
10
|
+
"""
|
|
11
|
+
Decorator that handles the outputs of the function.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
outputs : list[str]
|
|
16
|
+
List of named outputs to collect.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
Callable
|
|
21
|
+
Decorated function.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def decorator(func: Callable) -> Callable:
|
|
25
|
+
"""
|
|
26
|
+
Decorator that handles the outputs of the function.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
func : Callable
|
|
31
|
+
Function to decorate.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
Callable
|
|
36
|
+
Decorated function.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def wrapper(*args, **kwargs) -> dict:
|
|
40
|
+
"""
|
|
41
|
+
Wrapper that handles the outputs of the function.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
args : tuple
|
|
46
|
+
Function arguments.
|
|
47
|
+
kwargs : dict
|
|
48
|
+
Function keyword arguments.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
Any
|
|
53
|
+
Function outputs.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Initialize outputs
|
|
57
|
+
nonlocal outputs
|
|
58
|
+
|
|
59
|
+
# We pass the first argument as the project name
|
|
60
|
+
project_name = args[0]
|
|
61
|
+
args = args[1:]
|
|
62
|
+
|
|
63
|
+
# Execute the function
|
|
64
|
+
results = func(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
# Parse outputs based on the decorator signature
|
|
67
|
+
return collect_outputs(results, outputs, project_name)
|
|
68
|
+
|
|
69
|
+
wrapper = functools.wraps(func)(wrapper)
|
|
70
|
+
|
|
71
|
+
return wrapper
|
|
72
|
+
|
|
73
|
+
return decorator
|