digitalhub 0.8.0b15__py3-none-any.whl → 0.9.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 +19 -2
- digitalhub/client/_base/api_builder.py +16 -0
- digitalhub/client/_base/client.py +67 -0
- digitalhub/client/_base/key_builder.py +52 -0
- digitalhub/client/api.py +2 -38
- digitalhub/client/dhcore/api_builder.py +100 -0
- digitalhub/client/dhcore/client.py +100 -48
- digitalhub/client/dhcore/enums.py +27 -0
- digitalhub/client/dhcore/env.py +4 -2
- digitalhub/client/dhcore/key_builder.py +58 -0
- digitalhub/client/dhcore/utils.py +17 -17
- digitalhub/client/local/api_builder.py +100 -0
- digitalhub/client/local/client.py +22 -0
- digitalhub/client/local/key_builder.py +58 -0
- digitalhub/context/api.py +3 -38
- digitalhub/context/builder.py +10 -23
- digitalhub/context/context.py +20 -92
- digitalhub/entities/_base/context/entity.py +30 -22
- digitalhub/entities/_base/entity/_constructors/metadata.py +12 -1
- digitalhub/entities/_base/entity/_constructors/name.py +1 -1
- digitalhub/entities/_base/entity/_constructors/spec.py +1 -1
- digitalhub/entities/_base/entity/_constructors/status.py +3 -2
- digitalhub/entities/_base/entity/_constructors/uuid.py +1 -1
- digitalhub/entities/_base/entity/builder.py +6 -1
- digitalhub/entities/_base/entity/entity.py +32 -10
- digitalhub/entities/_base/entity/metadata.py +22 -0
- digitalhub/entities/_base/entity/spec.py +7 -2
- digitalhub/entities/_base/executable/entity.py +8 -8
- digitalhub/entities/_base/material/entity.py +49 -17
- digitalhub/entities/_base/material/status.py +0 -31
- digitalhub/entities/_base/material/utils.py +106 -0
- digitalhub/entities/_base/project/entity.py +341 -0
- digitalhub/entities/_base/unversioned/entity.py +3 -24
- digitalhub/entities/_base/versioned/entity.py +2 -26
- digitalhub/entities/_commons/enums.py +103 -0
- digitalhub/entities/_commons/utils.py +83 -0
- digitalhub/entities/_operations/processor.py +1873 -0
- digitalhub/entities/artifact/_base/builder.py +1 -1
- digitalhub/entities/artifact/_base/entity.py +1 -1
- digitalhub/entities/artifact/artifact/builder.py +2 -1
- digitalhub/entities/artifact/crud.py +46 -29
- digitalhub/entities/artifact/utils.py +62 -0
- digitalhub/entities/dataitem/_base/builder.py +1 -1
- digitalhub/entities/dataitem/_base/entity.py +6 -6
- digitalhub/entities/dataitem/crud.py +50 -66
- digitalhub/entities/dataitem/dataitem/builder.py +2 -1
- digitalhub/entities/dataitem/iceberg/builder.py +2 -1
- digitalhub/entities/dataitem/table/builder.py +2 -1
- digitalhub/entities/dataitem/table/entity.py +5 -10
- digitalhub/entities/dataitem/table/models.py +4 -5
- digitalhub/entities/dataitem/utils.py +137 -0
- digitalhub/entities/function/_base/builder.py +1 -1
- digitalhub/entities/function/_base/entity.py +6 -2
- digitalhub/entities/function/crud.py +36 -17
- digitalhub/entities/model/_base/builder.py +1 -1
- digitalhub/entities/model/_base/entity.py +1 -1
- digitalhub/entities/model/crud.py +46 -29
- digitalhub/entities/model/huggingface/builder.py +2 -1
- digitalhub/entities/model/huggingface/spec.py +4 -2
- digitalhub/entities/model/mlflow/builder.py +2 -1
- digitalhub/entities/model/mlflow/models.py +17 -9
- digitalhub/entities/model/mlflow/spec.py +6 -1
- digitalhub/entities/model/mlflow/utils.py +4 -2
- digitalhub/entities/model/model/builder.py +2 -1
- digitalhub/entities/model/sklearn/builder.py +2 -1
- digitalhub/entities/model/utils.py +62 -0
- digitalhub/entities/project/_base/builder.py +2 -2
- digitalhub/entities/project/_base/entity.py +82 -272
- digitalhub/entities/project/crud.py +110 -89
- digitalhub/entities/project/utils.py +35 -0
- digitalhub/entities/run/_base/builder.py +3 -1
- digitalhub/entities/run/_base/entity.py +52 -54
- digitalhub/entities/run/_base/spec.py +15 -7
- digitalhub/entities/run/crud.py +35 -17
- digitalhub/entities/secret/_base/builder.py +2 -2
- digitalhub/entities/secret/_base/entity.py +4 -10
- digitalhub/entities/secret/crud.py +36 -21
- digitalhub/entities/task/_base/builder.py +14 -14
- digitalhub/entities/task/_base/entity.py +21 -14
- digitalhub/entities/task/_base/models.py +35 -6
- digitalhub/entities/task/_base/spec.py +50 -13
- digitalhub/entities/task/_base/utils.py +18 -0
- digitalhub/entities/task/crud.py +35 -15
- digitalhub/entities/workflow/_base/builder.py +1 -1
- digitalhub/entities/workflow/_base/entity.py +22 -6
- digitalhub/entities/workflow/crud.py +36 -17
- digitalhub/factory/utils.py +1 -1
- digitalhub/readers/_base/reader.py +2 -2
- digitalhub/readers/_commons/enums.py +13 -0
- digitalhub/readers/api.py +3 -2
- digitalhub/readers/factory.py +12 -6
- digitalhub/readers/pandas/reader.py +20 -8
- digitalhub/runtimes/_base.py +0 -7
- digitalhub/runtimes/enums.py +12 -0
- digitalhub/stores/_base/store.py +59 -11
- digitalhub/stores/builder.py +5 -5
- digitalhub/stores/local/store.py +43 -4
- digitalhub/stores/remote/store.py +31 -5
- digitalhub/stores/s3/store.py +136 -57
- digitalhub/stores/sql/store.py +122 -47
- digitalhub/utils/exceptions.py +6 -0
- digitalhub/utils/file_utils.py +60 -2
- digitalhub/utils/generic_utils.py +45 -4
- digitalhub/utils/io_utils.py +18 -0
- digitalhub/utils/s3_utils.py +17 -0
- digitalhub/utils/uri_utils.py +153 -15
- {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/LICENSE.txt +1 -1
- {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/METADATA +11 -11
- {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/RECORD +117 -115
- {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/WHEEL +1 -1
- test/local/instances/test_validate.py +55 -0
- test/testkfp.py +4 -1
- digitalhub/datastores/_base/datastore.py +0 -85
- digitalhub/datastores/api.py +0 -37
- digitalhub/datastores/builder.py +0 -110
- digitalhub/datastores/local/datastore.py +0 -50
- digitalhub/datastores/remote/__init__.py +0 -0
- digitalhub/datastores/remote/datastore.py +0 -31
- digitalhub/datastores/s3/__init__.py +0 -0
- digitalhub/datastores/s3/datastore.py +0 -46
- digitalhub/datastores/sql/__init__.py +0 -0
- digitalhub/datastores/sql/datastore.py +0 -68
- digitalhub/entities/_base/api_utils.py +0 -620
- digitalhub/entities/_base/crud.py +0 -468
- digitalhub/entities/function/_base/models.py +0 -118
- digitalhub/entities/utils/__init__.py +0 -0
- digitalhub/entities/utils/api.py +0 -346
- digitalhub/entities/utils/entity_types.py +0 -19
- digitalhub/entities/utils/state.py +0 -31
- digitalhub/entities/utils/utils.py +0 -202
- /digitalhub/{context → entities/_base/project}/__init__.py +0 -0
- /digitalhub/{datastores → entities/_commons}/__init__.py +0 -0
- /digitalhub/{datastores/_base → entities/_operations}/__init__.py +0 -0
- /digitalhub/{datastores/local → readers/_commons}/__init__.py +0 -0
- {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/top_level.txt +0 -0
digitalhub/__init__.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
__version__ = "0.9.0b0"
|
|
1
2
|
from digitalhub.entities.artifact.crud import (
|
|
2
3
|
delete_artifact,
|
|
3
4
|
get_artifact,
|
|
4
5
|
get_artifact_versions,
|
|
5
6
|
import_artifact,
|
|
6
7
|
list_artifacts,
|
|
8
|
+
load_artifact,
|
|
7
9
|
log_artifact,
|
|
8
10
|
new_artifact,
|
|
9
11
|
update_artifact,
|
|
@@ -14,6 +16,7 @@ from digitalhub.entities.dataitem.crud import (
|
|
|
14
16
|
get_dataitem_versions,
|
|
15
17
|
import_dataitem,
|
|
16
18
|
list_dataitems,
|
|
19
|
+
load_dataitem,
|
|
17
20
|
log_dataitem,
|
|
18
21
|
new_dataitem,
|
|
19
22
|
update_dataitem,
|
|
@@ -24,6 +27,7 @@ from digitalhub.entities.function.crud import (
|
|
|
24
27
|
get_function_versions,
|
|
25
28
|
import_function,
|
|
26
29
|
list_functions,
|
|
30
|
+
load_function,
|
|
27
31
|
new_function,
|
|
28
32
|
update_function,
|
|
29
33
|
)
|
|
@@ -33,6 +37,7 @@ from digitalhub.entities.model.crud import (
|
|
|
33
37
|
get_model_versions,
|
|
34
38
|
import_model,
|
|
35
39
|
list_models,
|
|
40
|
+
load_model,
|
|
36
41
|
log_model,
|
|
37
42
|
new_model,
|
|
38
43
|
update_model,
|
|
@@ -42,27 +47,39 @@ from digitalhub.entities.project.crud import (
|
|
|
42
47
|
get_or_create_project,
|
|
43
48
|
get_project,
|
|
44
49
|
import_project,
|
|
50
|
+
list_projects,
|
|
45
51
|
load_project,
|
|
46
52
|
new_project,
|
|
53
|
+
search_entity,
|
|
47
54
|
update_project,
|
|
48
55
|
)
|
|
49
|
-
from digitalhub.entities.run.crud import delete_run, get_run, import_run, list_runs, new_run, update_run
|
|
56
|
+
from digitalhub.entities.run.crud import delete_run, get_run, import_run, list_runs, load_run, new_run, update_run
|
|
50
57
|
from digitalhub.entities.secret.crud import (
|
|
51
58
|
delete_secret,
|
|
52
59
|
get_secret,
|
|
53
60
|
get_secret_versions,
|
|
54
61
|
import_secret,
|
|
55
62
|
list_secrets,
|
|
63
|
+
load_secret,
|
|
56
64
|
new_secret,
|
|
57
65
|
update_secret,
|
|
58
66
|
)
|
|
59
|
-
from digitalhub.entities.task.crud import
|
|
67
|
+
from digitalhub.entities.task.crud import (
|
|
68
|
+
delete_task,
|
|
69
|
+
get_task,
|
|
70
|
+
import_task,
|
|
71
|
+
list_tasks,
|
|
72
|
+
load_task,
|
|
73
|
+
new_task,
|
|
74
|
+
update_task,
|
|
75
|
+
)
|
|
60
76
|
from digitalhub.entities.workflow.crud import (
|
|
61
77
|
delete_workflow,
|
|
62
78
|
get_workflow,
|
|
63
79
|
get_workflow_versions,
|
|
64
80
|
import_workflow,
|
|
65
81
|
list_workflows,
|
|
82
|
+
load_workflow,
|
|
66
83
|
new_workflow,
|
|
67
84
|
update_workflow,
|
|
68
85
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClientApiBuilder:
|
|
7
|
+
"""
|
|
8
|
+
This class is used to build the API for the client.
|
|
9
|
+
Depending on the client, the API is built differently.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def build_api(self) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Build the API for the client.
|
|
16
|
+
"""
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import typing
|
|
3
4
|
from abc import abstractmethod
|
|
4
5
|
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from digitalhub.client._base.api_builder import ClientApiBuilder
|
|
8
|
+
from digitalhub.client._base.key_builder import ClientKeyBuilder
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
class Client:
|
|
7
12
|
"""
|
|
@@ -12,6 +17,14 @@ class Client:
|
|
|
12
17
|
listing of objects and comes into two subclasses: Local and DHCore.
|
|
13
18
|
"""
|
|
14
19
|
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self._api_builder: ClientApiBuilder = None
|
|
22
|
+
self._key_builder: ClientKeyBuilder = None
|
|
23
|
+
|
|
24
|
+
##############################
|
|
25
|
+
# CRUD methods
|
|
26
|
+
##############################
|
|
27
|
+
|
|
15
28
|
@abstractmethod
|
|
16
29
|
def create_object(self, api: str, obj: dict, **kwargs) -> dict:
|
|
17
30
|
"""
|
|
@@ -48,6 +61,60 @@ class Client:
|
|
|
48
61
|
Read first object method.
|
|
49
62
|
"""
|
|
50
63
|
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def search_objects(self, api: str, **kwargs) -> dict:
|
|
66
|
+
"""
|
|
67
|
+
Search objects method.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
##############################
|
|
71
|
+
# Build methods
|
|
72
|
+
##############################
|
|
73
|
+
|
|
74
|
+
def build_api(self, category: str, operation: str, **kwargs) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Build the API for the client.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
category : str
|
|
81
|
+
API category.
|
|
82
|
+
operation : str
|
|
83
|
+
API operation.
|
|
84
|
+
**kwargs : dict
|
|
85
|
+
Additional parameters.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
str
|
|
90
|
+
API formatted.
|
|
91
|
+
"""
|
|
92
|
+
return self._api_builder.build_api(category, operation, **kwargs)
|
|
93
|
+
|
|
94
|
+
def build_key(self, category: str, *args, **kwargs) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Build the key for the client.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
category : str
|
|
101
|
+
Key category.
|
|
102
|
+
*args : tuple
|
|
103
|
+
Additional arguments.
|
|
104
|
+
**kwargs : dict
|
|
105
|
+
Additional parameters.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
str
|
|
110
|
+
Key formatted.
|
|
111
|
+
"""
|
|
112
|
+
return self._key_builder.build_key(category, *args, **kwargs)
|
|
113
|
+
|
|
114
|
+
##############################
|
|
115
|
+
# Interface methods
|
|
116
|
+
##############################
|
|
117
|
+
|
|
51
118
|
@staticmethod
|
|
52
119
|
@abstractmethod
|
|
53
120
|
def is_local() -> bool:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
from digitalhub.entities._commons.enums import ApiCategories
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ClientKeyBuilder:
|
|
9
|
+
"""
|
|
10
|
+
Class that build the key of entities.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def build_key(self, category: str, *args, **kwargs) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Build key.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
category : str
|
|
20
|
+
Key category.
|
|
21
|
+
*args : tuple
|
|
22
|
+
Positional arguments.
|
|
23
|
+
**kwargs : dict
|
|
24
|
+
Keyword arguments.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
str
|
|
29
|
+
Key.
|
|
30
|
+
"""
|
|
31
|
+
if category == ApiCategories.BASE.value:
|
|
32
|
+
return self.base_entity_key(*args, **kwargs)
|
|
33
|
+
return self.context_entity_key(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def base_entity_key(self, entity_id: str) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Build for base entity key.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def context_entity_key(
|
|
43
|
+
self,
|
|
44
|
+
project: str,
|
|
45
|
+
entity_type: str,
|
|
46
|
+
entity_kind: str,
|
|
47
|
+
entity_name: str,
|
|
48
|
+
entity_id: str | None = None,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Build for context entity key.
|
|
52
|
+
"""
|
digitalhub/client/api.py
CHANGED
|
@@ -8,26 +8,7 @@ if typing.TYPE_CHECKING:
|
|
|
8
8
|
from digitalhub.client._base.client import Client
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def
|
|
12
|
-
"""
|
|
13
|
-
Check if client exists.
|
|
14
|
-
|
|
15
|
-
Parameters
|
|
16
|
-
----------
|
|
17
|
-
local : bool
|
|
18
|
-
Check client existence by local.
|
|
19
|
-
|
|
20
|
-
Returns
|
|
21
|
-
-------
|
|
22
|
-
bool
|
|
23
|
-
True if client exists, False otherwise.
|
|
24
|
-
"""
|
|
25
|
-
if local:
|
|
26
|
-
return client_builder._local is not None
|
|
27
|
-
return client_builder._dhcore is not None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def build_client(local: bool = False, config: dict | None = None) -> None:
|
|
11
|
+
def get_client(local: bool = False, config: dict | None = None) -> Client:
|
|
31
12
|
"""
|
|
32
13
|
Wrapper around ClientBuilder.build.
|
|
33
14
|
|
|
@@ -43,21 +24,4 @@ def build_client(local: bool = False, config: dict | None = None) -> None:
|
|
|
43
24
|
Client
|
|
44
25
|
The client instance.
|
|
45
26
|
"""
|
|
46
|
-
client_builder.build(local, config)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def get_client(local: bool = False) -> Client:
|
|
50
|
-
"""
|
|
51
|
-
Wrapper around ClientBuilder.build.
|
|
52
|
-
|
|
53
|
-
Parameters
|
|
54
|
-
----------
|
|
55
|
-
local : bool
|
|
56
|
-
Whether to create a local client or not.
|
|
57
|
-
|
|
58
|
-
Returns
|
|
59
|
-
-------
|
|
60
|
-
Client
|
|
61
|
-
The client instance.
|
|
62
|
-
"""
|
|
63
|
-
return client_builder.build(local)
|
|
27
|
+
return client_builder.build(local, config)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from digitalhub.client._base.api_builder import ClientApiBuilder
|
|
4
|
+
from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
|
|
5
|
+
from digitalhub.utils.exceptions import BackendError
|
|
6
|
+
|
|
7
|
+
API_BASE = "/api/v1"
|
|
8
|
+
API_CONTEXT = f"{API_BASE}/-"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClientDHCoreApiBuilder(ClientApiBuilder):
|
|
12
|
+
"""
|
|
13
|
+
This class is used to build the API for the DHCore client.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def build_api(self, category: str, operation: str, **kwargs) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Build the API for the client.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
category : str
|
|
23
|
+
API category.
|
|
24
|
+
operation : str
|
|
25
|
+
API operation.
|
|
26
|
+
**kwargs : dict
|
|
27
|
+
Additional parameters.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
str
|
|
32
|
+
API formatted.
|
|
33
|
+
"""
|
|
34
|
+
if category == ApiCategories.BASE.value:
|
|
35
|
+
return self.build_api_base(operation, **kwargs)
|
|
36
|
+
return self.build_api_context(operation, **kwargs)
|
|
37
|
+
|
|
38
|
+
def build_api_base(self, operation: str, **kwargs) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Build the base API for the client.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
operation : str
|
|
45
|
+
API operation.
|
|
46
|
+
**kwargs : dict
|
|
47
|
+
Additional parameters.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
str
|
|
52
|
+
API formatted.
|
|
53
|
+
"""
|
|
54
|
+
entity_type = kwargs["entity_type"] + "s"
|
|
55
|
+
if operation in (
|
|
56
|
+
BackendOperations.CREATE.value,
|
|
57
|
+
BackendOperations.LIST.value,
|
|
58
|
+
):
|
|
59
|
+
return f"{API_BASE}/{entity_type}"
|
|
60
|
+
elif operation in (
|
|
61
|
+
BackendOperations.READ.value,
|
|
62
|
+
BackendOperations.UPDATE.value,
|
|
63
|
+
BackendOperations.DELETE.value,
|
|
64
|
+
):
|
|
65
|
+
return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}"
|
|
66
|
+
elif operation == BackendOperations.SHARE.value:
|
|
67
|
+
return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}/share"
|
|
68
|
+
raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
|
|
69
|
+
|
|
70
|
+
def build_api_context(self, operation: str, **kwargs) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Build the context API for the client.
|
|
73
|
+
"""
|
|
74
|
+
entity_type = kwargs["entity_type"] + "s"
|
|
75
|
+
project = kwargs["project"]
|
|
76
|
+
if operation in (
|
|
77
|
+
BackendOperations.CREATE.value,
|
|
78
|
+
BackendOperations.LIST.value,
|
|
79
|
+
):
|
|
80
|
+
return f"{API_CONTEXT}/{project}/{entity_type}"
|
|
81
|
+
elif operation in (
|
|
82
|
+
BackendOperations.READ.value,
|
|
83
|
+
BackendOperations.UPDATE.value,
|
|
84
|
+
BackendOperations.DELETE.value,
|
|
85
|
+
):
|
|
86
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}"
|
|
87
|
+
elif operation == BackendOperations.LOGS.value:
|
|
88
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/logs"
|
|
89
|
+
elif operation == BackendOperations.STOP.value:
|
|
90
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/stop"
|
|
91
|
+
elif operation == BackendOperations.RESUME.value:
|
|
92
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/resume"
|
|
93
|
+
elif operation == BackendOperations.DATA.value:
|
|
94
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/data"
|
|
95
|
+
elif operation == BackendOperations.FILES.value:
|
|
96
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/files/info"
|
|
97
|
+
elif operation == BackendOperations.SEARCH.value:
|
|
98
|
+
return f"{API_CONTEXT}/{project}/solr/search/item"
|
|
99
|
+
|
|
100
|
+
raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
|
|
@@ -4,14 +4,17 @@ import datetime
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import typing
|
|
7
|
-
from
|
|
7
|
+
from warnings import warn
|
|
8
8
|
|
|
9
|
-
from dotenv import
|
|
9
|
+
from dotenv import get_key, set_key
|
|
10
10
|
from requests import request
|
|
11
11
|
from requests.exceptions import HTTPError, JSONDecodeError, RequestException
|
|
12
12
|
|
|
13
13
|
from digitalhub.client._base.client import Client
|
|
14
|
-
from digitalhub.client.dhcore.
|
|
14
|
+
from digitalhub.client.dhcore.api_builder import ClientDHCoreApiBuilder
|
|
15
|
+
from digitalhub.client.dhcore.enums import AuthType, DhcoreEnvVar
|
|
16
|
+
from digitalhub.client.dhcore.env import ENV_FILE, FALLBACK_USER, LIB_VERSION, MAX_API_LEVEL, MIN_API_LEVEL
|
|
17
|
+
from digitalhub.client.dhcore.key_builder import ClientDHCoreKeyBuilder
|
|
15
18
|
from digitalhub.client.dhcore.models import BasicAuth, OAuth2TokenAuth
|
|
16
19
|
from digitalhub.utils.exceptions import (
|
|
17
20
|
BackendError,
|
|
@@ -22,6 +25,7 @@ from digitalhub.utils.exceptions import (
|
|
|
22
25
|
MissingSpecError,
|
|
23
26
|
UnauthorizedError,
|
|
24
27
|
)
|
|
28
|
+
from digitalhub.utils.uri_utils import has_remote_scheme
|
|
25
29
|
|
|
26
30
|
if typing.TYPE_CHECKING:
|
|
27
31
|
from requests import Response
|
|
@@ -46,6 +50,12 @@ class ClientDHCore(Client):
|
|
|
46
50
|
def __init__(self, config: dict | None = None) -> None:
|
|
47
51
|
super().__init__()
|
|
48
52
|
|
|
53
|
+
# API builder
|
|
54
|
+
self._api_builder = ClientDHCoreApiBuilder()
|
|
55
|
+
|
|
56
|
+
# Key builder
|
|
57
|
+
self._key_builder = ClientDHCoreKeyBuilder()
|
|
58
|
+
|
|
49
59
|
# Endpoints
|
|
50
60
|
self._endpoint_core: str | None = None
|
|
51
61
|
self._endpoint_issuer: str | None = None
|
|
@@ -213,6 +223,56 @@ class ClientDHCore(Client):
|
|
|
213
223
|
except IndexError:
|
|
214
224
|
raise BackendError("No object found.")
|
|
215
225
|
|
|
226
|
+
def search_objects(self, api: str, **kwargs) -> list[dict]:
|
|
227
|
+
"""
|
|
228
|
+
Search objects from DHCore.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
api : str
|
|
233
|
+
Search API.
|
|
234
|
+
**kwargs : dict
|
|
235
|
+
Keyword arguments to pass to the request.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
list[dict]
|
|
240
|
+
Response objects.
|
|
241
|
+
"""
|
|
242
|
+
if kwargs is None:
|
|
243
|
+
kwargs = {}
|
|
244
|
+
|
|
245
|
+
if "params" not in kwargs:
|
|
246
|
+
kwargs["params"] = {}
|
|
247
|
+
|
|
248
|
+
start_page = 0
|
|
249
|
+
if "page" not in kwargs["params"]:
|
|
250
|
+
kwargs["params"]["page"] = start_page
|
|
251
|
+
|
|
252
|
+
if "size" not in kwargs["params"]:
|
|
253
|
+
kwargs["params"]["size"] = 10
|
|
254
|
+
|
|
255
|
+
# Add sorting
|
|
256
|
+
if "sort" not in kwargs["params"]:
|
|
257
|
+
kwargs["params"]["sort"] = "metadata.updated,DESC"
|
|
258
|
+
|
|
259
|
+
objects_with_highlights = []
|
|
260
|
+
while True:
|
|
261
|
+
resp = self._prepare_call("GET", api, **kwargs)
|
|
262
|
+
contents = resp["content"]
|
|
263
|
+
total_pages = resp["totalPages"]
|
|
264
|
+
if not contents or kwargs["params"]["page"] >= total_pages:
|
|
265
|
+
break
|
|
266
|
+
objects_with_highlights.extend(contents)
|
|
267
|
+
kwargs["params"]["page"] += 1
|
|
268
|
+
|
|
269
|
+
objects = []
|
|
270
|
+
for obj in objects_with_highlights:
|
|
271
|
+
obj.pop("highlights", None)
|
|
272
|
+
objects.append(obj)
|
|
273
|
+
|
|
274
|
+
return objects
|
|
275
|
+
|
|
216
276
|
##############################
|
|
217
277
|
# Call methods
|
|
218
278
|
##############################
|
|
@@ -274,9 +334,9 @@ class ClientDHCore(Client):
|
|
|
274
334
|
dict
|
|
275
335
|
Keyword arguments with the authentication parameters.
|
|
276
336
|
"""
|
|
277
|
-
if self._auth_type ==
|
|
337
|
+
if self._auth_type == AuthType.BASIC.value:
|
|
278
338
|
kwargs["auth"] = self._user, self._password
|
|
279
|
-
elif self._auth_type ==
|
|
339
|
+
elif self._auth_type == AuthType.OAUTH2.value:
|
|
280
340
|
if "headers" not in kwargs:
|
|
281
341
|
kwargs["headers"] = {}
|
|
282
342
|
kwargs["headers"]["Authorization"] = f"Bearer {self._access_token}"
|
|
@@ -332,6 +392,8 @@ class ClientDHCore(Client):
|
|
|
332
392
|
core_api_level = int(response.headers["X-Api-Level"])
|
|
333
393
|
if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
|
|
334
394
|
raise BackendError("Backend API level not supported.")
|
|
395
|
+
if LIB_VERSION < core_api_level:
|
|
396
|
+
warn("Backend API level is higher than library version. You should consider updating the library.")
|
|
335
397
|
|
|
336
398
|
def _raise_for_error(self, response: Response) -> None:
|
|
337
399
|
"""
|
|
@@ -462,9 +524,6 @@ class ClientDHCore(Client):
|
|
|
462
524
|
-------
|
|
463
525
|
None
|
|
464
526
|
"""
|
|
465
|
-
# Load env from file
|
|
466
|
-
self._load_env()
|
|
467
|
-
|
|
468
527
|
self._get_endpoints_from_env()
|
|
469
528
|
|
|
470
529
|
if config is not None:
|
|
@@ -474,13 +533,13 @@ class ClientDHCore(Client):
|
|
|
474
533
|
self._access_token = config.access_token
|
|
475
534
|
self._refresh_token = config.refresh_token
|
|
476
535
|
self._client_id = config.client_id
|
|
477
|
-
self._auth_type =
|
|
536
|
+
self._auth_type = AuthType.OAUTH2.value
|
|
478
537
|
|
|
479
538
|
elif config.get("user") is not None and config.get("password") is not None:
|
|
480
539
|
config = BasicAuth(**config)
|
|
481
540
|
self._user = config.user
|
|
482
541
|
self._password = config.password
|
|
483
|
-
self._auth_type =
|
|
542
|
+
self._auth_type = AuthType.BASIC.value
|
|
484
543
|
|
|
485
544
|
return
|
|
486
545
|
|
|
@@ -502,12 +561,12 @@ class ClientDHCore(Client):
|
|
|
502
561
|
Exception
|
|
503
562
|
If the endpoint of DHCore is not set in the env variables.
|
|
504
563
|
"""
|
|
505
|
-
core_endpt = os.getenv(
|
|
564
|
+
core_endpt = os.getenv(DhcoreEnvVar.ENDPOINT.value)
|
|
506
565
|
if core_endpt is None:
|
|
507
566
|
raise BackendError("Endpoint not set as environment variables.")
|
|
508
567
|
self._endpoint_core = self._sanitize_endpoint(core_endpt)
|
|
509
568
|
|
|
510
|
-
issr_endpt = os.getenv(
|
|
569
|
+
issr_endpt = os.getenv(DhcoreEnvVar.ISSUER.value)
|
|
511
570
|
if issr_endpt is not None:
|
|
512
571
|
self._endpoint_issuer = self._sanitize_endpoint(issr_endpt)
|
|
513
572
|
|
|
@@ -519,9 +578,8 @@ class ClientDHCore(Client):
|
|
|
519
578
|
-------
|
|
520
579
|
None
|
|
521
580
|
"""
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
raise BackendError("Invalid endpoint scheme.")
|
|
581
|
+
if not has_remote_scheme(endpoint):
|
|
582
|
+
raise BackendError("Invalid endpoint scheme. Must start with http:// or https://.")
|
|
525
583
|
|
|
526
584
|
endpoint = endpoint.strip()
|
|
527
585
|
return endpoint.removesuffix("/")
|
|
@@ -534,19 +592,19 @@ class ClientDHCore(Client):
|
|
|
534
592
|
-------
|
|
535
593
|
None
|
|
536
594
|
"""
|
|
537
|
-
self._user = os.getenv(
|
|
538
|
-
self._refresh_token = os.getenv(
|
|
539
|
-
self._client_id = os.getenv(
|
|
595
|
+
self._user = os.getenv(DhcoreEnvVar.USER.value, FALLBACK_USER)
|
|
596
|
+
self._refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
|
|
597
|
+
self._client_id = os.getenv(DhcoreEnvVar.CLIENT_ID.value)
|
|
540
598
|
|
|
541
|
-
token = os.getenv(
|
|
599
|
+
token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
|
|
542
600
|
if token is not None and token != "":
|
|
543
|
-
self._auth_type =
|
|
601
|
+
self._auth_type = AuthType.OAUTH2.value
|
|
544
602
|
self._access_token = token
|
|
545
603
|
return
|
|
546
604
|
|
|
547
|
-
password = os.getenv(
|
|
605
|
+
password = os.getenv(DhcoreEnvVar.PASSWORD.value)
|
|
548
606
|
if self._user is not None and password is not None:
|
|
549
|
-
self._auth_type =
|
|
607
|
+
self._auth_type = AuthType.BASIC.value
|
|
550
608
|
self._password = password
|
|
551
609
|
return
|
|
552
610
|
|
|
@@ -563,11 +621,21 @@ class ClientDHCore(Client):
|
|
|
563
621
|
url = self._get_refresh_endpoint()
|
|
564
622
|
|
|
565
623
|
# Call refresh token endpoint
|
|
566
|
-
|
|
624
|
+
# Try token from env
|
|
625
|
+
refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
|
|
626
|
+
response = self._call_refresh_token_endpoint(url, refresh_token)
|
|
627
|
+
|
|
628
|
+
# Otherwise try token from file
|
|
629
|
+
if response.status_code in (400, 401, 403):
|
|
630
|
+
refresh_token = get_key(ENV_FILE, DhcoreEnvVar.REFRESH_TOKEN.value)
|
|
631
|
+
response = self._call_refresh_token_endpoint(url, refresh_token)
|
|
632
|
+
|
|
633
|
+
response.raise_for_status()
|
|
634
|
+
dict_response = response.json()
|
|
567
635
|
|
|
568
636
|
# Read new access token and refresh token
|
|
569
|
-
self._access_token =
|
|
570
|
-
self._refresh_token =
|
|
637
|
+
self._access_token = dict_response["access_token"]
|
|
638
|
+
self._refresh_token = dict_response["refresh_token"]
|
|
571
639
|
|
|
572
640
|
# Propagate new access token to env
|
|
573
641
|
self._write_env()
|
|
@@ -593,7 +661,7 @@ class ClientDHCore(Client):
|
|
|
593
661
|
self._raise_for_error(r)
|
|
594
662
|
return r.json().get("token_endpoint")
|
|
595
663
|
|
|
596
|
-
def _call_refresh_token_endpoint(self, url: str) ->
|
|
664
|
+
def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
|
|
597
665
|
"""
|
|
598
666
|
Call the refresh token endpoint.
|
|
599
667
|
|
|
@@ -601,17 +669,14 @@ class ClientDHCore(Client):
|
|
|
601
669
|
----------
|
|
602
670
|
url : str
|
|
603
671
|
Refresh token endpoint.
|
|
672
|
+
refresh_token : str
|
|
673
|
+
Refresh token.
|
|
604
674
|
|
|
605
675
|
Returns
|
|
606
676
|
-------
|
|
607
|
-
|
|
677
|
+
Response
|
|
608
678
|
Response object.
|
|
609
679
|
"""
|
|
610
|
-
# Get refersh token from .core file to avoid concurrency
|
|
611
|
-
# in a shared workspace
|
|
612
|
-
self._load_env()
|
|
613
|
-
refresh_token = os.getenv("DHCORE_REFRESH_TOKEN")
|
|
614
|
-
|
|
615
680
|
# Send request to get new access token
|
|
616
681
|
payload = {
|
|
617
682
|
"grant_type": "refresh_token",
|
|
@@ -619,20 +684,7 @@ class ClientDHCore(Client):
|
|
|
619
684
|
"refresh_token": refresh_token,
|
|
620
685
|
}
|
|
621
686
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
622
|
-
|
|
623
|
-
self._raise_for_error(r)
|
|
624
|
-
return r.json()
|
|
625
|
-
|
|
626
|
-
@staticmethod
|
|
627
|
-
def _load_env() -> None:
|
|
628
|
-
"""
|
|
629
|
-
Load the env variables from the .dhcore file.
|
|
630
|
-
|
|
631
|
-
Returns
|
|
632
|
-
-------
|
|
633
|
-
None
|
|
634
|
-
"""
|
|
635
|
-
load_dotenv(dotenv_path=ENV_FILE, override=True)
|
|
687
|
+
return request("POST", url, data=payload, headers=headers, timeout=60)
|
|
636
688
|
|
|
637
689
|
def _write_env(self) -> None:
|
|
638
690
|
"""
|
|
@@ -645,9 +697,9 @@ class ClientDHCore(Client):
|
|
|
645
697
|
"""
|
|
646
698
|
keys = {}
|
|
647
699
|
if self._access_token is not None:
|
|
648
|
-
keys[
|
|
700
|
+
keys[DhcoreEnvVar.ACCESS_TOKEN.value] = self._access_token
|
|
649
701
|
if self._refresh_token is not None:
|
|
650
|
-
keys[
|
|
702
|
+
keys[DhcoreEnvVar.REFRESH_TOKEN.value] = self._refresh_token
|
|
651
703
|
|
|
652
704
|
for k, v in keys.items():
|
|
653
705
|
set_key(dotenv_path=ENV_FILE, key_to_set=k, value_to_set=v)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DhcoreEnvVar(Enum):
|
|
7
|
+
"""
|
|
8
|
+
Environment variables.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
ENDPOINT = "DHCORE_ENDPOINT"
|
|
12
|
+
ISSUER = "DHCORE_ISSUER"
|
|
13
|
+
USER = "DHCORE_USER"
|
|
14
|
+
PASSWORD = "DHCORE_PASSWORD"
|
|
15
|
+
CLIENT_ID = "DHCORE_CLIENT_ID"
|
|
16
|
+
ACCESS_TOKEN = "DHCORE_ACCESS_TOKEN"
|
|
17
|
+
REFRESH_TOKEN = "DHCORE_REFRESH_TOKEN"
|
|
18
|
+
WORKFLOW_IMAGE = "DHCORE_WORKFLOW_IMAGE"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuthType(Enum):
|
|
22
|
+
"""
|
|
23
|
+
Authentication types.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
BASIC = "basic"
|
|
27
|
+
OAUTH2 = "oauth2"
|