digitalhub 0.14.0b5__py3-none-any.whl → 0.14.0b6__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 +1 -1
- digitalhub/context/api.py +1 -5
- digitalhub/context/builder.py +1 -1
- digitalhub/entities/_base/material/utils.py +0 -4
- digitalhub/entities/_commons/enums.py +1 -0
- digitalhub/entities/_commons/utils.py +19 -0
- digitalhub/entities/_processors/base/crud.py +1 -1
- digitalhub/entities/_processors/base/import_export.py +3 -2
- digitalhub/entities/_processors/base/processor.py +3 -3
- digitalhub/entities/_processors/context/crud.py +22 -24
- digitalhub/entities/_processors/context/import_export.py +2 -2
- digitalhub/entities/_processors/context/special_ops.py +10 -10
- digitalhub/entities/_processors/utils.py +5 -5
- digitalhub/entities/artifact/utils.py +2 -2
- digitalhub/entities/dataitem/utils.py +10 -14
- digitalhub/entities/model/utils.py +2 -2
- digitalhub/entities/project/_base/entity.py +248 -102
- digitalhub/entities/task/_base/models.py +10 -1
- digitalhub/stores/client/_base/key_builder.py +1 -1
- digitalhub/stores/client/builder.py +1 -1
- digitalhub/stores/client/dhcore/client.py +19 -303
- digitalhub/stores/client/dhcore/configurator.py +1 -1
- digitalhub/stores/client/dhcore/header_manager.py +61 -0
- digitalhub/stores/client/dhcore/http_handler.py +133 -0
- digitalhub/stores/client/dhcore/response_processor.py +102 -0
- digitalhub/stores/client/dhcore/utils.py +2 -60
- digitalhub/stores/client/local/client.py +2 -2
- digitalhub/stores/credentials/api.py +0 -4
- digitalhub/stores/credentials/ini_module.py +0 -6
- digitalhub/stores/data/builder.py +1 -1
- digitalhub/stores/data/s3/store.py +1 -1
- digitalhub/stores/data/sql/store.py +6 -6
- digitalhub/utils/generic_utils.py +0 -12
- digitalhub/utils/git_utils.py +0 -8
- digitalhub/utils/io_utils.py +0 -8
- digitalhub/utils/store_utils.py +1 -1
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b6.dist-info}/METADATA +1 -1
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b6.dist-info}/RECORD +41 -38
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b6.dist-info}/WHEEL +0 -0
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b6.dist-info}/licenses/AUTHORS +0 -0
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b6.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,6 +18,7 @@ class VolumeType(Enum):
|
|
|
18
18
|
PERSISTENT_VOLUME_CLAIM = "persistent_volume_claim"
|
|
19
19
|
EMPTY_DIR = "empty_dir"
|
|
20
20
|
EPHEMERAL = "ephemeral"
|
|
21
|
+
SHARED_VOLUME = "shared_volume"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class SpecEmptyDir(BaseModel):
|
|
@@ -46,6 +47,14 @@ class SpecEphemeral(BaseModel):
|
|
|
46
47
|
size: Optional[str] = None
|
|
47
48
|
|
|
48
49
|
|
|
50
|
+
class SharedVolumeSpec(BaseModel):
|
|
51
|
+
"""
|
|
52
|
+
Shared volume spec model.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
size: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
49
58
|
class Volume(BaseModel):
|
|
50
59
|
"""
|
|
51
60
|
Volume model.
|
|
@@ -62,7 +71,7 @@ class Volume(BaseModel):
|
|
|
62
71
|
mount_path: str
|
|
63
72
|
"""Volume mount path inside the container."""
|
|
64
73
|
|
|
65
|
-
spec: Optional[Union[SpecEmptyDir, SpecPVC, SpecEphemeral]] = None
|
|
74
|
+
spec: Optional[Union[SpecEmptyDir, SpecPVC, SpecEphemeral, SharedVolumeSpec]] = None
|
|
66
75
|
"""Volume spec."""
|
|
67
76
|
|
|
68
77
|
|
|
@@ -4,34 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
import typing
|
|
8
7
|
from typing import Any
|
|
9
|
-
from warnings import warn
|
|
10
|
-
|
|
11
|
-
from requests import request
|
|
12
|
-
from requests.exceptions import JSONDecodeError
|
|
13
8
|
|
|
14
9
|
from digitalhub.stores.client._base.client import Client
|
|
15
10
|
from digitalhub.stores.client.dhcore.api_builder import ClientDHCoreApiBuilder
|
|
16
|
-
from digitalhub.stores.client.dhcore.
|
|
17
|
-
from digitalhub.stores.client.dhcore.
|
|
11
|
+
from digitalhub.stores.client.dhcore.header_manager import HeaderManager
|
|
12
|
+
from digitalhub.stores.client.dhcore.http_handler import HttpRequestHandler
|
|
18
13
|
from digitalhub.stores.client.dhcore.key_builder import ClientDHCoreKeyBuilder
|
|
19
14
|
from digitalhub.stores.client.dhcore.params_builder import ClientDHCoreParametersBuilder
|
|
20
|
-
from digitalhub.utils.exceptions import BackendError
|
|
15
|
+
from digitalhub.utils.exceptions import BackendError
|
|
21
16
|
from digitalhub.utils.generic_utils import dump_json
|
|
22
17
|
|
|
23
|
-
if typing.TYPE_CHECKING:
|
|
24
|
-
from requests import Response
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# API levels that are supported
|
|
28
|
-
MAX_API_LEVEL = 20
|
|
29
|
-
MIN_API_LEVEL = 14
|
|
30
|
-
LIB_VERSION = 14
|
|
31
|
-
|
|
32
|
-
# Default timeout for requests (in seconds)
|
|
33
|
-
DEFAULT_TIMEOUT = 60
|
|
34
|
-
|
|
35
18
|
|
|
36
19
|
class ClientDHCore(Client):
|
|
37
20
|
"""
|
|
@@ -41,54 +24,19 @@ class ClientDHCore(Client):
|
|
|
41
24
|
multiple authentication methods: Basic (username/password), OAuth2 (token
|
|
42
25
|
with refresh), and Personal Access Token exchange. Automatically handles
|
|
43
26
|
API version compatibility, pagination, token refresh, error parsing, and
|
|
44
|
-
JSON serialization.
|
|
45
|
-
|
|
46
|
-
Parameters
|
|
47
|
-
----------
|
|
48
|
-
config : dict, optional
|
|
49
|
-
DHCore environment configuration. If None, loads from environment
|
|
50
|
-
variables and configuration files.
|
|
51
|
-
|
|
52
|
-
Attributes
|
|
53
|
-
----------
|
|
54
|
-
_api_builder : ClientDHCoreApiBuilder
|
|
55
|
-
Builds API endpoint URLs for different operations.
|
|
56
|
-
_key_builder : ClientDHCoreKeyBuilder
|
|
57
|
-
Builds storage keys for entities.
|
|
58
|
-
_params_builder : ClientDHCoreParametersBuilder
|
|
59
|
-
Builds request parameters for API calls.
|
|
60
|
-
_error_parser : ErrorParser
|
|
61
|
-
Parses backend responses and raises appropriate exceptions.
|
|
62
|
-
_configurator : ClientDHCoreConfigurator
|
|
63
|
-
Manages client configuration and authentication.
|
|
27
|
+
JSON serialization.
|
|
64
28
|
"""
|
|
65
29
|
|
|
66
30
|
def __init__(self, config: dict | None = None) -> None:
|
|
67
|
-
"""
|
|
68
|
-
Initialize DHCore client with API builders and configurators.
|
|
69
|
-
|
|
70
|
-
Parameters
|
|
71
|
-
----------
|
|
72
|
-
config : dict, optional
|
|
73
|
-
DHCore environment configuration. If None, loads from environment
|
|
74
|
-
variables and configuration files.
|
|
75
|
-
"""
|
|
76
31
|
super().__init__()
|
|
77
32
|
|
|
78
|
-
# API
|
|
33
|
+
# API, key and parameters builders
|
|
79
34
|
self._api_builder: ClientDHCoreApiBuilder = ClientDHCoreApiBuilder()
|
|
80
|
-
|
|
81
|
-
# Key builder
|
|
82
35
|
self._key_builder: ClientDHCoreKeyBuilder = ClientDHCoreKeyBuilder()
|
|
83
|
-
|
|
84
|
-
# Parameters builder
|
|
85
36
|
self._params_builder: ClientDHCoreParametersBuilder = ClientDHCoreParametersBuilder()
|
|
86
37
|
|
|
87
|
-
#
|
|
88
|
-
self.
|
|
89
|
-
|
|
90
|
-
# Client Configurator
|
|
91
|
-
self._configurator = ClientDHCoreConfigurator()
|
|
38
|
+
# HTTP request handling
|
|
39
|
+
self._http_handler = HttpRequestHandler()
|
|
92
40
|
|
|
93
41
|
##############################
|
|
94
42
|
# CRUD methods
|
|
@@ -113,17 +61,10 @@ class ClientDHCore(Client):
|
|
|
113
61
|
-------
|
|
114
62
|
dict
|
|
115
63
|
Created object as returned by the backend.
|
|
116
|
-
|
|
117
|
-
Raises
|
|
118
|
-
------
|
|
119
|
-
BackendError
|
|
120
|
-
If the backend returns an error response.
|
|
121
|
-
ClientError
|
|
122
|
-
If there are client-side configuration issues.
|
|
123
64
|
"""
|
|
124
|
-
kwargs =
|
|
65
|
+
kwargs = HeaderManager.set_json_content_type(**kwargs)
|
|
125
66
|
kwargs["data"] = dump_json(obj)
|
|
126
|
-
return self.
|
|
67
|
+
return self._http_handler.prepare_request("POST", api, **kwargs)
|
|
127
68
|
|
|
128
69
|
def read_object(self, api: str, **kwargs) -> dict:
|
|
129
70
|
"""
|
|
@@ -150,7 +91,7 @@ class ClientDHCore(Client):
|
|
|
150
91
|
EntityNotExistsError
|
|
151
92
|
If the requested object does not exist.
|
|
152
93
|
"""
|
|
153
|
-
return self.
|
|
94
|
+
return self._http_handler.prepare_request("GET", api, **kwargs)
|
|
154
95
|
|
|
155
96
|
def update_object(self, api: str, obj: Any, **kwargs) -> dict:
|
|
156
97
|
"""
|
|
@@ -171,17 +112,10 @@ class ClientDHCore(Client):
|
|
|
171
112
|
-------
|
|
172
113
|
dict
|
|
173
114
|
Updated object as returned by the backend.
|
|
174
|
-
|
|
175
|
-
Raises
|
|
176
|
-
------
|
|
177
|
-
BackendError
|
|
178
|
-
If the backend returns an error response.
|
|
179
|
-
EntityNotExistsError
|
|
180
|
-
If the object to update does not exist.
|
|
181
115
|
"""
|
|
182
|
-
kwargs =
|
|
116
|
+
kwargs = HeaderManager.set_json_content_type(**kwargs)
|
|
183
117
|
kwargs["data"] = dump_json(obj)
|
|
184
|
-
return self.
|
|
118
|
+
return self._http_handler.prepare_request("PUT", api, **kwargs)
|
|
185
119
|
|
|
186
120
|
def delete_object(self, api: str, **kwargs) -> dict:
|
|
187
121
|
"""
|
|
@@ -201,15 +135,8 @@ class ClientDHCore(Client):
|
|
|
201
135
|
-------
|
|
202
136
|
dict
|
|
203
137
|
Deletion result from backend or {"deleted": bool} wrapper.
|
|
204
|
-
|
|
205
|
-
Raises
|
|
206
|
-
------
|
|
207
|
-
BackendError
|
|
208
|
-
If the backend returns an error response.
|
|
209
|
-
EntityNotExistsError
|
|
210
|
-
If the object to delete does not exist.
|
|
211
138
|
"""
|
|
212
|
-
resp = self.
|
|
139
|
+
resp = self._http_handler.prepare_request("DELETE", api, **kwargs)
|
|
213
140
|
if isinstance(resp, bool):
|
|
214
141
|
resp = {"deleted": resp}
|
|
215
142
|
return resp
|
|
@@ -233,17 +160,12 @@ class ClientDHCore(Client):
|
|
|
233
160
|
-------
|
|
234
161
|
list[dict]
|
|
235
162
|
List containing all objects from all pages.
|
|
236
|
-
|
|
237
|
-
Raises
|
|
238
|
-
------
|
|
239
|
-
BackendError
|
|
240
|
-
If the backend returns an error response.
|
|
241
163
|
"""
|
|
242
164
|
kwargs = self._params_builder.set_pagination(partial=True, **kwargs)
|
|
243
165
|
|
|
244
166
|
objects = []
|
|
245
167
|
while True:
|
|
246
|
-
resp = self.
|
|
168
|
+
resp = self._http_handler.prepare_request("GET", api, **kwargs)
|
|
247
169
|
contents = resp["content"]
|
|
248
170
|
total_pages = resp["totalPages"]
|
|
249
171
|
objects.extend(contents)
|
|
@@ -271,11 +193,6 @@ class ClientDHCore(Client):
|
|
|
271
193
|
-------
|
|
272
194
|
dict
|
|
273
195
|
First object from the list.
|
|
274
|
-
|
|
275
|
-
Raises
|
|
276
|
-
------
|
|
277
|
-
BackendError
|
|
278
|
-
If no objects found or backend returns an error.
|
|
279
196
|
"""
|
|
280
197
|
try:
|
|
281
198
|
return self.list_objects(api, **kwargs)[0]
|
|
@@ -302,16 +219,11 @@ class ClientDHCore(Client):
|
|
|
302
219
|
-------
|
|
303
220
|
list[dict]
|
|
304
221
|
List of matching objects with search highlights removed.
|
|
305
|
-
|
|
306
|
-
Raises
|
|
307
|
-
------
|
|
308
|
-
BackendError
|
|
309
|
-
If the backend returns an error response.
|
|
310
222
|
"""
|
|
311
223
|
kwargs = self._params_builder.set_pagination(**kwargs)
|
|
312
224
|
objects_with_highlights: list[dict] = []
|
|
313
225
|
while True:
|
|
314
|
-
resp = self.
|
|
226
|
+
resp = self._http_handler.prepare_request("GET", api, **kwargs)
|
|
315
227
|
contents = resp["content"]
|
|
316
228
|
total_pages = resp["totalPages"]
|
|
317
229
|
objects_with_highlights.extend(contents)
|
|
@@ -326,163 +238,6 @@ class ClientDHCore(Client):
|
|
|
326
238
|
|
|
327
239
|
return objects
|
|
328
240
|
|
|
329
|
-
##############################
|
|
330
|
-
# Call methods
|
|
331
|
-
##############################
|
|
332
|
-
|
|
333
|
-
def _prepare_call(self, call_type: str, api: str, **kwargs) -> dict:
|
|
334
|
-
"""
|
|
335
|
-
Prepare DHCore API call with configuration and authentication.
|
|
336
|
-
|
|
337
|
-
Checks configuration, builds URL, and adds authentication parameters.
|
|
338
|
-
|
|
339
|
-
Parameters
|
|
340
|
-
----------
|
|
341
|
-
call_type : str
|
|
342
|
-
HTTP method type (GET, POST, PUT, DELETE, etc.).
|
|
343
|
-
api : str
|
|
344
|
-
API endpoint path to call.
|
|
345
|
-
**kwargs : dict
|
|
346
|
-
Additional HTTP request arguments.
|
|
347
|
-
|
|
348
|
-
Returns
|
|
349
|
-
-------
|
|
350
|
-
dict
|
|
351
|
-
Response from the API call.
|
|
352
|
-
|
|
353
|
-
Raises
|
|
354
|
-
------
|
|
355
|
-
ClientError
|
|
356
|
-
If client configuration is invalid.
|
|
357
|
-
BackendError
|
|
358
|
-
If backend returns an error response.
|
|
359
|
-
"""
|
|
360
|
-
self._configurator.check_config()
|
|
361
|
-
url = self._build_url(api)
|
|
362
|
-
full_kwargs = self._configurator.get_auth_parameters(kwargs)
|
|
363
|
-
return self._make_call(call_type, url, **full_kwargs)
|
|
364
|
-
|
|
365
|
-
def _build_url(self, api: str) -> str:
|
|
366
|
-
"""
|
|
367
|
-
Build complete URL for API call.
|
|
368
|
-
|
|
369
|
-
Combines configured endpoint with API path, automatically removing
|
|
370
|
-
leading slashes for proper URL construction.
|
|
371
|
-
|
|
372
|
-
Parameters
|
|
373
|
-
----------
|
|
374
|
-
api : str
|
|
375
|
-
API endpoint path. Leading slashes are automatically handled.
|
|
376
|
-
|
|
377
|
-
Returns
|
|
378
|
-
-------
|
|
379
|
-
str
|
|
380
|
-
Complete URL for the API call.
|
|
381
|
-
"""
|
|
382
|
-
endpoint = self._configurator.get_endpoint()
|
|
383
|
-
return f"{endpoint}/{api.removeprefix('/')}"
|
|
384
|
-
|
|
385
|
-
def _make_call(self, call_type: str, url: str, refresh: bool = True, **kwargs) -> dict:
|
|
386
|
-
"""
|
|
387
|
-
Execute HTTP request to DHCore API with automatic handling.
|
|
388
|
-
|
|
389
|
-
Handles API version checking, token refresh on 401 errors, response parsing,
|
|
390
|
-
and error handling with 60-second timeout.
|
|
391
|
-
|
|
392
|
-
Parameters
|
|
393
|
-
----------
|
|
394
|
-
call_type : str
|
|
395
|
-
HTTP method type (GET, POST, PUT, DELETE, etc.).
|
|
396
|
-
url : str
|
|
397
|
-
Complete URL to call.
|
|
398
|
-
refresh : bool, default True
|
|
399
|
-
Whether to attempt token refresh on authentication errors.
|
|
400
|
-
Set to False to prevent infinite recursion during refresh.
|
|
401
|
-
**kwargs : dict
|
|
402
|
-
Additional HTTP request arguments.
|
|
403
|
-
|
|
404
|
-
Returns
|
|
405
|
-
-------
|
|
406
|
-
dict
|
|
407
|
-
Parsed response from backend as dictionary.
|
|
408
|
-
|
|
409
|
-
Raises
|
|
410
|
-
------
|
|
411
|
-
ClientError
|
|
412
|
-
If backend API version is not supported.
|
|
413
|
-
BackendError
|
|
414
|
-
If backend returns error response or response parsing fails.
|
|
415
|
-
UnauthorizedError
|
|
416
|
-
If authentication fails and token refresh not possible.
|
|
417
|
-
"""
|
|
418
|
-
# Call the API
|
|
419
|
-
response = request(call_type, url, timeout=DEFAULT_TIMEOUT, **kwargs)
|
|
420
|
-
|
|
421
|
-
# Evaluate DHCore API version
|
|
422
|
-
self._check_core_version(response)
|
|
423
|
-
|
|
424
|
-
# Handle token refresh (redo call)
|
|
425
|
-
if (response.status_code in [401]) and (refresh) and self._configurator.refreshable_auth_types():
|
|
426
|
-
self._configurator.refresh_credentials(change_origin=True)
|
|
427
|
-
kwargs = self._configurator.get_auth_parameters(kwargs)
|
|
428
|
-
return self._make_call(call_type, url, refresh=False, **kwargs)
|
|
429
|
-
|
|
430
|
-
self._error_parser.parse(response)
|
|
431
|
-
return self._dictify_response(response)
|
|
432
|
-
|
|
433
|
-
def _check_core_version(self, response: Response) -> None:
|
|
434
|
-
"""
|
|
435
|
-
Validate DHCore API version compatibility.
|
|
436
|
-
|
|
437
|
-
Checks backend API version against supported range and warns if backend
|
|
438
|
-
version is newer than library. Supported: {MIN_API_LEVEL} to {MAX_API_LEVEL}.
|
|
439
|
-
|
|
440
|
-
Parameters
|
|
441
|
-
----------
|
|
442
|
-
response : Response
|
|
443
|
-
HTTP response containing X-Api-Level header.
|
|
444
|
-
|
|
445
|
-
Raises
|
|
446
|
-
------
|
|
447
|
-
ClientError
|
|
448
|
-
If backend API level is not supported by this client.
|
|
449
|
-
"""
|
|
450
|
-
if "X-Api-Level" in response.headers:
|
|
451
|
-
core_api_level = int(response.headers["X-Api-Level"])
|
|
452
|
-
if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
|
|
453
|
-
raise ClientError("Backend API level not supported.")
|
|
454
|
-
if LIB_VERSION < core_api_level:
|
|
455
|
-
warn("Backend API level is higher than library version. You should consider updating the library.")
|
|
456
|
-
|
|
457
|
-
def _dictify_response(self, response: Response) -> dict:
|
|
458
|
-
"""
|
|
459
|
-
Parse HTTP response body to dictionary.
|
|
460
|
-
|
|
461
|
-
Converts JSON response to Python dictionary, treating empty responses
|
|
462
|
-
as valid and returning empty dict.
|
|
463
|
-
|
|
464
|
-
Parameters
|
|
465
|
-
----------
|
|
466
|
-
response : Response
|
|
467
|
-
HTTP response object to parse.
|
|
468
|
-
|
|
469
|
-
Returns
|
|
470
|
-
-------
|
|
471
|
-
dict
|
|
472
|
-
Parsed response body as dictionary, or empty dict if body is empty.
|
|
473
|
-
|
|
474
|
-
Raises
|
|
475
|
-
------
|
|
476
|
-
BackendError
|
|
477
|
-
If response cannot be parsed as JSON.
|
|
478
|
-
"""
|
|
479
|
-
try:
|
|
480
|
-
return response.json()
|
|
481
|
-
except JSONDecodeError:
|
|
482
|
-
if response.text == "":
|
|
483
|
-
return {}
|
|
484
|
-
raise BackendError("Backend response could not be parsed.")
|
|
485
|
-
|
|
486
241
|
##############################
|
|
487
242
|
# Interface methods
|
|
488
243
|
##############################
|
|
@@ -506,48 +261,9 @@ class ClientDHCore(Client):
|
|
|
506
261
|
# Utility methods
|
|
507
262
|
##############################
|
|
508
263
|
|
|
509
|
-
|
|
510
|
-
def _ensure_header(**kwargs) -> dict:
|
|
264
|
+
def refresh_token(self) -> None:
|
|
511
265
|
"""
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
Ensures parameter dictionary has 'headers' key for HTTP headers,
|
|
515
|
-
guaranteeing consistent structure for all parameter building methods.
|
|
516
|
-
|
|
517
|
-
Parameters
|
|
518
|
-
----------
|
|
519
|
-
**kwargs : dict
|
|
520
|
-
Keyword arguments to format. May be empty or contain various
|
|
521
|
-
parameters for API operations.
|
|
522
|
-
|
|
523
|
-
Returns
|
|
524
|
-
-------
|
|
525
|
-
dict
|
|
526
|
-
Headers dictionary with guaranteed 'headers' key containing
|
|
527
|
-
empty dict if not already present.
|
|
528
|
-
"""
|
|
529
|
-
if "headers" not in kwargs:
|
|
530
|
-
kwargs["headers"] = {}
|
|
531
|
-
return kwargs
|
|
532
|
-
|
|
533
|
-
def _set_application_json_header(self, **kwargs) -> dict:
|
|
534
|
-
"""
|
|
535
|
-
Set Content-Type header to application/json.
|
|
536
|
-
|
|
537
|
-
Ensures that the 'Content-Type' header is set to 'application/json'
|
|
538
|
-
for requests that require JSON payloads.
|
|
539
|
-
|
|
540
|
-
Parameters
|
|
541
|
-
----------
|
|
542
|
-
**kwargs : dict
|
|
543
|
-
Keyword arguments to format. May be empty or contain various
|
|
544
|
-
parameters for API operations.
|
|
545
|
-
|
|
546
|
-
Returns
|
|
547
|
-
-------
|
|
548
|
-
dict
|
|
549
|
-
Headers dictionary with 'Content-Type' set to 'application/json'.
|
|
266
|
+
Manually trigger OAuth2 token refresh.
|
|
550
267
|
"""
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return kwargs
|
|
268
|
+
self._http_handler._configurator.check_config()
|
|
269
|
+
self._http_handler._configurator.refresh_credentials()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HeaderManager:
|
|
9
|
+
"""
|
|
10
|
+
Manages HTTP headers for DHCore client requests.
|
|
11
|
+
|
|
12
|
+
Provides utilities for setting and managing common HTTP headers
|
|
13
|
+
like Content-Type for JSON requests.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def ensure_headers(**kwargs) -> dict:
|
|
18
|
+
"""
|
|
19
|
+
Initialize headers dictionary in kwargs.
|
|
20
|
+
|
|
21
|
+
Ensures parameter dictionary has 'headers' key for HTTP headers,
|
|
22
|
+
guaranteeing consistent structure for all parameter building methods.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
**kwargs : dict
|
|
27
|
+
Keyword arguments to format. May be empty or contain various
|
|
28
|
+
parameters for API operations.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dict
|
|
33
|
+
Dictionary with guaranteed 'headers' key containing
|
|
34
|
+
empty dict if not already present.
|
|
35
|
+
"""
|
|
36
|
+
if "headers" not in kwargs:
|
|
37
|
+
kwargs["headers"] = {}
|
|
38
|
+
return kwargs
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def set_json_content_type(**kwargs) -> dict:
|
|
42
|
+
"""
|
|
43
|
+
Set Content-Type header to application/json.
|
|
44
|
+
|
|
45
|
+
Ensures that the 'Content-Type' header is set to 'application/json'
|
|
46
|
+
for requests that require JSON payloads.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
**kwargs : dict
|
|
51
|
+
Keyword arguments to format. May be empty or contain various
|
|
52
|
+
parameters for API operations.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
dict
|
|
57
|
+
Dictionary with 'Content-Type' header set to 'application/json'.
|
|
58
|
+
"""
|
|
59
|
+
kwargs = HeaderManager.ensure_headers(**kwargs)
|
|
60
|
+
kwargs["headers"]["Content-Type"] = "application/json"
|
|
61
|
+
return kwargs
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from requests import request
|
|
8
|
+
|
|
9
|
+
from digitalhub.stores.client.dhcore.configurator import ClientDHCoreConfigurator
|
|
10
|
+
from digitalhub.stores.client.dhcore.response_processor import ResponseProcessor
|
|
11
|
+
from digitalhub.utils.exceptions import BackendError
|
|
12
|
+
|
|
13
|
+
# Default timeout for requests (in seconds)
|
|
14
|
+
DEFAULT_TIMEOUT = 60
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HttpRequestHandler:
|
|
18
|
+
"""
|
|
19
|
+
Handles HTTP request execution for DHCore client.
|
|
20
|
+
|
|
21
|
+
Encapsulates all HTTP communication logic including request execution,
|
|
22
|
+
automatic token refresh on authentication failures, and response processing.
|
|
23
|
+
Works in coordination with configurator for authentication and response
|
|
24
|
+
processor for parsing.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self._configurator = ClientDHCoreConfigurator()
|
|
29
|
+
self._response_processor = ResponseProcessor()
|
|
30
|
+
|
|
31
|
+
def prepare_request(self, method: str, api: str, **kwargs) -> dict:
|
|
32
|
+
"""
|
|
33
|
+
Execute API call with full URL construction and authentication.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
method : str
|
|
38
|
+
HTTP method type (GET, POST, PUT, DELETE, etc.).
|
|
39
|
+
api : str
|
|
40
|
+
API endpoint path to call.
|
|
41
|
+
**kwargs : dict
|
|
42
|
+
Additional HTTP request arguments.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
dict
|
|
47
|
+
Response from the API call.
|
|
48
|
+
"""
|
|
49
|
+
full_kwargs = self._set_auth(**kwargs)
|
|
50
|
+
url = self._build_url(api)
|
|
51
|
+
return self._execute_request(method, url, **full_kwargs)
|
|
52
|
+
|
|
53
|
+
def _execute_request(
|
|
54
|
+
self,
|
|
55
|
+
method: str,
|
|
56
|
+
url: str,
|
|
57
|
+
refresh: bool = True,
|
|
58
|
+
**kwargs,
|
|
59
|
+
) -> dict:
|
|
60
|
+
"""
|
|
61
|
+
Execute HTTP request with automatic handling.
|
|
62
|
+
|
|
63
|
+
Sends HTTP request with authentication, handles token refresh on 401 errors,
|
|
64
|
+
validates API version compatibility, and parses response. Uses 60-second
|
|
65
|
+
timeout by default.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
method : str
|
|
70
|
+
HTTP method (GET, POST, PUT, DELETE, etc.).
|
|
71
|
+
url : str
|
|
72
|
+
Complete URL to request.
|
|
73
|
+
refresh : bool, default True
|
|
74
|
+
Whether to attempt token refresh on authentication errors.
|
|
75
|
+
Set to False during refresh to prevent infinite recursion.
|
|
76
|
+
**kwargs : dict
|
|
77
|
+
Additional HTTP request arguments (headers, params, data, etc.).
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
dict
|
|
82
|
+
Parsed response body as dictionary.
|
|
83
|
+
"""
|
|
84
|
+
# Execute HTTP request
|
|
85
|
+
response = request(method, url, timeout=DEFAULT_TIMEOUT, **kwargs)
|
|
86
|
+
|
|
87
|
+
# Process response (version check, error parsing, dictify)
|
|
88
|
+
try:
|
|
89
|
+
return self._response_processor.process(response)
|
|
90
|
+
except BackendError as e:
|
|
91
|
+
# Handle authentication errors with token refresh
|
|
92
|
+
if response.status_code == 401 and refresh and self._configurator.refreshable_auth_types():
|
|
93
|
+
self._configurator.refresh_credentials(change_origin=True)
|
|
94
|
+
kwargs = self._configurator.get_auth_parameters(kwargs)
|
|
95
|
+
return self._execute_request(method, url, refresh=False, **kwargs)
|
|
96
|
+
raise e
|
|
97
|
+
|
|
98
|
+
def _set_auth(self, **kwargs) -> dict:
|
|
99
|
+
"""
|
|
100
|
+
Prepare kwargs with authentication parameters.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
**kwargs : dict
|
|
105
|
+
Request parameters to augment with authentication.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
dict
|
|
110
|
+
kwargs enhanced with authentication parameters.
|
|
111
|
+
"""
|
|
112
|
+
self._configurator.check_config()
|
|
113
|
+
return self._configurator.get_auth_parameters(kwargs)
|
|
114
|
+
|
|
115
|
+
def _build_url(self, api: str) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Build complete URL for API call.
|
|
118
|
+
|
|
119
|
+
Combines configured endpoint with API path, automatically removing
|
|
120
|
+
leading slashes for proper URL construction.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
api : str
|
|
125
|
+
API endpoint path. Leading slashes are automatically handled.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
str
|
|
130
|
+
Complete URL for the API call.
|
|
131
|
+
"""
|
|
132
|
+
endpoint = self._configurator.get_endpoint()
|
|
133
|
+
return f"{endpoint}/{api.removeprefix('/')}"
|