digitalhub 0.13.4__py3-none-any.whl → 0.14.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 (116) hide show
  1. digitalhub/__init__.py +3 -8
  2. digitalhub/context/api.py +1 -5
  3. digitalhub/context/builder.py +1 -5
  4. digitalhub/context/context.py +2 -9
  5. digitalhub/entities/_base/_base/entity.py +0 -15
  6. digitalhub/entities/_base/context/entity.py +1 -1
  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 +169 -79
  10. digitalhub/entities/_base/material/entity.py +6 -22
  11. digitalhub/entities/_base/material/utils.py +1 -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/utils.py +83 -21
  17. digitalhub/entities/_constructors/_resources.py +151 -0
  18. digitalhub/entities/{_base/entity/_constructors → _constructors}/name.py +18 -0
  19. digitalhub/entities/_processors/base/__init__.py +3 -0
  20. digitalhub/entities/_processors/{base.py → base/crud.py} +14 -226
  21. digitalhub/entities/_processors/base/import_export.py +123 -0
  22. digitalhub/entities/_processors/base/processor.py +302 -0
  23. digitalhub/entities/_processors/base/special_ops.py +108 -0
  24. digitalhub/entities/_processors/context/__init__.py +3 -0
  25. digitalhub/entities/_processors/context/crud.py +652 -0
  26. digitalhub/entities/_processors/context/import_export.py +242 -0
  27. digitalhub/entities/_processors/context/material.py +123 -0
  28. digitalhub/entities/_processors/context/processor.py +400 -0
  29. digitalhub/entities/_processors/context/special_ops.py +476 -0
  30. digitalhub/entities/_processors/processors.py +12 -0
  31. digitalhub/entities/_processors/utils.py +12 -11
  32. digitalhub/entities/artifact/crud.py +58 -22
  33. digitalhub/entities/artifact/utils.py +3 -3
  34. digitalhub/entities/dataitem/crud.py +63 -20
  35. digitalhub/entities/dataitem/table/entity.py +24 -22
  36. digitalhub/entities/dataitem/utils.py +15 -15
  37. digitalhub/entities/function/_base/entity.py +3 -3
  38. digitalhub/entities/function/crud.py +55 -24
  39. digitalhub/entities/model/_base/entity.py +62 -20
  40. digitalhub/entities/model/crud.py +58 -22
  41. digitalhub/entities/model/utils.py +3 -3
  42. digitalhub/entities/project/_base/entity.py +321 -152
  43. digitalhub/entities/project/crud.py +15 -23
  44. digitalhub/entities/run/_base/builder.py +0 -4
  45. digitalhub/entities/run/_base/entity.py +70 -63
  46. digitalhub/entities/run/crud.py +79 -26
  47. digitalhub/entities/secret/_base/entity.py +1 -5
  48. digitalhub/entities/secret/crud.py +29 -26
  49. digitalhub/entities/task/_base/builder.py +0 -4
  50. digitalhub/entities/task/_base/entity.py +5 -5
  51. digitalhub/entities/task/_base/models.py +13 -16
  52. digitalhub/entities/task/crud.py +61 -29
  53. digitalhub/entities/trigger/_base/entity.py +1 -5
  54. digitalhub/entities/trigger/crud.py +64 -24
  55. digitalhub/entities/workflow/_base/entity.py +3 -3
  56. digitalhub/entities/workflow/crud.py +55 -21
  57. digitalhub/factory/entity.py +283 -0
  58. digitalhub/factory/enums.py +18 -0
  59. digitalhub/factory/registry.py +197 -0
  60. digitalhub/factory/runtime.py +44 -0
  61. digitalhub/factory/utils.py +3 -54
  62. digitalhub/runtimes/_base.py +2 -2
  63. digitalhub/stores/client/_base/enums.py +39 -0
  64. digitalhub/stores/client/_base/key_builder.py +2 -2
  65. digitalhub/stores/client/_base/params_builder.py +48 -0
  66. digitalhub/stores/client/api.py +6 -10
  67. digitalhub/stores/client/builder.py +4 -4
  68. digitalhub/stores/client/dhcore/api_builder.py +2 -1
  69. digitalhub/stores/client/dhcore/client.py +85 -429
  70. digitalhub/stores/client/dhcore/configurator.py +109 -328
  71. digitalhub/stores/client/dhcore/enums.py +0 -16
  72. digitalhub/stores/client/dhcore/error_parser.py +0 -4
  73. digitalhub/stores/client/dhcore/header_manager.py +61 -0
  74. digitalhub/stores/client/dhcore/http_handler.py +133 -0
  75. digitalhub/stores/client/dhcore/params_builder.py +147 -134
  76. digitalhub/stores/client/dhcore/response_processor.py +102 -0
  77. digitalhub/stores/client/dhcore/utils.py +6 -72
  78. digitalhub/stores/client/local/api_builder.py +1 -1
  79. digitalhub/stores/client/local/client.py +79 -47
  80. digitalhub/stores/client/local/params_builder.py +18 -41
  81. digitalhub/stores/credentials/api.py +0 -4
  82. digitalhub/stores/credentials/configurator.py +2 -28
  83. digitalhub/stores/credentials/enums.py +3 -0
  84. digitalhub/stores/credentials/handler.py +0 -12
  85. digitalhub/stores/credentials/ini_module.py +0 -22
  86. digitalhub/stores/credentials/store.py +0 -4
  87. digitalhub/stores/data/_base/store.py +0 -16
  88. digitalhub/stores/data/builder.py +1 -5
  89. digitalhub/stores/data/local/store.py +0 -103
  90. digitalhub/stores/data/remote/store.py +0 -4
  91. digitalhub/stores/data/s3/configurator.py +60 -14
  92. digitalhub/stores/data/s3/store.py +49 -16
  93. digitalhub/stores/data/sql/configurator.py +0 -8
  94. digitalhub/stores/data/sql/store.py +21 -10
  95. digitalhub/stores/readers/data/factory.py +0 -8
  96. digitalhub/stores/readers/data/pandas/reader.py +0 -16
  97. digitalhub/utils/file_utils.py +0 -17
  98. digitalhub/utils/generic_utils.py +0 -12
  99. digitalhub/utils/git_utils.py +0 -8
  100. digitalhub/utils/io_utils.py +0 -12
  101. digitalhub/utils/store_utils.py +44 -0
  102. {digitalhub-0.13.4.dist-info → digitalhub-0.14.0.dist-info}/METADATA +3 -2
  103. {digitalhub-0.13.4.dist-info → digitalhub-0.14.0.dist-info}/RECORD +111 -95
  104. digitalhub/entities/_processors/context.py +0 -1450
  105. digitalhub/entities/task/_base/utils.py +0 -22
  106. digitalhub/factory/factory.py +0 -381
  107. digitalhub/stores/client/dhcore/models.py +0 -40
  108. digitalhub/stores/data/s3/utils.py +0 -78
  109. /digitalhub/entities/{_base/entity/_constructors → _constructors}/__init__.py +0 -0
  110. /digitalhub/entities/{_base/entity/_constructors → _constructors}/metadata.py +0 -0
  111. /digitalhub/entities/{_base/entity/_constructors → _constructors}/spec.py +0 -0
  112. /digitalhub/entities/{_base/entity/_constructors → _constructors}/status.py +0 -0
  113. /digitalhub/entities/{_base/entity/_constructors → _constructors}/uuid.py +0 -0
  114. {digitalhub-0.13.4.dist-info → digitalhub-0.14.0.dist-info}/WHEEL +0 -0
  115. {digitalhub-0.13.4.dist-info → digitalhub-0.14.0.dist-info}/licenses/AUTHORS +0 -0
  116. {digitalhub-0.13.4.dist-info → digitalhub-0.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -22,54 +22,32 @@ if typing.TYPE_CHECKING:
22
22
 
23
23
  class ClientDHCoreConfigurator(Configurator):
24
24
  """
25
- Configurator object used to configure the client.
26
-
27
- The configurator starts reading the credentials from the
28
- environment and from the ini file and stores them into the
29
- creds_handler object.
30
-
31
- While reading the credentials from the two sources (environment and file),
32
- the configurator evaluate if the required keys are present in both sources.
33
- If the required keys are not present in both sources, the configurator
34
- will rise an error, otherwise decide which source to use.
35
-
36
- Once the credentials are read, the configurator check the current profile
37
- name from the ini file, and set it. The default one is __default. The
38
- profile is used to discriminate a set of credentials inside the ini file.
39
-
40
- The configurator finally set the authentication type based on the credentials.
41
- The logic is the following:
42
-
43
- 1. Check for a personal access token. Use it immediately to
44
- require a timed access token in an exchange endpoint.
45
- Switche then the origin to file and .
46
- Set the auth type to EXCHANGE.
47
- 2. Check for an access token and a refresh token.
48
- Set the auth type to OAUTH2.
49
- 3. Check for username and password.
50
- Set the auth type to BASIC.
51
- 4. If none of the above is true, leave the auth type to None.
25
+ DHCore client configurator for credential management and authentication.
26
+
27
+ Handles loading credentials from environment variables and configuration files,
28
+ evaluates authentication types, and manages token refresh operations. Supports
29
+ multiple authentication methods: EXCHANGE (personal access token), OAUTH2
30
+ (access + refresh tokens), ACCESS_TOKEN (access token only), and BASIC
31
+ (username + password).
32
+
33
+ The configurator automatically determines the best authentication method and
34
+ handles token exchange for personal access tokens by switching to file-based
35
+ credential storage.
52
36
  """
53
37
 
54
38
  keys = [*list_enum(CredsEnvVar)]
55
39
  required_keys = [CredsEnvVar.DHCORE_ENDPOINT.value]
56
- keys_to_unprefix = [
40
+ keys_to_prefix = [
57
41
  CredsEnvVar.DHCORE_REFRESH_TOKEN.value,
58
42
  CredsEnvVar.DHCORE_ACCESS_TOKEN.value,
59
43
  CredsEnvVar.DHCORE_ISSUER.value,
60
44
  CredsEnvVar.DHCORE_CLIENT_ID.value,
45
+ CredsEnvVar.OAUTH2_TOKEN_ENDPOINT.value,
61
46
  ]
62
47
 
63
48
  def __init__(self) -> None:
64
49
  """
65
- Initialize the DHCore configurator.
66
-
67
- Sets up the configurator by calling the parent constructor and
68
- initializing the authentication type evaluation process.
69
-
70
- Returns
71
- -------
72
- None
50
+ Initialize DHCore configurator and evaluate authentication type.
73
51
  """
74
52
  super().__init__()
75
53
  self._auth_type: str | None = None
@@ -81,20 +59,10 @@ class ClientDHCoreConfigurator(Configurator):
81
59
 
82
60
  def load_env_vars(self) -> None:
83
61
  """
84
- Load credentials from environment variables.
85
-
86
- Retrieves DHCore credentials from environment variables, sanitizes
87
- them (particularly endpoint URLs), and stores them in the credentials
88
- handler for the environment origin.
62
+ Load and sanitize credentials from environment variables.
89
63
 
90
- Returns
91
- -------
92
- None
93
-
94
- Notes
95
- -----
96
- This method sanitizes endpoint and issuer URLs to ensure they have
97
- proper schemes and removes trailing slashes.
64
+ Sanitizes endpoint and issuer URLs to ensure proper HTTP/HTTPS schemes
65
+ and removes trailing slashes.
98
66
  """
99
67
  env_creds = self._creds_handler.load_from_env(self.keys)
100
68
  env_creds = self._sanitize_env_vars(env_creds)
@@ -102,30 +70,25 @@ class ClientDHCoreConfigurator(Configurator):
102
70
 
103
71
  def _sanitize_env_vars(self, creds: dict) -> dict:
104
72
  """
105
- Sanitize credentials loaded from environment variables.
73
+ Sanitize environment variable credentials.
106
74
 
107
- Validates and normalizes endpoint and issuer URLs from environment
108
- variables. Ensures URLs have proper schemes and removes trailing slashes.
75
+ Validates and normalizes endpoint and issuer URLs. Environment variables
76
+ use full "DHCORE_" prefixes.
109
77
 
110
78
  Parameters
111
79
  ----------
112
80
  creds : dict
113
- Raw credentials dictionary loaded from environment variables.
81
+ Raw credentials from environment variables.
114
82
 
115
83
  Returns
116
84
  -------
117
85
  dict
118
- Sanitized credentials dictionary with normalized URLs.
86
+ Sanitized credentials with normalized URLs.
119
87
 
120
88
  Raises
121
89
  ------
122
90
  ClientError
123
91
  If endpoint or issuer URLs have invalid schemes.
124
-
125
- Notes
126
- -----
127
- Environment variables are expected to have the full "DHCORE_" prefix
128
- for issuer endpoints.
129
92
  """
130
93
  creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
131
94
  creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
@@ -133,112 +96,74 @@ class ClientDHCoreConfigurator(Configurator):
133
96
 
134
97
  def load_file_vars(self) -> None:
135
98
  """
136
- Load credentials from configuration file.
137
-
138
- Retrieves DHCore credentials from the .dhcore.ini file, handles
139
- compatibility with CLI format (keys without DHCORE_ prefix), and
140
- falls back to environment variables for missing endpoint and
141
- personal access token values.
142
-
143
- Returns
144
- -------
145
- None
99
+ Load credentials from configuration file with CLI compatibility.
146
100
 
147
- Notes
148
- -----
149
- This method handles the case where:
150
- - Endpoint might not be present in file response, falls back to env
151
- - Personal access token might not be present, falls back to env
152
- - File format uses keys without "DHCORE_" prefix for compatibility
101
+ Handles keys without "DHCORE_" prefix for CLI compatibility. Falls back
102
+ to environment variables for missing endpoint and personal access token values.
153
103
  """
154
- keys = [*self._remove_prefix_dhcore()]
155
- file_creds = self._creds_handler.load_from_file(keys)
156
- env_creds = self._creds_handler.load_from_env(self.keys)
157
-
158
- # Because in the response there is no endpoint
159
- if file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] is None:
160
- file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] = env_creds.get(CredsEnvVar.DHCORE_ENDPOINT.value)
104
+ file_creds = self._creds_handler.load_from_file(self.keys)
161
105
 
