digitalhub 0.12.0__py3-none-any.whl → 0.13.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.

Files changed (79) hide show
  1. digitalhub/__init__.py +1 -1
  2. digitalhub/context/api.py +5 -5
  3. digitalhub/context/builder.py +3 -5
  4. digitalhub/context/context.py +9 -1
  5. digitalhub/entities/_base/executable/entity.py +105 -57
  6. digitalhub/entities/_base/material/entity.py +11 -18
  7. digitalhub/entities/_base/material/utils.py +1 -1
  8. digitalhub/entities/_commons/metrics.py +64 -30
  9. digitalhub/entities/_commons/utils.py +36 -9
  10. digitalhub/entities/_processors/base.py +150 -79
  11. digitalhub/entities/_processors/context.py +366 -215
  12. digitalhub/entities/_processors/utils.py +74 -30
  13. digitalhub/entities/artifact/crud.py +4 -0
  14. digitalhub/entities/artifact/utils.py +28 -13
  15. digitalhub/entities/dataitem/crud.py +14 -2
  16. digitalhub/entities/dataitem/table/entity.py +3 -3
  17. digitalhub/entities/dataitem/utils.py +84 -35
  18. digitalhub/entities/model/crud.py +4 -0
  19. digitalhub/entities/model/utils.py +28 -13
  20. digitalhub/entities/project/_base/entity.py +0 -2
  21. digitalhub/entities/run/_base/entity.py +2 -2
  22. digitalhub/entities/task/_base/models.py +12 -3
  23. digitalhub/entities/trigger/_base/entity.py +11 -0
  24. digitalhub/factory/factory.py +25 -3
  25. digitalhub/factory/utils.py +11 -3
  26. digitalhub/runtimes/_base.py +1 -1
  27. digitalhub/runtimes/builder.py +18 -1
  28. digitalhub/stores/client/__init__.py +12 -0
  29. digitalhub/stores/client/_base/api_builder.py +14 -0
  30. digitalhub/stores/client/_base/client.py +93 -0
  31. digitalhub/stores/client/_base/key_builder.py +28 -0
  32. digitalhub/stores/client/_base/params_builder.py +14 -0
  33. digitalhub/stores/client/api.py +10 -5
  34. digitalhub/stores/client/builder.py +3 -1
  35. digitalhub/stores/client/dhcore/api_builder.py +17 -0
  36. digitalhub/stores/client/dhcore/client.py +325 -70
  37. digitalhub/stores/client/dhcore/configurator.py +485 -193
  38. digitalhub/stores/client/dhcore/enums.py +3 -0
  39. digitalhub/stores/client/dhcore/error_parser.py +35 -1
  40. digitalhub/stores/client/dhcore/params_builder.py +113 -17
  41. digitalhub/stores/client/dhcore/utils.py +40 -22
  42. digitalhub/stores/client/local/api_builder.py +17 -0
  43. digitalhub/stores/client/local/client.py +6 -8
  44. digitalhub/stores/credentials/api.py +35 -0
  45. digitalhub/stores/credentials/configurator.py +210 -0
  46. digitalhub/stores/credentials/enums.py +68 -0
  47. digitalhub/stores/credentials/handler.py +176 -0
  48. digitalhub/stores/{configurator → credentials}/ini_module.py +60 -28
  49. digitalhub/stores/credentials/store.py +81 -0
  50. digitalhub/stores/data/_base/store.py +27 -9
  51. digitalhub/stores/data/api.py +49 -9
  52. digitalhub/stores/data/builder.py +90 -41
  53. digitalhub/stores/data/local/store.py +4 -7
  54. digitalhub/stores/data/remote/store.py +4 -7
  55. digitalhub/stores/data/s3/configurator.py +65 -80
  56. digitalhub/stores/data/s3/store.py +69 -81
  57. digitalhub/stores/data/s3/utils.py +10 -10
  58. digitalhub/stores/data/sql/configurator.py +76 -73
  59. digitalhub/stores/data/sql/store.py +191 -102
  60. digitalhub/utils/exceptions.py +6 -0
  61. digitalhub/utils/file_utils.py +53 -30
  62. digitalhub/utils/generic_utils.py +41 -33
  63. digitalhub/utils/git_utils.py +24 -14
  64. digitalhub/utils/io_utils.py +19 -18
  65. digitalhub/utils/uri_utils.py +31 -31
  66. {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/METADATA +1 -1
  67. {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/RECORD +71 -74
  68. digitalhub/entities/_commons/types.py +0 -9
  69. digitalhub/stores/configurator/api.py +0 -35
  70. digitalhub/stores/configurator/configurator.py +0 -202
  71. digitalhub/stores/configurator/credentials_store.py +0 -69
  72. digitalhub/stores/configurator/enums.py +0 -25
  73. digitalhub/stores/data/s3/enums.py +0 -20
  74. digitalhub/stores/data/sql/enums.py +0 -20
  75. digitalhub/stores/data/utils.py +0 -38
  76. /digitalhub/stores/{configurator → credentials}/__init__.py +0 -0
  77. {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/WHEEL +0 -0
  78. {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/licenses/AUTHORS +0 -0
  79. {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  import typing
8
8
  from typing import Any
9
+ from warnings import warn
9
10
 
10
11
  from requests import request
11
12
  from requests.exceptions import JSONDecodeError
@@ -16,30 +17,91 @@ from digitalhub.stores.client.dhcore.configurator import ClientDHCoreConfigurato
16
17
  from digitalhub.stores.client.dhcore.error_parser import ErrorParser
17
18
  from digitalhub.stores.client.dhcore.key_builder import ClientDHCoreKeyBuilder
18
19
  from digitalhub.stores.client.dhcore.params_builder import ClientDHCoreParametersBuilder
19
- from digitalhub.utils.exceptions import BackendError
20
+ from digitalhub.utils.exceptions import BackendError, ClientError
20
21
  from digitalhub.utils.generic_utils import dump_json
21
22
 
22
23
  if typing.TYPE_CHECKING:
23
24
  from requests import Response
24
25
 
25
26
 
27
+ # API levels that are supported
28
+ MAX_API_LEVEL = 20
29
+ MIN_API_LEVEL = 13
30
+ LIB_VERSION = 13
31
+
32
+
26
33
  class ClientDHCore(Client):
27
34
  """
28
- DHCore client.
29
-
30
- The DHCore client is used to communicate with the Digitalhub Core
31
- backendAPI via REST. The client supports basic authentication and
32
- OAuth2 token authentication with token refresh.
33
- At creation, the client tries to get the endpoint and authentication
34
- parameters from the .dhcore file and the environment variables. In
35
- case the user incours into an authentication/endpoint error during
36
- the client creation, the user has the possibility to update the
37
- correct parameters using the `set_dhcore_env` function. If the DHCore
38
- client is already initialized, this function will override the
39
- configuration, otherwise it simply set the environment variables.
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
40
85
  """
41
86
 
42
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
+ """
43
105
  super().__init__()
44
106
 
45
107
  # API builder
@@ -56,7 +118,6 @@ class ClientDHCore(Client):
56
118
 
57
119
  # Client Configurator
58
120
  self._configurator = ClientDHCoreConfigurator()
59
- self._configurator.configure(config)
60
121
 
61
122
  ##############################
62
123
  # CRUD methods
@@ -66,19 +127,31 @@ class ClientDHCore(Client):
66
127
  """
67
128
  Create an object in DHCore.
68
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
+
69
134
  Parameters
70
135
  ----------
71
136
  api : str
72
- Create API.
137
+ The API endpoint path for creating the object.
73
138
  obj : Any
74
- Object to create.
139
+ The object to create. Will be serialized to JSON.
75
140
  **kwargs : dict
76
- Keyword arguments to pass to the request.
141
+ Additional keyword arguments to pass to the HTTP request,
142
+ such as headers, params, etc.
77
143
 
78
144
  Returns
79
145
  -------
80
146
  dict
81
- Response object.
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.
82
155
  """
83
156
  if "headers" not in kwargs:
84
157
  kwargs["headers"] = {}
@@ -90,17 +163,27 @@ class ClientDHCore(Client):
90
163
  """
91
164
  Get an object from DHCore.
92
165
 
166
+ Sends a GET request to the DHCore backend to retrieve an existing object.
167
+
93
168
  Parameters
94
169
  ----------
95
170
  api : str
96
- Read API.
171
+ The API endpoint path for reading the object.
97
172
  **kwargs : dict
98
- Keyword arguments to pass to the request.
173
+ Additional keyword arguments to pass to the HTTP request,
174
+ such as headers, params, etc.
99
175
 
100
176
  Returns
101
177
  -------
102
178
  dict
103
- Response object.
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.
104
187
  """
105
188
  return self._prepare_call("GET", api, **kwargs)
106
189
 
@@ -108,19 +191,31 @@ class ClientDHCore(Client):
108
191
  """
109
192
  Update an object in DHCore.
110
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
+
111
198
  Parameters
112
199
  ----------
113
200
  api : str
114
- Update API.
115
- obj : dict
116
- Object to update.
201
+ The API endpoint path for updating the object.
202
+ obj : Any
203
+ The updated object data. Will be serialized to JSON.
117
204
  **kwargs : dict
118
- Keyword arguments to pass to the request.
205
+ Additional keyword arguments to pass to the HTTP request,
206
+ such as headers, params, etc.
119
207
 
120
208
  Returns
121
209
  -------
122
210
  dict
123
- Response object.
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.
124
219
  """
125
220
  if "headers" not in kwargs:
126
221
  kwargs["headers"] = {}
@@ -132,17 +227,30 @@ class ClientDHCore(Client):
132
227
  """
133
228
  Delete an object from DHCore.
134
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
+
135
234
  Parameters
136
235
  ----------
137
236
  api : str
138
- Delete API.
237
+ The API endpoint path for deleting the object.
139
238
  **kwargs : dict
140
- Keyword arguments to pass to the request.
239
+ Additional keyword arguments to pass to the HTTP request,
240
+ such as headers, params, cascade options, etc.
141
241
 
142
242
  Returns
143
243
  -------
144
244
  dict
145
- Response object.
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.
146
254
  """
147
255
  resp = self._prepare_call("DELETE", api, **kwargs)
148
256
  if isinstance(resp, bool):
@@ -153,21 +261,33 @@ class ClientDHCore(Client):
153
261
  """
154
262
  List objects from DHCore.
155
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
+
156
268
  Parameters
157
269
  ----------
158
270
  api : str
159
- List API.
271
+ The API endpoint path for listing objects.
160
272
  **kwargs : dict
161
- Keyword arguments to pass to the request.
273
+ Additional keyword arguments to pass to the HTTP request.
274
+ Can include 'params' dict with pagination parameters.
162
275
 
163
276
  Returns
164
277
  -------
165
278
  list[dict]
166
- Response objects.
167
- """
168
- if kwargs is None:
169
- kwargs = {}
279
+ A list containing all objects from all pages.
170
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
+ """
171
291
  if "params" not in kwargs:
172
292
  kwargs["params"] = {}
173
293
 
@@ -189,19 +309,27 @@ class ClientDHCore(Client):
189
309
 
190
310
  def list_first_object(self, api: str, **kwargs) -> dict:
191
311
  """
192
- List first objects.
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.
193
316
 
194
317
  Parameters
195
318
  ----------
196
319
  api : str
197
- The api to list the objects with.
320
+ The API endpoint path for listing objects.
198
321
  **kwargs : dict
199
- Keyword arguments passed to the request.
322
+ Additional keyword arguments to pass to the HTTP request.
200
323
 
201
324
  Returns
202
325
  -------
203
326
  dict
204
- The list of objects.
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.
205
333
  """
206
334
  try:
207
335
  return self.list_objects(api, **kwargs)[0]
@@ -212,21 +340,35 @@ class ClientDHCore(Client):
212
340
  """
213
341
  Search objects from DHCore.
214
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
+
215
347
  Parameters
216
348
  ----------
217
349
  api : str
218
- Search API.
350
+ The API endpoint path for searching objects (usually Solr search).
219
351
  **kwargs : dict
220
- Keyword arguments to pass to the request.
352
+ Additional keyword arguments to pass to the HTTP request.
353
+ Can include search parameters, filters, pagination options, etc.
221
354
 
222
355
  Returns
223
356
  -------
224
357
  list[dict]
225
- Response objects.
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.
226
371
  """
227
- if kwargs is None:
228
- kwargs = {}
229
-
230
372
  if "params" not in kwargs:
231
373
  kwargs["params"] = {}
232
374
 
@@ -266,73 +408,177 @@ class ClientDHCore(Client):
266
408
  """
267
409
  Prepare a call to the DHCore API.
268
410
 
411
+ Handles the preparation of an API call by checking configuration,
412
+ building the URL, and adding authentication parameters.
413
+
269
414
  Parameters
270
415
  ----------
271
416
  call_type : str
272
- The type of call to prepare.
417
+ The HTTP method type (GET, POST, PUT, DELETE, etc.).
273
418
  api : str
274
- The api to call.
419
+ The API endpoint path to call.
275
420
  **kwargs : dict
276
- Keyword arguments to pass to the request.
421
+ Additional keyword arguments to pass to the HTTP request.
277
422
 
278
423
  Returns
279
424
  -------
280
425
  dict
281
- Response object.
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.
282
434
  """
283
435
  self._configurator.check_config()
284
- if kwargs is None:
285
- kwargs = {}
286
- url = self._configurator.build_url(api)
287
- kwargs = self._configurator.set_request_auth(kwargs)
288
- return self._make_call(call_type, url, **kwargs)
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.
289
446
 
290
- def _make_call(self, call_type: str, url: str, refresh_token: bool = True, **kwargs) -> dict:
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:
291
466
  """
292
467
  Make a call to the DHCore API.
293
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
+
294
473
  Parameters
295
474
  ----------
296
475
  call_type : str
297
- The type of call to make.
476
+ The HTTP method type (GET, POST, PUT, DELETE, etc.).
298
477
  url : str
299
- The URL to call.
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.
300
482
  **kwargs : dict
301
- Keyword arguments to pass to the request.
483
+ Additional keyword arguments to pass to the HTTP request.
302
484
 
303
485
  Returns
304
486
  -------
305
487
  dict
306
- Response object.
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
307
506
  """
308
507
  # Call the API
309
508
  response = request(call_type, url, timeout=60, **kwargs)
310
509
 
311
510
  # Evaluate DHCore API version
312
- self._configurator.check_core_version(response)
511
+ self._check_core_version(response)
313
512
 
314
- # Handle token refresh
315
- if response.status_code in [401] and refresh_token and self._configurator.oauth2_auth():
316
- self._configurator.get_new_access_token()
317
- kwargs = self._configurator.set_request_auth(kwargs)
318
- return self._make_call(call_type, url, refresh_token=False, **kwargs)
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)
319
518
 
320
519
  self._error_parser.parse(response)
321
520
  return self._dictify_response(response)
322
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
+
323
556
  def _dictify_response(self, response: Response) -> dict:
324
557
  """
325
- Return dict from response.
558
+ Parse HTTP response to dictionary.
559
+
560
+ Converts the HTTP response body from JSON to a Python dictionary.
561
+ Handles empty responses gracefully.
326
562
 
327
563
  Parameters
328
564
  ----------
329
565
  response : Response
330
- The response object.
566
+ The HTTP response object to parse.
331
567
 
332
568
  Returns
333
569
  -------
334
570
  dict
335
- The parsed response object.
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.
336
582
  """
337
583
  try:
338
584
  return response.json()
@@ -348,11 +594,20 @@ class ClientDHCore(Client):
348
594
  @staticmethod
349
595
  def is_local() -> bool:
350
596
  """
351
- Declare if Client is local.
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.
352
601
 
353
602
  Returns
354
603
  -------
355
604
  bool
356
- False
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.
357
612
  """
358
613
  return False