digitalhub 0.7.0b2__py3-none-any.whl → 0.8.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.
Potentially problematic release.
This version of digitalhub might be problematic. Click here for more details.
- digitalhub/__init__.py +63 -93
- digitalhub/client/__init__.py +0 -0
- digitalhub/client/_base/__init__.py +0 -0
- digitalhub/client/_base/client.py +56 -0
- digitalhub/client/api.py +63 -0
- digitalhub/client/builder.py +50 -0
- digitalhub/client/dhcore/__init__.py +0 -0
- digitalhub/client/dhcore/client.py +669 -0
- digitalhub/client/dhcore/env.py +21 -0
- digitalhub/client/dhcore/models.py +46 -0
- digitalhub/client/dhcore/utils.py +111 -0
- digitalhub/client/local/__init__.py +0 -0
- digitalhub/client/local/client.py +533 -0
- digitalhub/context/__init__.py +0 -0
- digitalhub/context/api.py +93 -0
- digitalhub/context/builder.py +94 -0
- digitalhub/context/context.py +136 -0
- digitalhub/datastores/__init__.py +0 -0
- digitalhub/datastores/_base/__init__.py +0 -0
- digitalhub/datastores/_base/datastore.py +85 -0
- digitalhub/datastores/api.py +37 -0
- digitalhub/datastores/builder.py +110 -0
- digitalhub/datastores/local/__init__.py +0 -0
- digitalhub/datastores/local/datastore.py +50 -0
- digitalhub/datastores/remote/__init__.py +0 -0
- digitalhub/datastores/remote/datastore.py +31 -0
- digitalhub/datastores/s3/__init__.py +0 -0
- digitalhub/datastores/s3/datastore.py +46 -0
- digitalhub/datastores/sql/__init__.py +0 -0
- digitalhub/datastores/sql/datastore.py +68 -0
- digitalhub/entities/__init__.py +0 -0
- digitalhub/entities/_base/__init__.py +0 -0
- digitalhub/entities/_base/_base/__init__.py +0 -0
- digitalhub/entities/_base/_base/entity.py +82 -0
- digitalhub/entities/_base/api_utils.py +620 -0
- digitalhub/entities/_base/context/__init__.py +0 -0
- digitalhub/entities/_base/context/entity.py +118 -0
- digitalhub/entities/_base/crud.py +468 -0
- digitalhub/entities/_base/entity/__init__.py +0 -0
- digitalhub/entities/_base/entity/_constructors/__init__.py +0 -0
- digitalhub/entities/_base/entity/_constructors/metadata.py +44 -0
- digitalhub/entities/_base/entity/_constructors/name.py +31 -0
- digitalhub/entities/_base/entity/_constructors/spec.py +33 -0
- digitalhub/entities/_base/entity/_constructors/status.py +52 -0
- digitalhub/entities/_base/entity/_constructors/uuid.py +26 -0
- digitalhub/entities/_base/entity/builder.py +175 -0
- digitalhub/entities/_base/entity/entity.py +106 -0
- digitalhub/entities/_base/entity/metadata.py +59 -0
- digitalhub/entities/_base/entity/spec.py +58 -0
- digitalhub/entities/_base/entity/status.py +43 -0
- digitalhub/entities/_base/executable/__init__.py +0 -0
- digitalhub/entities/_base/executable/entity.py +405 -0
- digitalhub/entities/_base/material/__init__.py +0 -0
- digitalhub/entities/_base/material/entity.py +214 -0
- digitalhub/entities/_base/material/spec.py +22 -0
- digitalhub/entities/_base/material/status.py +49 -0
- digitalhub/entities/_base/runtime_entity/__init__.py +0 -0
- digitalhub/entities/_base/runtime_entity/builder.py +106 -0
- digitalhub/entities/_base/unversioned/__init__.py +0 -0
- digitalhub/entities/_base/unversioned/builder.py +66 -0
- digitalhub/entities/_base/unversioned/entity.py +49 -0
- digitalhub/entities/_base/versioned/__init__.py +0 -0
- digitalhub/entities/_base/versioned/builder.py +68 -0
- digitalhub/entities/_base/versioned/entity.py +53 -0
- digitalhub/entities/artifact/__init__.py +0 -0
- digitalhub/entities/artifact/_base/__init__.py +0 -0
- digitalhub/entities/artifact/_base/builder.py +86 -0
- digitalhub/entities/artifact/_base/entity.py +39 -0
- digitalhub/entities/artifact/_base/spec.py +15 -0
- digitalhub/entities/artifact/_base/status.py +9 -0
- digitalhub/entities/artifact/artifact/__init__.py +0 -0
- digitalhub/entities/artifact/artifact/builder.py +18 -0
- digitalhub/entities/artifact/artifact/entity.py +32 -0
- digitalhub/entities/artifact/artifact/spec.py +27 -0
- digitalhub/entities/artifact/artifact/status.py +15 -0
- digitalhub/entities/artifact/crud.py +332 -0
- digitalhub/entities/builders.py +63 -0
- digitalhub/entities/dataitem/__init__.py +0 -0
- digitalhub/entities/dataitem/_base/__init__.py +0 -0
- digitalhub/entities/dataitem/_base/builder.py +86 -0
- digitalhub/entities/dataitem/_base/entity.py +75 -0
- digitalhub/entities/dataitem/_base/spec.py +15 -0
- digitalhub/entities/dataitem/_base/status.py +20 -0
- digitalhub/entities/dataitem/crud.py +372 -0
- digitalhub/entities/dataitem/dataitem/__init__.py +0 -0
- digitalhub/entities/dataitem/dataitem/builder.py +18 -0
- digitalhub/entities/dataitem/dataitem/entity.py +32 -0
- digitalhub/entities/dataitem/dataitem/spec.py +15 -0
- digitalhub/entities/dataitem/dataitem/status.py +9 -0
- digitalhub/entities/dataitem/iceberg/__init__.py +0 -0
- digitalhub/entities/dataitem/iceberg/builder.py +18 -0
- digitalhub/entities/dataitem/iceberg/entity.py +32 -0
- digitalhub/entities/dataitem/iceberg/spec.py +15 -0
- digitalhub/entities/dataitem/iceberg/status.py +9 -0
- digitalhub/entities/dataitem/table/__init__.py +0 -0
- digitalhub/entities/dataitem/table/builder.py +18 -0
- digitalhub/entities/dataitem/table/entity.py +146 -0
- digitalhub/entities/dataitem/table/models.py +62 -0
- digitalhub/entities/dataitem/table/spec.py +25 -0
- digitalhub/entities/dataitem/table/status.py +9 -0
- digitalhub/entities/function/__init__.py +0 -0
- digitalhub/entities/function/_base/__init__.py +0 -0
- digitalhub/entities/function/_base/builder.py +79 -0
- digitalhub/entities/function/_base/entity.py +98 -0
- digitalhub/entities/function/_base/models.py +118 -0
- digitalhub/entities/function/_base/spec.py +15 -0
- digitalhub/entities/function/_base/status.py +9 -0
- digitalhub/entities/function/crud.py +279 -0
- digitalhub/entities/model/__init__.py +0 -0
- digitalhub/entities/model/_base/__init__.py +0 -0
- digitalhub/entities/model/_base/builder.py +86 -0
- digitalhub/entities/model/_base/entity.py +34 -0
- digitalhub/entities/model/_base/spec.py +49 -0
- digitalhub/entities/model/_base/status.py +9 -0
- digitalhub/entities/model/crud.py +331 -0
- digitalhub/entities/model/huggingface/__init__.py +0 -0
- digitalhub/entities/model/huggingface/builder.py +18 -0
- digitalhub/entities/model/huggingface/entity.py +32 -0
- digitalhub/entities/model/huggingface/spec.py +36 -0
- digitalhub/entities/model/huggingface/status.py +9 -0
- digitalhub/entities/model/mlflow/__init__.py +0 -0
- digitalhub/entities/model/mlflow/builder.py +18 -0
- digitalhub/entities/model/mlflow/entity.py +32 -0
- digitalhub/entities/model/mlflow/models.py +26 -0
- digitalhub/entities/model/mlflow/spec.py +44 -0
- digitalhub/entities/model/mlflow/status.py +9 -0
- digitalhub/entities/model/mlflow/utils.py +81 -0
- digitalhub/entities/model/model/__init__.py +0 -0
- digitalhub/entities/model/model/builder.py +18 -0
- digitalhub/entities/model/model/entity.py +32 -0
- digitalhub/entities/model/model/spec.py +15 -0
- digitalhub/entities/model/model/status.py +9 -0
- digitalhub/entities/model/sklearn/__init__.py +0 -0
- digitalhub/entities/model/sklearn/builder.py +18 -0
- digitalhub/entities/model/sklearn/entity.py +32 -0
- digitalhub/entities/model/sklearn/spec.py +15 -0
- digitalhub/entities/model/sklearn/status.py +9 -0
- digitalhub/entities/project/__init__.py +0 -0
- digitalhub/entities/project/_base/__init__.py +0 -0
- digitalhub/entities/project/_base/builder.py +128 -0
- digitalhub/entities/project/_base/entity.py +2078 -0
- digitalhub/entities/project/_base/spec.py +50 -0
- digitalhub/entities/project/_base/status.py +9 -0
- digitalhub/entities/project/crud.py +357 -0
- digitalhub/entities/run/__init__.py +0 -0
- digitalhub/entities/run/_base/__init__.py +0 -0
- digitalhub/entities/run/_base/builder.py +94 -0
- digitalhub/entities/run/_base/entity.py +307 -0
- digitalhub/entities/run/_base/spec.py +50 -0
- digitalhub/entities/run/_base/status.py +9 -0
- digitalhub/entities/run/crud.py +219 -0
- digitalhub/entities/secret/__init__.py +0 -0
- digitalhub/entities/secret/_base/__init__.py +0 -0
- digitalhub/entities/secret/_base/builder.py +81 -0
- digitalhub/entities/secret/_base/entity.py +74 -0
- digitalhub/entities/secret/_base/spec.py +35 -0
- digitalhub/entities/secret/_base/status.py +9 -0
- digitalhub/entities/secret/crud.py +290 -0
- digitalhub/entities/task/__init__.py +0 -0
- digitalhub/entities/task/_base/__init__.py +0 -0
- digitalhub/entities/task/_base/builder.py +91 -0
- digitalhub/entities/task/_base/entity.py +136 -0
- digitalhub/entities/task/_base/models.py +208 -0
- digitalhub/entities/task/_base/spec.py +53 -0
- digitalhub/entities/task/_base/status.py +9 -0
- digitalhub/entities/task/crud.py +228 -0
- digitalhub/entities/utils/__init__.py +0 -0
- digitalhub/entities/utils/api.py +346 -0
- digitalhub/entities/utils/entity_types.py +19 -0
- digitalhub/entities/utils/state.py +31 -0
- digitalhub/entities/utils/utils.py +202 -0
- digitalhub/entities/workflow/__init__.py +0 -0
- digitalhub/entities/workflow/_base/__init__.py +0 -0
- digitalhub/entities/workflow/_base/builder.py +79 -0
- digitalhub/entities/workflow/_base/entity.py +74 -0
- digitalhub/entities/workflow/_base/spec.py +15 -0
- digitalhub/entities/workflow/_base/status.py +9 -0
- digitalhub/entities/workflow/crud.py +278 -0
- digitalhub/factory/__init__.py +0 -0
- digitalhub/factory/api.py +277 -0
- digitalhub/factory/factory.py +268 -0
- digitalhub/factory/utils.py +90 -0
- digitalhub/readers/__init__.py +0 -0
- digitalhub/readers/_base/__init__.py +0 -0
- digitalhub/readers/_base/builder.py +26 -0
- digitalhub/readers/_base/reader.py +70 -0
- digitalhub/readers/api.py +80 -0
- digitalhub/readers/factory.py +133 -0
- digitalhub/readers/pandas/__init__.py +0 -0
- digitalhub/readers/pandas/builder.py +29 -0
- digitalhub/readers/pandas/reader.py +207 -0
- digitalhub/runtimes/__init__.py +0 -0
- digitalhub/runtimes/_base.py +102 -0
- digitalhub/runtimes/builder.py +32 -0
- digitalhub/stores/__init__.py +0 -0
- digitalhub/stores/_base/__init__.py +0 -0
- digitalhub/stores/_base/store.py +189 -0
- digitalhub/stores/api.py +54 -0
- digitalhub/stores/builder.py +211 -0
- digitalhub/stores/local/__init__.py +0 -0
- digitalhub/stores/local/store.py +230 -0
- digitalhub/stores/remote/__init__.py +0 -0
- digitalhub/stores/remote/store.py +143 -0
- digitalhub/stores/s3/__init__.py +0 -0
- digitalhub/stores/s3/store.py +563 -0
- digitalhub/stores/sql/__init__.py +0 -0
- digitalhub/stores/sql/store.py +328 -0
- digitalhub/utils/__init__.py +0 -0
- digitalhub/utils/data_utils.py +127 -0
- digitalhub/utils/exceptions.py +67 -0
- digitalhub/utils/file_utils.py +204 -0
- digitalhub/utils/generic_utils.py +183 -0
- digitalhub/utils/git_utils.py +148 -0
- digitalhub/utils/io_utils.py +116 -0
- digitalhub/utils/logger.py +17 -0
- digitalhub/utils/s3_utils.py +58 -0
- digitalhub/utils/uri_utils.py +56 -0
- {digitalhub-0.7.0b2.dist-info → digitalhub-0.8.0.dist-info}/METADATA +30 -13
- digitalhub-0.8.0.dist-info/RECORD +231 -0
- {digitalhub-0.7.0b2.dist-info → digitalhub-0.8.0.dist-info}/WHEEL +1 -1
- test/local/CRUD/test_artifacts.py +96 -0
- test/local/CRUD/test_dataitems.py +96 -0
- test/local/CRUD/test_models.py +95 -0
- test/test_crud_functions.py +1 -1
- test/test_crud_runs.py +1 -1
- test/test_crud_tasks.py +1 -1
- digitalhub-0.7.0b2.dist-info/RECORD +0 -14
- test/test_crud_artifacts.py +0 -96
- test/test_crud_dataitems.py +0 -96
- {digitalhub-0.7.0b2.dist-info → digitalhub-0.8.0.dist-info}/LICENSE.txt +0 -0
- {digitalhub-0.7.0b2.dist-info → digitalhub-0.8.0.dist-info}/top_level.txt +0 -0
- /test/{test_imports.py → local/imports/test_imports.py} +0 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from digitalhub.entities._base.api_utils import list_entity_api_ctx
|
|
6
|
+
from digitalhub.entities._base.versioned.entity import VersionedEntity
|
|
7
|
+
from digitalhub.entities.run.crud import delete_run, get_run, list_runs
|
|
8
|
+
from digitalhub.entities.task.crud import delete_task
|
|
9
|
+
from digitalhub.entities.utils.entity_types import EntityTypes
|
|
10
|
+
from digitalhub.factory.api import build_entity_from_dict, build_entity_from_params
|
|
11
|
+
from digitalhub.utils.exceptions import EntityAlreadyExistsError, EntityError
|
|
12
|
+
|
|
13
|
+
if typing.TYPE_CHECKING:
|
|
14
|
+
from digitalhub.entities._base.entity.metadata import Metadata
|
|
15
|
+
from digitalhub.entities._base.entity.spec import Spec
|
|
16
|
+
from digitalhub.entities._base.entity.status import Status
|
|
17
|
+
from digitalhub.entities.run._base.entity import Run
|
|
18
|
+
from digitalhub.entities.task._base.entity import Task
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ExecutableEntity(VersionedEntity):
|
|
22
|
+
"""
|
|
23
|
+
A class representing an entity that can be executed.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
project: str,
|
|
29
|
+
name: str,
|
|
30
|
+
uuid: str,
|
|
31
|
+
kind: str,
|
|
32
|
+
metadata: Metadata,
|
|
33
|
+
spec: Spec,
|
|
34
|
+
status: Status,
|
|
35
|
+
user: str | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
super().__init__(project, name, uuid, kind, metadata, spec, status, user)
|
|
38
|
+
|
|
39
|
+
# Initialize tasks
|
|
40
|
+
self._tasks: dict[str, Task] = {}
|
|
41
|
+
|
|
42
|
+
##############################
|
|
43
|
+
# Helpers
|
|
44
|
+
##############################
|
|
45
|
+
|
|
46
|
+
def _get_executable_string(self) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Get executable string.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
str
|
|
53
|
+
Executable string.
|
|
54
|
+
"""
|
|
55
|
+
return f"{self.kind}://{self.project}/{self.name}:{self.id}"
|
|
56
|
+
|
|
57
|
+
##############################
|
|
58
|
+
# Tasks
|
|
59
|
+
##############################
|
|
60
|
+
|
|
61
|
+
def _get_or_create_task(self, kind: str) -> Task:
|
|
62
|
+
"""
|
|
63
|
+
Get or create task.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
kind : str
|
|
68
|
+
Kind the object.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
Task
|
|
73
|
+
Task.
|
|
74
|
+
"""
|
|
75
|
+
if self._tasks.get(kind) is None:
|
|
76
|
+
try:
|
|
77
|
+
self._tasks[kind] = self.get_task(kind)
|
|
78
|
+
except EntityError:
|
|
79
|
+
self._tasks[kind] = self.new_task(kind)
|
|
80
|
+
return self._tasks[kind]
|
|
81
|
+
|
|
82
|
+
def import_tasks(self, tasks: list[dict]) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Import tasks from yaml.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
tasks : list[dict]
|
|
89
|
+
List of tasks to import.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
None
|
|
94
|
+
"""
|
|
95
|
+
# Loop over tasks list, in the case where the function
|
|
96
|
+
# is imported from local file.
|
|
97
|
+
for task in tasks:
|
|
98
|
+
# If task is not a dictionary, skip it
|
|
99
|
+
if not isinstance(task, dict):
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
# Create a new object from dictionary.
|
|
103
|
+
# the form in which tasks are stored in function
|
|
104
|
+
# status
|
|
105
|
+
task_obj = build_entity_from_dict(task)
|
|
106
|
+
|
|
107
|
+
# Try to save it in backend to been able to use
|
|
108
|
+
# it for launching runs. In fact, tasks must be
|
|
109
|
+
# persisted in backend to be able to launch runs.
|
|
110
|
+
# Ignore if task already exists
|
|
111
|
+
try:
|
|
112
|
+
task_obj.save()
|
|
113
|
+
except EntityAlreadyExistsError:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
# Set task if function is the same. Overwrite
|
|
117
|
+
# status task dict with the new task object
|
|
118
|
+
if task_obj.spec.function == self._get_executable_string():
|
|
119
|
+
self._tasks[task_obj.kind] = task_obj
|
|
120
|
+
|
|
121
|
+
def new_task(self, task_kind: str, **kwargs) -> Task:
|
|
122
|
+
"""
|
|
123
|
+
Create new task. If the task already exists, update it.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
task_kind : str
|
|
128
|
+
Kind the object.
|
|
129
|
+
**kwargs : dict
|
|
130
|
+
Keyword arguments.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
Task
|
|
135
|
+
New task.
|
|
136
|
+
"""
|
|
137
|
+
self._raise_if_exists(task_kind)
|
|
138
|
+
|
|
139
|
+
if kwargs is None:
|
|
140
|
+
kwargs = {}
|
|
141
|
+
|
|
142
|
+
# Override kwargs
|
|
143
|
+
kwargs["project"] = self.project
|
|
144
|
+
kwargs["function"] = self._get_executable_string()
|
|
145
|
+
kwargs["kind"] = task_kind
|
|
146
|
+
|
|
147
|
+
# Create object instance
|
|
148
|
+
task = build_entity_from_params(**kwargs)
|
|
149
|
+
task.save()
|
|
150
|
+
|
|
151
|
+
self._tasks[task_kind] = task
|
|
152
|
+
return task
|
|
153
|
+
|
|
154
|
+
def get_task(self, kind: str) -> Task:
|
|
155
|
+
"""
|
|
156
|
+
Get task.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
kind : str
|
|
161
|
+
Kind the object.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
Task
|
|
166
|
+
Task.
|
|
167
|
+
|
|
168
|
+
Raises
|
|
169
|
+
------
|
|
170
|
+
EntityError
|
|
171
|
+
If task is not created.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
return self._tasks[kind]
|
|
175
|
+
except KeyError:
|
|
176
|
+
resp = self._get_task_from_backend(kind)
|
|
177
|
+
if not resp:
|
|
178
|
+
raise EntityError(f"Task {kind} is not created")
|
|
179
|
+
self._tasks[kind] = build_entity_from_dict(resp[0])
|
|
180
|
+
return self._tasks[kind]
|
|
181
|
+
|
|
182
|
+
def update_task(self, kind: str, **kwargs) -> Task:
|
|
183
|
+
"""
|
|
184
|
+
Update task.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
kind : str
|
|
189
|
+
Kind the object.
|
|
190
|
+
**kwargs : dict
|
|
191
|
+
Keyword arguments.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
Task
|
|
196
|
+
Task.
|
|
197
|
+
"""
|
|
198
|
+
self._raise_if_not_exists(kind)
|
|
199
|
+
|
|
200
|
+
if kwargs is None:
|
|
201
|
+
kwargs = {}
|
|
202
|
+
|
|
203
|
+
# Update kwargs
|
|
204
|
+
kwargs["project"] = self.project
|
|
205
|
+
kwargs["kind"] = kind
|
|
206
|
+
kwargs["function"] = self._get_executable_string()
|
|
207
|
+
kwargs["uuid"] = self._tasks[kind].id
|
|
208
|
+
|
|
209
|
+
# Update task
|
|
210
|
+
task = build_entity_from_params(**kwargs)
|
|
211
|
+
task.save(update=True)
|
|
212
|
+
self._tasks[kind] = task
|
|
213
|
+
return task
|
|
214
|
+
|
|
215
|
+
def delete_task(self, kind: str, cascade: bool = True) -> dict:
|
|
216
|
+
"""
|
|
217
|
+
Delete task.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
kind : str
|
|
222
|
+
Kind the object.
|
|
223
|
+
cascade : bool
|
|
224
|
+
Flag to determine if cascade deletion must be performed.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
dict
|
|
229
|
+
Response from backend.
|
|
230
|
+
"""
|
|
231
|
+
resp = delete_task(self._tasks[kind].key, cascade=cascade)
|
|
232
|
+
self._tasks.pop(kind, None)
|
|
233
|
+
return resp
|
|
234
|
+
|
|
235
|
+
def _get_task_from_backend(self, kind: str) -> list:
|
|
236
|
+
"""
|
|
237
|
+
List tasks from backend filtered by function and kind.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
kind : str
|
|
242
|
+
Kind the object.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
list
|
|
247
|
+
Response from backend.
|
|
248
|
+
"""
|
|
249
|
+
params = {"function": self._get_executable_string(), "kind": kind}
|
|
250
|
+
return list_entity_api_ctx(self.project, EntityTypes.TASK.value, params=params)
|
|
251
|
+
|
|
252
|
+
def _check_task_in_backend(self, kind: str) -> bool:
|
|
253
|
+
"""
|
|
254
|
+
Check if task exists in backend.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
kind : str
|
|
259
|
+
Kind the object.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
bool
|
|
264
|
+
Flag to determine if task exists in backend.
|
|
265
|
+
"""
|
|
266
|
+
resp = self._get_task_from_backend(kind)
|
|
267
|
+
if not resp:
|
|
268
|
+
return False
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
def _raise_if_exists(self, kind: str) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Raise error if task is created.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
kind : str
|
|
278
|
+
Kind the object.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
None
|
|
283
|
+
|
|
284
|
+
Raises
|
|
285
|
+
------
|
|
286
|
+
EntityError
|
|
287
|
+
If task already exists.
|
|
288
|
+
"""
|
|
289
|
+
if self._check_task_in_backend(kind):
|
|
290
|
+
raise EntityError(f"Task '{kind}' already exists.")
|
|
291
|
+
|
|
292
|
+
def _raise_if_not_exists(self, kind: str) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Raise error if task is not created.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
kind : str
|
|
299
|
+
Kind the object.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
None
|
|
304
|
+
|
|
305
|
+
Raises
|
|
306
|
+
------
|
|
307
|
+
EntityError
|
|
308
|
+
If task does not exist.
|
|
309
|
+
"""
|
|
310
|
+
if self._tasks.get(kind) is None:
|
|
311
|
+
raise EntityError(f"Task '{kind}' does not exist.")
|
|
312
|
+
|
|
313
|
+
##############################
|
|
314
|
+
# Runs
|
|
315
|
+
##############################
|
|
316
|
+
|
|
317
|
+
def get_run(
|
|
318
|
+
self,
|
|
319
|
+
identifier: str,
|
|
320
|
+
**kwargs,
|
|
321
|
+
) -> Run:
|
|
322
|
+
"""
|
|
323
|
+
Get object from backend.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
identifier : str
|
|
328
|
+
Entity key (store://...) or entity ID.
|
|
329
|
+
**kwargs : dict
|
|
330
|
+
Parameters to pass to the API call.
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
Run
|
|
335
|
+
Object instance.
|
|
336
|
+
|
|
337
|
+
Examples
|
|
338
|
+
--------
|
|
339
|
+
Using entity key:
|
|
340
|
+
>>> obj = executable.get_run("store://my-secret-key")
|
|
341
|
+
|
|
342
|
+
Using entity ID:
|
|
343
|
+
>>> obj = executable.get_run("123")
|
|
344
|
+
"""
|
|
345
|
+
obj = get_run(
|
|
346
|
+
identifier=identifier,
|
|
347
|
+
project=self.project,
|
|
348
|
+
**kwargs,
|
|
349
|
+
)
|
|
350
|
+
self.refresh()
|
|
351
|
+
return obj
|
|
352
|
+
|
|
353
|
+
def list_runs(self, **kwargs) -> list[Run]:
|
|
354
|
+
"""
|
|
355
|
+
List all runs from backend.
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
**kwargs : dict
|
|
360
|
+
Parameters to pass to the API call.
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
list[Run]
|
|
365
|
+
List of object instances.
|
|
366
|
+
|
|
367
|
+
Examples
|
|
368
|
+
--------
|
|
369
|
+
>>> objs = executable.list_runs()
|
|
370
|
+
"""
|
|
371
|
+
if kwargs is None:
|
|
372
|
+
kwargs = {}
|
|
373
|
+
kwargs["params"] = {"function": self._get_executable_string()}
|
|
374
|
+
return list_runs(self.project, **kwargs)
|
|
375
|
+
|
|
376
|
+
def delete_run(
|
|
377
|
+
self,
|
|
378
|
+
identifier: str,
|
|
379
|
+
**kwargs,
|
|
380
|
+
) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Delete run from backend.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
identifier : str
|
|
387
|
+
Entity key (store://...) or entity ID.
|
|
388
|
+
**kwargs : dict
|
|
389
|
+
Parameters to pass to the API call.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
dict
|
|
394
|
+
Response from backend.
|
|
395
|
+
|
|
396
|
+
Examples
|
|
397
|
+
--------
|
|
398
|
+
>>> executable.delete_run("store://my-run-key")
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
delete_run(
|
|
402
|
+
identifier=identifier,
|
|
403
|
+
project=self.project,
|
|
404
|
+
**kwargs,
|
|
405
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from digitalhub.entities._base.api_utils import files_info_get_api, files_info_put_api
|
|
7
|
+
from digitalhub.entities._base.versioned.entity import VersionedEntity
|
|
8
|
+
from digitalhub.stores.api import get_store
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from digitalhub.entities._base.entity.metadata import Metadata
|
|
12
|
+
from digitalhub.entities._base.material.spec import MaterialSpec
|
|
13
|
+
from digitalhub.entities._base.material.status import MaterialStatus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MaterialEntity(VersionedEntity):
|
|
17
|
+
"""
|
|
18
|
+
A class representing an entity that can be materialized
|
|
19
|
+
as file(s).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
project: str,
|
|
25
|
+
name: str,
|
|
26
|
+
uuid: str,
|
|
27
|
+
kind: str,
|
|
28
|
+
metadata: Metadata,
|
|
29
|
+
spec: MaterialSpec,
|
|
30
|
+
status: MaterialStatus,
|
|
31
|
+
user: str | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(project, name, uuid, kind, metadata, spec, status, user)
|
|
34
|
+
self.spec: MaterialSpec
|
|
35
|
+
self.status: MaterialStatus
|
|
36
|
+
|
|
37
|
+
def save(self, update: bool = False) -> MaterialEntity:
|
|
38
|
+
"""
|
|
39
|
+
Save entity into backend.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
update : bool
|
|
44
|
+
Flag to indicate update.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
MaterialEntity
|
|
49
|
+
Entity saved.
|
|
50
|
+
"""
|
|
51
|
+
obj = self.to_dict()
|
|
52
|
+
|
|
53
|
+
files = None
|
|
54
|
+
if self.status.files is not None and len(self.status.files) > 5 and not self._context().local:
|
|
55
|
+
files = obj["status"].pop("files")
|
|
56
|
+
|
|
57
|
+
if not update:
|
|
58
|
+
new_obj: MaterialEntity = self._save(obj)
|
|
59
|
+
else:
|
|
60
|
+
new_obj: MaterialEntity = self._update(obj)
|
|
61
|
+
|
|
62
|
+
# Handle files info
|
|
63
|
+
if files is not None:
|
|
64
|
+
files_info_put_api(self.project, self.ENTITY_TYPE, self.id, files)
|
|
65
|
+
self.status.add_files_info(files)
|
|
66
|
+
|
|
67
|
+
return new_obj
|
|
68
|
+
|
|
69
|
+
##############################
|
|
70
|
+
# I/O Methods
|
|
71
|
+
##############################
|
|
72
|
+
|
|
73
|
+
def as_file(self) -> list[str]:
|
|
74
|
+
"""
|
|
75
|
+
Get object as file(s). It downloads the object from storage in
|
|
76
|
+
a temporary folder and returns the list of downloaded files paths.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
list[str]
|
|
81
|
+
List of file paths.
|
|
82
|
+
"""
|
|
83
|
+
store = get_store(self.spec.path)
|
|
84
|
+
paths = self.status.get_file_paths()
|
|
85
|
+
dst = store._build_temp()
|
|
86
|
+
return store.download(self.spec.path, dst=dst, src=paths)
|
|
87
|
+
|
|
88
|
+
def download(
|
|
89
|
+
self,
|
|
90
|
+
destination: str | None = None,
|
|
91
|
+
overwrite: bool = False,
|
|
92
|
+
) -> str:
|
|
93
|
+
"""
|
|
94
|
+
This function downloads one or more file from storage on local
|
|
95
|
+
machine.
|
|
96
|
+
It looks inside the object's status for the file(s) path under
|
|
97
|
+
files attribute. If it does not find it, it will try to download
|
|
98
|
+
what it can from spec.path.
|
|
99
|
+
The files are downloaded into a destination folder. If the destination
|
|
100
|
+
is not specified, it will set by default under the context path
|
|
101
|
+
as '<ctx-root>/<entity_type>', e.g. './dataitem'.
|
|
102
|
+
The overwrite flag allows to overwrite existing file(s) in the
|
|
103
|
+
destination folder.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
destination : str
|
|
108
|
+
Destination path as filename or directory.
|
|
109
|
+
overwrite : bool
|
|
110
|
+
Specify if overwrite existing file(s). If file(s) already
|
|
111
|
+
exist and overwrite is False, it will raise an error.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
str
|
|
116
|
+
Downloaded path.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
Download a single file:
|
|
121
|
+
|
|
122
|
+
>>> entity.status.files[0]
|
|
123
|
+
{
|
|
124
|
+
"path ": "data.csv",
|
|
125
|
+
"name ": "data.csv",
|
|
126
|
+
"content_type ": "text/csv;charset=utf-8 "
|
|
127
|
+
}
|
|
128
|
+
>>> path = entity.download()
|
|
129
|
+
>>> print(path)
|
|
130
|
+
dataitem/data.csv
|
|
131
|
+
"""
|
|
132
|
+
store = get_store(self.spec.path)
|
|
133
|
+
paths = self.status.get_file_paths()
|
|
134
|
+
|
|
135
|
+
if destination is None:
|
|
136
|
+
dst = self._context().root / self.ENTITY_TYPE
|
|
137
|
+
else:
|
|
138
|
+
dst = Path(destination)
|
|
139
|
+
|
|
140
|
+
return store.download(self.spec.path, dst=dst, src=paths, overwrite=overwrite)
|
|
141
|
+
|
|
142
|
+
def upload(self, source: str | list[str]) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Upload object from given local path to spec path destination.
|
|
145
|
+
Source must be a local path. If the path is a folder, destination
|
|
146
|
+
path (object's spec path) must be a folder or a partition ending
|
|
147
|
+
with '/' (s3).
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
source : str | list[str]
|
|
152
|
+
Local filepath, directory or list of filepaths.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
None
|
|
157
|
+
|
|
158
|
+
Examples
|
|
159
|
+
--------
|
|
160
|
+
Upload a single file:
|
|
161
|
+
|
|
162
|
+
>>> entity.spec.path = 's3://bucket/data.csv'
|
|
163
|
+
>>> entity.upload('./data.csv')
|
|
164
|
+
|
|
165
|
+
Upload a folder:
|
|
166
|
+
>>> entity.spec.path = 's3://bucket/data/'
|
|
167
|
+
>>> entity.upload('./data')
|
|
168
|
+
"""
|
|
169
|
+
# Get store and upload object
|
|
170
|
+
store = get_store(self.spec.path)
|
|
171
|
+
paths = store.upload(source, self.spec.path)
|
|
172
|
+
|
|
173
|
+
# Update files info
|
|
174
|
+
files_info = store.get_file_info(paths)
|
|
175
|
+
self._update_files_info(files_info)
|
|
176
|
+
|
|
177
|
+
##############################
|
|
178
|
+
# Private Helpers
|
|
179
|
+
##############################
|
|
180
|
+
|
|
181
|
+
def _update_files_info(self, files_info: list[dict] | None = None) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Update files info.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
files_info : list[dict] | None
|
|
188
|
+
Files info.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
None
|
|
193
|
+
"""
|
|
194
|
+
if files_info is None:
|
|
195
|
+
return
|
|
196
|
+
self.refresh()
|
|
197
|
+
self.status.add_files_info(files_info)
|
|
198
|
+
self.save(update=True)
|
|
199
|
+
|
|
200
|
+
def _get_files_info(self) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Get files info from backend.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
None
|
|
207
|
+
"""
|
|
208
|
+
if not self._context().local and not self.status.files:
|
|
209
|
+
files = files_info_get_api(
|
|
210
|
+
project=self.project,
|
|
211
|
+
entity_type=self.ENTITY_TYPE,
|
|
212
|
+
entity_id=self.id,
|
|
213
|
+
)
|
|
214
|
+
self.status.add_files_info(files)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from digitalhub.entities._base.entity.spec import Spec, SpecValidator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MaterialSpec(Spec):
|
|
7
|
+
"""
|
|
8
|
+
Material specifications.class.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, path: str, **kwargs) -> None:
|
|
12
|
+
super().__init__(**kwargs)
|
|
13
|
+
self.path = path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MaterialValidator(SpecValidator):
|
|
17
|
+
"""
|
|
18
|
+
Material parameters class.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
path: str
|
|
22
|
+
"""Target path to file(s)"""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from digitalhub.entities._base.entity.status import Status
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MaterialStatus(Status):
|
|
7
|
+
"""
|
|
8
|
+
Material Status class.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
state: str,
|
|
14
|
+
message: str | None = None,
|
|
15
|
+
files: list[dict] | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(state, message)
|
|
18
|
+
if files is None:
|
|
19
|
+
files = []
|
|
20
|
+
self.files = files
|
|
21
|
+
|
|
22
|
+
def add_files_info(self, files: list[dict]) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Add a file to the status.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
files : list[dict]
|
|
29
|
+
Files to add.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
None
|
|
34
|
+
"""
|
|
35
|
+
path_list = self.get_file_paths()
|
|
36
|
+
for f in files:
|
|
37
|
+
if f.get("path") not in path_list:
|
|
38
|
+
self.files.append(f)
|
|
39
|
+
|
|
40
|
+
def get_file_paths(self) -> list[str]:
|
|
41
|
+
"""
|
|
42
|
+
Get the paths of the files in the status.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
list[str]
|
|
47
|
+
Paths of the files in the status.
|
|
48
|
+
"""
|
|
49
|
+
return [f.get("path") for f in self.files]
|
|
File without changes
|