digitalhub 0.14.0b7__py3-none-any.whl → 0.14.1b0__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 (68) hide show
  1. digitalhub/__init__.py +2 -2
  2. digitalhub/context/api.py +42 -1
  3. digitalhub/context/context.py +3 -6
  4. digitalhub/entities/_base/context/entity.py +0 -3
  5. digitalhub/entities/_base/material/entity.py +2 -2
  6. digitalhub/entities/_processors/base/crud.py +14 -23
  7. digitalhub/entities/_processors/base/import_export.py +0 -5
  8. digitalhub/entities/_processors/base/processor.py +1 -4
  9. digitalhub/entities/_processors/base/special_ops.py +4 -8
  10. digitalhub/entities/_processors/context/crud.py +5 -5
  11. digitalhub/entities/_processors/context/import_export.py +5 -5
  12. digitalhub/entities/_processors/context/material.py +2 -2
  13. digitalhub/entities/_processors/context/special_ops.py +13 -13
  14. digitalhub/entities/_processors/utils.py +2 -111
  15. digitalhub/entities/function/_base/entity.py +0 -3
  16. digitalhub/entities/project/_base/builder.py +0 -6
  17. digitalhub/entities/project/_base/entity.py +4 -12
  18. digitalhub/entities/project/_base/spec.py +4 -4
  19. digitalhub/entities/project/crud.py +9 -44
  20. digitalhub/entities/project/utils.py +7 -3
  21. digitalhub/entities/workflow/_base/entity.py +0 -5
  22. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
  23. digitalhub/stores/client/builder.py +20 -32
  24. digitalhub/stores/client/{dhcore/client.py → client.py} +64 -23
  25. digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +122 -176
  26. digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
  27. digitalhub/stores/client/{dhcore/http_handler.py → http_handler.py} +4 -5
  28. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +13 -13
  29. digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
  30. digitalhub/stores/client/{dhcore/response_processor.py → response_processor.py} +1 -1
  31. digitalhub/stores/client/{dhcore/utils.py → utils.py} +2 -7
  32. digitalhub/stores/{credentials → configurator}/api.py +5 -5
  33. digitalhub/stores/configurator/configurator.py +123 -0
  34. digitalhub/stores/{credentials → configurator}/enums.py +25 -10
  35. digitalhub/stores/configurator/handler.py +213 -0
  36. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -0
  37. digitalhub/stores/data/_base/store.py +0 -4
  38. digitalhub/stores/data/api.py +2 -4
  39. digitalhub/stores/data/builder.py +5 -37
  40. digitalhub/stores/data/s3/configurator.py +30 -114
  41. digitalhub/stores/data/s3/store.py +9 -22
  42. digitalhub/stores/data/sql/configurator.py +49 -71
  43. digitalhub/stores/data/sql/store.py +20 -55
  44. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/METADATA +1 -1
  45. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/RECORD +51 -66
  46. digitalhub/stores/client/_base/api_builder.py +0 -34
  47. digitalhub/stores/client/_base/client.py +0 -243
  48. digitalhub/stores/client/_base/params_builder.py +0 -82
  49. digitalhub/stores/client/api.py +0 -32
  50. digitalhub/stores/client/dhcore/__init__.py +0 -3
  51. digitalhub/stores/client/dhcore/enums.py +0 -18
  52. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  53. digitalhub/stores/client/local/__init__.py +0 -3
  54. digitalhub/stores/client/local/api_builder.py +0 -116
  55. digitalhub/stores/client/local/client.py +0 -605
  56. digitalhub/stores/client/local/enums.py +0 -15
  57. digitalhub/stores/client/local/key_builder.py +0 -62
  58. digitalhub/stores/client/local/params_builder.py +0 -97
  59. digitalhub/stores/credentials/__init__.py +0 -3
  60. digitalhub/stores/credentials/configurator.py +0 -185
  61. digitalhub/stores/credentials/handler.py +0 -164
  62. digitalhub/stores/credentials/store.py +0 -77
  63. /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
  64. /digitalhub/stores/client/{dhcore/header_manager.py → header_manager.py} +0 -0
  65. /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
  66. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/WHEEL +0 -0
  67. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/licenses/AUTHORS +0 -0
  68. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/licenses/LICENSE +0 -0
