digitalhub 0.13.0b3__py3-none-any.whl → 0.14.9__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.
Files changed (139) hide show
  1. digitalhub/__init__.py +3 -8
  2. digitalhub/context/api.py +43 -6
  3. digitalhub/context/builder.py +1 -5
  4. digitalhub/context/context.py +28 -13
  5. digitalhub/entities/_base/_base/entity.py +0 -15
  6. digitalhub/entities/_base/context/entity.py +1 -4
  7. digitalhub/entities/_base/entity/builder.py +5 -5
  8. digitalhub/entities/_base/entity/entity.py +0 -8
  9. digitalhub/entities/_base/executable/entity.py +195 -87
  10. digitalhub/entities/_base/material/entity.py +11 -23
  11. digitalhub/entities/_base/material/utils.py +28 -4
  12. digitalhub/entities/_base/runtime_entity/builder.py +53 -18
  13. digitalhub/entities/_base/unversioned/entity.py +1 -1
  14. digitalhub/entities/_base/versioned/entity.py +1 -1
  15. digitalhub/entities/_commons/enums.py +1 -31
  16. digitalhub/entities/_commons/metrics.py +64 -30
  17. digitalhub/entities/_commons/utils.py +119 -30
  18. digitalhub/entities/_constructors/_resources.py +151 -0
  19. digitalhub/entities/{_base/entity/_constructors → _constructors}/name.py +18 -0
  20. digitalhub/entities/_processors/base/crud.py +381 -0
  21. digitalhub/entities/_processors/base/import_export.py +118 -0
  22. digitalhub/entities/_processors/base/processor.py +299 -0
  23. digitalhub/entities/_processors/base/special_ops.py +104 -0
  24. digitalhub/entities/_processors/context/crud.py +652 -0
  25. digitalhub/entities/_processors/context/import_export.py +242 -0
  26. digitalhub/entities/_processors/context/material.py +123 -0
  27. digitalhub/entities/_processors/context/processor.py +400 -0
  28. digitalhub/entities/_processors/context/special_ops.py +476 -0
  29. digitalhub/entities/_processors/processors.py +12 -0
  30. digitalhub/entities/_processors/utils.py +38 -102
  31. digitalhub/entities/artifact/crud.py +58 -22
  32. digitalhub/entities/artifact/utils.py +28 -13
  33. digitalhub/entities/builders.py +2 -0
  34. digitalhub/entities/dataitem/crud.py +63 -20
  35. digitalhub/entities/dataitem/table/entity.py +27 -22
  36. digitalhub/entities/dataitem/utils.py +82 -32
  37. digitalhub/entities/function/_base/entity.py +3 -6
  38. digitalhub/entities/function/crud.py +55 -24
  39. digitalhub/entities/model/_base/entity.py +62 -20
  40. digitalhub/entities/model/crud.py +59 -23
  41. digitalhub/entities/model/mlflow/utils.py +29 -20
  42. digitalhub/entities/model/utils.py +28 -13
  43. digitalhub/entities/project/_base/builder.py +0 -6
  44. digitalhub/entities/project/_base/entity.py +337 -164
  45. digitalhub/entities/project/_base/spec.py +4 -4
  46. digitalhub/entities/project/crud.py +28 -71
  47. digitalhub/entities/project/utils.py +7 -3
  48. digitalhub/entities/run/_base/builder.py +0 -4
  49. digitalhub/entities/run/_base/entity.py +70 -63
  50. digitalhub/entities/run/crud.py +79 -26
  51. digitalhub/entities/secret/_base/entity.py +1 -5
  52. digitalhub/entities/secret/crud.py +31 -28
  53. digitalhub/entities/task/_base/builder.py +0 -4
  54. digitalhub/entities/task/_base/entity.py +5 -5
  55. digitalhub/entities/task/_base/models.py +13 -16
  56. digitalhub/entities/task/crud.py +61 -29
  57. digitalhub/entities/trigger/_base/entity.py +1 -5
  58. digitalhub/entities/trigger/crud.py +89 -30
  59. digitalhub/entities/workflow/_base/entity.py +3 -8
  60. digitalhub/entities/workflow/crud.py +55 -24
  61. digitalhub/factory/entity.py +283 -0
  62. digitalhub/factory/enums.py +18 -0
  63. digitalhub/factory/registry.py +197 -0
  64. digitalhub/factory/runtime.py +44 -0
  65. digitalhub/factory/utils.py +3 -54
  66. digitalhub/runtimes/_base.py +2 -2
  67. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +3 -3
  68. digitalhub/stores/client/builder.py +19 -31
  69. digitalhub/stores/client/client.py +322 -0
  70. digitalhub/stores/client/configurator.py +408 -0
  71. digitalhub/stores/client/enums.py +50 -0
  72. digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -4
  73. digitalhub/stores/client/header_manager.py +61 -0
  74. digitalhub/stores/client/http_handler.py +152 -0
  75. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
  76. digitalhub/stores/client/params_builder.py +330 -0
  77. digitalhub/stores/client/response_processor.py +102 -0
  78. digitalhub/stores/client/utils.py +35 -0
  79. digitalhub/stores/{credentials → configurator}/api.py +5 -9
  80. digitalhub/stores/configurator/configurator.py +123 -0
  81. digitalhub/stores/{credentials → configurator}/enums.py +27 -10
  82. digitalhub/stores/configurator/handler.py +213 -0
  83. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -22
  84. digitalhub/stores/data/_base/store.py +0 -20
  85. digitalhub/stores/data/api.py +5 -7
  86. digitalhub/stores/data/builder.py +53 -27
  87. digitalhub/stores/data/local/store.py +0 -103
  88. digitalhub/stores/data/remote/store.py +0 -4
  89. digitalhub/stores/data/s3/configurator.py +39 -77
  90. digitalhub/stores/data/s3/store.py +57 -37
  91. digitalhub/stores/data/sql/configurator.py +66 -46
  92. digitalhub/stores/data/sql/store.py +171 -104
  93. digitalhub/stores/readers/data/factory.py +0 -8
  94. digitalhub/stores/readers/data/pandas/reader.py +9 -19
  95. digitalhub/utils/file_utils.py +0 -17
  96. digitalhub/utils/generic_utils.py +1 -14
  97. digitalhub/utils/git_utils.py +0 -8
  98. digitalhub/utils/io_utils.py +0 -12
  99. digitalhub/utils/store_utils.py +44 -0
  100. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/METADATA +5 -4
  101. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/RECORD +112 -113
  102. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
  103. digitalhub/entities/_commons/types.py +0 -9
  104. digitalhub/entities/_processors/base.py +0 -531
  105. digitalhub/entities/_processors/context.py +0 -1299
  106. digitalhub/entities/task/_base/utils.py +0 -22
  107. digitalhub/factory/factory.py +0 -381
  108. digitalhub/stores/client/_base/api_builder.py +0 -34
  109. digitalhub/stores/client/_base/client.py +0 -243
  110. digitalhub/stores/client/_base/params_builder.py +0 -34
  111. digitalhub/stores/client/api.py +0 -36
  112. digitalhub/stores/client/dhcore/client.py +0 -613
  113. digitalhub/stores/client/dhcore/configurator.py +0 -675
  114. digitalhub/stores/client/dhcore/enums.py +0 -34
  115. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  116. digitalhub/stores/client/dhcore/models.py +0 -40
  117. digitalhub/stores/client/dhcore/params_builder.py +0 -278
  118. digitalhub/stores/client/dhcore/utils.py +0 -94
  119. digitalhub/stores/client/local/api_builder.py +0 -116
  120. digitalhub/stores/client/local/client.py +0 -573
  121. digitalhub/stores/client/local/enums.py +0 -15
  122. digitalhub/stores/client/local/key_builder.py +0 -62
  123. digitalhub/stores/client/local/params_builder.py +0 -120
  124. digitalhub/stores/credentials/__init__.py +0 -3
  125. digitalhub/stores/credentials/configurator.py +0 -210
  126. digitalhub/stores/credentials/handler.py +0 -176
  127. digitalhub/stores/credentials/store.py +0 -81
  128. digitalhub/stores/data/enums.py +0 -15
  129. digitalhub/stores/data/s3/utils.py +0 -78
  130. /digitalhub/entities/{_base/entity/_constructors → _constructors}/__init__.py +0 -0
  131. /digitalhub/entities/{_base/entity/_constructors → _constructors}/metadata.py +0 -0
  132. /digitalhub/entities/{_base/entity/_constructors → _constructors}/spec.py +0 -0
  133. /digitalhub/entities/{_base/entity/_constructors → _constructors}/status.py +0 -0
  134. /digitalhub/entities/{_base/entity/_constructors → _constructors}/uuid.py +0 -0
  135. /digitalhub/{stores/client/_base → entities/_processors/base}/__init__.py +0 -0
  136. /digitalhub/{stores/client/dhcore → entities/_processors/context}/__init__.py +0 -0
  137. /digitalhub/stores/{client/local → configurator}/__init__.py +0 -0
  138. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
  139. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,36 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from __future__ import annotations
