digitalhub 0.14.0b4__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.

Files changed (42) hide show
  1. digitalhub/__init__.py +1 -1
  2. digitalhub/context/api.py +1 -5
  3. digitalhub/context/builder.py +1 -1
  4. digitalhub/context/context.py +15 -1
  5. digitalhub/entities/_base/material/utils.py +0 -4
  6. digitalhub/entities/_commons/enums.py +1 -0
  7. digitalhub/entities/_commons/utils.py +19 -0
  8. digitalhub/entities/_processors/base/crud.py +1 -1
  9. digitalhub/entities/_processors/base/import_export.py +3 -2
  10. digitalhub/entities/_processors/base/processor.py +3 -3
  11. digitalhub/entities/_processors/context/crud.py +22 -24
  12. digitalhub/entities/_processors/context/import_export.py +2 -2
  13. digitalhub/entities/_processors/context/special_ops.py +10 -10
  14. digitalhub/entities/_processors/utils.py +5 -5
  15. digitalhub/entities/artifact/utils.py +2 -2
  16. digitalhub/entities/dataitem/utils.py +10 -14
  17. digitalhub/entities/model/utils.py +2 -2
  18. digitalhub/entities/project/_base/entity.py +248 -102
  19. digitalhub/entities/task/_base/models.py +10 -1
  20. digitalhub/stores/client/_base/key_builder.py +1 -1
  21. digitalhub/stores/client/builder.py +1 -1
  22. digitalhub/stores/client/dhcore/client.py +19 -303
  23. digitalhub/stores/client/dhcore/configurator.py +1 -1
  24. digitalhub/stores/client/dhcore/header_manager.py +61 -0
  25. digitalhub/stores/client/dhcore/http_handler.py +133 -0
  26. digitalhub/stores/client/dhcore/response_processor.py +102 -0
  27. digitalhub/stores/client/dhcore/utils.py +2 -60
  28. digitalhub/stores/client/local/client.py +2 -2
  29. digitalhub/stores/credentials/api.py +0 -4
  30. digitalhub/stores/credentials/ini_module.py +0 -6
  31. digitalhub/stores/data/builder.py +1 -1
  32. digitalhub/stores/data/s3/store.py +1 -1
  33. digitalhub/stores/data/sql/store.py +6 -6
  34. digitalhub/utils/generic_utils.py +0 -12
  35. digitalhub/utils/git_utils.py +0 -8
  36. digitalhub/utils/io_utils.py +0 -8
  37. digitalhub/utils/store_utils.py +1 -1
  38. {digitalhub-0.14.0b4.dist-info → digitalhub-0.14.0b6.dist-info}/METADATA +1 -1
  39. {digitalhub-0.14.0b4.dist-info → digitalhub-0.14.0b6.dist-info}/RECORD +42 -39
  40. {digitalhub-0.14.0b4.dist-info → digitalhub-0.14.0b6.dist-info}/WHEEL +0 -0
  41. {digitalhub-0.14.0b4.dist-info → digitalhub-0.14.0b6.dist-info}/licenses/AUTHORS +0 -0
  42. {digitalhub-0.14.0b4.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
 
@@ -74,7 +74,7 @@ class ClientKeyBuilder:
74
74
  The entity kind.
75
75
  entity_name : str
76
76
  The entity name.
77
- entity_id : str, optional
77
+ entity_id : str
78
78
  The entity identifier. If None, key will not include version.
79
79
 
80
80
  Returns
@@ -35,7 +35,7 @@ class ClientBuilder:
35
35
  ----------
36
36
  local : bool, default False
37
37
  Whether to create a local client or not.
38
- config : dict, optional
38
+ config : dict
39
39
  DHCore environment configuration.
40
40
 
41
41
  Returns
@@ -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.configurator import ClientDHCoreConfigurator
17
- from digitalhub.stores.client.dhcore.error_parser import ErrorParser
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, ClientError
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. Supports API versions {MIN_API_LEVEL} to {MAX_API_LEVEL}.
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 builder
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
- # Error parser
88
- self._error_parser = ErrorParser()
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 = self._set_application_json_header(**kwargs)
65
+ kwargs = HeaderManager.set_json_content_type(**kwargs)
125
66
  kwargs["data"] = dump_json(obj)
126
- return self._prepare_call("POST", api, **kwargs)
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._prepare_call("GET", api, **kwargs)
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 = self._set_application_json_header(**kwargs)
116
+ kwargs = HeaderManager.set_json_content_type(**kwargs)
183
117
  kwargs["data"] = dump_json(obj)
184
- return self._prepare_call("PUT", api, **kwargs)
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._prepare_call("DELETE", api, **kwargs)
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._prepare_call("GET", api, **kwargs)
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._prepare_call("GET", api, **kwargs)
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
- @staticmethod
510
- def _ensure_header(**kwargs) -> dict:
264
+ def refresh_token(self) -> None:
511
265
  """
512
- Initialize header dictionary.
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
- kwargs = self._ensure_header(**kwargs)
552
- kwargs["headers"]["Content-Type"] = "application/json"
553
- return kwargs
268
+ self._http_handler._configurator.check_config()
269
+ self._http_handler._configurator.refresh_credentials()
@@ -151,7 +151,7 @@ class ClientDHCoreConfigurator(Configurator):
151
151
 
152
152
  Parameters
153
153
  ----------
154
- endpoint : str, optional
154
+ endpoint : str
155
155
  Endpoint URL to sanitize.
156
156
 
157
157
  Returns
@@ -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('/')}"