digitalhub 0.14.0b5__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 (93) hide show
  1. digitalhub/__init__.py +2 -2
  2. digitalhub/context/api.py +43 -6
  3. digitalhub/context/builder.py +1 -1
  4. digitalhub/context/context.py +3 -6
  5. digitalhub/entities/_base/context/entity.py +0 -3
  6. digitalhub/entities/_base/executable/entity.py +29 -11
  7. digitalhub/entities/_base/material/entity.py +2 -2
  8. digitalhub/entities/_base/material/utils.py +0 -4
  9. digitalhub/entities/_commons/enums.py +1 -0
  10. digitalhub/entities/_commons/utils.py +19 -0
  11. digitalhub/entities/_processors/base/crud.py +15 -24
  12. digitalhub/entities/_processors/base/import_export.py +3 -7
  13. digitalhub/entities/_processors/base/processor.py +4 -7
  14. digitalhub/entities/_processors/base/special_ops.py +4 -8
  15. digitalhub/entities/_processors/context/crud.py +27 -29
  16. digitalhub/entities/_processors/context/import_export.py +7 -7
  17. digitalhub/entities/_processors/context/material.py +2 -2
  18. digitalhub/entities/_processors/context/special_ops.py +25 -25
  19. digitalhub/entities/_processors/utils.py +7 -116
  20. digitalhub/entities/artifact/crud.py +3 -3
  21. digitalhub/entities/artifact/utils.py +2 -2
  22. digitalhub/entities/builders.py +2 -0
  23. digitalhub/entities/dataitem/crud.py +3 -3
  24. digitalhub/entities/dataitem/utils.py +10 -14
  25. digitalhub/entities/function/_base/entity.py +0 -3
  26. digitalhub/entities/function/crud.py +3 -3
  27. digitalhub/entities/model/crud.py +3 -3
  28. digitalhub/entities/model/mlflow/utils.py +29 -20
  29. digitalhub/entities/model/utils.py +2 -2
  30. digitalhub/entities/project/_base/builder.py +0 -6
  31. digitalhub/entities/project/_base/entity.py +264 -114
  32. digitalhub/entities/project/_base/spec.py +4 -4
  33. digitalhub/entities/project/crud.py +16 -51
  34. digitalhub/entities/project/utils.py +7 -3
  35. digitalhub/entities/secret/crud.py +2 -2
  36. digitalhub/entities/task/_base/models.py +13 -16
  37. digitalhub/entities/trigger/crud.py +28 -9
  38. digitalhub/entities/workflow/_base/entity.py +0 -5
  39. digitalhub/entities/workflow/crud.py +3 -6
  40. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
  41. digitalhub/stores/client/builder.py +20 -32
  42. digitalhub/stores/client/client.py +322 -0
  43. digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +148 -195
  44. digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
  45. digitalhub/stores/client/header_manager.py +61 -0
  46. digitalhub/stores/client/http_handler.py +152 -0
  47. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
  48. digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
  49. digitalhub/stores/client/response_processor.py +102 -0
  50. digitalhub/stores/client/utils.py +35 -0
  51. digitalhub/stores/{credentials → configurator}/api.py +5 -9
  52. digitalhub/stores/configurator/configurator.py +123 -0
  53. digitalhub/stores/{credentials → configurator}/enums.py +26 -10
  54. digitalhub/stores/configurator/handler.py +213 -0
  55. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -6
  56. digitalhub/stores/data/_base/store.py +0 -4
  57. digitalhub/stores/data/api.py +4 -6
  58. digitalhub/stores/data/builder.py +6 -38
  59. digitalhub/stores/data/s3/configurator.py +30 -114
  60. digitalhub/stores/data/s3/store.py +9 -22
  61. digitalhub/stores/data/sql/configurator.py +49 -71
  62. digitalhub/stores/data/sql/store.py +26 -61
  63. digitalhub/utils/generic_utils.py +0 -12
  64. digitalhub/utils/git_utils.py +0 -8
  65. digitalhub/utils/io_utils.py +0 -8
  66. digitalhub/utils/store_utils.py +1 -1
  67. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/METADATA +3 -3
  68. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/RECORD +73 -86
  69. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
  70. digitalhub/stores/client/_base/api_builder.py +0 -34
  71. digitalhub/stores/client/_base/client.py +0 -243
  72. digitalhub/stores/client/_base/params_builder.py +0 -82
  73. digitalhub/stores/client/api.py +0 -32
  74. digitalhub/stores/client/dhcore/__init__.py +0 -3
  75. digitalhub/stores/client/dhcore/client.py +0 -553
  76. digitalhub/stores/client/dhcore/enums.py +0 -18
  77. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  78. digitalhub/stores/client/dhcore/utils.py +0 -86
  79. digitalhub/stores/client/local/__init__.py +0 -3
  80. digitalhub/stores/client/local/api_builder.py +0 -116
  81. digitalhub/stores/client/local/client.py +0 -605
  82. digitalhub/stores/client/local/enums.py +0 -15
  83. digitalhub/stores/client/local/key_builder.py +0 -62
  84. digitalhub/stores/client/local/params_builder.py +0 -97
  85. digitalhub/stores/credentials/__init__.py +0 -3
  86. digitalhub/stores/credentials/configurator.py +0 -185
  87. digitalhub/stores/credentials/handler.py +0 -164
  88. digitalhub/stores/credentials/store.py +0 -77
  89. digitalhub/stores/data/enums.py +0 -15
  90. /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
  91. /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
  92. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
  93. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/LICENSE +0 -0
