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.
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_core.entities.entity_types import EntityTypes
4
+ from digitalhub_core.registry.registry import registry
5
+ from digitalhub_core.registry.utils import create_info
6
+ from digitalhub_runtime_python.utils.utils import handler
7
+
8
+ root = "digitalhub_runtime_python"
9
+ runtime_info = {
10
+ "module": f"{root}.runtimes.runtime",
11
+ "class_name": "RuntimePython",
12
+ "kind_registry_module": f"{root}.runtimes.kind_registry",
13
+ "kind_registry_class_name": "kind_registry",
14
+ }
15
+
16
+ root_ent = f"{root}.entities"
17
+
18
+
19
+ # Function
20
+ entity_type = EntityTypes.FUNCTIONS.value
21
+ func_kind = "python"
22
+ prefix = entity_type.removesuffix("s").capitalize()
23
+ suffix = func_kind.capitalize()
24
+ func_info = create_info(root_ent, entity_type, prefix, suffix, runtime_info)
25
+ registry.register(func_kind, func_info)
26
+
27
+
28
+ # Tasks
29
+ entity_type = EntityTypes.TASKS.value
30
+ for i in ["job", "build"]:
31
+ task_kind = f"{func_kind}+{i}"
32
+ prefix = entity_type.removesuffix("s").capitalize()
33
+ suffix = i.capitalize()
34
+ task_info = create_info(root_ent, entity_type, prefix, suffix, runtime_info)
35
+ registry.register(task_kind, task_info)
36
+
37
+
38
+ # Runs
39
+ entity_type = EntityTypes.RUNS.value
40
+ run_kind = f"{func_kind}+run"
41
+ prefix = entity_type.removesuffix("s").capitalize()
42
+ suffix = func_kind.capitalize()
43
+ run_info = create_info(root_ent, entity_type, prefix, suffix, runtime_info)
44
+ registry.register(run_kind, run_info)
File without changes
@@ -0,0 +1,144 @@
1
+ """
2
+ Function Conatiner specification module.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+
8
+ from digitalhub_core.entities.functions.spec import FunctionParams, FunctionSpec, SourceCodeStruct
9
+ from digitalhub_core.utils.exceptions import EntityError
10
+ from digitalhub_core.utils.generic_utils import decode_string, encode_source, encode_string
11
+ from digitalhub_core.utils.uri_utils import map_uri_scheme
12
+
13
+
14
+ class FunctionSpecPython(FunctionSpec):
15
+ """
16
+ Specification for a Function job.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ source: dict,
22
+ image: str | None = None,
23
+ base_image: str | None = None,
24
+ python_version: str | None = None,
25
+ requirements: list | None = None,
26
+ ) -> None:
27
+ """
28
+ Constructor.
29
+ """
30
+ super().__init__()
31
+
32
+ self.image = image
33
+ self.base_image = base_image
34
+ self.python_version = python_version
35
+ self.requirements = requirements
36
+
37
+ source = self._source_check(source)
38
+ self.source = SourceCodeStruct(**source)
39
+
40
+ @staticmethod
41
+ def _source_check(source: dict) -> dict:
42
+ """
43
+ Check source.
44
+
45
+ Parameters
46
+ ----------
47
+ source : dict
48
+ Source.
49
+
50
+ Returns
51
+ -------
52
+ dict
53
+ Checked source.
54
+ """
55
+ if source is None:
56
+ raise EntityError("Source must be provided.")
57
+
58
+ # Source check
59
+ source_path = source.get("source")
60
+ code = source.get("code")
61
+ base64 = source.get("base64")
62
+ handler = source.get("handler")
63
+ source["lang"] = "python"
64
+
65
+ if handler is None:
66
+ raise EntityError("Handler must be provided.")
67
+
68
+ if source_path is None and code is None and base64 is None:
69
+ raise EntityError("Source must be provided.")
70
+
71
+ if base64 is not None:
72
+ return source
73
+
74
+ if code is not None:
75
+ source["base64"] = encode_string(code)
76
+ return source
77
+
78
+ if source_path is not None:
79
+ if map_uri_scheme(source_path) == "local":
80
+ if not (Path(source_path).suffix == ".py" and Path(source_path).is_file()):
81
+ raise EntityError("Source is not a valid python file.")
82
+ source["base64"] = encode_source(source_path)
83
+ else:
84
+ if handler is None:
85
+ raise EntityError("Handler must be provided if source is not local.")
86
+
87
+ return source
88
+
89
+ def show_source_code(self) -> str:
90
+ """
91
+ Show source code.
92
+
93
+ Returns
94
+ -------
95
+ str
96
+ Source code.
97
+ """
98
+ if self.source.code is not None:
99
+ return str(self.source.code)
100
+ if self.source.base64 is not None:
101
+ try:
102
+ return decode_string(self.source.base64)
103
+ except Exception:
104
+ raise EntityError("Something got wrong during source code decoding.")
105
+ if (self.source.source is not None) and (map_uri_scheme(self.source.source) == "local"):
106
+ try:
107
+ return Path(self.source.source).read_text()
108
+ except Exception:
109
+ raise EntityError("Cannot access source code.")
110
+ return ""
111
+
112
+ def to_dict(self) -> dict:
113
+ """
114
+ Override to_dict to exclude code from source.
115
+
116
+ Returns
117
+ -------
118
+ dict
119
+ Dictionary representation of the object.
120
+ """
121
+ dict_ = super().to_dict()
122
+ dict_["source"] = self.source.to_dict()
123
+ return dict_
124
+
125
+
126
+ class FunctionParamsPython(FunctionParams):
127
+ """
128
+ Function python parameters model.
129
+ """
130
+
131
+ source: dict
132
+ "Source code"
133
+
134
+ image: str = None
135
+ "Image"
136
+
137
+ base_image: str = None
138
+ "Base image"
139
+
140
+ python_version: str = None
141
+ "Python version"
142
+
143
+ requirements: list = None
144
+ "Requirements list, as used by the runtime"
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_core.entities.functions.status import FunctionStatus
4
+
5
+
6
+ class FunctionStatusPython(FunctionStatus):
7
+ """
8
+ Function Python status.
9
+ """
File without changes
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_ml.entities.runs.spec import RunParamsMl, RunSpecMl
4
+
5
+
6
+ class RunSpecPython(RunSpecMl):
7
+ """Run Python specification."""
8
+
9
+ def __init__(
10
+ self,
11
+ task: str,
12
+ local_execution: bool = False,
13
+ **kwargs,
14
+ ) -> None:
15
+ """
16
+ Constructor.
17
+ """
18
+ super().__init__(task, local_execution)
19
+
20
+ self.source = kwargs.get("source")
21
+ self.image = kwargs.get("image")
22
+ self.base_image = kwargs.get("base_image")
23
+ self.python_version = kwargs.get("python_version")
24
+ self.requirements = kwargs.get("requirements")
25
+
26
+ self.function = kwargs.get("function")
27
+ self.node_selector = kwargs.get("node_selector")
28
+ self.volumes = kwargs.get("volumes")
29
+ self.resources = kwargs.get("resources")
30
+ self.affinity = kwargs.get("affinity")
31
+ self.tolerations = kwargs.get("tolerations")
32
+ self.env = kwargs.get("env")
33
+ self.secrets = kwargs.get("secrets")
34
+ self.backoff_limit = kwargs.get("backoff_limit")
35
+ self.schedule = kwargs.get("schedule")
36
+ self.replicas = kwargs.get("replicas")
37
+
38
+ # Task job
39
+
40
+ # Task build
41
+ self.instructions = kwargs.get("instructions")
42
+
43
+ self.inputs = kwargs.get("inputs")
44
+ self.outputs = kwargs.get("outputs")
45
+ self.parameters = kwargs.get("parameters")
46
+
47
+
48
+ class RunParamsPython(RunParamsMl):
49
+ """Run Python parameters."""
50
+
51
+ # Function parameters
52
+ source: dict = None
53
+ image: str = None
54
+ base_image: str = None
55
+ python_version: str = None
56
+ requirements: list = None
57
+
58
+ # Task parameters
59
+ function: str = None
60
+ node_selector: list[dict] = None
61
+ volumes: list[dict] = None
62
+ resources: list[dict] = None
63
+ affinity: dict = None
64
+ tolerations: list[dict] = None
65
+ env: list[dict] = None
66
+ secrets: list[str] = None
67
+
68
+ # Task job
69
+ backoff_limit: int = None
70
+
71
+ # Task serve
72
+ service_type: str = None
73
+ replicas: int = None
74
+
75
+ # Task build
76
+ instructions: list[str] = None
77
+
78
+ # Run parameters
79
+ inputs: dict = None
80
+ outputs: dict = None
81
+ parameters: dict = None
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_ml.entities.runs.status import RunStatusMl
4
+
5
+
6
+ class RunStatusPython(RunStatusMl):
7
+ """
8
+ Run Python status.
9
+ """
File without changes
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class CorePort(BaseModel):
7
+ """
8
+ Port mapper model.
9
+ """
10
+
11
+ port: int
12
+ target_port: int
13
+
14
+
15
+ class ContextRef(BaseModel):
16
+ """
17
+ ContextRef model.
18
+ """
19
+
20
+ destination: str = None
21
+ protocol: str = None
22
+ source: str = None
23
+
24
+
25
+ class ContextSource(BaseModel):
26
+ """
27
+ ContextSource model.
28
+ """
29
+
30
+ base64: str = None
31
+ name: str = None
@@ -0,0 +1,87 @@
1
+ """
2
+ Task Python specification module.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Literal
7
+
8
+ from digitalhub_core.entities.tasks.spec import TaskParamsK8s, TaskSpecK8s
9
+
10
+
11
+ class TaskSpecJob(TaskSpecK8s):
12
+ """Task Job specification."""
13
+
14
+ def __init__(
15
+ self,
16
+ function: str,
17
+ **kwargs,
18
+ ) -> None:
19
+ """
20
+ Constructor.
21
+ """
22
+ super().__init__(function, **kwargs)
23
+
24
+ self.backoff_limit = kwargs.get("backoff_limit")
25
+
26
+
27
+ class TaskParamsJob(TaskParamsK8s):
28
+ """
29
+ TaskParamsJob model.
30
+ """
31
+
32
+ backoff_limit: int = None
33
+ """Backoff limit."""
34
+
35
+
36
+ class TaskSpecBuild(TaskSpecK8s):
37
+ """Task Build specification."""
38
+
39
+ def __init__(
40
+ self,
41
+ function: str,
42
+ instructions: list | None = None,
43
+ **kwargs,
44
+ ) -> None:
45
+ """
46
+ Constructor.
47
+ """
48
+ super().__init__(function, **kwargs)
49
+
50
+ self.instructions = instructions
51
+
52
+
53
+ class TaskParamsBuild(TaskParamsK8s):
54
+ """
55
+ TaskParamsBuild model.
56
+ """
57
+
58
+ instructions: list[str] = None
59
+ """Build instructions."""
60
+
61
+
62
+ class TaskSpecServe(TaskSpecK8s):
63
+ """Task Serve specification."""
64
+
65
+ def __init__(
66
+ self,
67
+ function: str,
68
+ replicas: int | None = None,
69
+ service_type: str | None = None,
70
+ **kwargs,
71
+ ) -> None:
72
+ """
73
+ Constructor.
74
+ """
75
+ super().__init__(function, **kwargs)
76
+
77
+ self.replicas = replicas
78
+ self.service_type = service_type
79
+
80
+
81
+ class TaskParamsServe(TaskParamsK8s):
82
+ """
83
+ TaskParamsServe model.
84
+ """
85
+
86
+ replicas: int = None
87
+ service_type: Literal["ClusterIP", "NodePort", "LoadBalancer"] = "NodePort"
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_core.entities.tasks.status import TaskStatus
4
+
5
+
6
+ class TaskStatusJob(TaskStatus):
7
+ """
8
+ Task Job status.
9
+ """
10
+
11
+
12
+ class TaskStatusBuild(TaskStatus):
13
+ """
14
+ Task Build status.
15
+ """
File without changes
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub_core.runtimes.kind_registry import KindRegistry
4
+
5
+ kind_registry = KindRegistry(
6
+ {
7
+ "executable": {"kind": "python"},
8
+ "task": [
9
+ {"kind": "python+job", "action": "job"},
10
+ {"kind": "python+serve", "action": "serve"},
11
+ {"kind": "python+build", "action": "build"},
12
+ ],
13
+ "run": {"kind": "python+run"},
14
+ }
15
+ )
@@ -0,0 +1,159 @@
1
+ """
2
+ Runtime class for running Python functions.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import typing
7
+ from typing import Callable
8
+
9
+ from digitalhub_core.context.builder import get_context
10
+ from digitalhub_core.runtimes.base import Runtime
11
+ from digitalhub_core.utils.logger import LOGGER
12
+ from digitalhub_runtime_python.utils.configuration import get_function_from_source
13
+ from digitalhub_runtime_python.utils.inputs import compose_inputs
14
+ from digitalhub_runtime_python.utils.outputs import build_status, parse_outputs
15
+
16
+ if typing.TYPE_CHECKING:
17
+ from digitalhub_core.runtimes.kind_registry import KindRegistry
18
+
19
+
20
+ class RuntimePython(Runtime):
21
+ """
22
+ Runtime Python class.
23
+ """
24
+
25
+ def __init__(self, kind_registry: KindRegistry, project: str) -> None:
26
+ """
27
+ Constructor.
28
+ """
29
+ super().__init__(kind_registry, project)
30
+ ctx = get_context(self.project)
31
+ self.root = ctx.runtime_dir
32
+ self.tmp_dir = ctx.tmp_dir
33
+
34
+ self.root.mkdir(parents=True, exist_ok=True)
35
+ self.tmp_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ def build(self, function: dict, task: dict, run: dict) -> dict:
38
+ """
39
+ Build run spec.
40
+
41
+ Parameters
42
+ ----------
43
+ function : dict
44
+ The function.
45
+ task : dict
46
+ The task.
47
+ run : dict
48
+ The run.
49
+
50
+ Returns
51
+ -------
52
+ dict
53
+ The run spec.
54
+ """
55
+ return {
56
+ **function.get("spec", {}),
57
+ **task.get("spec", {}),
58
+ **run.get("spec", {}),
59
+ }
60
+
61
+ def run(self, run: dict) -> dict:
62
+ """
63
+ Run function.
64
+
65
+ Returns
66
+ -------
67
+ dict
68
+ Status of the executed run.
69
+ """
70
+ LOGGER.info("Validating task.")
71
+ self._validate_task(run)
72
+
73
+ LOGGER.info("Starting task.")
74
+ spec = run.get("spec")
75
+ project = run.get("project")
76
+
77
+ LOGGER.info("Configuring execution.")
78
+ fnc, wrapped = self._configure_execution(spec)
79
+
80
+ LOGGER.info("Composing function arguments.")
81
+ fnc_args = self._compose_args(fnc, spec, project)
82
+
83
+ LOGGER.info("Executing run.")
84
+ if wrapped:
85
+ results: dict = self._execute(fnc, project, **fnc_args)
86
+ else:
87
+ exec_result = self._execute(fnc, **fnc_args)
88
+ LOGGER.info("Collecting outputs.")
89
+ results = parse_outputs(exec_result, list(spec.get("outputs", {})), project)
90
+
91
+ status = build_status(results, spec.get("outputs"))
92
+
93
+ # Return run status
94
+ LOGGER.info("Task completed, returning run status.")
95
+ return status
96
+
97
+ @staticmethod
98
+ def _get_executable(action: str) -> Callable:
99
+ """
100
+ Select function according to action.
101
+
102
+ Parameters
103
+ ----------
104
+ action : str
105
+ Action to execute.
106
+
107
+ Returns
108
+ -------
109
+ Callable
110
+ Function to execute.
111
+ """
112
+ raise NotImplementedError
113
+
114
+ ####################
115
+ # Configuration
116
+ ####################
117
+
118
+ def _configure_execution(self, spec: dict) -> tuple[Callable, bool]:
119
+ """
120
+ Configure execution.
121
+
122
+ Parameters
123
+ ----------
124
+ spec : dict
125
+ Run spec.
126
+
127
+ Returns
128
+ -------
129
+ Callable
130
+ Function to execute.
131
+ """
132
+ fnc = get_function_from_source(
133
+ self.root,
134
+ spec.get("source", {}),
135
+ )
136
+ return fnc, hasattr(fnc, "__wrapped__")
137
+
138
+ def _compose_args(self, func: Callable, spec: dict, project: str) -> dict:
139
+ """
140
+ Collect inputs.
141
+
142
+ Parameters
143
+ ----------
144
+ func : Callable
145
+ Function to execute.
146
+ spec : dict
147
+ Run specs.
148
+ project : str
149
+ Project name.
150
+
151
+ Returns
152
+ -------
153
+ dict
154
+ Parameters.
155
+ """
156
+ inputs = spec.get("inputs", {})
157
+ parameters = spec.get("parameters", {})
158
+ local_execution = spec.get("local_execution")
159
+ return compose_inputs(inputs, parameters, local_execution, func, project)