digitalhub 0.9.2__py3-none-any.whl → 0.10.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 +2 -3
- digitalhub/client/_base/api_builder.py +1 -1
- digitalhub/client/_base/client.py +25 -2
- digitalhub/client/_base/params_builder.py +16 -0
- digitalhub/client/dhcore/api_builder.py +9 -3
- digitalhub/client/dhcore/client.py +30 -398
- digitalhub/client/dhcore/configurator.py +361 -0
- digitalhub/client/dhcore/error_parser.py +107 -0
- digitalhub/client/dhcore/models.py +13 -23
- digitalhub/client/dhcore/params_builder.py +178 -0
- digitalhub/client/dhcore/utils.py +4 -44
- digitalhub/client/local/api_builder.py +13 -18
- digitalhub/client/local/client.py +18 -2
- digitalhub/client/local/enums.py +11 -0
- digitalhub/client/local/params_builder.py +116 -0
- digitalhub/configurator/api.py +31 -0
- digitalhub/configurator/configurator.py +195 -0
- digitalhub/configurator/credentials_store.py +65 -0
- digitalhub/configurator/ini_module.py +74 -0
- digitalhub/entities/_base/_base/entity.py +2 -2
- digitalhub/entities/_base/context/entity.py +4 -4
- digitalhub/entities/_base/entity/builder.py +5 -5
- digitalhub/entities/_base/executable/entity.py +2 -2
- digitalhub/entities/_base/material/entity.py +12 -12
- digitalhub/entities/_base/material/status.py +1 -1
- digitalhub/entities/_base/material/utils.py +2 -2
- digitalhub/entities/_base/unversioned/entity.py +2 -2
- digitalhub/entities/_base/versioned/entity.py +2 -2
- digitalhub/entities/_commons/enums.py +2 -0
- digitalhub/entities/_commons/metrics.py +164 -0
- digitalhub/entities/_commons/types.py +5 -0
- digitalhub/entities/_commons/utils.py +2 -2
- digitalhub/entities/_processors/base.py +527 -0
- digitalhub/entities/{_operations/processor.py → _processors/context.py} +212 -837
- digitalhub/entities/_processors/utils.py +158 -0
- digitalhub/entities/artifact/artifact/spec.py +3 -1
- digitalhub/entities/artifact/crud.py +13 -12
- digitalhub/entities/artifact/utils.py +1 -1
- digitalhub/entities/builders.py +6 -18
- digitalhub/entities/dataitem/_base/entity.py +0 -41
- digitalhub/entities/dataitem/crud.py +27 -15
- digitalhub/entities/dataitem/table/entity.py +49 -35
- digitalhub/entities/dataitem/table/models.py +4 -3
- digitalhub/{utils/data_utils.py → entities/dataitem/table/utils.py} +46 -54
- digitalhub/entities/dataitem/utils.py +58 -10
- digitalhub/entities/function/crud.py +9 -9
- digitalhub/entities/model/_base/entity.py +120 -0
- digitalhub/entities/model/_base/spec.py +6 -17
- digitalhub/entities/model/_base/status.py +10 -0
- digitalhub/entities/model/crud.py +13 -12
- digitalhub/entities/model/huggingface/spec.py +9 -4
- digitalhub/entities/model/mlflow/models.py +2 -2
- digitalhub/entities/model/mlflow/spec.py +7 -7
- digitalhub/entities/model/mlflow/utils.py +44 -5
- digitalhub/entities/project/_base/entity.py +317 -9
- digitalhub/entities/project/_base/spec.py +8 -6
- digitalhub/entities/project/crud.py +12 -11
- digitalhub/entities/run/_base/entity.py +103 -6
- digitalhub/entities/run/_base/spec.py +4 -2
- digitalhub/entities/run/_base/status.py +12 -0
- digitalhub/entities/run/crud.py +8 -8
- digitalhub/entities/secret/_base/entity.py +3 -3
- digitalhub/entities/secret/_base/spec.py +4 -2
- digitalhub/entities/secret/crud.py +11 -9
- digitalhub/entities/task/_base/entity.py +4 -4
- digitalhub/entities/task/_base/models.py +51 -40
- digitalhub/entities/task/_base/spec.py +2 -0
- digitalhub/entities/task/_base/utils.py +2 -2
- digitalhub/entities/task/crud.py +12 -8
- digitalhub/entities/workflow/crud.py +9 -9
- digitalhub/factory/utils.py +9 -9
- digitalhub/readers/{_base → data/_base}/builder.py +1 -1
- digitalhub/readers/{_base → data/_base}/reader.py +16 -4
- digitalhub/readers/{api.py → data/api.py} +2 -2
- digitalhub/readers/{factory.py → data/factory.py} +3 -3
- digitalhub/readers/{pandas → data/pandas}/builder.py +2 -2
- digitalhub/readers/{pandas → data/pandas}/reader.py +110 -30
- digitalhub/readers/query/__init__.py +0 -0
- digitalhub/stores/_base/store.py +59 -69
- digitalhub/stores/api.py +8 -33
- digitalhub/stores/builder.py +44 -161
- digitalhub/stores/local/store.py +106 -89
- digitalhub/stores/remote/store.py +86 -11
- digitalhub/stores/s3/configurator.py +108 -0
- digitalhub/stores/s3/enums.py +17 -0
- digitalhub/stores/s3/models.py +21 -0
- digitalhub/stores/s3/store.py +154 -70
- digitalhub/{utils/s3_utils.py → stores/s3/utils.py} +7 -3
- digitalhub/stores/sql/configurator.py +88 -0
- digitalhub/stores/sql/enums.py +16 -0
- digitalhub/stores/sql/models.py +24 -0
- digitalhub/stores/sql/store.py +106 -85
- digitalhub/{readers/_commons → utils}/enums.py +5 -1
- digitalhub/utils/exceptions.py +6 -0
- digitalhub/utils/file_utils.py +8 -7
- digitalhub/utils/generic_utils.py +28 -15
- digitalhub/utils/git_utils.py +16 -9
- digitalhub/utils/types.py +5 -0
- digitalhub/utils/uri_utils.py +2 -2
- {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/METADATA +25 -31
- {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/RECORD +108 -99
- {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/WHEEL +1 -2
- digitalhub/client/dhcore/env.py +0 -23
- digitalhub/entities/_base/project/entity.py +0 -341
- digitalhub-0.9.2.dist-info/top_level.txt +0 -2
- test/local/CRUD/test_artifacts.py +0 -96
- test/local/CRUD/test_dataitems.py +0 -96
- test/local/CRUD/test_models.py +0 -95
- test/local/imports/test_imports.py +0 -66
- test/local/instances/test_validate.py +0 -55
- test/test_crud_functions.py +0 -109
- test/test_crud_runs.py +0 -86
- test/test_crud_tasks.py +0 -81
- test/testkfp.py +0 -37
- test/testkfp_pipeline.py +0 -22
- /digitalhub/{entities/_base/project → configurator}/__init__.py +0 -0
- /digitalhub/entities/{_operations → _processors}/__init__.py +0 -0
- /digitalhub/readers/{_base → data}/__init__.py +0 -0
- /digitalhub/readers/{_commons → data/_base}/__init__.py +0 -0
- /digitalhub/readers/{pandas → data/pandas}/__init__.py +0 -0
- {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info/licenses}/LICENSE.txt +0 -0
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
6
|
from digitalhub.client.api import get_client
|
|
7
|
-
from digitalhub.client.dhcore.enums import
|
|
7
|
+
from digitalhub.client.dhcore.enums import DhcoreEnvVar
|
|
8
8
|
|
|
9
9
|
if typing.TYPE_CHECKING:
|
|
10
10
|
from digitalhub.client.dhcore.client import ClientDHCore
|
|
@@ -21,8 +21,7 @@ def set_dhcore_env(
|
|
|
21
21
|
"""
|
|
22
22
|
Function to set environment variables for DHCore config.
|
|
23
23
|
Note that if the environment variable is already set, it
|
|
24
|
-
will be overwritten.
|
|
25
|
-
configuration.
|
|
24
|
+
will be overwritten.
|
|
26
25
|
|
|
27
26
|
Parameters
|
|
28
27
|
----------
|
|
@@ -56,47 +55,8 @@ def set_dhcore_env(
|
|
|
56
55
|
if client_id is not None:
|
|
57
56
|
os.environ[DhcoreEnvVar.CLIENT_ID.value] = client_id
|
|
58
57
|
|
|
59
|
-
update_client_from_env()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def update_client_from_env() -> None:
|
|
63
|
-
"""
|
|
64
|
-
Function to update client from environment variables.
|
|
65
|
-
|
|
66
|
-
Returns
|
|
67
|
-
-------
|
|
68
|
-
None
|
|
69
|
-
"""
|
|
70
58
|
client: ClientDHCore = get_client(local=False)
|
|
71
|
-
|
|
72
|
-
# Update endpoint
|
|
73
|
-
endpoint = os.getenv(DhcoreEnvVar.ENDPOINT.value)
|
|
74
|
-
if endpoint is not None:
|
|
75
|
-
client._endpoint_core = endpoint
|
|
76
|
-
|
|
77
|
-
# Update auth
|
|
78
|
-
|
|
79
|
-
# If token is set, it will override the other auth options
|
|
80
|
-
access_token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
|
|
81
|
-
refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
|
|
82
|
-
client_id = os.getenv(DhcoreEnvVar.CLIENT_ID.value)
|
|
83
|
-
|
|
84
|
-
if access_token is not None:
|
|
85
|
-
if refresh_token is not None:
|
|
86
|
-
client._refresh_token = refresh_token
|
|
87
|
-
if client_id is not None:
|
|
88
|
-
client._client_id = client_id
|
|
89
|
-
client._access_token = access_token
|
|
90
|
-
client._auth_type = AuthType.OAUTH2.value
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
# Otherwise, if user and password are set, basic auth will be used
|
|
94
|
-
username = os.getenv(DhcoreEnvVar.USER.value)
|
|
95
|
-
password = os.getenv(DhcoreEnvVar.PASSWORD.value)
|
|
96
|
-
if username is not None and password is not None:
|
|
97
|
-
client._user = username
|
|
98
|
-
client._password = password
|
|
99
|
-
client._auth_type = AuthType.BASIC.value
|
|
59
|
+
client._configurator.configure()
|
|
100
60
|
|
|
101
61
|
|
|
102
62
|
def refresh_token() -> None:
|
|
@@ -108,4 +68,4 @@ def refresh_token() -> None:
|
|
|
108
68
|
None
|
|
109
69
|
"""
|
|
110
70
|
client: ClientDHCore = get_client(local=False)
|
|
111
|
-
client.
|
|
71
|
+
client._configurator.get_new_access_token()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from digitalhub.client._base.api_builder import ClientApiBuilder
|
|
4
|
+
from digitalhub.client.local.enums import LocalClientVar
|
|
4
5
|
from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
|
|
5
6
|
from digitalhub.utils.exceptions import BackendError
|
|
6
7
|
|
|
@@ -63,15 +64,16 @@ class ClientLocalApiBuilder(ClientApiBuilder):
|
|
|
63
64
|
BackendOperations.DELETE.value,
|
|
64
65
|
):
|
|
65
66
|
return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}"
|
|
66
|
-
|
|
67
|
-
raise BackendError("Share API not implemented for Local.")
|
|
68
|
-
raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
|
|
67
|
+
raise BackendError(f"API for operation '{operation}' for entity type '{entity_type}' not implemented in Local.")
|
|
69
68
|
|
|
70
69
|
def build_api_context(self, operation: str, **kwargs) -> str:
|
|
71
70
|
"""
|
|
72
71
|
Build the context API for the client.
|
|
73
72
|
"""
|
|
74
|
-
|
|
73
|
+
try:
|
|
74
|
+
entity_type = kwargs["entity_type"] + "s"
|
|
75
|
+
except KeyError:
|
|
76
|
+
pass
|
|
75
77
|
project = kwargs["project"]
|
|
76
78
|
if operation in (
|
|
77
79
|
BackendOperations.CREATE.value,
|
|
@@ -84,17 +86,10 @@ class ClientLocalApiBuilder(ClientApiBuilder):
|
|
|
84
86
|
BackendOperations.DELETE.value,
|
|
85
87
|
):
|
|
86
88
|
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}"
|
|
87
|
-
elif operation
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
raise BackendError("Secret API (read/set value) not implemented for Local.")
|
|
95
|
-
elif operation == BackendOperations.FILES.value:
|
|
96
|
-
raise BackendError("Files API not implemented for Local.")
|
|
97
|
-
elif operation == BackendOperations.SEARCH.value:
|
|
98
|
-
raise BackendError("Search API not implemented for Local.")
|
|
99
|
-
|
|
100
|
-
raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
|
|
89
|
+
elif operation in (
|
|
90
|
+
BackendOperations.LOGS.value,
|
|
91
|
+
BackendOperations.FILES.value,
|
|
92
|
+
BackendOperations.METRICS.value,
|
|
93
|
+
):
|
|
94
|
+
return LocalClientVar.EMPTY.value
|
|
95
|
+
raise BackendError(f"API for operation '{operation}' for entity type '{entity_type}' not implemented in Local.")
|
|
@@ -2,10 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from digitalhub.client._base.client import Client
|
|
7
8
|
from digitalhub.client.local.api_builder import ClientLocalApiBuilder
|
|
9
|
+
from digitalhub.client.local.enums import LocalClientVar
|
|
8
10
|
from digitalhub.client.local.key_builder import ClientLocalKeyBuilder
|
|
11
|
+
from digitalhub.client.local.params_builder import ClientLocalParametersBuilder
|
|
9
12
|
from digitalhub.utils.exceptions import BackendError
|
|
10
13
|
|
|
11
14
|
|
|
@@ -26,13 +29,14 @@ class ClientLocal(Client):
|
|
|
26
29
|
super().__init__()
|
|
27
30
|
self._api_builder = ClientLocalApiBuilder()
|
|
28
31
|
self._key_builder = ClientLocalKeyBuilder()
|
|
32
|
+
self._params_builder = ClientLocalParametersBuilder()
|
|
29
33
|
self._db: dict[str, dict[str, dict]] = {}
|
|
30
34
|
|
|
31
35
|
##############################
|
|
32
36
|
# CRUD
|
|
33
37
|
##############################
|
|
34
38
|
|
|
35
|
-
def create_object(self, api: str, obj:
|
|
39
|
+
def create_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
36
40
|
"""
|
|
37
41
|
Create an object in local.
|
|
38
42
|
|
|
@@ -48,6 +52,11 @@ class ClientLocal(Client):
|
|
|
48
52
|
dict
|
|
49
53
|
The created object.
|
|
50
54
|
"""
|
|
55
|
+
if api == LocalClientVar.EMPTY.value:
|
|
56
|
+
return {}
|
|
57
|
+
if not isinstance(obj, dict):
|
|
58
|
+
raise TypeError("Object must be a dictionary")
|
|
59
|
+
|
|
51
60
|
entity_type, _, context_api = self._parse_api(api)
|
|
52
61
|
try:
|
|
53
62
|
# Check if entity_type is valid
|
|
@@ -114,6 +123,8 @@ class ClientLocal(Client):
|
|
|
114
123
|
dict
|
|
115
124
|
The read object.
|
|
116
125
|
"""
|
|
126
|
+
if api == LocalClientVar.EMPTY.value:
|
|
127
|
+
return {}
|
|
117
128
|
entity_type, entity_id, context_api = self._parse_api(api)
|
|
118
129
|
if entity_id is None:
|
|
119
130
|
msg = self._format_msg(4)
|
|
@@ -155,7 +166,7 @@ class ClientLocal(Client):
|
|
|
155
166
|
msg = self._format_msg(3, entity_type=entity_type, entity_id=entity_id)
|
|
156
167
|
raise BackendError(msg)
|
|
157
168
|
|
|
158
|
-
def update_object(self, api: str, obj:
|
|
169
|
+
def update_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
159
170
|
"""
|
|
160
171
|
Update an object in local.
|
|
161
172
|
|
|
@@ -171,6 +182,11 @@ class ClientLocal(Client):
|
|
|
171
182
|
dict
|
|
172
183
|
The updated object.
|
|
173
184
|
"""
|
|
185
|
+
if api == LocalClientVar.EMPTY.value:
|
|
186
|
+
return {}
|
|
187
|
+
if not isinstance(obj, dict):
|
|
188
|
+
raise TypeError("Object must be a dictionary")
|
|
189
|
+
|
|
174
190
|
entity_type, entity_id, context_api = self._parse_api(api)
|
|
175
191
|
try:
|
|
176
192
|
# API example
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from digitalhub.client._base.params_builder import ClientParametersBuilder
|
|
4
|
+
from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ClientLocalParametersBuilder(ClientParametersBuilder):
|
|
8
|
+
"""
|
|
9
|
+
This class is used to build the parameters for the Local client calls.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
|
|
13
|
+
"""
|
|
14
|
+
Build the parameters for the client call.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
category : str
|
|
19
|
+
API category.
|
|
20
|
+
operation : str
|
|
21
|
+
API operation.
|
|
22
|
+
**kwargs : dict
|
|
23
|
+
Parameters to build.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
dict
|
|
28
|
+
Parameters formatted.
|
|
29
|
+
"""
|
|
30
|
+
if category == ApiCategories.BASE.value:
|
|
31
|
+
return self.build_parameters_base(operation, **kwargs)
|
|
32
|
+
return self.build_parameters_context(operation, **kwargs)
|
|
33
|
+
|
|
34
|
+
def build_parameters_base(self, operation: str, **kwargs) -> dict:
|
|
35
|
+
"""
|
|
36
|
+
Build the base parameters for the client call.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
operation : str
|
|
41
|
+
API operation.
|
|
42
|
+
**kwargs : dict
|
|
43
|
+
Parameters to build.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
dict
|
|
48
|
+
Parameters formatted.
|
|
49
|
+
"""
|
|
50
|
+
kwargs = self._set_params(**kwargs)
|
|
51
|
+
if operation == BackendOperations.DELETE.value:
|
|
52
|
+
if (cascade := kwargs.pop("cascade", None)) is not None:
|
|
53
|
+
kwargs["params"]["cascade"] = str(cascade).lower()
|
|
54
|
+
return kwargs
|
|
55
|
+
|
|
56
|
+
def build_parameters_context(self, operation: str, **kwargs) -> dict:
|
|
57
|
+
"""
|
|
58
|
+
Build the context parameters for the client call.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
operation : str
|
|
63
|
+
API operation.
|
|
64
|
+
**kwargs : dict
|
|
65
|
+
Parameters to build.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
dict
|
|
70
|
+
Parameters formatted.
|
|
71
|
+
"""
|
|
72
|
+
kwargs = self._set_params(**kwargs)
|
|
73
|
+
|
|
74
|
+
# Handle read
|
|
75
|
+
if operation == BackendOperations.READ_ALL_VERSIONS.value:
|
|
76
|
+
kwargs["params"]["versions"] = "all"
|
|
77
|
+
kwargs["params"]["name"] = kwargs.pop("entity_name")
|
|
78
|
+
# Handle delete
|
|
79
|
+
elif operation == BackendOperations.DELETE.value:
|
|
80
|
+
# Handle cascade
|
|
81
|
+
if cascade := kwargs.pop("cascade", None) is not None:
|
|
82
|
+
kwargs["params"]["cascade"] = str(cascade).lower()
|
|
83
|
+
|
|
84
|
+
# Handle delete all versions
|
|
85
|
+
entity_id = kwargs.pop("entity_id")
|
|
86
|
+
entity_name = kwargs.pop("entity_name")
|
|
87
|
+
if not kwargs.pop("delete_all_versions", False):
|
|
88
|
+
if entity_id is None:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"If `delete_all_versions` is False, `entity_id` must be provided,"
|
|
91
|
+
" either as an argument or in key `identifier`.",
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
kwargs["params"]["name"] = entity_name
|
|
95
|
+
return kwargs
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _set_params(**kwargs) -> dict:
|
|
99
|
+
"""
|
|
100
|
+
Format params parameter.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
**kwargs : dict
|
|
105
|
+
Keyword arguments.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
dict
|
|
110
|
+
Parameters with initialized params.
|
|
111
|
+
"""
|
|
112
|
+
if not kwargs:
|
|
113
|
+
kwargs = {}
|
|
114
|
+
if "params" not in kwargs:
|
|
115
|
+
kwargs["params"] = {}
|
|
116
|
+
return kwargs
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from digitalhub.configurator.configurator import configurator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def set_current_env(environment: str) -> None:
|
|
7
|
+
"""
|
|
8
|
+
Set the current credentials set.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
environment : str
|
|
13
|
+
Credentials set name.
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
None
|
|
18
|
+
"""
|
|
19
|
+
configurator.set_current_env(environment)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_current_env() -> str:
|
|
23
|
+
"""
|
|
24
|
+
Get the current credentials set.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
str
|
|
29
|
+
Credentials set name.
|
|
30
|
+
"""
|
|
31
|
+
return configurator.get_current_env()
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from digitalhub.configurator.credentials_store import CredentialsStore
|
|
6
|
+
from digitalhub.configurator.ini_module import load_from_config, write_config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EnvConfigurator:
|
|
10
|
+
"""
|
|
11
|
+
Configurator object used to configure clients and store credentials.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
# Store
|
|
16
|
+
self._creds_store = CredentialsStore()
|
|
17
|
+
|
|
18
|
+
# Current credentials set (__default by default)
|
|
19
|
+
self._environment = "__default"
|
|
20
|
+
|
|
21
|
+
##############################
|
|
22
|
+
# Public methods
|
|
23
|
+
##############################
|
|
24
|
+
|
|
25
|
+
def set_current_env(self, creds_set: str) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Set the current credentials set.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
creds_set : str
|
|
32
|
+
Credentials set name.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
None
|
|
37
|
+
"""
|
|
38
|
+
self._environment = creds_set
|
|
39
|
+
|
|
40
|
+
def get_current_env(self) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Get the current credentials set.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
str
|
|
47
|
+
Credentials set name.
|
|
48
|
+
"""
|
|
49
|
+
return self._environment
|
|
50
|
+
|
|
51
|
+
##############################
|
|
52
|
+
# Private methods
|
|
53
|
+
##############################
|
|
54
|
+
|
|
55
|
+
def load_var(self, var_name: str) -> str | None:
|
|
56
|
+
"""
|
|
57
|
+
Get env variable from credentials store, env or file (in order).
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
var_name : str
|
|
62
|
+
Environment variable name.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
str | None
|
|
67
|
+
Environment variable value.
|
|
68
|
+
"""
|
|
69
|
+
var = self.get_credential(var_name)
|
|
70
|
+
if var is None:
|
|
71
|
+
var = self.load_from_env(var_name)
|
|
72
|
+
if var is None:
|
|
73
|
+
var = self.load_from_config(var_name)
|
|
74
|
+
return var
|
|
75
|
+
|
|
76
|
+
def load_from_env(self, var: str) -> str | None:
|
|
77
|
+
"""
|
|
78
|
+
Load variable from env.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
var : str
|
|
83
|
+
Environment variable name.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
str | None
|
|
88
|
+
Environment variable value.
|
|
89
|
+
"""
|
|
90
|
+
env_var = os.getenv(var)
|
|
91
|
+
if env_var != "":
|
|
92
|
+
return env_var
|
|
93
|
+
|
|
94
|
+
def load_from_config(self, var: str) -> str | None:
|
|
95
|
+
"""
|
|
96
|
+
Load variable from config file.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
var : str
|
|
101
|
+
Environment variable name.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
str | None
|
|
106
|
+
Environment variable value.
|
|
107
|
+
"""
|
|
108
|
+
return load_from_config(var)
|
|
109
|
+
|
|
110
|
+
def write_env(self, key_to_include: list[str] | None = None) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Write the env variables to the .dhcore file.
|
|
113
|
+
It will overwrite any existing env variables.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
key_to_include : list[str]
|
|
118
|
+
List of keys to include.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
None
|
|
123
|
+
"""
|
|
124
|
+
if key_to_include is None:
|
|
125
|
+
key_to_include = []
|
|
126
|
+
creds = self.get_credential_list(key_to_include)
|
|
127
|
+
write_config(creds, self._environment)
|
|
128
|
+
|
|
129
|
+
##############################
|
|
130
|
+
# Credentials store methods
|
|
131
|
+
##############################
|
|
132
|
+
|
|
133
|
+
def set_credential(self, key: str, value: str) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Register a credential value.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
key : str
|
|
140
|
+
The key.
|
|
141
|
+
value : str
|
|
142
|
+
The value.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
None
|
|
147
|
+
"""
|
|
148
|
+
self._creds_store.set(self._environment, key, value)
|
|
149
|
+
|
|
150
|
+
def get_credential(self, key: str) -> dict:
|
|
151
|
+
"""
|
|
152
|
+
Get single credential value from key.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
key : str
|
|
157
|
+
The key.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
dict
|
|
162
|
+
The credential.
|
|
163
|
+
"""
|
|
164
|
+
return self._creds_store.get(self._environment, key)
|
|
165
|
+
|
|
166
|
+
def get_all_credentials(self) -> dict:
|
|
167
|
+
"""
|
|
168
|
+
Get all the credentials from the current credentials set.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
dict
|
|
173
|
+
The credentials.
|
|
174
|
+
"""
|
|
175
|
+
return self._creds_store.get_all(self._environment)
|
|
176
|
+
|
|
177
|
+
def get_credential_list(self, keys: list[str]) -> list[str]:
|
|
178
|
+
"""
|
|
179
|
+
Get the list of credentials.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
keys : list[str]
|
|
184
|
+
The list of keys.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
list[str]
|
|
189
|
+
The list of credentials.
|
|
190
|
+
"""
|
|
191
|
+
return {k: v for k, v in self.get_all_credentials().items() if k in keys}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Define global configurator
|
|
195
|
+
configurator = EnvConfigurator()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CredentialsStore:
|
|
5
|
+
"""
|
|
6
|
+
Credentials store to store and retrieve credentials.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self._credentials: dict[str, dict] = {}
|
|
11
|
+
|
|
12
|
+
def set(self, profile: str, key: str, value: str) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Set credentials.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
profile : str
|
|
19
|
+
The profile of the credentials.
|
|
20
|
+
key : str
|
|
21
|
+
The key of the credentials.
|
|
22
|
+
value : str
|
|
23
|
+
The value of the credentials.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
None
|
|
28
|
+
"""
|
|
29
|
+
if profile not in self._credentials:
|
|
30
|
+
self._credentials[profile] = {}
|
|
31
|
+
self._credentials[profile][key] = value
|
|
32
|
+
|
|
33
|
+
def get(self, profile: str, key: str) -> str | None:
|
|
34
|
+
"""
|
|
35
|
+
Get credentials.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
profile : str
|
|
40
|
+
The profile of the credentials.
|
|
41
|
+
key : str
|
|
42
|
+
The key of the credentials.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
str | None
|
|
47
|
+
The value of the credentials.
|
|
48
|
+
"""
|
|
49
|
+
return self._credentials.get(profile, {}).get(key)
|
|
50
|
+
|
|
51
|
+
def get_all(self, profile: str) -> dict[str, str]:
|
|
52
|
+
"""
|
|
53
|
+
Get all credentials.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
profile : str
|
|
58
|
+
The profile of the credentials.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
dict[str, str]
|
|
63
|
+
The credentials.
|
|
64
|
+
"""
|
|
65
|
+
return self._credentials.get(profile, {})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from configparser import ConfigParser
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from digitalhub.utils.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
# File where to write DHCORE_ACCESS_TOKEN and DHCORE_REFRESH_TOKEN
|
|
9
|
+
# It's used because we inject the variables in jupyter env,
|
|
10
|
+
# but refresh token is only available once. Is it's used, we cannot
|
|
11
|
+
# overwrite it with coder, so we need to store the new one in a file,
|
|
12
|
+
# preserved for jupyter restart
|
|
13
|
+
ENV_FILE = Path.home() / ".dhcore.ini"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_from_config(var: str) -> str | None:
|
|
17
|
+
"""
|
|
18
|
+
Load variable from config file.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
profile : str
|
|
23
|
+
Credentials set name.
|
|
24
|
+
var : str
|
|
25
|
+
Environment variable name.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
str | None
|
|
30
|
+
Environment variable value.
|
|
31
|
+
"""
|
|
32
|
+
cfg = ConfigParser()
|
|
33
|
+
cfg.read(ENV_FILE)
|
|
34
|
+
try:
|
|
35
|
+
profile = cfg["DEFAULT"]["current_environment"]
|
|
36
|
+
return cfg[profile].get(var)
|
|
37
|
+
except KeyError:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def write_config(creds: dict, environment: str) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Write the env variables to the .dhcore.ini file.
|
|
44
|
+
It will overwrite any existing env variables.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
creds : dict
|
|
49
|
+
Credentials.
|
|
50
|
+
environment : str
|
|
51
|
+
Credentials set name.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
None
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
cfg = ConfigParser()
|
|
59
|
+
cfg.read(ENV_FILE)
|
|
60
|
+
|
|
61
|
+
sections = cfg.sections()
|
|
62
|
+
if environment not in sections:
|
|
63
|
+
cfg.add_section(environment)
|
|
64
|
+
|
|
65
|
+
cfg["DEFAULT"]["current_environment"] = environment
|
|
66
|
+
for k, v in creds.items():
|
|
67
|
+
cfg[environment][k] = v
|
|
68
|
+
|
|
69
|
+
ENV_FILE.touch(exist_ok=True)
|
|
70
|
+
with open(ENV_FILE, "w") as inifile:
|
|
71
|
+
cfg.write(inifile)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise ClientError(f"Failed to write env file: {e}")
|