@@ -5,13 +5,13 @@
5
5
  from __future__ import annotations
6
6
 
7
7
  import typing
8
+ from warnings import warn
8
9
 
9
10
  from requests import request
10
11
 
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
12
+ from digitalhub.stores.client.enums import AuthType
13
+ from digitalhub.stores.configurator.configurator import configurator
14
+ from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
15
15
  from digitalhub.utils.exceptions import ClientError
16
16
  from digitalhub.utils.generic_utils import list_enum
17
17
  from digitalhub.utils.uri_utils import has_remote_scheme
@@ -20,7 +20,10 @@ if typing.TYPE_CHECKING:
20
20
  from requests import Response
21
21
 
22
22
 
23
- class ClientDHCoreConfigurator(Configurator):
23
+ DEFAULT_TIMEOUT = 60
24
+
25
+
26
+ class ClientConfigurator:
24
27
  """
25
28
  DHCore client configurator for credential management and authentication.
26
29
 
@@ -35,20 +38,13 @@ class ClientDHCoreConfigurator(Configurator):
35
38
  credential storage.
36
39
  """
37
40
 
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
- ]
41
+ keys = [*list_enum(ConfigurationVars), *list_enum(CredentialsVars)]
46
42
 
47
43
  def __init__(self) -> None:
48
44
  """
49
45
  Initialize DHCore configurator and evaluate authentication type.
50
46
  """
51
- super().__init__()
47
+ self._validate()
52
48
  self._auth_type: str | None = None
53
49
  self.set_auth_type()
54
50
 
@@ -56,92 +52,6 @@ class ClientDHCoreConfigurator(Configurator):
56
52
  # Credentials methods
57
53
  ##############################
58
54
 
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
55
  @staticmethod
146
56
  def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
147
57
  """
@@ -151,7 +61,7 @@ class ClientDHCoreConfigurator(Configurator):
151
61
 
152
62
  Parameters
153
63
  ----------
154
- endpoint : str, optional
64
+ endpoint : str
155
65
  Endpoint URL to sanitize.
156
66
 
157
67
  Returns
@@ -188,24 +98,9 @@ class ClientDHCoreConfigurator(Configurator):
188
98
  KeyError
189
99
  If endpoint not configured in current credential source.
190
100
  """
191
- creds = self._creds_handler.get_credentials(self._origin)
192
- return creds[CredsEnvVar.DHCORE_ENDPOINT.value]
193
-
194
- ##############################
195
- # Origin methods
196
- ##############################
197
-
198
- def change_origin(self) -> None:
199
- """
200
- Switch credential source and re-evaluate authentication type.
201
-
202
- Changes between environment and file credential sources, then re-evaluates
203
- authentication type based on the new credentials.
204
- """
205
- super().change_origin()
206
-
207
- # Re-evaluate the auth type
208
- self.set_auth_type()
101
+ config = configurator.get_configuration()
102
+ endpoint = config[ConfigurationVars.DHCORE_ENDPOINT.value]
103
+ return self._sanitize_endpoint(endpoint)
209
104
 