162
106
  # Because in the response there is no personal access token
163
- if file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is None:
164
- file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] = env_creds.get(
165
- CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
166
- )
107
+ pat = CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
108
+ if file_creds[pat] is None:
109
+ file_creds[pat] = self._creds_handler.load_from_env([pat]).get(pat)
110
+
111
+ # Because in the response there is no endpoint
112
+ endpoint = CredsEnvVar.DHCORE_ENDPOINT.value
113
+ if file_creds[endpoint] is None:
114
+ file_creds[endpoint] = self._creds_handler.load_from_env([endpoint]).get(endpoint)
167
115
 
168
116
  file_creds = self._sanitize_file_vars(file_creds)
169
117
  self._creds_handler.set_credentials(self._file, file_creds)
170
118
 
171
119
  def _sanitize_file_vars(self, creds: dict) -> dict:
172
120
  """
173
- Sanitize credentials loaded from configuration file.
121
+ Sanitize configuration file credentials.
174
122
 
175
- Handles the different key formats used in configuration files compared
176
- to environment variables. File format omits "DHCORE_" prefix for
177
- certain keys for CLI compatibility.
123
+ Handles different key formats between file and environment variables.
124
+ File format omits "DHCORE_" prefix for: issuer, client_id, access_token,
125
+ refresh_token. Full names used for: endpoint, user, password, personal_access_token.
178
126
 
179
127
  Parameters
180
128
  ----------
181
129
  creds : dict
182
- Raw credentials dictionary loaded from configuration file.
130
+ Raw credentials from configuration file.
183
131
 
184
132
  Returns
185
133
  -------
186
134
  dict
187
- Sanitized credentials dictionary with standardized key names
188
- and normalized URLs, filtered to include only valid keys.
135
+ Sanitized credentials with standardized keys and normalized URLs.
189
136
 
190
137
  Raises
191
138
  ------
192
139
  ClientError
193
140
  If endpoint or issuer URLs have invalid schemes.
194
-
195
- Notes
196
- -----
197
- File format expects these keys without "DHCORE_" prefix:
198
- - issuer, client_id, access_token, refresh_token
199
- But uses full names for: endpoint, user, password, personal_access_token
200
141
  """