@@ -6,17 +6,16 @@ from __future__ import annotations
6
6
 
7
7
  from typing import Any
8
8
 
9
- from digitalhub.stores.client._base.client import Client
10
- from digitalhub.stores.client.dhcore.api_builder import ClientDHCoreApiBuilder
11
- from digitalhub.stores.client.dhcore.header_manager import HeaderManager
12
- from digitalhub.stores.client.dhcore.http_handler import HttpRequestHandler
13
- from digitalhub.stores.client.dhcore.key_builder import ClientDHCoreKeyBuilder
14
- from digitalhub.stores.client.dhcore.params_builder import ClientDHCoreParametersBuilder
9
+ from digitalhub.stores.client.api_builder import ClientApiBuilder
10
+ from digitalhub.stores.client.header_manager import HeaderManager
11
+ from digitalhub.stores.client.http_handler import HttpRequestHandler
12
+ from digitalhub.stores.client.key_builder import ClientKeyBuilder
13
+ from digitalhub.stores.client.params_builder import ClientParametersBuilder
15
14
  from digitalhub.utils.exceptions import BackendError
16
15
  from digitalhub.utils.generic_utils import dump_json
17
16
 
18
17
 
19
- class ClientDHCore(Client):
18
+ class Client:
20
19
  """
21
20
  DHCore client for remote DigitalHub Core backend communication.
22
21
 
@@ -27,13 +26,11 @@ class ClientDHCore(Client):
27
26
  JSON serialization.
28
27
  """
29
28
 
30
- def __init__(self, config: dict | None = None) -> None:
31
- super().__init__()
32
-
29
+ def __init__(self) -> None:
33
30
  # API, key and parameters builders
34
- self._api_builder: ClientDHCoreApiBuilder = ClientDHCoreApiBuilder()
35
- self._key_builder: ClientDHCoreKeyBuilder = ClientDHCoreKeyBuilder()
36
- self._params_builder: ClientDHCoreParametersBuilder = ClientDHCoreParametersBuilder()
31
+ self._api_builder: ClientApiBuilder = ClientApiBuilder()
32
+ self._key_builder: ClientKeyBuilder = ClientKeyBuilder()
33
+ self._params_builder: ClientParametersBuilder = ClientParametersBuilder()
37
34
 
38
35
  # HTTP request handling
39
36
  self._http_handler = HttpRequestHandler()
@@ -239,23 +236,68 @@ class ClientDHCore(Client):
239
236
  return objects
240
237
 
241
238
  ##############################
242
- # Interface methods
239
+ # Build methods
243
240
  ##############################
244
241
 
245
- @staticmethod
246
- def is_local() -> bool:
242
+ def build_api(self, category: str, operation: str, **kwargs) -> str:
247
243
  """
248
- Check if this client operates locally.
244
+ Build the API for the client.
249
245
 
250
- Used to distinguish between ClientDHCore (remote) and ClientLocal
251
- implementations.
246
+ Parameters
247
+ ----------
248
+ category : str
249
+ API category.
250
+ operation : str
251
+ API operation.
252
+ **kwargs : dict
253
+ Additional parameters.
252
254
 
253
255
  Returns
254
256
  -------
255
- bool
256
- False, indicating this client communicates with remote DHCore backend.
257
+ str
258
+ API formatted.
259
+ """
260
+ return self._api_builder.build_api(category, operation, **kwargs)
261
+
262
+ def build_key(self, category: str, *args, **kwargs) -> str:
263
+ """
264
+ Build the key for the client.
265
+
266
+ Parameters
267
+ ----------
268
+ category : str
269
+ Key category.
270
+ *args : tuple
271
+ Additional arguments.
272
+ **kwargs : dict
273
+ Additional parameters.
274
+
275
+ Returns
276
+ -------
277
+ str
278
+ Key formatted.
279
+ """
280
+ return self._key_builder.build_key(category, *args, **kwargs)
281
+
282
+ def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
283
+ """
284
+ Build the parameters for the client call.
285
+
286
+ Parameters
287
+ ----------
288
+ category : str
289
+ API category.
290
+ operation : str
291
+ API operation.
292
+ **kwargs : dict
293
+ Parameters to build.
294
+
295
+ Returns
296
+ -------
297
+ dict
298
+ Parameters formatted.
257
299
  """