210
105
  ##############################
211
106
  # Auth methods
@@ -220,15 +115,13 @@ class ClientDHCoreConfigurator(Configurator):
220
115
  (username + password). For EXCHANGE type, automatically exchanges the
221
116
  personal access token and switches to file-based credentials storage.
222
117
  """
223
- creds = creds_handler.get_credentials(self._origin)
118
+ creds = configurator.get_credentials()
224
119
  self._auth_type = self._eval_auth_type(creds)
225
120
  # If we have an exchange token, we need to get a new access token.
226
121
  # Therefore, we change the origin to file, where the refresh token is written.
227
122
  # We also try to fetch the PAT from both env and file
228
123
  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()
124
+ self.refresh_credentials()
232
125
 
233
126
  def refreshable_auth_types(self) -> bool:
234
127
  """
@@ -261,84 +154,106 @@ class ClientDHCoreConfigurator(Configurator):
261
154
  dict
262
155
  Modified kwargs with authentication parameters.
263
156
  """
264
- creds = creds_handler.get_credentials(self._origin)
157
+ creds = configurator.get_credentials()
265
158
  if self._auth_type in (
266
159
  AuthType.EXCHANGE.value,
267
160
  AuthType.OAUTH2.value,
268
161
  AuthType.ACCESS_TOKEN.value,
269
162
  ):
270
- access_token = creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value]
163
+ access_token = creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value]
271
164
  if "headers" not in kwargs:
272
165
  kwargs["headers"] = {}
273
166
  kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
274
167
  elif self._auth_type == AuthType.BASIC.value:
275
- user = creds[CredsEnvVar.DHCORE_USER.value]
276
- password = creds[CredsEnvVar.DHCORE_PASSWORD.value]
168
+ user = creds[CredentialsVars.DHCORE_USER.value]
169
+ password = creds[CredentialsVars.DHCORE_PASSWORD.value]
277
170
  kwargs["auth"] = (user, password)
278
171
  return kwargs
279
172
 
280
- def refresh_credentials(self, change_origin: bool = False) -> None:
173
+ def _evaluate_auth_flow(self, url: str, creds: dict) -> Response:
281
174
  """
282
- Refresh authentication tokens using OAuth2 flows.
283
-
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.
175
+ Evaluate the auth flow to execute.
287
176
 
288
177
  Parameters