201
142
  creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
202
- creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(
203
- creds[CredsEnvVar.DHCORE_ISSUER.value.removeprefix("DHCORE_")]
204
- )
205
- creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] = creds[
206
- CredsEnvVar.DHCORE_REFRESH_TOKEN.value.removeprefix("DHCORE_")
207
- ]
208
- creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] = creds[
209
- CredsEnvVar.DHCORE_ACCESS_TOKEN.value.removeprefix("DHCORE_")
210
- ]
211
- creds[CredsEnvVar.DHCORE_CLIENT_ID.value] = creds[CredsEnvVar.DHCORE_CLIENT_ID.value.removeprefix("DHCORE_")]
143
+ creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
212
144
  return {k: v for k, v in creds.items() if k in self.keys}
213
145
 
214
146
  @staticmethod
215
147
  def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
216
148
  """
217
- Sanitize and validate endpoint URL.
149
+ Validate and normalize endpoint URL.
218
150
 
219
- Validates that the endpoint URL has a proper HTTP/HTTPS scheme,
220
- trims whitespace, and removes trailing slashes for consistency.
151
+ Ensures proper HTTP/HTTPS scheme, trims whitespace, and removes trailing slashes.
221
152
 
222
153
  Parameters
223
154
  ----------
224
- endpoint : str, optional
225
- The endpoint URL to sanitize. If None, returns None.
155
+ endpoint : str
156
+ Endpoint URL to sanitize.
226
157
 
227
158
  Returns
228
159
  -------
229
160
  str or None
230
- The sanitized endpoint URL with trailing slash removed,
231
- or None if input was None.
161
+ Sanitized URL or None if input was None.
232
162
 
233
163
  Raises
234
164
  ------
235
165
  ClientError
236
- If the endpoint does not start with http:// or https://.
237
-
238
- Notes
239
- -----
240
- This method ensures endpoint URLs are properly formatted for
241
- HTTP requests and prevents common URL formatting issues.
166
+ If endpoint lacks http:// or https:// scheme.
242
167
  """