258
- return False
300
+ return self._params_builder.build_parameters(category, operation, **kwargs)
259
301
 
260
302
  ##############################
261
303
  # Utility methods
@@ -265,5 +307,4 @@ class ClientDHCore(Client):
265
307
  """
266
308
  Manually trigger OAuth2 token refresh.
267
309
  """
268
- self._http_handler._configurator.check_config()
269
310
  self._http_handler._configurator.refresh_credentials()
@@ -8,10 +8,9 @@ import typing
8
8
 
9
9
  from requests import request
10
10
 
11
- from digitalhub.stores.client.dhcore.enums import AuthType
12
- from digitalhub.stores.credentials.configurator import Configurator
13
- from digitalhub.stores.credentials.enums import CredsEnvVar
14
- from digitalhub.stores.credentials.handler import creds_handler
11
+ from digitalhub.stores.client.enums import AuthType
12
+ from digitalhub.stores.configurator.configurator import configurator
13
+ from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
15
14
  from digitalhub.utils.exceptions import ClientError
16
15
  from digitalhub.utils.generic_utils import list_enum
17
16
  from digitalhub.utils.uri_utils import has_remote_scheme
@@ -20,7 +19,10 @@ if typing.TYPE_CHECKING:
20
19
  from requests import Response
21
20
 
22
21
 
23
- class ClientDHCoreConfigurator(Configurator):
22
+ DEFAULT_TIMEOUT = 60
23
+
24
+
25
+ class ClientConfigurator:
24
26
  """
25
27
  DHCore client configurator for credential management and authentication.
26
28
 
@@ -35,20 +37,13 @@ class ClientDHCoreConfigurator(Configurator):
35
37
  credential storage.
36
38
  """
37
39
 
38
- keys = [*list_enum(CredsEnvVar)]
39
- required_keys = [CredsEnvVar.DHCORE_ENDPOINT.value]
40
- keys_to_prefix = [
41
- CredsEnvVar.DHCORE_REFRESH_TOKEN.value,
42
- CredsEnvVar.DHCORE_ACCESS_TOKEN.value,
43
- CredsEnvVar.DHCORE_ISSUER.value,
44
- CredsEnvVar.DHCORE_CLIENT_ID.value,
45
- ]
40
+ keys = [*list_enum(ConfigurationVars), *list_enum(CredentialsVars)]
46
41
 
47
42
  def __init__(self) -> None:
48
43
  """
49
44
  Initialize DHCore configurator and evaluate authentication type.
50
45
  """
51
- super().__init__()
46
+ self._validate()
52
47
  self._auth_type: str | None = None
53
48
  self.set_auth_type()
54
49
 
@@ -56,92 +51,6 @@ class ClientDHCoreConfigurator(Configurator):
56
51
  # Credentials methods
57
52
  ##############################
58
53
 
