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
digitalhub/__init__.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
__version__ = "0.9.0b0"
|
|
2
1
|
from digitalhub.entities.artifact.crud import (
|
|
3
2
|
delete_artifact,
|
|
4
3
|
get_artifact,
|
|
@@ -85,15 +84,15 @@ from digitalhub.entities.workflow.crud import (
|
|
|
85
84
|
)
|
|
86
85
|
|
|
87
86
|
try:
|
|
88
|
-
from digitalhub.entities.model.mlflow.utils import from_mlflow_run
|
|
87
|
+
from digitalhub.entities.model.mlflow.utils import from_mlflow_run, get_mlflow_model_metrics
|
|
89
88
|
except ImportError:
|
|
90
89
|
...
|
|
91
90
|
|
|
92
91
|
from digitalhub.client.dhcore.utils import refresh_token, set_dhcore_env
|
|
92
|
+
from digitalhub.configurator.api import get_current_env, set_current_env
|
|
93
93
|
|
|
94
94
|
# Register entities into registry
|
|
95
95
|
from digitalhub.factory.utils import register_entities, register_runtimes_entities
|
|
96
|
-
from digitalhub.stores.api import set_store
|
|
97
96
|
|
|
98
97
|
register_entities()
|
|
99
98
|
register_runtimes_entities()
|
|
@@ -2,10 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
4
|
from abc import abstractmethod
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
if typing.TYPE_CHECKING:
|
|
7
8
|
from digitalhub.client._base.api_builder import ClientApiBuilder
|
|
8
9
|
from digitalhub.client._base.key_builder import ClientKeyBuilder
|
|
10
|
+
from digitalhub.client._base.params_builder import ClientParametersBuilder
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class Client:
|
|
@@ -20,13 +22,14 @@ class Client:
|
|
|
20
22
|
def __init__(self) -> None:
|
|
21
23
|
self._api_builder: ClientApiBuilder = None
|
|
22
24
|
self._key_builder: ClientKeyBuilder = None
|
|
25
|
+
self._params_builder: ClientParametersBuilder = None
|
|
23
26
|
|
|
24
27
|
##############################
|
|
25
28
|
# CRUD methods
|
|
26
29
|
##############################
|
|
27
30
|
|
|
28
31
|
@abstractmethod
|
|
29
|
-
def create_object(self, api: str, obj:
|
|
32
|
+
def create_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
30
33
|
"""
|
|
31
34
|
Create object method.
|
|
32
35
|
"""
|
|
@@ -38,7 +41,7 @@ class Client:
|
|
|
38
41
|
"""
|
|
39
42
|
|
|
40
43
|
@abstractmethod
|
|
41
|
-
def update_object(self, api: str, obj:
|
|
44
|
+
def update_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
42
45
|
"""
|
|
43
46
|
Update object method.
|
|
44
47
|
"""
|
|
@@ -111,6 +114,26 @@ class Client:
|
|
|
111
114
|
"""
|
|
112
115
|
return self._key_builder.build_key(category, *args, **kwargs)
|
|
113
116
|
|
|
117
|
+
def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
|
|
118
|
+
"""
|
|
119
|
+
Build the parameters for the client call.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
category : str
|
|
124
|
+
API category.
|
|
125
|
+
operation : str
|
|
126
|
+
API operation.
|
|
127
|
+
**kwargs : dict
|
|
128
|
+
Parameters to build.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
dict
|
|
133
|
+
Parameters formatted.
|
|
134
|
+
"""
|
|
135
|
+
return self._params_builder.build_parameters(category, operation, **kwargs)
|
|
136
|
+
|
|
114
137
|
##############################
|
|
115
138
|
# Interface methods
|
|
116
139
|
##############################
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClientParametersBuilder:
|
|
7
|
+
"""
|
|
8
|
+
This class is used to build the parameters for the client call.
|
|
9
|
+
Depending on the client, the parameters are built differently.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
|
|
14
|
+
"""
|
|
15
|
+
Build the parameters for the client call.
|
|
16
|
+
"""
|
|
@@ -71,8 +71,11 @@ class ClientDHCoreApiBuilder(ClientApiBuilder):
|
|
|
71
71
|
"""
|
|
72
72
|
Build the context API for the client.
|
|
73
73
|
"""
|
|
74
|
-
entity_type = kwargs["entity_type"] + "s"
|
|
75
74
|
project = kwargs["project"]
|
|
75
|
+
if operation == BackendOperations.SEARCH.value:
|
|
76
|
+
return f"{API_CONTEXT}/{project}/solr/search/item"
|
|
77
|
+
|
|
78
|
+
entity_type = kwargs["entity_type"] + "s"
|
|
76
79
|
if operation in (
|
|
77
80
|
BackendOperations.CREATE.value,
|
|
78
81
|
BackendOperations.LIST.value,
|
|
@@ -94,7 +97,10 @@ class ClientDHCoreApiBuilder(ClientApiBuilder):
|
|
|
94
97
|
return f"{API_CONTEXT}/{project}/{entity_type}/data"
|
|
95
98
|
elif operation == BackendOperations.FILES.value:
|
|
96
99
|
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/files/info"
|
|
97
|
-
elif operation == BackendOperations.
|
|
98
|
-
|
|
100
|
+
elif operation == BackendOperations.METRICS.value:
|
|
101
|
+
if kwargs["metric_name"] is not None:
|
|
102
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/metrics/{kwargs['metric_name']}"
|
|
103
|
+
else:
|
|
104
|
+
return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/metrics"
|
|
99
105
|
|
|
100
106
|
raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import datetime
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
3
|
import typing
|
|
7
|
-
from
|
|
4
|
+
from typing import Any
|
|
8
5
|
|
|
9
|
-
from dotenv import get_key, set_key
|
|
10
6
|
from requests import request
|
|
11
|
-
from requests.exceptions import
|
|
7
|
+
from requests.exceptions import JSONDecodeError
|
|
12
8
|
|
|
13
9
|
from digitalhub.client._base.client import Client
|
|
14
10
|
from digitalhub.client.dhcore.api_builder import ClientDHCoreApiBuilder
|
|
15
|
-
from digitalhub.client.dhcore.
|
|
16
|
-
from digitalhub.client.dhcore.
|
|
11
|
+
from digitalhub.client.dhcore.configurator import ClientDHCoreConfigurator
|
|
12
|
+
from digitalhub.client.dhcore.error_parser import ErrorParser
|
|
17
13
|
from digitalhub.client.dhcore.key_builder import ClientDHCoreKeyBuilder
|
|
18
|
-
from digitalhub.client.dhcore.
|
|
19
|
-
from digitalhub.utils.exceptions import
|
|
20
|
-
|
|
21
|
-
BadRequestError,
|
|
22
|
-
EntityAlreadyExistsError,
|
|
23
|
-
EntityNotExistsError,
|
|
24
|
-
ForbiddenError,
|
|
25
|
-
MissingSpecError,
|
|
26
|
-
UnauthorizedError,
|
|
27
|
-
)
|
|
28
|
-
from digitalhub.utils.uri_utils import has_remote_scheme
|
|
14
|
+
from digitalhub.client.dhcore.params_builder import ClientDHCoreParametersBuilder
|
|
15
|
+
from digitalhub.utils.exceptions import BackendError
|
|
16
|
+
from digitalhub.utils.generic_utils import dump_json
|
|
29
17
|
|
|
30
18
|
if typing.TYPE_CHECKING:
|
|
31
19
|
from requests import Response
|
|
@@ -56,28 +44,21 @@ class ClientDHCore(Client):
|
|
|
56
44
|
# Key builder
|
|
57
45
|
self._key_builder = ClientDHCoreKeyBuilder()
|
|
58
46
|
|
|
59
|
-
#
|
|
60
|
-
self.
|
|
61
|
-
self._endpoint_issuer: str | None = None
|
|
47
|
+
# Parameters builder
|
|
48
|
+
self._params_builder = ClientDHCoreParametersBuilder()
|
|
62
49
|
|
|
63
|
-
#
|
|
64
|
-
self.
|
|
50
|
+
# Error parser
|
|
51
|
+
self._error_parser = ErrorParser()
|
|
65
52
|
|
|
66
|
-
#
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
# OAuth2
|
|
71
|
-
self._access_token: str | None = None
|
|
72
|
-
self._refresh_token: str | None = None
|
|
73
|
-
|
|
74
|
-
self._configure(config)
|
|
53
|
+
# Client Configurator
|
|
54
|
+
self._configurator = ClientDHCoreConfigurator()
|
|
55
|
+
self._configurator.configure(config)
|
|
75
56
|
|
|
76
57
|
##############################
|
|
77
58
|
# CRUD methods
|
|
78
59
|
##############################
|
|
79
60
|
|
|
80
|
-
def create_object(self, api: str, obj:
|
|
61
|
+
def create_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
81
62
|
"""
|
|
82
63
|
Create an object in DHCore.
|
|
83
64
|
|
|
@@ -85,7 +66,7 @@ class ClientDHCore(Client):
|
|
|
85
66
|
----------
|
|
86
67
|
api : str
|
|
87
68
|
Create API.
|
|
88
|
-
obj :
|
|
69
|
+
obj : Any
|
|
89
70
|
Object to create.
|
|
90
71
|
**kwargs : dict
|
|
91
72
|
Keyword arguments to pass to the request.
|
|
@@ -98,7 +79,7 @@ class ClientDHCore(Client):
|
|
|
98
79
|
if "headers" not in kwargs:
|
|
99
80
|
kwargs["headers"] = {}
|
|
100
81
|
kwargs["headers"]["Content-Type"] = "application/json"
|
|
101
|
-
kwargs["data"] =
|
|
82
|
+
kwargs["data"] = dump_json(obj)
|
|
102
83
|
return self._prepare_call("POST", api, **kwargs)
|
|
103
84
|
|
|
104
85
|
def read_object(self, api: str, **kwargs) -> dict:
|
|
@@ -119,7 +100,7 @@ class ClientDHCore(Client):
|
|
|
119
100
|
"""
|
|
120
101
|
return self._prepare_call("GET", api, **kwargs)
|
|
121
102
|
|
|
122
|
-
def update_object(self, api: str, obj:
|
|
103
|
+
def update_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
123
104
|
"""
|
|
124
105
|
Update an object in DHCore.
|
|
125
106
|
|
|
@@ -140,7 +121,7 @@ class ClientDHCore(Client):
|
|
|
140
121
|
if "headers" not in kwargs:
|
|
141
122
|
kwargs["headers"] = {}
|
|
142
123
|
kwargs["headers"]["Content-Type"] = "application/json"
|
|
143
|
-
kwargs["data"] =
|
|
124
|
+
kwargs["data"] = dump_json(obj)
|
|
144
125
|
return self._prepare_call("PUT", api, **kwargs)
|
|
145
126
|
|
|
146
127
|
def delete_object(self, api: str, **kwargs) -> dict:
|
|
@@ -256,7 +237,7 @@ class ClientDHCore(Client):
|
|
|
256
237
|
if "sort" not in kwargs["params"]:
|
|
257
238
|
kwargs["params"]["sort"] = "metadata.updated,DESC"
|
|
258
239
|
|
|
259
|
-
objects_with_highlights = []
|
|
240
|
+
objects_with_highlights: list[dict] = []
|
|
260
241
|
while True:
|
|
261
242
|
resp = self._prepare_call("GET", api, **kwargs)
|
|
262
243
|
contents = resp["content"]
|
|
@@ -277,25 +258,6 @@ class ClientDHCore(Client):
|
|
|
277
258
|
# Call methods
|
|
278
259
|
##############################
|
|
279
260
|
|
|
280
|
-
@staticmethod
|
|
281
|
-
def _json_serialize(obj: dict) -> dict:
|
|
282
|
-
"""
|
|
283
|
-
JSON datetime to ISO format serializer.
|
|
284
|
-
|
|
285
|
-
Parameters
|
|
286
|
-
----------
|
|
287
|
-
obj : dict
|
|
288
|
-
The object to serialize.
|
|
289
|
-
|
|
290
|
-
Returns
|
|
291
|
-
-------
|
|
292
|
-
dict
|
|
293
|
-
The serialized object.
|
|
294
|
-
"""
|
|
295
|
-
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
296
|
-
return obj.isoformat()
|
|
297
|
-
raise TypeError("Type %s not serializable" % type(obj))
|
|
298
|
-
|
|
299
261
|
def _prepare_call(self, call_type: str, api: str, **kwargs) -> dict:
|
|
300
262
|
"""
|
|
301
263
|
Prepare a call to the DHCore API.
|
|
@@ -316,32 +278,10 @@ class ClientDHCore(Client):
|
|
|
316
278
|
"""
|
|
317
279
|
if kwargs is None:
|
|
318
280
|
kwargs = {}
|
|
319
|
-
url = self.
|
|
320
|
-
kwargs = self.
|
|
281
|
+
url = self._configurator.build_url(api)
|
|
282
|
+
kwargs = self._configurator.set_request_auth(kwargs)
|
|
321
283
|
return self._make_call(call_type, url, **kwargs)
|
|
322
284
|
|
|
323
|
-
def _set_auth(self, kwargs: dict) -> dict:
|
|
324
|
-
"""
|
|
325
|
-
Set the authentication type.
|
|
326
|
-
|
|
327
|
-
Parameters
|
|
328
|
-
----------
|
|
329
|
-
kwargs : dict
|
|
330
|
-
Keyword arguments to pass to the request.
|
|
331
|
-
|
|
332
|
-
Returns
|
|
333
|
-
-------
|
|
334
|
-
dict
|
|
335
|
-
Keyword arguments with the authentication parameters.
|
|
336
|
-
"""
|
|
337
|
-
if self._auth_type == AuthType.BASIC.value:
|
|
338
|
-
kwargs["auth"] = self._user, self._password
|
|
339
|
-
elif self._auth_type == AuthType.OAUTH2.value:
|
|
340
|
-
if "headers" not in kwargs:
|
|
341
|
-
kwargs["headers"] = {}
|
|
342
|
-
kwargs["headers"]["Authorization"] = f"Bearer {self._access_token}"
|
|
343
|
-
return kwargs
|
|
344
|
-
|
|
345
285
|
def _make_call(self, call_type: str, url: str, refresh_token: bool = True, **kwargs) -> dict:
|
|
346
286
|
"""
|
|
347
287
|
Make a call to the DHCore API.
|
|
@@ -364,126 +304,20 @@ class ClientDHCore(Client):
|
|
|
364
304
|
response = request(call_type, url, timeout=60, **kwargs)
|
|
365
305
|
|
|
366
306
|
# Evaluate DHCore API version
|
|
367
|
-
self.
|
|
307
|
+
self._configurator.check_core_version(response)
|
|
368
308
|
|
|
369
309
|
# Handle token refresh
|
|
370
|
-
if response.status_code in [401] and refresh_token:
|
|
371
|
-
self.
|
|
372
|
-
kwargs = self.
|
|
310
|
+
if response.status_code in [401] and refresh_token and self._configurator.oauth2_auth():
|
|
311
|
+
self._configurator.get_new_access_token()
|
|
312
|
+
kwargs = self._configurator.set_request_auth(kwargs)
|
|
373
313
|
return self._make_call(call_type, url, refresh_token=False, **kwargs)
|
|
374
314
|
|
|
375
|
-
self.
|
|
376
|
-
return self.
|
|
377
|
-
|
|
378
|
-
def _check_core_version(self, response: Response) -> None:
|
|
379
|
-
"""
|
|
380
|
-
Raise an exception if DHCore API version is not supported.
|
|
381
|
-
|
|
382
|
-
Parameters
|
|
383
|
-
----------
|
|
384
|
-
response : Response
|
|
385
|
-
The response object.
|
|
386
|
-
|
|
387
|
-
Returns
|
|
388
|
-
-------
|
|
389
|
-
None
|
|
390
|
-
"""
|
|
391
|
-
if "X-Api-Level" in response.headers:
|
|
392
|
-
core_api_level = int(response.headers["X-Api-Level"])
|
|
393
|
-
if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
|
|
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.")
|
|
397
|
-
|
|
398
|
-
def _raise_for_error(self, response: Response) -> None:
|
|
399
|
-
"""
|
|
400
|
-
Handle DHCore API errors.
|
|
315
|
+
self._error_parser.parse(response)
|
|
316
|
+
return self._dictify_response(response)
|
|
401
317
|
|
|
402
|
-
|
|
403
|
-
----------
|
|
404
|
-
response : Response
|
|
405
|
-
The response object.
|
|
406
|
-
|
|
407
|
-
Returns
|
|
408
|
-
-------
|
|
409
|
-
None
|
|
318
|
+
def _dictify_response(self, response: Response) -> dict:
|
|
410
319
|
"""
|
|
411
|
-
|
|
412
|
-
response.raise_for_status()
|
|
413
|
-
|
|
414
|
-
# Backend errors
|
|
415
|
-
except RequestException as e:
|
|
416
|
-
# Handle timeout
|
|
417
|
-
if isinstance(e, TimeoutError):
|
|
418
|
-
msg = "Request to DHCore backend timed out."
|
|
419
|
-
raise TimeoutError(msg)
|
|
420
|
-
|
|
421
|
-
# Handle connection error
|
|
422
|
-
elif isinstance(e, ConnectionError):
|
|
423
|
-
msg = "Unable to connect to DHCore backend."
|
|
424
|
-
raise ConnectionError(msg)
|
|
425
|
-
|
|
426
|
-
# Handle HTTP errors
|
|
427
|
-
elif isinstance(e, HTTPError):
|
|
428
|
-
txt_resp = f"Response: {response.text}."
|
|
429
|
-
|
|
430
|
-
# Bad request
|
|
431
|
-
if response.status_code == 400:
|
|
432
|
-
# Missing spec in backend
|
|
433
|
-
if "missing spec" in response.text:
|
|
434
|
-
msg = f"Missing spec in backend. {txt_resp}"
|
|
435
|
-
raise MissingSpecError(msg)
|
|
436
|
-
|
|
437
|
-
# Duplicated entity
|
|
438
|
-
elif "Duplicated entity" in response.text:
|
|
439
|
-
msg = f"Entity already exists. {txt_resp}"
|
|
440
|
-
raise EntityAlreadyExistsError(msg)
|
|
441
|
-
|
|
442
|
-
# Other errors
|
|
443
|
-
else:
|
|
444
|
-
msg = f"Bad request. {txt_resp}"
|
|
445
|
-
raise BadRequestError(msg)
|
|
446
|
-
|
|
447
|
-
# Unauthorized errors
|
|
448
|
-
elif response.status_code == 401:
|
|
449
|
-
msg = f"Unauthorized. {txt_resp}"
|
|
450
|
-
raise UnauthorizedError(msg)
|
|
451
|
-
|
|
452
|
-
# Forbidden errors
|
|
453
|
-
elif response.status_code == 403:
|
|
454
|
-
msg = f"Forbidden. {txt_resp}"
|
|
455
|
-
raise ForbiddenError(msg)
|
|
456
|
-
|
|
457
|
-
# Entity not found
|
|
458
|
-
elif response.status_code == 404:
|
|
459
|
-
# Put with entity not found
|
|
460
|
-
if "No such EntityName" in response.text:
|
|
461
|
-
msg = f"Entity does not exists. {txt_resp}"
|
|
462
|
-
raise EntityNotExistsError(msg)
|
|
463
|
-
|
|
464
|
-
# Other cases
|
|
465
|
-
else:
|
|
466
|
-
msg = f"Not found. {txt_resp}"
|
|
467
|
-
raise BackendError(msg)
|
|
468
|
-
|
|
469
|
-
# Other errors
|
|
470
|
-
else:
|
|
471
|
-
msg = f"Backend error. {txt_resp}"
|
|
472
|
-
raise BackendError(msg) from e
|
|
473
|
-
|
|
474
|
-
# Other requests errors
|
|
475
|
-
else:
|
|
476
|
-
msg = f"Some error occurred. {e}"
|
|
477
|
-
raise BackendError(msg) from e
|
|
478
|
-
|
|
479
|
-
# Other generic errors
|
|
480
|
-
except Exception as e:
|
|
481
|
-
msg = f"Some error occurred: {e}"
|
|
482
|
-
raise RuntimeError(msg) from e
|
|
483
|
-
|
|
484
|
-
def _parse_response(self, response: Response) -> dict:
|
|
485
|
-
"""
|
|
486
|
-
Parse the response object.
|
|
320
|
+
Return dict from response.
|
|
487
321
|
|
|
488
322
|
Parameters
|
|
489
323
|
----------
|
|
@@ -502,208 +336,6 @@ class ClientDHCore(Client):
|
|
|
502
336
|
return {}
|
|
503
337
|
raise BackendError("Backend response could not be parsed.")
|
|
504
338
|
|
|
505
|
-
##############################
|
|
506
|
-
# Configuration methods
|
|
507
|
-
##############################
|
|
508
|
-
|
|
509
|
-
def _configure(self, config: dict | None = None) -> None:
|
|
510
|
-
"""
|
|
511
|
-
Configure the client attributes with config (given or from
|
|
512
|
-
environment).
|
|
513
|
-
Regarding authentication parameters, the config parameter
|
|
514
|
-
takes precedence over the env variables, and the token
|
|
515
|
-
over the basic auth. Furthermore, the config parameter is
|
|
516
|
-
validated against the proper pydantic model.
|
|
517
|
-
|
|
518
|
-
Parameters
|
|
519
|
-
----------
|
|
520
|
-
config : dict
|
|
521
|
-
Configuration dictionary.
|
|
522
|
-
|
|
523
|
-
Returns
|
|
524
|
-
-------
|
|
525
|
-
None
|
|
526
|
-
"""
|
|
527
|
-
self._get_endpoints_from_env()
|
|
528
|
-
|
|
529
|
-
if config is not None:
|
|
530
|
-
if config.get("access_token") is not None:
|
|
531
|
-
config = OAuth2TokenAuth(**config)
|
|
532
|
-
self._user = config.user
|
|
533
|
-
self._access_token = config.access_token
|
|
534
|
-
self._refresh_token = config.refresh_token
|
|
535
|
-
self._client_id = config.client_id
|
|
536
|
-
self._auth_type = AuthType.OAUTH2.value
|
|
537
|
-
|
|
538
|
-
elif config.get("user") is not None and config.get("password") is not None:
|
|
539
|
-
config = BasicAuth(**config)
|
|
540
|
-
self._user = config.user
|
|
541
|
-
self._password = config.password
|
|
542
|
-
self._auth_type = AuthType.BASIC.value
|
|
543
|
-
|
|
544
|
-
return
|
|
545
|
-
|
|
546
|
-
self._get_auth_from_env()
|
|
547
|
-
|
|
548
|
-
# Propagate access and refresh token to env file
|
|
549
|
-
self._write_env()
|
|
550
|
-
|
|
551
|
-
def _get_endpoints_from_env(self) -> None:
|
|
552
|
-
"""
|
|
553
|
-
Get the DHCore endpoint and token issuer endpoint from env.
|
|
554
|
-
|
|
555
|
-
Returns
|
|
556
|
-
-------
|
|
557
|
-
None
|
|
558
|
-
|
|
559
|
-
Raises
|
|
560
|
-
------
|
|
561
|
-
Exception
|
|
562
|
-
If the endpoint of DHCore is not set in the env variables.
|
|
563
|
-
"""
|
|
564
|
-
core_endpt = os.getenv(DhcoreEnvVar.ENDPOINT.value)
|
|
565
|
-
if core_endpt is None:
|
|
566
|
-
raise BackendError("Endpoint not set as environment variables.")
|
|
567
|
-
self._endpoint_core = self._sanitize_endpoint(core_endpt)
|
|
568
|
-
|
|
569
|
-
issr_endpt = os.getenv(DhcoreEnvVar.ISSUER.value)
|
|
570
|
-
if issr_endpt is not None:
|
|
571
|
-
self._endpoint_issuer = self._sanitize_endpoint(issr_endpt)
|
|
572
|
-
|
|
573
|
-
def _sanitize_endpoint(self, endpoint: str) -> str:
|
|
574
|
-
"""
|
|
575
|
-
Sanitize the endpoint.
|
|
576
|
-
|
|
577
|
-
Returns
|
|
578
|
-
-------
|
|
579
|
-
None
|
|
580
|
-
"""
|
|
581
|
-
if not has_remote_scheme(endpoint):
|
|
582
|
-
raise BackendError("Invalid endpoint scheme. Must start with http:// or https://.")
|
|
583
|
-
|
|
584
|
-
endpoint = endpoint.strip()
|
|
585
|
-
return endpoint.removesuffix("/")
|
|
586
|
-
|
|
587
|
-
def _get_auth_from_env(self) -> None:
|
|
588
|
-
"""
|
|
589
|
-
Get authentication parameters from the env.
|
|
590
|
-
|
|
591
|
-
Returns
|
|
592
|
-
-------
|
|
593
|
-
None
|
|
594
|
-
"""
|
|
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)
|
|
598
|
-
|
|
599
|
-
token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
|
|
600
|
-
if token is not None and token != "":
|
|
601
|
-
self._auth_type = AuthType.OAUTH2.value
|
|
602
|
-
self._access_token = token
|
|
603
|
-
return
|
|
604
|
-
|
|
605
|
-
password = os.getenv(DhcoreEnvVar.PASSWORD.value)
|
|
606
|
-
if self._user is not None and password is not None:
|
|
607
|
-
self._auth_type = AuthType.BASIC.value
|
|
608
|
-
self._password = password
|
|
609
|
-
return
|
|
610
|
-
|
|
611
|
-
def _get_new_access_token(self) -> None:
|
|
612
|
-
"""
|
|
613
|
-
Get a new access token.
|
|
614
|
-
|
|
615
|
-
Returns
|
|
616
|
-
-------
|
|
617
|
-
None
|
|
618
|
-
"""
|
|
619
|
-
# Call issuer and get endpoint for
|
|
620
|
-
# refreshing access token
|
|
621
|
-
url = self._get_refresh_endpoint()
|
|
622
|
-
|
|
623
|
-
# Call refresh token endpoint
|
|
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()
|
|
635
|
-
|
|
636
|
-
# Read new access token and refresh token
|
|
637
|
-
self._access_token = dict_response["access_token"]
|
|
638
|
-
self._refresh_token = dict_response["refresh_token"]
|
|
639
|
-
|
|
640
|
-
# Propagate new access token to env
|
|
641
|
-
self._write_env()
|
|
642
|
-
|
|
643
|
-
def _get_refresh_endpoint(self) -> str:
|
|
644
|
-
"""
|
|
645
|
-
Get the refresh endpoint.
|
|
646
|
-
|
|
647
|
-
Returns
|
|
648
|
-
-------
|
|
649
|
-
str
|
|
650
|
-
Refresh endpoint.
|
|
651
|
-
"""
|
|
652
|
-
# Get issuer endpoint
|
|
653
|
-
if self._endpoint_issuer is None:
|
|
654
|
-
raise BackendError("Issuer endpoint not set.")
|
|
655
|
-
|
|
656
|
-
# Standard issuer endpoint path
|
|
657
|
-
url = self._endpoint_issuer + "/.well-known/openid-configuration"
|
|
658
|
-
|
|
659
|
-
# Call
|
|
660
|
-
r = request("GET", url, timeout=60)
|
|
661
|
-
self._raise_for_error(r)
|
|
662
|
-
return r.json().get("token_endpoint")
|
|
663
|
-
|
|
664
|
-
def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
|
|
665
|
-
"""
|
|
666
|
-
Call the refresh token endpoint.
|
|
667
|
-
|
|
668
|
-
Parameters
|
|
669
|
-
----------
|
|
670
|
-
url : str
|
|
671
|
-
Refresh token endpoint.
|
|
672
|
-
refresh_token : str
|
|
673
|
-
Refresh token.
|
|
674
|
-
|
|
675
|
-
Returns
|
|
676
|
-
-------
|
|
677
|
-
Response
|
|
678
|
-
Response object.
|
|
679
|
-
"""
|
|
680
|
-
# Send request to get new access token
|
|
681
|
-
payload = {
|
|
682
|
-
"grant_type": "refresh_token",
|
|
683
|
-
"client_id": self._client_id,
|
|
684
|
-
"refresh_token": refresh_token,
|
|
685
|
-
}
|
|
686
|
-
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
687
|
-
return request("POST", url, data=payload, headers=headers, timeout=60)
|
|
688
|
-
|
|
689
|
-
def _write_env(self) -> None:
|
|
690
|
-
"""
|
|
691
|
-
Write the env variables to the .dhcore file.
|
|
692
|
-
It will overwrite any existing env variables.
|
|
693
|
-
|
|
694
|
-
Returns
|
|
695
|
-
-------
|
|
696
|
-
None
|
|
697
|
-
"""
|
|
698
|
-
keys = {}
|
|
699
|
-
if self._access_token is not None:
|
|
700
|
-
keys[DhcoreEnvVar.ACCESS_TOKEN.value] = self._access_token
|
|
701
|
-
if self._refresh_token is not None:
|
|
702
|
-
keys[DhcoreEnvVar.REFRESH_TOKEN.value] = self._refresh_token
|
|
703
|
-
|
|
704
|
-
for k, v in keys.items():
|
|
705
|
-
set_key(dotenv_path=ENV_FILE, key_to_set=k, value_to_set=v)
|
|
706
|
-
|
|
707
339
|
##############################
|
|
708
340
|
# Interface methods
|
|
709
341
|
##############################
|