243
168
  if endpoint is None:
244
169
  return
@@ -252,23 +177,17 @@ class ClientDHCoreConfigurator(Configurator):
252
177
  """
253
178
  Get the configured DHCore backend endpoint.
254
179
 
255
- Retrieves the DHCore endpoint URL from the current credential source
256
- (environment or file based on current origin).
180
+ Returns the sanitized and validated endpoint URL from current credential source.
257
181
 
258
182
  Returns
259
183
  -------
260
184
  str
261
- The DHCore backend endpoint URL.
185
+ DHCore backend endpoint URL.
262
186
 
263
187
  Raises
264
188
  ------
265
189
  KeyError
266
- If the endpoint is not configured in the current credential source.
267
-
268
- Notes
269
- -----
270
- The endpoint returned is already sanitized and validated during
271
- the credential loading process.
190
+ If endpoint not configured in current credential source.
272
191
  """
273
192
  creds = self._creds_handler.get_credentials(self._origin)
274
193
  return creds[CredsEnvVar.DHCORE_ENDPOINT.value]
@@ -279,22 +198,10 @@ class ClientDHCoreConfigurator(Configurator):
279
198
 
280
199
  def change_origin(self) -> None:
281
200
  """
282
- Change the credentials origin and re-evaluate authentication type.
283
-
284
- Switches the credential source (between environment and file) and
285
- re-evaluates the authentication type based on the new credential set.
286
- This is typically called when the current credential source fails
287
- or when switching contexts.
201
+ Switch credential source and re-evaluate authentication type.
288
202
 
289
- Returns
290
- -------
291
- None
292
-
293
- Notes
294
- -----
295
- This method extends the parent class behavior by also re-evaluating
296
- the authentication type, which may change based on different
297
- credentials available in the new source.
203
+ Changes between environment and file credential sources, then re-evaluates
204
+ authentication type based on the new credentials.
298
205
  """
