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
@@ -4,9 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from abc import abstractmethod
8
-
9
- from digitalhub.entities._commons.enums import ApiCategories
7
+ from digitalhub.stores.client.enums import ApiCategories
10
8
 
11
9
 
12
10
  class ClientKeyBuilder:
@@ -36,7 +34,6 @@ class ClientKeyBuilder:
36
34
  return self.base_entity_key(*args, **kwargs)
37
35
  return self.context_entity_key(*args, **kwargs)
38
36
 
39
- @abstractmethod
40
37
  def base_entity_key(self, entity_id: str) -> str:
41
38
  """
42
39
  Build for base entity key.
@@ -44,15 +41,15 @@ class ClientKeyBuilder:
44
41
  Parameters
45
42
  ----------
46
43
  entity_id : str
47
- The entity identifier.
44
+ Entity id.
48
45
 
49
46
  Returns
50
47
  -------
51
48
  str
52
- The formatted base entity key.
49
+ Key.
53
50
  """
51
+ return f"store://{entity_id}"
54
52
 
55
- @abstractmethod
56
53
  def context_entity_key(
57
54
  self,
58
55
  project: str,
@@ -67,18 +64,21 @@ class ClientKeyBuilder:
67
64
  Parameters
68
65
  ----------
69
66
  project : str
70
- The project name.
67
+ Project name.
71
68
  entity_type : str
72
- The entity type.
69
+ Entity type.
73
70
  entity_kind : str
74
- The entity kind.
71
+ Entity kind.
75
72
  entity_name : str
76
- The entity name.
77
- entity_id : str, optional
78
- The entity identifier. If None, key will not include version.
73
+ Entity name.
74
+ entity_id : str
75
+ Entity ID.
79
76
 
80
77
  Returns
81
78
  -------
82
79
  str
83
- The formatted context entity key.
80
+ Key.
84
81
  """
82
+ if entity_id is None:
83
+ return f"store://{project}/{entity_type}/{entity_kind}/{entity_name}"
84
+ return f"store://{project}/{entity_type}/{entity_kind}/{entity_name}:{entity_id}"
@@ -0,0 +1,330 @@
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 typing import Any
8
+
9
+ from digitalhub.stores.client.enums import ApiCategories, BackendOperations
10
+
11
+ DEFAULT_START_PAGE = 0
12
+ DEFAULT_SIZE = 25
13
+ DEFAULT_SORT = "metadata.updated,DESC"
14
+
15
+
16
+ class ClientParametersBuilder:
17
+ """
18
+ Parameter builder for DHCore client API calls.
19
+
20
+ Constructs HTTP request parameters for DHCore API operations, handling
21
+ parameter formats and query structures for both base-level operations
22
+ (project management) and context-level operations (entity operations
23
+ within projects). Supports query parameter formatting, search filter
24
+ construction for Solr-based searches, cascade deletion options,
25
+ versioning parameters, and entity sharing parameters.
26
+ """
27
+
28
+ def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
29
+ """
30
+ Build HTTP request parameters for DHCore API calls.
31
+
32
+ Parameters
33
+ ----------
34
+ category : str
35
+ API category: 'base' for project-level operations or 'context'
36
+ for entity operations within projects.
37
+ operation : str
38
+ Specific API operation (create, read, update, delete, list, search, etc.).
39
+ **kwargs : dict
40
+ Raw parameters to transform including entity identifiers, filter
41
+ criteria, pagination options, etc.
42
+
43
+ Returns
44
+ -------
45
+ dict
46
+ Formatted parameters dictionary with 'params' key for query parameters
47
+ and other request-specific parameters.
48
+ """
49
+ if category == ApiCategories.BASE.value:
50
+ return self.build_parameters_base(operation, **kwargs)
51
+ return self.build_parameters_context(operation, **kwargs)
52
+
53
+ def build_parameters_base(self, operation: str, **kwargs) -> dict:
54
+ """
55
+ Constructs HTTP request parameters for project operations.
56
+
57
+ Parameters
58
+ ----------
59
+ operation : str
60
+ API operation.
61
+ **kwargs : dict
62
+ Operation-specific parameters.
63
+ Returns
64
+ -------
65
+ dict
66
+ Formatted parameters with 'params' containing query parameters.
67
+ """
68
+ kwargs = self._ensure_params(**kwargs)
69
+
70
+ # Handle delete
71
+ if operation == BackendOperations.DELETE.value:
72
+ if (cascade := kwargs.pop("cascade", None)) is not None:
73
+ kwargs = self._add_param("cascade", str(cascade).lower(), **kwargs)
74
+
75
+ # Handle share
76
+ elif operation == BackendOperations.SHARE.value:
77
+ kwargs = self._add_param("user", kwargs.pop("user"), **kwargs)
78
+ if kwargs.pop("unshare", False):
79
+ kwargs = self._add_param("id", kwargs.pop("id"), **kwargs)
80
+
81
+ return kwargs
82
+
83
+ def build_parameters_context(self, operation: str, **kwargs) -> dict:
84
+ """
85
+ Constructs HTTP request parameters for entity management and search within
86
+ projects.
87
+
88
+ Parameters
89
+ ----------
90
+ operation : str
91
+ API operation.
92
+ **kwargs : dict
93
+ Operation-specific parameters.
94
+
95
+ Returns
96
+ -------
97
+ dict
98
+ Formatted parameters with 'params'.
99
+ """
100
+ kwargs = self._ensure_params(**kwargs)
101
+
102
+ # Handle read
103
+ if operation == BackendOperations.READ.value:
104
+ if (name := kwargs.pop("name", None)) is not None:
105
+ kwargs = self._add_param("name", name, **kwargs)
106
+
107
+ # Handle read all versions
108
+ elif operation == BackendOperations.READ_ALL_VERSIONS.value:
109
+ kwargs = self._add_param("versions", "all", **kwargs)
110
+ kwargs = self._add_param("name", kwargs.pop("name"), **kwargs)
111
+
112
+ # Handle list
113
+ elif operation == BackendOperations.LIST.value:
114
+ possible_list_params = [
115
+ "q",
116
+ "name",
117
+ "kind",
118
+ "user",
119
+ "state",
120
+ "created",
121
+ "updated",
122
+ "versions",
123
+ "function",
124
+ "workflow",
125
+ "action",
126
+ "task",
127
+ ]
128
+ list_params = {k: kwargs.get(k, None) for k in possible_list_params}
129
+ list_params = self._filter_none_params(**list_params)
130
+ for k, v in list_params.items():
131
+ kwargs = self._add_param(k, v, **kwargs)
132
+ for k in possible_list_params:
133
+ kwargs.pop(k, None)
134
+
135
+ # Handle delete
136
+ elif operation == BackendOperations.DELETE.value:
137
+ if (cascade := kwargs.pop("cascade", None)) is not None:
138
+ kwargs = self._add_param("cascade", str(cascade).lower(), **kwargs)
139
+
140
+ elif operation == BackendOperations.DELETE_ALL_VERSIONS.value:
141
+ if (cascade := kwargs.pop("cascade", None)) is not None:
142
+ kwargs = self._add_param("cascade", str(cascade).lower(), **kwargs)
143
+ kwargs = self._add_param("name", kwargs.pop("name"), **kwargs)
144
+
145
+ # Handle search
146
+ elif operation == BackendOperations.SEARCH.value:
147
+ # Handle fq
148
+ if (fq := kwargs.pop("fq", None)) is not None:
149
+ kwargs = self._add_param("fq", fq, **kwargs)
150
+
151
+ # Add search query
152
+ if (query := kwargs.pop("query", None)) is not None:
153
+ kwargs = self._add_param("q", query, **kwargs)
154
+
155
+ # Add search filters
156
+ fq = []
157
+
158
+ # Entity types
159
+ if (entity_types := kwargs.pop("entity_types", None)) is not None:
160
+ if not isinstance(entity_types, list):
161
+ entity_types = [entity_types]
162
+ if len(entity_types) == 1:
163
+ entity_types = entity_types[0]
164
+ else:
165
+ entity_types = " OR ".join(entity_types)
166
+ fq.append(f"type:({entity_types})")
167
+
168
+ # Name
169
+ if (name := kwargs.pop("name", None)) is not None:
170
+ fq.append(f'metadata.name:"{name}"')
171
+
172
+ # Kind
173
+ if (kind := kwargs.pop("kind", None)) is not None:
174
+ fq.append(f'kind:"{kind}"')
175
+
176
+ # Time
177
+ created = kwargs.pop("created", None)
178
+ updated = kwargs.pop("updated", None)
179
+ created = created if created is not None else "*"
180
+ updated = updated if updated is not None else "*"
181
+ fq.append(f"metadata.updated:[{created} TO {updated}]")
182
+
183
+ # Description
184
+ if (description := kwargs.pop("description", None)) is not None:
185
+ fq.append(f'metadata.description:"{description}"')
186
+
187
+ # Labels
188
+ if (labels := kwargs.pop("labels", None)) is not None:
189
+ if len(labels) == 1:
190
+ labels = labels[0]
191
+ else:
192
+ labels = " AND ".join(labels)
193
+ fq.append(f"metadata.labels:({labels})")
194
+
195
+ # Add filters
196
+ kwargs = self._add_param("fq", fq, **kwargs)
197
+
198
+ return kwargs
199
+
200
+ def set_pagination(self, partial: bool = False, **kwargs) -> dict:
201
+ """
202
+ Ensure pagination parameters are set in kwargs.
203
+
204
+ Parameters
205
+ ----------
206
+ **kwargs : dict
207
+ Keyword arguments to format. May be empty or contain various
208
+ parameters for API operations.
209
+
210
+ Returns
211
+ -------
212
+ dict
213
+ Pagination parameters set in 'params' of kwargs.
214
+ """
215
+ kwargs = self._ensure_params(**kwargs)
216
+
217
+ if "page" not in kwargs["params"]:
218
+ kwargs["params"]["page"] = DEFAULT_START_PAGE
219
+
220
+ if partial:
221
+ return kwargs
222
+
223
+ if "size" not in kwargs["params"]:
224
+ kwargs["params"]["size"] = DEFAULT_SIZE
225
+
226
+ if "sort" not in kwargs["params"]:
227
+ kwargs["params"]["sort"] = DEFAULT_SORT
228
+
229
+ return kwargs
230
+
231
+ @staticmethod
232
+ def _ensure_params(**kwargs) -> dict:
233
+ """
234
+ Initialize parameter dictionary with query parameters structure.
235
+
236
+ Ensures parameter dictionary has 'params' key for HTTP query parameters,
237
+ guaranteeing consistent structure for all parameter building methods.
238
+
239
+ Parameters
240
+ ----------
241
+ **kwargs : dict
242
+ Keyword arguments to format. May be empty or contain various
243
+ parameters for API operations.
244
+
245
+ Returns
246
+ -------
247
+ dict
248
+ Parameters dictionary with guaranteed 'params' key containing
249
+ empty dict if not already present.
250
+ """
251
+ if "params" not in kwargs:
252
+ kwargs["params"] = {}
253
+ return kwargs
254
+
255
+ @staticmethod
256
+ def _add_param(key: str, value: Any | None, **kwargs) -> dict:
257
+ """
258
+ Add a single query parameter to kwargs.
259
+
260
+ Parameters
261
+ ----------
262
+ key : str
263
+ Parameter key.
264
+ value : Any
265
+ Parameter value.
266
+ **kwargs : dict
267
+ Keyword arguments to format. May be empty or contain various
268
+ parameters for API operations.
269
+
270
+ Returns
271
+ -------
272
+ dict
273
+ Parameters dictionary with added key-value pair in 'params'.
274
+ """
275
+ kwargs["params"][key] = value
276
+ return kwargs
277
+
278
+ @staticmethod
279
+ def read_page_number(**kwargs) -> int:
280
+ """
281
+ Read current page number from kwargs.
282
+
283
+ Parameters
284
+ ----------
285
+ **kwargs : dict
286
+ Keyword arguments to format. May be empty or contain various
287
+ parameters for API operations.
288
+
289
+ Returns
290
+ -------
291
+ int
292
+ Current page number.
293
+ """
294
+ return kwargs["params"]["page"]
295
+
296
+ @staticmethod
297
+ def increment_page_number(**kwargs) -> dict:
298
+ """
299
+ Increment page number in kwargs.
300
+
301
+ Parameters
302
+ ----------
303
+ **kwargs : dict
304
+ Keyword arguments to format. May be empty or contain various
305
+ parameters for API operations.
306
+
307
+ Returns
308
+ -------
309
+ dict
310
+ Parameters dictionary with incremented 'page' number in 'params'.
311
+ """
312
+ kwargs["params"]["page"] += 1
313
+ return kwargs
314
+
315
+ @staticmethod
316
+ def _filter_none_params(**kwargs) -> dict:
317
+ """
318
+ Filter out None values from kwargs.
319
+
320
+ Parameters
321
+ ----------
322
+ **kwargs : dict
323
+ Keyword arguments to filter.
324
+
325
+ Returns
326
+ -------
327
+ dict
328
+ Filtered kwargs.
329
+ """
330
+ return {k: v for k, v in kwargs.items() if v is not None}
@@ -0,0 +1,102 @@
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 warnings import warn
9
+
10
+ from requests.exceptions import JSONDecodeError
11
+
12
+ from digitalhub.stores.client.error_parser import ErrorParser
13
+ from digitalhub.utils.exceptions import BackendError, ClientError
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from requests import Response
17
+
18
+
19
+ # API levels that are supported
20
+ MAX_API_LEVEL = 20
21
+ MIN_API_LEVEL = 14
22
+ LIB_VERSION = 14
23
+
24
+
25
+ class ResponseProcessor:
26
+ """
27
+ Processes and validates HTTP responses from DHCore backend.
28
+
29
+ Handles API version validation, error parsing, and response body parsing
30
+ to dictionary. Supports API versions {MIN_API_LEVEL} to {MAX_API_LEVEL}.
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ self._error_parser = ErrorParser()
35
+
36
+ def process(self, response: Response) -> dict:
37
+ """
38
+ Process HTTP response with validation and parsing.
39
+
40
+ Performs API version compatibility check, error parsing for failed
41
+ responses, and JSON deserialization.
42
+
43
+ Parameters
44
+ ----------
45
+ response : Response
46
+ HTTP response object from backend.
47
+
48
+ Returns
49
+ -------
50
+ dict
51
+ Parsed response body as dictionary.
52
+ """
53
+ self._check_api_version(response)
54
+ self._error_parser.parse(response)
55
+ return self._parse_json(response)
56
+
57
+ def _check_api_version(self, response: Response) -> None:
58
+ """
59
+ Validate DHCore API version compatibility.
60
+
61
+ Checks backend API version against supported range and warns if backend
62
+ version is newer than library. Supported: {MIN_API_LEVEL} to {MAX_API_LEVEL}.
63
+
64
+ Parameters
65
+ ----------
66
+ response : Response
67
+ HTTP response containing X-Api-Level header.
68
+ """
69
+ if "X-Api-Level" not in response.headers:
70
+ return
71
+
72
+ core_api_level = int(response.headers["X-Api-Level"])
73
+ if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
74
+ raise ClientError("Backend API level not supported.")
75
+
76
+ if LIB_VERSION < core_api_level:
77
+ warn("Backend API level is higher than library version. You should consider updating the library.")
78
+
79
+ @staticmethod
80
+ def _parse_json(response: Response) -> dict:
81
+ """
82
+ Parse HTTP response body to dictionary.
83
+
84
+ Converts JSON response to Python dictionary, treating empty responses
85
+ as valid and returning empty dict.
86
+
87
+ Parameters
88
+ ----------
89
+ response : Response
90
+ HTTP response object to parse.
91
+
92
+ Returns
93
+ -------
94
+ dict
95
+ Parsed response body as dictionary, or empty dict if body is empty.
96
+ """
97
+ try:
98
+ return response.json()
99
+ except JSONDecodeError:
100
+ if response.text == "":
101
+ return {}
102
+ raise BackendError("Backend response could not be parsed.")
@@ -0,0 +1,35 @@
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 digitalhub.stores.client.builder import get_client
8
+
9
+
10
+ def refresh_token() -> None:
11
+ """
12
+ Refresh the current OAuth2 access token.
13
+
14
+ Uses the refresh token stored in client configuration to obtain a new
15
+ access token. Requires OAuth2 authentication configuration.
16
+
17
+
18
+ Raises
19
+ ------
20
+ ClientError
21
+ If client not properly configured or token refresh fails.
22
+ """
23
+ get_client().refresh_token()
24
+
25
+
26
+ def get_credentials_and_config() -> dict:
27
+ """
28
+ Get current client credentials and configuration.
29
+
30
+ Returns
31
+ -------
32
+ dict
33
+ Current client credentials and configuration details.
34
+ """
35
+ return get_client().get_credentials_and_config()
@@ -4,23 +4,19 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from digitalhub.stores.credentials.handler import creds_handler
7
+ from digitalhub.stores.configurator.configurator import configurator
8
8
 
9
9
 
10
- def set_current_profile(environment: str) -> None:
10
+ def set_current_profile(profile: str) -> None:
11
11
  """
12
12
  Set the current credentials profile.
13
13
 
14
14
  Parameters
15
15
  ----------
16
- environment : str
16
+ profile : str
17
17
  Name of the credentials profile to set.
18
-
19
- Returns
20
- -------
21
- None
22
18
  """
23
- creds_handler.set_current_profile(environment)
19
+ configurator.set_current_profile(profile)
24
20
 
25
21
 
26
22
  def get_current_profile() -> str:
@@ -32,4 +28,4 @@ def get_current_profile() -> str:
32
28
  str
33
29
  Name of the current credentials profile.
34
30
  """
35
- return creds_handler.get_current_profile()
31
+ return configurator.get_current_profile()
@@ -0,0 +1,123 @@
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 digitalhub.stores.configurator.handler import ConfigurationHandler
8
+
9
+
10
+ class Configurator:
11
+ """
12
+ Configurator class for configuration and credentials management.
13
+ """
14
+
15
+ def __init__(self):
16
+ self._handler = ConfigurationHandler()
17
+ self._reload_from_env = False
18
+
19
+ ##############################
20
+ # Configuration
21
+ ##############################
22
+
23
+ def get_configuration(self) -> dict:
24
+ """
25
+ Retrieve the current configuration.
26
+
27
+ Returns
28
+ -------
29
+ dict
30
+ Dictionary of configuration variables.
31
+ """
32
+ return self._handler.get_configuration()
33
+
34
+ ##############################
35
+ # Credentials
36
+ ##############################
37
+
38
+ def get_credentials(self) -> dict:
39
+ """
40
+ Retrieve the current credentials.
41
+
42
+ Returns
43
+ -------
44
+ dict
45
+ Dictionary of credentials.
46
+ """
47
+ return self._handler.get_credentials()
48
+
49
+ def eval_retry(self) -> bool:
50
+ """
51
+ Evaluate credentials reload based on retry logic.
52
+
53
+ Returns
54
+ -------
55
+ bool
56
+ True if a retry action was performed, otherwise False.
57
+ """
58
+ current_creds = self.get_credentials()
59
+ reread_creds = self._handler.load_credentials()
60
+
61
+ # Compare cached and file credentials.
62
+ # If different, reload in cache.
63
+ if current_creds != reread_creds:
64
+ self.reload_credentials()
65
+ return True
66
+
67
+ # Check if we need to reload from env only
68
+ if not self._reload_from_env:
69
+ self._handler.reload_credentials_from_env()
70
+ self._reload_from_env = True
71
+ return True
72
+
73
+ return False
74
+
75
+ def reload_credentials(self) -> None:
76
+ """
77
+ Reload credentials from environment and file.
78
+ """
79
+ self._handler.reload_credentials()
80
+
81
+ ###############################
82
+ # Profile methods
83
+ ###############################
84
+
85
+ def set_current_profile(self, profile: str) -> None:
86
+ """
87
+ Set the current profile.
88
+
89
+ Parameters
90
+ ----------
91
+ profile : str
92
+ Name of the profile to set.
93
+ """
94
+ self._handler.set_current_profile(profile)
95
+
96
+ ################################
97
+ # Other methods
98
+ ################################
99
+
100
+ def write_file(self, variables: dict) -> None:
101
+ """
102
+ Write the current configuration and credentials to file.
103
+
104
+ Parameters
105
+ ----------
106
+ variables : dict
107
+ Dictionary of variables to write.
108
+ """
109
+ self._handler.write_file(variables)
110
+
111
+ def get_config_creds(self) -> dict:
112
+ """
113
+ Get merged configuration and credentials.
114
+
115
+ Returns
116
+ -------
117
+ dict
118
+ Merged configuration and credentials dictionary.
119
+ """
120
+ return {**self.get_configuration(), **self.get_credentials()}
121
+
122
+
123
+ configurator = Configurator()