59
- def load_env_vars(self) -> None:
60
- """
61
- Load and sanitize credentials from environment variables.
62
-
63
- Sanitizes endpoint and issuer URLs to ensure proper HTTP/HTTPS schemes
64
- and removes trailing slashes.
65
- """
66
- env_creds = self._creds_handler.load_from_env(self.keys)
67
- env_creds = self._sanitize_env_vars(env_creds)
68
- self._creds_handler.set_credentials(self._env, env_creds)
69
-
70
- def _sanitize_env_vars(self, creds: dict) -> dict:
71
- """
72
- Sanitize environment variable credentials.
73
-
74
- Validates and normalizes endpoint and issuer URLs. Environment variables
75
- use full "DHCORE_" prefixes.
76
-
77
- Parameters
78
- ----------
79
- creds : dict
80
- Raw credentials from environment variables.
81
-
82
- Returns
83
- -------
84
- dict
85
- Sanitized credentials with normalized URLs.
86
-
87
- Raises
88
- ------
89
- ClientError
90
- If endpoint or issuer URLs have invalid schemes.
91
- """
92
- creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
93
- creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
94
- return creds
95
-
96
- def load_file_vars(self) -> None:
97
- """
98
- Load credentials from configuration file with CLI compatibility.
99
-
100
- Handles keys without "DHCORE_" prefix for CLI compatibility. Falls back
101
- to environment variables for missing endpoint and personal access token values.
102
- """
103
- file_creds = self._creds_handler.load_from_file(self.keys)
104
-
105
- # Because in the response there is no personal access token
106
- pat = CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
107
- if file_creds[pat] is None:
108
- file_creds[pat] = self._creds_handler.load_from_env([pat]).get(pat)
109
-
110
- # Because in the response there is no endpoint
111
- endpoint = CredsEnvVar.DHCORE_ENDPOINT.value
112
- if file_creds[endpoint] is None:
113
- file_creds[endpoint] = self._creds_handler.load_from_env([endpoint]).get(endpoint)
114
-
115
- file_creds = self._sanitize_file_vars(file_creds)
116
- self._creds_handler.set_credentials(self._file, file_creds)
117
-
118
- def _sanitize_file_vars(self, creds: dict) -> dict:
119
- """
120
- Sanitize configuration file credentials.
121
-
122
- Handles different key formats between file and environment variables.
123
- File format omits "DHCORE_" prefix for: issuer, client_id, access_token,
124
- refresh_token. Full names used for: endpoint, user, password, personal_access_token.
125
-
126
- Parameters
127
- ----------
128
- creds : dict
129
- Raw credentials from configuration file.
130
-
131
- Returns
132
- -------
133
- dict
134
- Sanitized credentials with standardized keys and normalized URLs.
135
-
136
- Raises
137
- ------
138
- ClientError
139
- If endpoint or issuer URLs have invalid schemes.
140
- """
141
- creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
142
- creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
143
- return {k: v for k, v in creds.items() if k in self.keys}
144
-
145
54
  @staticmethod
146
55
  def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
147
56
  """
@@ -188,8 +97,9 @@ class ClientDHCoreConfigurator(Configurator):
188
97
  KeyError
189
98
  If endpoint not configured in current credential source.
190
99
  """
191
- creds = self._creds_handler.get_credentials(self._origin)
192
- return creds[CredsEnvVar.DHCORE_ENDPOINT.value]
100
+ config = configurator.get_configuration()
101
+ endpoint = config[ConfigurationVars.DHCORE_ENDPOINT.value]
102
+ return self._sanitize_endpoint(endpoint)
193
103
 
194
104
  ##############################
195
105
  # Origin methods
@@ -220,15 +130,13 @@ class ClientDHCoreConfigurator(Configurator):
220
130
  (username + password). For EXCHANGE type, automatically exchanges the
221
131
  personal access token and switches to file-based credentials storage.
222
132
  """
223
- creds = creds_handler.get_credentials(self._origin)
133
+ creds = configurator.get_credentials()
224
134
  self._auth_type = self._eval_auth_type(creds)
225
135
  # If we have an exchange token, we need to get a new access token.
226
136
  # Therefore, we change the origin to file, where the refresh token is written.
227
137
  # We also try to fetch the PAT from both env and file
228
138
  if self._auth_type == AuthType.EXCHANGE.value:
229
- self.refresh_credentials(change_origin=True)
230
- # Just to ensure we get the right source from file
231
- self.change_to_file()
139
+ self.refresh_credentials(retry=True)
232
140
 
233
141
  def refreshable_auth_types(self) -> bool:
234
142
  """
@@ -261,83 +169,97 @@ class ClientDHCoreConfigurator(Configurator):
261
169
  dict
262
170
  Modified kwargs with authentication parameters.
263
171
  """
264
- creds = creds_handler.get_credentials(self._origin)
172
+ creds = configurator.get_credentials()
265
173
  if self._auth_type in (
266
174
  AuthType.EXCHANGE.value,
267
175
  AuthType.OAUTH2.value,
268
176
  AuthType.ACCESS_TOKEN.value,
269
177
  ):
270
- access_token = creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value]
178
+ access_token = creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value]
271
179
  if "headers" not in kwargs:
272
180
  kwargs["headers"] = {}
273
181
  kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
274
182
  elif self._auth_type == AuthType.BASIC.value:
275
- user = creds[CredsEnvVar.DHCORE_USER.value]
276
- password = creds[CredsEnvVar.DHCORE_PASSWORD.value]
183
+ user = creds[CredentialsVars.DHCORE_USER.value]
184
+ password = creds[CredentialsVars.DHCORE_PASSWORD.value]
277
185
  kwargs["auth"] = (user, password)
278
186
  return kwargs
279
187
 
280
- def refresh_credentials(self, change_origin: bool = False) -> None:
188
+ def refresh_credentials(self) -> None:
281
189
  """