299
206
  super().change_origin()
300
207
 
@@ -307,29 +214,12 @@ class ClientDHCoreConfigurator(Configurator):
307
214
 
308
215
  def set_auth_type(self) -> None:
309
216
  """
310
- Evaluate and set the authentication type from available credentials.
311
-
312
- Analyzes the available credentials and determines the appropriate
313
- authentication method based on the following priority:
314
- 1. EXCHANGE - Personal access token available
315
- 2. OAUTH2 - Access token and refresh token available
316
- 3. ACCESS_TOKEN - Only access token available
317
- 4. BASIC - Username and password available
318
- 5. None - No valid credentials found
319
-
320
- For EXCHANGE authentication, automatically performs token exchange
321
- and switches to file-based credential storage.
322
-
323
- Returns
324
- -------
325
- None
217
+ Determine authentication type from available credentials.
326
218
 
327
- Notes
328
- -----
329
- When EXCHANGE authentication is detected, this method automatically:
330
- - Performs credential refresh to exchange the personal access token
331
- - Changes origin to file-based storage for the new tokens
332
- - Updates the authentication type accordingly
219
+ Evaluates credentials in priority order: EXCHANGE (personal access token),
220
+ OAUTH2 (access + refresh tokens), ACCESS_TOKEN (access only), BASIC
221
+ (username + password). For EXCHANGE type, automatically exchanges the
222
+ personal access token and switches to file-based credentials storage.
333
223
  """