289
178
  ----------
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.
179
+ url : str
180
+ Token endpoint URL.
181
+ creds : dict
182
+ Available credential values.
297
183
  """
298
- if not self.refreshable_auth_types():
299
- raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
300
-
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:
184
+ if (client_id := creds.get(ConfigurationVars.DHCORE_CLIENT_ID.value)) is None:
309
185
  raise ClientError("Client id not set.")
310
186
 
311
- # Handling of token exchange or refresh
187
+ # Handling of token refresh
312
188
  if self._auth_type == AuthType.OAUTH2.value:
313
- response = self._call_refresh_endpoint(
189
+ return self._call_refresh_endpoint(
314
190
  url,
315
191
  client_id=client_id,
316
- refresh_token=creds.get(CredsEnvVar.DHCORE_REFRESH_TOKEN.value),
192
+ refresh_token=creds.get(CredentialsVars.DHCORE_REFRESH_TOKEN.value),
317
193
  grant_type="refresh_token",
318
194
  scope="credentials",
319
195
  )
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
196
 
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)
197
+ ## Handling of token exchange
198
+ return self._call_refresh_endpoint(
199
+ url,
200
+ client_id=client_id,
201
+ subject_token=creds.get(CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value),
202
+ subject_token_type="urn:ietf:params:oauth:token-type:pat",
203
+ grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
204
+ scope="credentials",
205
+ )
206
+
207
+ def refresh_credentials(self) -> None:
208
+ """
209
+ Refresh authentication tokens using OAuth2 flows.
210
+ """
211
+ if not self.refreshable_auth_types():
212
+ raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
213
+
214
+ # Get credentials and configuration
215
+ creds = configurator.get_config_creds()
216
+
217
+ # Get token refresh from creds
218
+ if (url := creds.get(ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value)) is None:
219
+ url = self._get_refresh_endpoint()
220
+ url = self._sanitize_endpoint(url)
336
221
 
222
+ # Execute the appropriate auth flow
223
+ response = self._evaluate_auth_flow(url, creds)
224
+
225
+ # Raise an error if the response indicates failure
337
226
  response.raise_for_status()
338
227
 
339
- # Read new credentials and propagate to config file
228
+ # Export new credentials to file
340
229
  self._export_new_creds(response.json())
341
230
 
231
+ configurator.reload_credentials()
232
+
233
+ def evaluate_refresh(self) -> bool:
234
+ """
235
+ Check if token refresh should be attempted.
236
+
237
+ Returns
238
+ -------
239
+ bool
240
+ True if token refresh is applicable, otherwise False.
241
+ """
242
+ try:
243
+ self.refresh_credentials()
244
+ return True
245
+ except Exception:
246
+ if not configurator.eval_retry():
247
+ warn(
248
+ "Failed to refresh credentials after retry"
249
+ " (checked credentials from file and env)."
250
+ " Please check your credentials"
251
+ " and make sure they are up to date."
252
+ " (refresh tokens, password, etc.)."
253
+ )
254
+ return False
255
+ return self.evaluate_refresh()
256
+
342
257
  def _get_refresh_endpoint(self) -> str:
343
258
  """
344
259
  Discover OAuth2 token endpoint from issuer well-known configuration.
@@ -346,28 +261,25 @@ class ClientDHCoreConfigurator(Configurator):
346
261
  Queries /.well-known/openid-configuration to extract token_endpoint for
347
262
  credential refresh operations.
348
263
 
264
+ Parameters
265
+ ----------
266
+ creds : dict
267
+ Available credential values.
268
+
349
269
  Returns
350
270
  -------
351
271
  str
352
272
  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
273
  """
274
+ config = configurator.get_configuration()
275
+
363
276
  # 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:
277
+ if (endpoint_issuer := config.get(ConfigurationVars.DHCORE_ISSUER.value)) is None:
367
278
  raise ClientError("Issuer endpoint not set.")
368
279
 
369
280
  # Standard issuer endpoint path
370
281
  url = endpoint_issuer + "/.well-known/openid-configuration"
282
+ url = self._sanitize_endpoint(url)
371
283
 
372
284
  # Call issuer to get refresh endpoint
373
285
  r = request("GET", url, timeout=60)
@@ -400,7 +312,13 @@ class ClientDHCoreConfigurator(Configurator):
400
312
  # Send request to get new access token
401
313
  payload = {**kwargs}
402
314
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
403
- return request("POST", url, data=payload, headers=headers, timeout=60)
315
+ return request(
316
+ "POST",
317
+ url,
318
+ data=payload,
319
+ headers=headers,
320
+ timeout=DEFAULT_TIMEOUT,
321
+ )
404
322
 
405
323
  def _eval_auth_type(self, creds: dict) -> str | None:
406
324
  """
@@ -419,16 +337,19 @@ class ClientDHCoreConfigurator(Configurator):
419
337
  str or None
420
338
  Authentication type from AuthType enum, or None if no valid credentials.