6
-
7
- import typing
8
-
9
- from digitalhub.stores.client.builder import client_builder
10
-
11
- if typing.TYPE_CHECKING:
12
- from digitalhub.stores.client._base.client import Client
13
-
14
-
15
- def get_client(local: bool = False, config: dict | None = None) -> Client:
16
- """
17
- Wrapper around ClientBuilder.build.
18
-
19
- Parameters
20
- ----------
21
- local : bool, default False
22
- Whether to create a local client or not. If True, creates a
23
- ClientLocal instance that operates in-memory. If False, creates
24
- a ClientDHCore instance that communicates with a remote backend.
25
- config : dict, optional
26
- DHCore environment configuration. Only used when local=False.
27
- If None, configuration will be loaded from environment variables
28
- and configuration files.
29
-
30
- Returns
31
- -------
32
- Client
33
- The client instance. Either ClientLocal or ClientDHCore depending
34
- on the local parameter.
35
- """
36
- return client_builder.build(local, config)
@@ -1,613 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from __future__ import annotations
6
-
7
- import typing
8
- from typing import Any
9
- from warnings import warn
10
-
11
- from requests import request
12
- from requests.exceptions import JSONDecodeError
13
-
14
- from digitalhub.stores.client._base.client import Client
15
- 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
18
- from digitalhub.stores.client.dhcore.key_builder import ClientDHCoreKeyBuilder
19
- from digitalhub.stores.client.dhcore.params_builder import ClientDHCoreParametersBuilder
20
- from digitalhub.utils.exceptions import BackendError, ClientError
21
- from digitalhub.utils.generic_utils import dump_json
22
-
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 = 13
30
- LIB_VERSION = 13
31
-
32
-
33
- class ClientDHCore(Client):
34
- """
35
- DHCore client for remote DigitalHub Core backend communication.
36
-
37
- The DHCore client is used to communicate with the DigitalHub Core
38
- backend API via REST. The client supports multiple authentication methods:
39
- - Basic authentication (username/password)
40
- - OAuth2 token authentication with automatic token refresh
41
- - Personal access token exchange
42
-
43
- At initialization, the client attempts to load endpoint and authentication
44
- parameters from environment variables and the .dhcore configuration file.
45
- If authentication or endpoint errors occur during client creation, users
46
- can update the configuration using the `set_dhcore_env` function from
47
- the utils module.
48
-
49
- The client automatically handles:
50
- - API version compatibility checking
51
- - Pagination for list operations
52
- - Token refresh on authentication errors
53
- - Error parsing and exception mapping
54
- - JSON serialization/deserialization
55
-
56
- Parameters
57
- ----------
58
- config : dict, optional
59
- DHCore environment configuration. If None, configuration will
60
- be loaded from environment variables and configuration files.
61
-
62
- Attributes
63
- ----------
64
- _api_builder : ClientDHCoreApiBuilder
65
- Builds API endpoint URLs for different operations.
66
- _key_builder : ClientDHCoreKeyBuilder
67
- Builds storage keys for entities.
68
- _params_builder : ClientDHCoreParametersBuilder
69
- Builds request parameters for API calls.
70
- _error_parser : ErrorParser
71
- Parses backend responses and raises appropriate exceptions.
72
- _configurator : ClientDHCoreConfigurator
73
- Manages client configuration and authentication.
74
-
75
- Notes
76
- -----
77
- Supported DHCore API versions: {MIN_API_LEVEL} to {MAX_API_LEVEL}
78
- Current library API version: {LIB_VERSION}
79
-
80
- Examples
81
- --------
82
- >>> from digitalhub.stores.client.api import get_client
83
- >>> client = get_client(local=False)
84
- >>> # Client is now ready for API operations
85
- """
86
-
87
- def __init__(self, config: dict | None = None) -> None:
88
- """
89
- Initialize DHCore client.
90
-
91
- Creates a new DHCore client instance with all necessary components
92
- for communicating with the DigitalHub Core backend. Sets up API
93
- builders, configurators, and error handling.
94
-
95
- Parameters
96
- ----------
97
- config : dict, optional
98
- DHCore environment configuration. If None, configuration will
99
- be loaded from environment variables and configuration files.
100
-
101
- Returns
102
- -------
103
- None
104
- """
105
- super().__init__()
106
-
107
- # API builder
108
- self._api_builder = ClientDHCoreApiBuilder()
109
-
110
- # Key builder
111
- self._key_builder = ClientDHCoreKeyBuilder()
112
-
113
- # Parameters builder
114
- self._params_builder = ClientDHCoreParametersBuilder()
115
-
116
- # Error parser
117
- self._error_parser = ErrorParser()
118
-
119
- # Client Configurator
120
- self._configurator = ClientDHCoreConfigurator()
121
-
122
- ##############################
123
- # CRUD methods
124
- ##############################
125
-
126
- def create_object(self, api: str, obj: Any, **kwargs) -> dict:
127
- """
128
- Create an object in DHCore.
129
-
130
- Sends a POST request to the DHCore backend to create a new object.
131
- Automatically sets the appropriate Content-Type header and serializes
132
- the object to JSON format.
133
-
134
- Parameters
135
- ----------
136
- api : str
137
- The API endpoint path for creating the object.
138
- obj : Any
139
- The object to create. Will be serialized to JSON.
140
- **kwargs : dict
141
- Additional keyword arguments to pass to the HTTP request,
142
- such as headers, params, etc.
143
-
144
- Returns
145
- -------
146
- dict
147
- The created object as returned by the backend.
148
-
149
- Raises
150
- ------
151
- BackendError
152
- If the backend returns an error response.
153
- ClientError
154
- If there are client-side configuration issues.
155
- """
156
- if "headers" not in kwargs:
157
- kwargs["headers"] = {}
158
- kwargs["headers"]["Content-Type"] = "application/json"
159
- kwargs["data"] = dump_json(obj)
160
- return self._prepare_call("POST", api, **kwargs)
161
-
162
- def read_object(self, api: str, **kwargs) -> dict:
163
- """
164
- Get an object from DHCore.
165
-
166
- Sends a GET request to the DHCore backend to retrieve an existing object.
167
-
168
- Parameters
169
- ----------
170
- api : str
171
- The API endpoint path for reading the object.
172
- **kwargs : dict
173
- Additional keyword arguments to pass to the HTTP request,
174
- such as headers, params, etc.
175
-
176
- Returns
177
- -------
178
- dict
179
- The retrieved object as returned by the backend.
180
-
181
- Raises
182
- ------
183
- BackendError
184
- If the backend returns an error response.
185
- EntityNotExistsError
186
- If the requested object does not exist.
187
- """
188
- return self._prepare_call("GET", api, **kwargs)
189
-
190
- def update_object(self, api: str, obj: Any, **kwargs) -> dict:
191
- """
192
- Update an object in DHCore.
193
-
194
- Sends a PUT request to the DHCore backend to update an existing object.
195
- Automatically sets the appropriate Content-Type header and serializes
196
- the object to JSON format.
197
-
198
- Parameters
199
- ----------
200
- api : str
201
- The API endpoint path for updating the object.
202
- obj : Any
203
- The updated object data. Will be serialized to JSON.
204
- **kwargs : dict
205
- Additional keyword arguments to pass to the HTTP request,
206
- such as headers, params, etc.
207
-
208
- Returns
209
- -------
210
- dict
211
- The updated object as returned by the backend.
212
-
213
- Raises
214
- ------
215
- BackendError
216
- If the backend returns an error response.
217
- EntityNotExistsError
218
- If the object to update does not exist.
219
- """
220
- if "headers" not in kwargs:
221
- kwargs["headers"] = {}
222
- kwargs["headers"]["Content-Type"] = "application/json"
223
- kwargs["data"] = dump_json(obj)
224
- return self._prepare_call("PUT", api, **kwargs)
225
-
226
- def delete_object(self, api: str, **kwargs) -> dict:
227
- """
228
- Delete an object from DHCore.
229
-
230
- Sends a DELETE request to the DHCore backend to remove an object.
231
- If the backend returns a boolean response, it will be wrapped in
232
- a dictionary with a "deleted" key.
233
-
234
- Parameters
235
- ----------
236
- api : str
237
- The API endpoint path for deleting the object.
238
- **kwargs : dict
239
- Additional keyword arguments to pass to the HTTP request,
240
- such as headers, params, cascade options, etc.
241
-
242
- Returns
243
- -------
244
- dict
245
- The deletion result. Either the backend response or
246
- {"deleted": True/False} if backend returns a boolean.
247
-
248
- Raises
249
- ------
250
- BackendError
251
- If the backend returns an error response.
252
- EntityNotExistsError
253
- If the object to delete does not exist.
254
- """
255
- resp = self._prepare_call("DELETE", api, **kwargs)
256
- if isinstance(resp, bool):
257
- resp = {"deleted": resp}
258
- return resp
259
-
260
- def list_objects(self, api: str, **kwargs) -> list[dict]:
261
- """
262
- List objects from DHCore.
263
-
264
- Sends GET requests to the DHCore backend to retrieve a paginated list
265
- of objects. Automatically handles pagination by making multiple requests
266
- until all objects are retrieved.
267
-
268
- Parameters
269
- ----------
270
- api : str
271
- The API endpoint path for listing objects.
272
- **kwargs : dict
273
- Additional keyword arguments to pass to the HTTP request.
274
- Can include 'params' dict with pagination parameters.
275
-
276
- Returns
277
- -------
278
- list[dict]
279
- A list containing all objects from all pages.
280
-
281
- Raises
282
- ------
283
- BackendError
284
- If the backend returns an error response.
285
-
286
- Notes
287
- -----
288
- This method automatically handles pagination starting from page 0
289
- and continues until all pages are retrieved.
290
- """
291
- if "params" not in kwargs:
292
- kwargs["params"] = {}
293
-
294
- start_page = 0
295
- if "page" not in kwargs["params"]:
296
- kwargs["params"]["page"] = start_page
297
-
298
- objects = []
299
- while True:
300
- resp = self._prepare_call("GET", api, **kwargs)
301
- contents = resp["content"]
302
- total_pages = resp["totalPages"]
303
- if not contents or kwargs["params"]["page"] >= total_pages:
304
- break
305
- objects.extend(contents)
306
- kwargs["params"]["page"] += 1
307
-
308
- return objects
309
-
310
- def list_first_object(self, api: str, **kwargs) -> dict:
311
- """
312
- Get the first object from a list in DHCore.
313
-
314
- Retrieves the first object from a paginated list by calling
315
- list_objects and returning the first item.
316
-
317
- Parameters
318
- ----------
319
- api : str
320
- The API endpoint path for listing objects.
321
- **kwargs : dict
322
- Additional keyword arguments to pass to the HTTP request.
323
-
324
- Returns
325
- -------
326
- dict
327
- The first object from the list.
328
-
329
- Raises
330
- ------
331
- BackendError
332
- If no objects are found or if the backend returns an error.
333
- """
334
- try:
335
- return self.list_objects(api, **kwargs)[0]
336
- except IndexError:
337
- raise BackendError("No object found.")
338
-
339
- def search_objects(self, api: str, **kwargs) -> list[dict]:
340
- """
341
- Search objects from DHCore.
342
-
343
- Performs a search query against the DHCore backend using Solr search
344
- capabilities. Handles pagination and removes search highlights from
345
- the returned objects.
346
-
347
- Parameters
348
- ----------
349
- api : str
350
- The API endpoint path for searching objects (usually Solr search).
351
- **kwargs : dict
352
- Additional keyword arguments to pass to the HTTP request.
353
- Can include search parameters, filters, pagination options, etc.
354
-
355
- Returns
356
- -------
357
- list[dict]
358
- A list of objects matching the search criteria, with search
359
- highlights removed.
360
-
361
- Raises
362
- ------
363
- BackendError
364
- If the backend returns an error response.
365
-
366
- Notes
367
- -----
368
- This method sets default values for pagination (page=0, size=10)
369
- and sorting (by metadata.updated descending) if not provided.
370
- Search highlights are automatically removed from results.
371
- """
372
- if "params" not in kwargs:
373
- kwargs["params"] = {}
374
-
375
- start_page = 0
376
- if "page" not in kwargs["params"]:
377
- kwargs["params"]["page"] = start_page
378
-
379
- if "size" not in kwargs["params"]:
380
- kwargs["params"]["size"] = 10
381
-
382
- # Add sorting
383
- if "sort" not in kwargs["params"]:
384
- kwargs["params"]["sort"] = "metadata.updated,DESC"
385
-
386
- objects_with_highlights: list[dict] = []
387
- while True:
388
- resp = self._prepare_call("GET", api, **kwargs)
389
- contents = resp["content"]
390
- total_pages = resp["totalPages"]
391
- if not contents or kwargs["params"]["page"] >= total_pages:
392
- break
393
- objects_with_highlights.extend(contents)
394
- kwargs["params"]["page"] += 1
395
-
396
- objects = []
397
- for obj in objects_with_highlights:
398
- obj.pop("highlights", None)
399
- objects.append(obj)
400
-
401
- return objects
402
-
403
- ##############################
404
- # Call methods
405
- ##############################
406
-
407
- def _prepare_call(self, call_type: str, api: str, **kwargs) -> dict:
408
- """
409
- Prepare a call to the DHCore API.
410
-
411
- Handles the preparation of an API call by checking configuration,
412
- building the URL, and adding authentication parameters.
413
-
414
- Parameters
415
- ----------
416
- call_type : str
417
- The HTTP method type (GET, POST, PUT, DELETE, etc.).
418
- api : str
419
- The API endpoint path to call.
420
- **kwargs : dict
421
- Additional keyword arguments to pass to the HTTP request.
422
-
423
- Returns
424
- -------
425
- dict
426
- The response from the API call.
427
-
428
- Raises
429
- ------
430
- ClientError
431
- If the client configuration is invalid.
432
- BackendError
433
- If the backend returns an error response.
434
- """
435
- self._configurator.check_config()
436
- url = self._build_url(api)
437
- full_kwargs = self._configurator.get_auth_parameters(kwargs)
438
- return self._make_call(call_type, url, **full_kwargs)
439
-
440
- def _build_url(self, api: str) -> str:
441
- """
442
- Build the complete URL for an API call.
443
-
444
- Combines the configured endpoint with the API path to create
445
- the full URL for the HTTP request.
446
-
447
- Parameters
448
- ----------
449
- api : str
450
- The API endpoint path. Leading slashes are automatically handled.
451
-
452
- Returns
453
- -------
454
- str
455
- The complete URL for the API call.
456
-
457
- Notes
458
- -----
459
- This method automatically removes leading slashes from the API path
460
- to ensure proper URL construction.
461
- """
462
- endpoint = self._configurator.get_endpoint()
463
- return f"{endpoint}/{api.removeprefix('/')}"
464
-
465
- def _make_call(self, call_type: str, url: str, refresh: bool = True, **kwargs) -> dict:
466
- """
467
- Make a call to the DHCore API.
468
-
469
- Executes the actual HTTP request to the DHCore backend, handles
470
- API version checking, automatic token refresh on 401 errors,
471
- and error parsing.
472
-
473
- Parameters
474
- ----------
475
- call_type : str
476
- The HTTP method type (GET, POST, PUT, DELETE, etc.).
477
- url : str
478
- The complete URL to call.
479
- refresh : bool, default True
480
- Whether to attempt token refresh on authentication errors.
481
- Set to False to prevent infinite recursion during refresh.
482
- **kwargs : dict
483
- Additional keyword arguments to pass to the HTTP request.
484
-
485
- Returns
486
- -------
487
- dict
488
- The parsed response from the backend as a dictionary.
489
-
490
- Raises
491
- ------
492
- ClientError
493
- If the backend API version is not supported.
494
- BackendError
495
- If the backend returns an error response or response parsing fails.
496
- UnauthorizedError
497
- If authentication fails and token refresh is not possible.
498
-
499
- Notes
500
- -----
501
- This method automatically handles:
502
- - API version compatibility checking
503
- - OAuth2 token refresh on 401 errors
504
- - Response parsing and error handling
505
- - 60-second timeout for all requests
506
- """
507
- # Call the API
508
- response = request(call_type, url, timeout=60, **kwargs)
509
-
510
- # Evaluate DHCore API version
511
- self._check_core_version(response)
512
-
513
- # Handle token refresh (redo call)
514
- if (response.status_code in [401]) and (refresh) and self._configurator.refreshable_auth_types():
515
- self._configurator.refresh_credentials(change_origin=True)
516
- kwargs = self._configurator.get_auth_parameters(kwargs)
517
- return self._make_call(call_type, url, refresh=False, **kwargs)
518
-
519
- self._error_parser.parse(response)
520
- return self._dictify_response(response)
521
-
522
- def _check_core_version(self, response: Response) -> None:
523
- """
524
- Check DHCore API version compatibility.
525
-
526
- Validates that the DHCore backend API version is compatible with
527
- this client library. Issues warnings if the backend version is
528
- newer than the library version.
529
-
530
- Parameters
531
- ----------
532
- response : Response
533
- The HTTP response object containing the X-Api-Level header.
534
-
535
- Returns
536
- -------
537
- None
538
-
539
- Raises
540
- ------
541
- ClientError
542
- If the backend API level is not supported by this client.
543
-
544
- Notes
545
- -----
546
- Supported API levels: {MIN_API_LEVEL} to {MAX_API_LEVEL}
547
- Current library version: {LIB_VERSION}
548
- """
549
- if "X-Api-Level" in response.headers:
550
- core_api_level = int(response.headers["X-Api-Level"])
551
- if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
552
- raise ClientError("Backend API level not supported.")
553
- if LIB_VERSION < core_api_level:
554
- warn("Backend API level is higher than library version. You should consider updating the library.")
555
-
556
- def _dictify_response(self, response: Response) -> dict:
557
- """
558
- Parse HTTP response to dictionary.
559
-
560
- Converts the HTTP response body from JSON to a Python dictionary.
561
- Handles empty responses gracefully.
562
-
563
- Parameters
564
- ----------
565
- response : Response
566
- The HTTP response object to parse.
567
-
568
- Returns
569
- -------
570
- dict
571
- The parsed response body as a dictionary. Returns empty dict
572
- if response body is empty.
573
-
574
- Raises
575
- ------
576
- BackendError
577
- If the response cannot be parsed as JSON.
578
-
579
- Notes
580
- -----
581
- Empty response bodies are treated as valid and return an empty dict.
582
- """
583
- try:
584
- return response.json()
585
- except JSONDecodeError:
586
- if response.text == "":
587
- return {}
588
- raise BackendError("Backend response could not be parsed.")
589
-
590
- ##############################
591
- # Interface methods
592
- ##############################
593
-
594
- @staticmethod
595
- def is_local() -> bool:
596
- """
597
- Check if this client operates locally.
598
-
599
- Returns a flag indicating whether this client instance operates
600
- on local data or communicates with a remote backend.
601
-
602
- Returns
603
- -------
604
- bool
605
- False, indicating this client communicates with a remote
606
- DHCore backend, not local storage.
607
-
608
- Notes
609
- -----
610
- This method is used to distinguish between ClientDHCore (returns False)
611
- and ClientLocal (returns True) implementations.
612
- """
613
- return False