334
224
  creds = creds_handler.get_credentials(self._origin)
335
225
  self._auth_type = self._eval_auth_type(creds)
@@ -343,55 +233,34 @@ class ClientDHCoreConfigurator(Configurator):
343
233
 
344
234
  def refreshable_auth_types(self) -> bool:
345
235
  """
346
- Check if the current authentication type supports token refresh.
236
+ Check if current authentication supports token refresh.
347
237
 
348
- Determines whether the current authentication method supports
349
- automatic token refresh capabilities.
238
+ Returns True for OAUTH2 (refresh token) and EXCHANGE (personal access token),
239
+ False for BASIC and ACCESS_TOKEN.
350
240
 
351
241
  Returns
352
242
  -------
353
243
  bool
354
- True if the authentication type supports refresh (OAUTH2 or EXCHANGE),
355
- False otherwise (BASIC or ACCESS_TOKEN).
356
-
357
- Notes
358
- -----
359
- Only OAUTH2 and EXCHANGE authentication types support refresh:
360
- - OAUTH2: Uses refresh token to get new access tokens
361
- - EXCHANGE: Uses personal access token for token exchange
362
- - BASIC and ACCESS_TOKEN do not support refresh
244
+ Whether authentication type supports refresh.
363
245
  """
364
246
  return self._auth_type in [AuthType.OAUTH2.value, AuthType.EXCHANGE.value]
365
247
 
366
248
  def get_auth_parameters(self, kwargs: dict) -> dict:
367
249
  """
368
- Add authentication parameters to HTTP request arguments.
250
+ Add authentication headers/parameters to HTTP request kwargs.
369
251
 
370
- Modifies the provided kwargs dictionary to include the appropriate
371
- authentication headers or parameters based on the current authentication
372
- type and available credentials.
252
+ Adds Authorization Bearer header for token-based auth or auth tuple
253
+ for basic authentication.
373
254
 
374
255
  Parameters
375
256
  ----------
376
257
  kwargs : dict
377
- HTTP request keyword arguments to be modified with authentication.
258
+ HTTP request arguments to modify.
378
259
 
379
260
  Returns
380
261
  -------
381
262
  dict
382
- The modified kwargs dictionary with authentication parameters added.
383
-
384
- Notes
385
- -----
386
- Authentication is added based on auth type:
387
- - OAUTH2/EXCHANGE/ACCESS_TOKEN: Adds Authorization Bearer header
388
- - BASIC: Adds auth tuple with username/password
389
- - None: No authentication added
390
-
391
- The method assumes that:
392
- - Authentication type has been properly set
393
- - For EXCHANGE type, refresh token has been obtained
394
- - Required credentials are available for the current auth type
263
+ Modified kwargs with authentication parameters.
395
264
  """
396
265
  creds = creds_handler.get_credentials(self._origin)