421
339
  """
422
- if creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
340
+ if creds[CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
423
341
  return AuthType.EXCHANGE.value
424
342
  if (
425
- creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None
426
- and creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] is not None
343
+ creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None
344
+ and creds[CredentialsVars.DHCORE_REFRESH_TOKEN.value] is not None
427
345
  ):
428
346
  return AuthType.OAUTH2.value
429
- if creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None:
347
+ if creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None:
430
348
  return AuthType.ACCESS_TOKEN.value
431
- if creds[CredsEnvVar.DHCORE_USER.value] is not None and creds[CredsEnvVar.DHCORE_PASSWORD.value] is not None:
349
+ if (
350
+ creds[CredentialsVars.DHCORE_USER.value] is not None
351
+ and creds[CredentialsVars.DHCORE_PASSWORD.value] is not None
352
+ ):
432
353
  return AuthType.BASIC.value
433
354
  return None
434
355
 
@@ -444,12 +365,44 @@ class ClientDHCoreConfigurator(Configurator):
444
365
  response : dict
445
366
  OAuth2 token response with new credentials.
446
367
  """
447
- for key in self.keys_to_prefix:
368
+ keys_to_prefix = [
369
+ CredentialsVars.DHCORE_REFRESH_TOKEN.value,
370
+ CredentialsVars.DHCORE_ACCESS_TOKEN.value,
371
+ ConfigurationVars.DHCORE_CLIENT_ID.value,
372
+ ConfigurationVars.DHCORE_ISSUER.value,
373
+ ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value,
374
+ ]
375
+ for key in keys_to_prefix:
376
+ if key == ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value:
377
+ prefix = "oauth2_"
378
+ else:
379
+ prefix = "dhcore_"
448
380
  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()
381
+ if key.removeprefix(prefix) in response:
382
+ response[key] = response.pop(key.removeprefix(prefix))
383
+ configurator.write_file(response)
384
+
385
+ def _validate(self) -> None:
386
+ """
387
+ Validate if all required keys are present in the configuration.
388
+ """
389
+ required_keys = [ConfigurationVars.DHCORE_ENDPOINT.value]
390
+ current_keys = configurator.get_config_creds()
391
+ for key in required_keys:
392
+ if current_keys.get(key) is None:
393
+ raise ClientError(f"Required configuration key '{key}' is missing.")
453
394
 
454
- # Change current origin to file because of refresh
455
- self.change_to_file()
395
+ ###############################
396
+ # Utility methods
397
+ ###############################
398
+
399
+ def get_credentials_and_config(self) -> dict:
400
+ """
401
+ Get current authentication credentials and configuration.
402
+
403
+ Returns
404
+ -------
405
+ dict
406
+ Current authentication credentials and configuration.
407
+ """
408
+ return configurator.get_config_creds()
@@ -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.
@@ -0,0 +1,61 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+
8
+ class HeaderManager:
9
+ """
10
+ Manages HTTP headers for DHCore client requests.
11
+
12
+ Provides utilities for setting and managing common HTTP headers
13
+ like Content-Type for JSON requests.
14
+ """
15
+
16
+ @staticmethod
17
+ def ensure_headers(**kwargs) -> dict:
18
+ """
19
+ Initialize headers dictionary in kwargs.
20
+
21
+ Ensures parameter dictionary has 'headers' key for HTTP headers,
22
+ guaranteeing consistent structure for all parameter building methods.
23
+
24
+ Parameters
25
+ ----------
26
+ **kwargs : dict
27
+ Keyword arguments to format. May be empty or contain various
28
+ parameters for API operations.
29
+
30
+ Returns
31
+ -------
32
+ dict
33
+ Dictionary with guaranteed 'headers' key containing
34
+ empty dict if not already present.
35
+ """
36
+ if "headers" not in kwargs:
37
+ kwargs["headers"] = {}
38
+ return kwargs
39
+
40
+ @staticmethod
41
+ def set_json_content_type(**kwargs) -> dict:
42
+ """
43
+ Set Content-Type header to application/json.
44
+
45
+ Ensures that the 'Content-Type' header is set to 'application/json'
46
+ for requests that require JSON payloads.
47
+
48
+ Parameters
49
+ ----------
50
+ **kwargs : dict
51
+ Keyword arguments to format. May be empty or contain various
52
+ parameters for API operations.
53
+
54
+ Returns
55
+ -------
56
+ dict
57
+ Dictionary with 'Content-Type' header set to 'application/json'.
58
+ """
59
+ kwargs = HeaderManager.ensure_headers(**kwargs)
60
+ kwargs["headers"]["Content-Type"] = "application/json"
61
+ return kwargs