282
190
  Refresh authentication tokens using OAuth2 flows.
191
+ """
192
+ # Get credentials and configuration
193
+ creds = configurator.get_config_creds()
194
+
195
+ # Get token refresh from creds
196
+ if (url := creds.get(ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value)) is None:
197
+ url = self._get_refresh_endpoint()
198
+ url = self._sanitize_endpoint(url)
199
+
200
+ # Execute the appropriate auth flow
201
+ response = self._evaluate_auth_flow(url, creds)
283
202
 
284
- Uses refresh_token grant for OAUTH2 or token exchange for EXCHANGE authentication.
285
- On 400/401/403 errors with change_origin=True, attempts to switch credential
286
- sources and retry. Saves new credentials to configuration file.
203
+ # Evaluate a retry
204
+ self._evaluate_retry(response)
205
+
206
+ # Raise an error if the response indicates failure
207
+ response.raise_for_status()
208
+
209
+ # Export new credentials to file
210
+ self._export_new_creds(response.json())
211
+
212
+ def _evaluate_auth_flow(self, url: str, creds: dict) -> Response:
213
+ """
214
+ Evaluate the auth flow to execute.
287
215
 
288
216
  Parameters
289
217
  ----------
290
- change_origin : bool, default False
291
- Whether to switch credential sources on auth failure.
292
-
293
- Raises
294
- ------
295
- ClientError
296
- If auth type doesn't support refresh or credentials missing.
218
+ url : str
219
+ Token endpoint URL.
220
+ creds : dict
221
+ Available credential values.
297
222
  """
298
223
  if not self.refreshable_auth_types():
299
224
  raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
300
225
 
301
- # Get refresh endpoint
302
- url = self._get_refresh_endpoint()
303
-
304
- # Get credentials
305
- creds = self._creds_handler.get_credentials(self._origin)
306
-
307
- # Get client id
308
- if (client_id := creds.get(CredsEnvVar.DHCORE_CLIENT_ID.value)) is None:
226
+ if (client_id := creds.get(ConfigurationVars.DHCORE_CLIENT_ID.value)) is None:
309
227
  raise ClientError("Client id not set.")
310
228
 
311
- # Handling of token exchange or refresh
229
+ # Handling of token refresh
312
230
  if self._auth_type == AuthType.OAUTH2.value:
313
- response = self._call_refresh_endpoint(
231
+ return self._call_refresh_endpoint(
314
232
  url,
315
233
  client_id=client_id,
316
- refresh_token=creds.get(CredsEnvVar.DHCORE_REFRESH_TOKEN.value),
234
+ refresh_token=creds.get(CredentialsVars.DHCORE_REFRESH_TOKEN.value),
317
235
  grant_type="refresh_token",
318
236
  scope="credentials",
319
237
  )
320
- elif self._auth_type == AuthType.EXCHANGE.value:
321
- response = self._call_refresh_endpoint(
322
- url,
323
- client_id=client_id,
324
- subject_token=creds.get(CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value),
325
- subject_token_type="urn:ietf:params:oauth:token-type:pat",
326
- grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
327
- scope="credentials",
328
- )
329
-
330
- # Change origin of creds if needed
331
- if response.status_code in (400, 401, 403):
332
- if not change_origin:
333
- raise ClientError("Unable to refresh credentials. Please check your credentials.")
334
- self.eval_change_origin()
335
- self.refresh_credentials(change_origin=False)
336
238
 
337
- response.raise_for_status()
239
+ ## Handling of token exchange
240
+ return self._call_refresh_endpoint(
241
+ url,
242
+ client_id=client_id,
243
+ subject_token=creds.get(CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value),
244
+ subject_token_type="urn:ietf:params:oauth:token-type:pat",
245
+ grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
246
+ scope="credentials",
247
+ )
338
248
 
339
- # Read new credentials and propagate to config file
340
- self._export_new_creds(response.json())
249
+ def _evaluate_retry(self, response: Response) -> None:
250
+ """
251
+ Evaluate the status of retry lifecycle.
252
+ """
253
+ if response.status_code not in (400, 401, 403):
254
+ return
255
+ if configurator.eval_retry():
256
+ self.refresh_credentials()
257
+ raise ClientError(
258
+ "Failed to refresh credentials after retry"
259
+ " (checked credentials from file and env)."
260
+ " Please check your credentials"
261
+ " (refresh tokens, password, etc.)."
262
+ )
341
263
 
342
264
  def _get_refresh_endpoint(self) -> str:
343
265
  """