397
266
  if self._auth_type in (
@@ -411,51 +280,32 @@ class ClientDHCoreConfigurator(Configurator):
411
280
 
412
281
  def refresh_credentials(self, change_origin: bool = False) -> None:
413
282
  """
414
- Refresh authentication credentials by obtaining new access tokens.
283
+ Refresh authentication tokens using OAuth2 flows.
415
284
 
416
- Performs credential refresh using either OAuth2 refresh token flow
417
- or personal access token exchange, depending on the current
418
- authentication type. Updates stored credentials with new tokens.
285
+ Uses refresh_token grant for OAUTH2 or token exchange for EXCHANGE authentication.
286
+ On 400/401/403 errors with change_origin=True, attempts to switch credential
287
+ sources and retry. Saves new credentials to configuration file.
419
288
 
420
289
  Parameters
421
290
  ----------
422
291
  change_origin : bool, default False
423
- Whether to allow changing credential source if refresh fails.
424
- If True and refresh fails, attempts to switch credential sources
425
- and retry once.
426
-
427
- Returns
428
- -------
429
- None
292
+ Whether to switch credential sources on auth failure.
430
293
 
431
294
  Raises
432
295
  ------
433
296
  ClientError
434
- If the authentication type doesn't support refresh, if required
435
- credentials are missing, or if refresh fails and change_origin
436
- is False.
437
-
438
- Notes
439
- -----
440
- Refresh behavior by authentication type:
441
- - OAUTH2: Uses refresh_token grant to get new access/refresh tokens
442
- - EXCHANGE: Uses token exchange with personal access token
443
-
444
- If refresh fails with 400/401/403 status and change_origin=True,
445
- attempts to switch credential sources and retry once.
446
-
447
- New credentials are automatically saved to the configuration file
448
- and the origin is switched to file-based storage.
297
+ If auth type doesn't support refresh or credentials missing.
449
298
  """
450
299
  if not self.refreshable_auth_types():
451
300
  raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
452
301
 
453
- # Get refresh endpoint
454
- url = self._get_refresh_endpoint()
455
-
456
302
  # Get credentials
457
303
  creds = self._creds_handler.get_credentials(self._origin)
458
304
 
305
+ # Get token refresh from creds
306
+ if (url := creds.get(CredsEnvVar.OAUTH2_TOKEN_ENDPOINT.value)) is None:
307
+ url = self._get_refresh_endpoint(creds)
308
+
459
309
  # Get client id
460
310
  if (client_id := creds.get(CredsEnvVar.DHCORE_CLIENT_ID.value)) is None:
461
311
  raise ClientError("Client id not set.")
@@ -491,67 +341,24 @@ class ClientDHCoreConfigurator(Configurator):
491
341
  # Read new credentials and propagate to config file
492
342
  self._export_new_creds(response.json())
493
343
 
494
- def _remove_prefix_dhcore(self) -> list[str]:
344
+ def _get_refresh_endpoint(self, creds: dict) -> str:
495
345
  """
496
- Remove DHCORE_ prefix from selected credential keys for CLI compatibility.
497
-
498
- Creates a list of credential key names with "DHCORE_" prefix removed
499
- from specific keys that are stored without the prefix in configuration
500
- files for compatibility with CLI tools.
501
-
502
- Returns
503
- -------
504
- list[str]
505
- List of credential keys with selective prefix removal applied.
506
-
507
- Notes
508
- -----
509
- Keys that have prefix removed (defined in keys_to_unprefix):
510
- - DHCORE_REFRESH_TOKEN -> refresh_token
511
- - DHCORE_ACCESS_TOKEN -> access_token
512
- - DHCORE_ISSUER -> issuer
513
- - DHCORE_CLIENT_ID -> client_id
514
-
515
- Other keys retain their full names for consistency.
516
- """
517
- new_list = []
518
- for key in self.keys:
519
- if key in self.keys_to_unprefix:
520
- new_list.append(key.removeprefix("DHCORE_"))
521
- else:
522
- new_list.append(key)
523
- return new_list
346
+ Discover OAuth2 token endpoint from issuer well-known configuration.
524
347
 
525
- def _get_refresh_endpoint(self) -> str:
526
- """
527
- Discover the OAuth2 token refresh endpoint from the issuer.
348
+ Queries /.well-known/openid-configuration to extract token_endpoint for
349
+ credential refresh operations.
528
350
 
529
- Queries the OAuth2 issuer's well-known configuration endpoint to
530
- discover the token endpoint used for credential refresh operations.
351
+ Parameters
352
+ ----------
353
+ creds : dict
354
+ Available credential values.
531
355
 
532
356
  Returns
533
357
  -------
534
358
  str
535
- The token endpoint URL for credential refresh.
536
-
537
- Raises
538
- ------
539
- ClientError
540
- If the issuer endpoint is not configured.
541
- HTTPError
542
- If the well-known configuration endpoint is not accessible.
543
- KeyError
544
- If the token_endpoint is not found in the issuer configuration.
545
-
546
- Notes
547
- -----
548
- This method follows the OAuth2/OpenID Connect discovery standard by:
549
- 1. Accessing the issuer's /.well-known/openid-configuration endpoint
550
- 2. Extracting the token_endpoint from the configuration
551
- 3. Using this endpoint for subsequent token refresh operations
359
+ Token endpoint URL for credential refresh.
552
360
  """
553
361
  # Get issuer endpoint
554
- creds = self._creds_handler.get_credentials(self._origin)
555
362
  endpoint_issuer = creds.get(CredsEnvVar.DHCORE_ISSUER.value)
556
363
  if endpoint_issuer is None:
557
364
  raise ClientError("Issuer endpoint not set.")
@@ -570,30 +377,22 @@ class ClientDHCoreConfigurator(Configurator):
570
377
  **kwargs,
571
378
  ) -> Response:
572
379
  """
573
- Make HTTP request to OAuth2 token refresh endpoint.
380
+ Make OAuth2 token refresh request.
574
381
 
575
- Performs a POST request to the OAuth2 token endpoint with the
576
- appropriate form-encoded payload for token refresh or exchange.
382
+ Sends POST request with form-encoded payload using required OAuth2
383
+ content type and 60-second timeout.
577
384
 
578
385
  Parameters
579
386
  ----------
580
387
  url : str
581
- The token endpoint URL to call.
388
+ Token endpoint URL.
582
389
  **kwargs : dict
583
- Token request parameters such as grant_type, client_id,
584
- refresh_token, subject_token, etc.
390
+ Token request parameters (grant_type, client_id, etc.).
585
391
 
586
392
  Returns
587
393
  -------
588
394
  Response
589
- The HTTP response object from the token endpoint.
590
-
591
- Notes
592
- -----
593
- This method:
594
- - Uses application/x-www-form-urlencoded content type as required by OAuth2
595
- - Sets a 60-second timeout for the request
596
- - Returns the raw response for caller to handle status and parsing
395
+ Raw HTTP response for caller handling.
597
396
  """
598
397
  # Send request to get new access token
599
398
  payload = {**kwargs}
@@ -602,30 +401,20 @@ class ClientDHCoreConfigurator(Configurator):
602
401
 
603
402
  def _eval_auth_type(self, creds: dict) -> str | None:
604
403
  """
605
- Evaluate authentication type based on available credentials.
404
+ Determine authentication type from available credentials.
606
405
 
607
- Analyzes the provided credentials and determines the most appropriate
608
- authentication method based on which credential types are available.
406
+ Evaluates in priority order: EXCHANGE (personal access token), OAUTH2
407
+ (access + refresh tokens), ACCESS_TOKEN (access only), BASIC (username + password).
609
408
 
610
409
  Parameters
611
410
  ----------
612
411
  creds : dict
613
- Dictionary containing credential values.
412
+ Available credential values.
614
413
 
615
414
  Returns
616
415
  -------
617
416
  str or None
618
- The determined authentication type from AuthType enum, or None
619
- if no valid authentication method can be determined.
620
-
621
- Notes
622
- -----
623
- Authentication type priority (checked in order):
624
- 1. EXCHANGE - Personal access token is available
625
- 2. OAUTH2 - Both access token and refresh token are available
626
- 3. ACCESS_TOKEN - Only access token is available
627
- 4. BASIC - Both username and password are available
628
- 5. None - No valid credential combination found
417
+ Authentication type from AuthType enum, or None if no valid credentials.
629
418
  """
630
419
  if creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
631
420
  return AuthType.EXCHANGE.value
@@ -642,32 +431,24 @@ class ClientDHCoreConfigurator(Configurator):
642
431
 
643
432
  def _export_new_creds(self, response: dict) -> None:
644
433
  """
645
- Save new credentials from token refresh response.
434
+ Save refreshed credentials and switch to file-based storage.
646
435
 
647
- Takes the response from a successful token refresh operation and
648
- persists the new credentials to the configuration file, then
649
- reloads file-based credentials and switches to file origin.
436
+ Persists new tokens (access_token, refresh_token, etc.) to configuration
437
+ file and switches credential origin to file storage.
650
438
 
651
439
  Parameters
652
440
  ----------
653
441
  response : dict
654
- Token response containing new access_token, refresh_token,
655
- and other credential information.
656
-
657
- Returns
658
- -------
659
- None
660
-
661
- Notes
662
- -----
663
- This method:
664
- 1. Writes new credentials to the configuration file
665
- 2. Reloads file-based credentials to ensure consistency
666
- 3. Changes current origin to file since new tokens are file-based
667
-
668
- The response typically contains access_token, refresh_token,
669
- token_type, expires_in, and other OAuth2 standard fields.
442
+ OAuth2 token response with new credentials.
670
443
  """
444
+ for key in self.keys_to_prefix:
445
+ if key == CredsEnvVar.OAUTH2_TOKEN_ENDPOINT.value:
446
+ prefix = "oauth2_"
447
+ else:
448
+ prefix = "dhcore_"
449
+ key = key.lower()
450
+ if key.removeprefix(prefix) in response:
451
+ response[key] = response.pop(key.removeprefix(prefix))
671
452
  creds_handler.write_env(response)
672
453
  self.load_file_vars()
673
454