@@ -346,28 +268,25 @@ class ClientDHCoreConfigurator(Configurator):
346
268
  Queries /.well-known/openid-configuration to extract token_endpoint for
347
269
  credential refresh operations.
348
270
 
271
+ Parameters
272
+ ----------
273
+ creds : dict
274
+ Available credential values.
275
+
349
276
  Returns
350
277
  -------
351
278
  str
352
279
  Token endpoint URL for credential refresh.
353
-
354
- Raises
355
- ------
356
- ClientError
357
- If issuer endpoint not configured.
358
- HTTPError
359
- If well-known configuration endpoint inaccessible.
360
- KeyError
361
- If token_endpoint not found in issuer configuration.
362
280
  """
281
+ config = configurator.get_configuration()
282
+
363
283
  # Get issuer endpoint
364
- creds = self._creds_handler.get_credentials(self._origin)
365
- endpoint_issuer = creds.get(CredsEnvVar.DHCORE_ISSUER.value)
366
- if endpoint_issuer is None:
284
+ if (endpoint_issuer := config.get(ConfigurationVars.DHCORE_ISSUER.value)) is None:
367
285
  raise ClientError("Issuer endpoint not set.")
368
286
 
369
287
  # Standard issuer endpoint path
370
288
  url = endpoint_issuer + "/.well-known/openid-configuration"
289
+ url = self._sanitize_endpoint(url)
371
290
 
372
291
  # Call issuer to get refresh endpoint
373
292
  r = request("GET", url, timeout=60)
@@ -400,7 +319,13 @@ class ClientDHCoreConfigurator(Configurator):
400
319
  # Send request to get new access token
401
320
  payload = {**kwargs}
402
321
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
403
- return request("POST", url, data=payload, headers=headers, timeout=60)
322
+ return request(
323
+ "POST",
324
+ url,
325
+ data=payload,
326
+ headers=headers,
327
+ timeout=DEFAULT_TIMEOUT,
328
+ )
404
329
 
405
330
  def _eval_auth_type(self, creds: dict) -> str | None:
406
331
  """
@@ -419,16 +344,19 @@ class ClientDHCoreConfigurator(Configurator):
419
344
  str or None
420
345
  Authentication type from AuthType enum, or None if no valid credentials.
421
346
  """
422
- if creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
347
+ if creds[CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
423
348
  return AuthType.EXCHANGE.value
424
349
  if (
425
- creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None
426
- and creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] is not None
350
+ creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None
351
+ and creds[CredentialsVars.DHCORE_REFRESH_TOKEN.value] is not None
427
352
  ):
428
353
  return AuthType.OAUTH2.value
429
- if creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None:
354
+ if creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None:
430
355
  return AuthType.ACCESS_TOKEN.value
431
- if creds[CredsEnvVar.DHCORE_USER.value] is not None and creds[CredsEnvVar.DHCORE_PASSWORD.value] is not None:
356
+ if (
357
+ creds[CredentialsVars.DHCORE_USER.value] is not None
358
+ and creds[CredentialsVars.DHCORE_PASSWORD.value] is not None
359
+ ):
432
360
  return AuthType.BASIC.value
433
361
  return None
434
362
 
@@ -444,12 +372,30 @@ class ClientDHCoreConfigurator(Configurator):
444
372
  response : dict
445
373
  OAuth2 token response with new credentials.
446
374
  """
447
- for key in self.keys_to_prefix:
375
+ keys_to_prefix = [
376
+ CredentialsVars.DHCORE_REFRESH_TOKEN.value,
377
+ CredentialsVars.DHCORE_ACCESS_TOKEN.value,
378
+ ConfigurationVars.DHCORE_CLIENT_ID.value,
379
+ ConfigurationVars.DHCORE_ISSUER.value,
380
+ ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value,
381
+ ]
382
+ for key in keys_to_prefix:
383
+ if key == ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value:
384
+ prefix = "oauth2_"
385
+ else:
386
+ prefix = "dhcore_"
448
387
  key = key.lower()
449
- if key.removeprefix("dhcore_") in response:
450
- response[key] = response.pop(key.removeprefix("dhcore_"))
451
- creds_handler.write_env(response)
452
- self.load_file_vars()
388
+ if key.removeprefix(prefix) in response:
389
+ response[key] = response.pop(key.removeprefix(prefix))
390
+ configurator.write_file(response)
391
+ configurator.reload_credentials()
453
392
 
454
- # Change current origin to file because of refresh
455
- self.change_to_file()
393
+ def _validate(self) -> None:
394
+ """
395
+ Validate if all required keys are present in the configuration.
396
+ """
397
+ required_keys = [ConfigurationVars.DHCORE_ENDPOINT.value]
398
+ current_keys = configurator.get_config_creds()
399
+ for key in required_keys:
400
+ if current_keys.get(key) is None:
401
+ raise ClientError(f"Required configuration key '{key}' is missing.")
@@ -7,6 +7,17 @@ from __future__ import annotations
7
7
  from enum import Enum
8
8
 
9
9
 
10
+ class AuthType(Enum):
11
+ """
12
+ Authentication types.
13
+ """
14
+
15
+ BASIC = "basic"
16
+ OAUTH2 = "oauth2"
17
+ EXCHANGE = "exchange"
18
+ ACCESS_TOKEN = "access_token_only"
19
+
20
+
10
21
  class ApiCategories(Enum):
11
22
  """
12
23
  API categories.
@@ -6,8 +6,8 @@ from __future__ import annotations
6
6
 
7
7
  from requests import request
8
8
 
9
- from digitalhub.stores.client.dhcore.configurator import ClientDHCoreConfigurator
10
- from digitalhub.stores.client.dhcore.response_processor import ResponseProcessor
9
+ from digitalhub.stores.client.configurator import ClientConfigurator
10
+ from digitalhub.stores.client.response_processor import ResponseProcessor
11
11
  from digitalhub.utils.exceptions import BackendError
12
12
 
13
13
  # Default timeout for requests (in seconds)
@@ -25,7 +25,7 @@ class HttpRequestHandler:
25
25
  """
26
26
 
27
27
  def __init__(self) -> None:
28
- self._configurator = ClientDHCoreConfigurator()
28
+ self._configurator = ClientConfigurator()
29
29
  self._response_processor = ResponseProcessor()
30
30
 
31
31
  def prepare_request(self, method: str, api: str, **kwargs) -> dict:
@@ -90,7 +90,7 @@ class HttpRequestHandler:
90
90
  except BackendError as e:
91
91
  # Handle authentication errors with token refresh
92
92
  if response.status_code == 401 and refresh and self._configurator.refreshable_auth_types():
93
- self._configurator.refresh_credentials(change_origin=True)
93
+ self._configurator.refresh_credentials(retry=True)
94
94
  kwargs = self._configurator.get_auth_parameters(kwargs)
95
95
  return self._execute_request(method, url, refresh=False, **kwargs)
96
96
  raise e
@@ -109,7 +109,6 @@ class HttpRequestHandler:
109
109
  dict
110
110
  kwargs enhanced with authentication parameters.
111
111
  """
112
- self._configurator.check_config()
113
112
  return self._configurator.get_auth_parameters(kwargs)
114
113
 
115
114
  def _build_url(self, api: str) -> str:
@@ -4,9 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from abc import abstractmethod
8
-
9
- from digitalhub.stores.client._base.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.
73
+ Entity name.
77
74
  entity_id : str
78
- The entity identifier. If None, key will not include version.
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}"