digitalhub 0.9.2__py3-none-any.whl → 0.10.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 (121) hide show
  1. digitalhub/__init__.py +2 -3
  2. digitalhub/client/_base/api_builder.py +1 -1
  3. digitalhub/client/_base/client.py +25 -2
  4. digitalhub/client/_base/params_builder.py +16 -0
  5. digitalhub/client/dhcore/api_builder.py +9 -3
  6. digitalhub/client/dhcore/client.py +30 -398
  7. digitalhub/client/dhcore/configurator.py +361 -0
  8. digitalhub/client/dhcore/error_parser.py +107 -0
  9. digitalhub/client/dhcore/models.py +13 -23
  10. digitalhub/client/dhcore/params_builder.py +178 -0
  11. digitalhub/client/dhcore/utils.py +4 -44
  12. digitalhub/client/local/api_builder.py +13 -18
  13. digitalhub/client/local/client.py +18 -2
  14. digitalhub/client/local/enums.py +11 -0
  15. digitalhub/client/local/params_builder.py +116 -0
  16. digitalhub/configurator/api.py +31 -0
  17. digitalhub/configurator/configurator.py +195 -0
  18. digitalhub/configurator/credentials_store.py +65 -0
  19. digitalhub/configurator/ini_module.py +74 -0
  20. digitalhub/entities/_base/_base/entity.py +2 -2
  21. digitalhub/entities/_base/context/entity.py +4 -4
  22. digitalhub/entities/_base/entity/builder.py +5 -5
  23. digitalhub/entities/_base/executable/entity.py +2 -2
  24. digitalhub/entities/_base/material/entity.py +12 -12
  25. digitalhub/entities/_base/material/status.py +1 -1
  26. digitalhub/entities/_base/material/utils.py +2 -2
  27. digitalhub/entities/_base/unversioned/entity.py +2 -2
  28. digitalhub/entities/_base/versioned/entity.py +2 -2
  29. digitalhub/entities/_commons/enums.py +2 -0
  30. digitalhub/entities/_commons/metrics.py +164 -0
  31. digitalhub/entities/_commons/types.py +5 -0
  32. digitalhub/entities/_commons/utils.py +2 -2
  33. digitalhub/entities/_processors/base.py +527 -0
  34. digitalhub/entities/{_operations/processor.py → _processors/context.py} +212 -837
  35. digitalhub/entities/_processors/utils.py +158 -0
  36. digitalhub/entities/artifact/artifact/spec.py +3 -1
  37. digitalhub/entities/artifact/crud.py +13 -12
  38. digitalhub/entities/artifact/utils.py +1 -1
  39. digitalhub/entities/builders.py +6 -18
  40. digitalhub/entities/dataitem/_base/entity.py +0 -41
  41. digitalhub/entities/dataitem/crud.py +27 -15
  42. digitalhub/entities/dataitem/table/entity.py +49 -35
  43. digitalhub/entities/dataitem/table/models.py +4 -3
  44. digitalhub/{utils/data_utils.py → entities/dataitem/table/utils.py} +46 -54
  45. digitalhub/entities/dataitem/utils.py +58 -10
  46. digitalhub/entities/function/crud.py +9 -9
  47. digitalhub/entities/model/_base/entity.py +120 -0
  48. digitalhub/entities/model/_base/spec.py +6 -17
  49. digitalhub/entities/model/_base/status.py +10 -0
  50. digitalhub/entities/model/crud.py +13 -12
  51. digitalhub/entities/model/huggingface/spec.py +9 -4
  52. digitalhub/entities/model/mlflow/models.py +2 -2
  53. digitalhub/entities/model/mlflow/spec.py +7 -7
  54. digitalhub/entities/model/mlflow/utils.py +44 -5
  55. digitalhub/entities/project/_base/entity.py +317 -9
  56. digitalhub/entities/project/_base/spec.py +8 -6
  57. digitalhub/entities/project/crud.py +12 -11
  58. digitalhub/entities/run/_base/entity.py +103 -6
  59. digitalhub/entities/run/_base/spec.py +4 -2
  60. digitalhub/entities/run/_base/status.py +12 -0
  61. digitalhub/entities/run/crud.py +8 -8
  62. digitalhub/entities/secret/_base/entity.py +3 -3
  63. digitalhub/entities/secret/_base/spec.py +4 -2
  64. digitalhub/entities/secret/crud.py +11 -9
  65. digitalhub/entities/task/_base/entity.py +4 -4
  66. digitalhub/entities/task/_base/models.py +51 -40
  67. digitalhub/entities/task/_base/spec.py +2 -0
  68. digitalhub/entities/task/_base/utils.py +2 -2
  69. digitalhub/entities/task/crud.py +12 -8
  70. digitalhub/entities/workflow/crud.py +9 -9
  71. digitalhub/factory/utils.py +9 -9
  72. digitalhub/readers/{_base → data/_base}/builder.py +1 -1
  73. digitalhub/readers/{_base → data/_base}/reader.py +16 -4
  74. digitalhub/readers/{api.py → data/api.py} +2 -2
  75. digitalhub/readers/{factory.py → data/factory.py} +3 -3
  76. digitalhub/readers/{pandas → data/pandas}/builder.py +2 -2
  77. digitalhub/readers/{pandas → data/pandas}/reader.py +110 -30
  78. digitalhub/readers/query/__init__.py +0 -0
  79. digitalhub/stores/_base/store.py +59 -69
  80. digitalhub/stores/api.py +8 -33
  81. digitalhub/stores/builder.py +44 -161
  82. digitalhub/stores/local/store.py +106 -89
  83. digitalhub/stores/remote/store.py +86 -11
  84. digitalhub/stores/s3/configurator.py +108 -0
  85. digitalhub/stores/s3/enums.py +17 -0
  86. digitalhub/stores/s3/models.py +21 -0
  87. digitalhub/stores/s3/store.py +154 -70
  88. digitalhub/{utils/s3_utils.py → stores/s3/utils.py} +7 -3
  89. digitalhub/stores/sql/configurator.py +88 -0
  90. digitalhub/stores/sql/enums.py +16 -0
  91. digitalhub/stores/sql/models.py +24 -0
  92. digitalhub/stores/sql/store.py +106 -85
  93. digitalhub/{readers/_commons → utils}/enums.py +5 -1
  94. digitalhub/utils/exceptions.py +6 -0
  95. digitalhub/utils/file_utils.py +8 -7
  96. digitalhub/utils/generic_utils.py +28 -15
  97. digitalhub/utils/git_utils.py +16 -9
  98. digitalhub/utils/types.py +5 -0
  99. digitalhub/utils/uri_utils.py +2 -2
  100. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/METADATA +25 -31
  101. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/RECORD +108 -99
  102. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info}/WHEEL +1 -2
  103. digitalhub/client/dhcore/env.py +0 -23
  104. digitalhub/entities/_base/project/entity.py +0 -341
  105. digitalhub-0.9.2.dist-info/top_level.txt +0 -2
  106. test/local/CRUD/test_artifacts.py +0 -96
  107. test/local/CRUD/test_dataitems.py +0 -96
  108. test/local/CRUD/test_models.py +0 -95
  109. test/local/imports/test_imports.py +0 -66
  110. test/local/instances/test_validate.py +0 -55
  111. test/test_crud_functions.py +0 -109
  112. test/test_crud_runs.py +0 -86
  113. test/test_crud_tasks.py +0 -81
  114. test/testkfp.py +0 -37
  115. test/testkfp_pipeline.py +0 -22
  116. /digitalhub/{entities/_base/project → configurator}/__init__.py +0 -0
  117. /digitalhub/entities/{_operations → _processors}/__init__.py +0 -0
  118. /digitalhub/readers/{_base → data}/__init__.py +0 -0
  119. /digitalhub/readers/{_commons → data/_base}/__init__.py +0 -0
  120. /digitalhub/readers/{pandas → data/pandas}/__init__.py +0 -0
  121. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0.dist-info/licenses}/LICENSE.txt +0 -0
@@ -0,0 +1,361 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import typing
5
+ from warnings import warn
6
+
7
+ from requests import request
8
+
9
+ from digitalhub.client.dhcore.enums import AuthType, DhcoreEnvVar
10
+ from digitalhub.client.dhcore.models import BasicAuth, OAuth2TokenAuth
11
+ from digitalhub.configurator.configurator import configurator
12
+ from digitalhub.stores.s3.enums import S3StoreEnv
13
+ from digitalhub.stores.sql.enums import SqlStoreEnv
14
+ from digitalhub.utils.exceptions import ClientError
15
+ from digitalhub.utils.uri_utils import has_remote_scheme
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from requests import Response
19
+
20
+
21
+ # Default key used to store authentication information
22
+ AUTH_KEY = "_auth"
23
+
24
+ # API levels that are supported
25
+ MAX_API_LEVEL = 20
26
+ MIN_API_LEVEL = 10
27
+ LIB_VERSION = 10
28
+
29
+
30
+ class ClientDHCoreConfigurator:
31
+ """
32
+ Configurator object used to configure the client.
33
+ """
34
+
35
+ ##############################
36
+ # Configuration methods
37
+ ##############################
38
+
39
+ def configure(self, config: dict | None = None) -> None:
40
+ """
41
+ Configure the client attributes with config (given or from
42
+ environment).
43
+ Regarding authentication parameters, the config parameter
44
+ takes precedence over the env variables, and the token
45
+ over the basic auth. Furthermore, the config parameter is
46
+ validated against the proper pydantic model.
47
+
48
+ Parameters
49
+ ----------
50
+ config : dict
51
+ Configuration dictionary.
52
+
53
+ Returns
54
+ -------
55
+ None
56
+ """
57
+ if config is None:
58
+ self._get_core_endpoint()
59
+ self._get_auth_vars()
60
+ return
61
+
62
+ # Read passed config
63
+ # Validate and save credentials
64
+ if config.get("access_token") is not None:
65
+ config = OAuth2TokenAuth(**config)
66
+ for pair in [
67
+ (AUTH_KEY, AuthType.OAUTH2.value),
68
+ (DhcoreEnvVar.ENDPOINT.value, config.endpoint),
69
+ (DhcoreEnvVar.ISSUER.value, config.issuer),
70
+ (DhcoreEnvVar.ACCESS_TOKEN.value, config.access_token),
71
+ (DhcoreEnvVar.REFRESH_TOKEN.value, config.refresh_token),
72
+ (DhcoreEnvVar.CLIENT_ID.value, config.client_id),
73
+ ]:
74
+ configurator.set_credential(*pair)
75
+
76
+ elif config.get("user") is not None and config.get("password") is not None:
77
+ config = BasicAuth(**config)
78
+ for pair in [
79
+ (AUTH_KEY, AuthType.BASIC.value),
80
+ (DhcoreEnvVar.ENDPOINT.value, config.endpoint),
81
+ (DhcoreEnvVar.USER.value, config.user),
82
+ (DhcoreEnvVar.PASSWORD.value, config.password),
83
+ ]:
84
+ configurator.set_credential(*pair)
85
+
86
+ else:
87
+ raise ClientError("Invalid credentials format.")
88
+
89
+ def check_core_version(self, response: Response) -> None:
90
+ """
91
+ Raise an exception if DHCore API version is not supported.
92
+
93
+ Parameters
94
+ ----------
95
+ response : Response
96
+ The response object.
97
+
98
+ Returns
99
+ -------
100
+ None
101
+ """
102
+ if "X-Api-Level" in response.headers:
103
+ core_api_level = int(response.headers["X-Api-Level"])
104
+ if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
105
+ raise ClientError("Backend API level not supported.")
106
+ if LIB_VERSION < core_api_level:
107
+ warn("Backend API level is higher than library version. You should consider updating the library.")
108
+
109
+ def build_url(self, api: str) -> str:
110
+ """
111
+ Build the url.
112
+
113
+ Parameters
114
+ ----------
115
+ api : str
116
+ The api to call.
117
+
118
+ Returns
119
+ -------
120
+ str
121
+ The url.
122
+ """
123
+ api = api.removeprefix("/")
124
+ return f"{configurator.get_credential(DhcoreEnvVar.ENDPOINT.value)}/{api}"
125
+
126
+ ##############################
127
+ # Private methods
128
+ ##############################
129
+
130
+ @staticmethod
131
+ def _sanitize_endpoint(endpoint: str) -> str:
132
+ """
133
+ Sanitize the endpoint.
134
+
135
+ Returns
136
+ -------
137
+ None
138
+ """
139
+ if not has_remote_scheme(endpoint):
140
+ raise ClientError("Invalid endpoint scheme. Must start with http:// or https://.")
141
+
142
+ endpoint = endpoint.strip()
143
+ return endpoint.removesuffix("/")
144
+
145
+ def _get_core_endpoint(self) -> None:
146
+ """
147
+ Get the DHCore endpoint from env.
148
+
149
+ Returns
150
+ -------
151
+ None
152
+
153
+ Raises
154
+ ------
155
+ Exception
156
+ If the endpoint of DHCore is not set in the env variables.
157
+ """
158
+ endpoint = configurator.load_var(DhcoreEnvVar.ENDPOINT.value)
159
+ if endpoint is None:
160
+ raise ClientError("Endpoint not set as environment variables.")
161
+ endpoint = self._sanitize_endpoint(endpoint)
162
+ configurator.set_credential(DhcoreEnvVar.ENDPOINT.value, endpoint)
163
+
164
+ def _get_auth_vars(self) -> None:
165
+ """
166
+ Get authentication parameters from the env.
167
+
168
+ Returns
169
+ -------
170
+ None
171
+ """
172
+ # Give priority to access token
173
+ access_token = configurator.load_var(DhcoreEnvVar.ACCESS_TOKEN.value)
174
+ if access_token is not None:
175
+ configurator.set_credential(AUTH_KEY, AuthType.OAUTH2.value)
176
+ configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value, access_token)
177
+
178
+ # Fallback to basic
179
+ else:
180
+ user = configurator.load_var(DhcoreEnvVar.USER.value)
181
+ password = configurator.load_var(DhcoreEnvVar.PASSWORD.value)
182
+ if user is not None and password is not None:
183
+ configurator.set_credential(AUTH_KEY, AuthType.BASIC.value)
184
+ configurator.set_credential(DhcoreEnvVar.USER.value, user)
185
+ configurator.set_credential(DhcoreEnvVar.PASSWORD.value, password)
186
+
187
+ ##############################
188
+ # Auth methods
189
+ ##############################
190
+
191
+ def basic_auth(self) -> bool:
192
+ """
193
+ Get basic auth.
194
+
195
+ Returns
196
+ -------
197
+ bool
198
+ """
199
+ auth_type = configurator.get_credential(AUTH_KEY)
200
+ return auth_type == AuthType.BASIC.value
201
+
202
+ def oauth2_auth(self) -> bool:
203
+ """
204
+ Get oauth2 auth.
205
+
206
+ Returns
207
+ -------
208
+ bool
209
+ """
210
+ auth_type = configurator.get_credential(AUTH_KEY)
211
+ return auth_type == AuthType.OAUTH2.value
212
+
213
+ def set_request_auth(self, kwargs: dict) -> dict:
214
+ """
215
+ Get the authentication header.
216
+
217
+ Parameters
218
+ ----------
219
+ kwargs : dict
220
+ Keyword arguments to pass to the request.
221
+
222
+ Returns
223
+ -------
224
+ dict
225
+ Authentication header.
226
+ """
227
+ creds = configurator.get_all_credentials()
228
+ if AUTH_KEY not in creds:
229
+ return kwargs
230
+ if self.basic_auth():
231
+ user = creds[DhcoreEnvVar.USER.value]
232
+ password = creds[DhcoreEnvVar.PASSWORD.value]
233
+ kwargs["auth"] = (user, password)
234
+ elif self.oauth2_auth():
235
+ if "headers" not in kwargs:
236
+ kwargs["headers"] = {}
237
+ access_token = creds[DhcoreEnvVar.ACCESS_TOKEN.value]
238
+ kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
239
+ return kwargs
240
+
241
+ def get_new_access_token(self) -> None:
242
+ """
243
+ Get a new access token.
244
+
245
+ Returns
246
+ -------
247
+ None
248
+ """
249
+ # Call issuer and get endpoint for
250
+ # refreshing access token
251
+ url = self._get_refresh_endpoint()
252
+
253
+ # Call refresh token endpoint
254
+ # Try token from env
255
+ refresh_token = configurator.load_from_env(DhcoreEnvVar.REFRESH_TOKEN.value)
256
+ response = self._call_refresh_token_endpoint(url, refresh_token)
257
+
258
+ # Otherwise try token from file
259
+ if response.status_code in (400, 401, 403):
260
+ refresh_token = configurator.load_from_config(DhcoreEnvVar.REFRESH_TOKEN.value)
261
+ response = self._call_refresh_token_endpoint(url, refresh_token)
262
+
263
+ response.raise_for_status()
264
+ dict_response = response.json()
265
+
266
+ # Read new access token and refresh token
267
+ configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value, dict_response["access_token"])
268
+ configurator.set_credential(DhcoreEnvVar.REFRESH_TOKEN.value, dict_response["refresh_token"])
269
+
270
+ # Set new credential in stores
271
+ if (access_key := dict_response.get("aws_access_key_id")) is not None:
272
+ configurator.set_credential(S3StoreEnv.ACCESS_KEY_ID.value, access_key)
273
+ os.environ[S3StoreEnv.ACCESS_KEY_ID.value] = access_key
274
+ if (secret_key := dict_response.get("aws_secret_access_key")) is not None:
275
+ configurator.set_credential(S3StoreEnv.SECRET_ACCESS_KEY.value, secret_key)
276
+ os.environ[S3StoreEnv.SECRET_ACCESS_KEY.value] = secret_key
277
+ if (db_username := dict_response.get("db_username")) is not None:
278
+ configurator.set_credential(SqlStoreEnv.USERNAME.value, db_username)
279
+ os.environ[SqlStoreEnv.USERNAME.value] = db_username
280
+ if (db_password := dict_response.get("db_password")) is not None:
281
+ configurator.set_credential(SqlStoreEnv.PASSWORD.value, db_password)
282
+ os.environ[SqlStoreEnv.PASSWORD.value] = db_password
283
+
284
+ # Propagate new access token to config file
285
+ self._write_env()
286
+
287
+ def _write_env(self) -> None:
288
+ """
289
+ Write the env variables to the .dhcore.ini file.
290
+ It will overwrite any existing env variables.
291
+
292
+ Returns
293
+ -------
294
+ None
295
+ """
296
+ configurator.write_env(
297
+ [
298
+ DhcoreEnvVar.ACCESS_TOKEN.value,
299
+ DhcoreEnvVar.REFRESH_TOKEN.value,
300
+ S3StoreEnv.ACCESS_KEY_ID.value,
301
+ S3StoreEnv.SECRET_ACCESS_KEY.value,
302
+ SqlStoreEnv.USERNAME.value,
303
+ SqlStoreEnv.PASSWORD.value,
304
+ ]
305
+ )
306
+
307
+ def _get_refresh_endpoint(self) -> str:
308
+ """
309
+ Get the refresh endpoint.
310
+
311
+ Returns
312
+ -------
313
+ str
314
+ Refresh endpoint.
315
+ """
316
+ # Get issuer endpoint
317
+ endpoint_issuer = configurator.load_var(DhcoreEnvVar.ISSUER.value)
318
+ if endpoint_issuer is not None:
319
+ endpoint_issuer = self._sanitize_endpoint(endpoint_issuer)
320
+ configurator.set_credential(DhcoreEnvVar.ISSUER.value, endpoint_issuer)
321
+ else:
322
+ raise ClientError("Issuer endpoint not set.")
323
+
324
+ # Standard issuer endpoint path
325
+ url = endpoint_issuer + "/.well-known/openid-configuration"
326
+
327
+ # Call issuer to get refresh endpoint
328
+ r = request("GET", url, timeout=60)
329
+ r.raise_for_status()
330
+ return r.json().get("token_endpoint")
331
+
332
+ def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
333
+ """
334
+ Call the refresh token endpoint.
335
+
336
+ Parameters
337
+ ----------
338
+ url : str
339
+ Refresh token endpoint.
340
+ refresh_token : str
341
+ Refresh token.
342
+
343
+ Returns
344
+ -------
345
+ Response
346
+ Response object.
347
+ """
348
+ # Get client id
349
+ client_id = configurator.load_var(DhcoreEnvVar.CLIENT_ID.value)
350
+ if client_id is None:
351
+ raise ClientError("Client id not set.")
352
+
353
+ # Send request to get new access token
354
+ payload = {
355
+ "grant_type": "refresh_token",
356
+ "client_id": client_id,
357
+ "refresh_token": refresh_token,
358
+ "scope": "openid credentials offline_access",
359
+ }
360
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
361
+ return request("POST", url, data=payload, headers=headers, timeout=60)
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from requests.exceptions import HTTPError, RequestException
6
+
7
+ from digitalhub.utils.exceptions import (
8
+ BackendError,
9
+ BadRequestError,
10
+ EntityAlreadyExistsError,
11
+ EntityNotExistsError,
12
+ ForbiddenError,
13
+ MissingSpecError,
14
+ UnauthorizedError,
15
+ )
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from requests import Response
19
+
20
+
21
+ class ErrorParser:
22
+ @staticmethod
23
+ def parse(response: Response) -> None:
24
+ """
25
+ Handle DHCore API errors.
26
+
27
+ Parameters
28
+ ----------
29
+ response : Response
30
+ The response object.
31
+
32
+ Returns
33
+ -------
34
+ None
35
+ """
36
+ try:
37
+ response.raise_for_status()
38
+
39
+ # Backend errors
40
+ except RequestException as e:
41
+ # Handle timeout
42
+ if isinstance(e, TimeoutError):
43
+ msg = "Request to DHCore backend timed out."
44
+ raise TimeoutError(msg)
45
+
46
+ # Handle connection error
47
+ elif isinstance(e, ConnectionError):
48
+ msg = "Unable to connect to DHCore backend."
49
+ raise ConnectionError(msg)
50
+
51
+ # Handle HTTP errors
52
+ elif isinstance(e, HTTPError):
53
+ txt_resp = f"Response: {response.text}."
54
+
55
+ # Bad request
56
+ if response.status_code == 400:
57
+ # Missing spec in backend
58
+ if "missing spec" in response.text:
59
+ msg = f"Missing spec in backend. {txt_resp}"
60
+ raise MissingSpecError(msg)
61
+
62
+ # Duplicated entity
63
+ elif "Duplicated entity" in response.text:
64
+ msg = f"Entity already exists. {txt_resp}"
65
+ raise EntityAlreadyExistsError(msg)
66
+
67
+ # Other errors
68
+ else:
69
+ msg = f"Bad request. {txt_resp}"
70
+ raise BadRequestError(msg)
71
+
72
+ # Unauthorized errors
73
+ elif response.status_code == 401:
74
+ msg = f"Unauthorized. {txt_resp}"
75
+ raise UnauthorizedError(msg)
76
+
77
+ # Forbidden errors
78
+ elif response.status_code == 403:
79
+ msg = f"Forbidden. {txt_resp}"
80
+ raise ForbiddenError(msg)
81
+
82
+ # Entity not found
83
+ elif response.status_code == 404:
84
+ # Put with entity not found
85
+ if "No such EntityName" in response.text:
86
+ msg = f"Entity does not exists. {txt_resp}"
87
+ raise EntityNotExistsError(msg)
88
+
89
+ # Other cases
90
+ else:
91
+ msg = f"Not found. {txt_resp}"
92
+ raise BackendError(msg)
93
+
94
+ # Other errors
95
+ else:
96
+ msg = f"Backend error. {txt_resp}"
97
+ raise BackendError(msg) from e
98
+
99
+ # Other requests errors
100
+ else:
101
+ msg = f"Some error occurred. {e}"
102
+ raise BackendError(msg) from e
103
+
104
+ # Other generic errors
105
+ except Exception as e:
106
+ msg = f"Some error occurred: {e}"
107
+ raise RuntimeError(msg) from e
@@ -2,45 +2,35 @@ from __future__ import annotations
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
5
- from digitalhub.client.dhcore.env import FALLBACK_USER
6
5
 
7
-
8
- class AuthConfig(BaseModel):
6
+ class ClientConfig(BaseModel):
9
7
  """Client configuration model."""
10
8
 
11
- user: str = FALLBACK_USER
12
- """Username."""
9
+ endpoint: str
10
+ """API endpoint."""
13
11
 
14
12
 
15
- class BasicAuth(AuthConfig):
13
+ class BasicAuth(ClientConfig):
16
14
  """Basic authentication model."""
17
15
 
16
+ user: str
17
+ """Username."""
18
+
18
19
  password: str
19
20
  """Basic authentication password."""
20
21
 
21
22
 
22
- class ClientParams(AuthConfig):
23
- """Client id authentication model."""
24
-
25
- client_id: str = None
26
- """OAuth2 client id."""
27
-
28
- client_scecret: str = None
29
- """OAuth2 client secret."""
30
-
31
-
32
- class OAuth2TokenAuth(ClientParams):
23
+ class OAuth2TokenAuth(ClientConfig):
33
24
  """OAuth2 token authentication model."""
34
25
 
35
26
  access_token: str
36
27
  """OAuth2 token."""
37
28
 
38
- refresh_token: str = None
29
+ refresh_token: str
39
30
  """OAuth2 refresh token."""
40
31
 
32
+ client_id: str
33
+ """OAuth2 client id."""
41
34
 
42
- class TokenExchangeAuth(ClientParams):
43
- """Token exchange authentication model."""
44
-
45
- exchange_token: str
46
- """Exchange token."""
35
+ issuer_endpoint: str
36
+ """OAuth2 issuer endpoint."""
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.client._base.params_builder import ClientParametersBuilder
4
+ from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
5
+
6
+
7
+ class ClientDHCoreParametersBuilder(ClientParametersBuilder):
8
+ """
9
+ This class is used to build the parameters for the DHCore client calls.
10
+ """
11
+
12
+ def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
13
+ """
14
+ Build the parameters for the client call.
15
+
16
+ Parameters
17
+ ----------
18
+ category : str
19
+ API category.
20
+ operation : str
21
+ API operation.
22
+ **kwargs : dict
23
+ Parameters to build.
24
+
25
+ Returns
26
+ -------
27
+ dict
28
+ Parameters formatted.
29
+ """
30
+ if category == ApiCategories.BASE.value:
31
+ return self.build_parameters_base(operation, **kwargs)
32
+ return self.build_parameters_context(operation, **kwargs)
33
+
34
+ def build_parameters_base(self, operation: str, **kwargs) -> dict:
35
+ """
36
+ Build the base parameters for the client call.
37
+
38
+ Parameters
39
+ ----------
40
+ operation : str
41
+ API operation.
42
+ **kwargs : dict
43
+ Parameters to build.
44
+
45
+ Returns
46
+ -------
47
+ dict
48
+ Parameters formatted.
49
+ """
50
+ kwargs = self._set_params(**kwargs)
51
+ if operation == BackendOperations.DELETE.value:
52
+ if (cascade := kwargs.pop("cascade", None)) is not None:
53
+ kwargs["params"]["cascade"] = str(cascade).lower()
54
+ elif operation == BackendOperations.SHARE.value:
55
+ kwargs["params"]["user"] = kwargs.pop("user")
56
+ if kwargs.pop("unshare", False):
57
+ kwargs["params"]["id"] = kwargs.pop("id")
58
+
59
+ return kwargs
60
+
61
+ def build_parameters_context(self, operation: str, **kwargs) -> dict:
62
+ """
63
+ Build the context parameters for the client call.
64
+
65
+ Parameters
66
+ ----------
67
+ operation : str
68
+ API operation.
69
+ **kwargs : dict
70
+ Parameters to build.
71
+
72
+ Returns
73
+ -------
74
+ dict
75
+ Parameters formatted.
76
+ """
77
+ kwargs = self._set_params(**kwargs)
78
+
79
+ # Handle read
80
+ if operation == BackendOperations.READ.value:
81
+ name = kwargs.pop("entity_name", None)
82
+ if name is not None:
83
+ kwargs["params"]["name"] = name
84
+ elif operation == BackendOperations.READ_ALL_VERSIONS.value:
85
+ kwargs["params"]["versions"] = "all"
86
+ kwargs["params"]["name"] = kwargs.pop("entity_name")
87
+ # Handle delete
88
+ elif operation == BackendOperations.DELETE.value:
89
+ # Handle cascade
90
+ if (cascade := kwargs.pop("cascade", None)) is not None:
91
+ kwargs["params"]["cascade"] = str(cascade).lower()
92
+
93
+ # Handle delete all versions
94
+ entity_id = kwargs.pop("entity_id")
95
+ entity_name = kwargs.pop("entity_name")
96
+ if not kwargs.pop("delete_all_versions", False):
97
+ if entity_id is None:
98
+ raise ValueError(
99
+ "If `delete_all_versions` is False, `entity_id` must be provided,"
100
+ " either as an argument or in key `identifier`.",
101
+ )
102
+ else:
103
+ kwargs["params"]["name"] = entity_name
104
+ # Handle search
105
+ elif operation == BackendOperations.SEARCH.value:
106
+ # Handle fq
107
+ if (fq := kwargs.pop("fq", None)) is not None:
108
+ kwargs["params"]["fq"] = fq
109
+
110
+ # Add search query
111
+ if (query := kwargs.pop("query", None)) is not None:
112
+ kwargs["params"]["q"] = query
113
+
114
+ # Add search filters
115
+ fq = []
116
+
117
+ # Entity types
118
+ if (entity_types := kwargs.pop("entity_types", None)) is not None:
119
+ if not isinstance(entity_types, list):
120
+ entity_types = [entity_types]
121
+ if len(entity_types) == 1:
122
+ entity_types = entity_types[0]
123
+ else:
124
+ entity_types = " OR ".join(entity_types)
125
+ fq.append(f"type:({entity_types})")
126
+
127
+ # Name
128
+ if (name := kwargs.pop("name", None)) is not None:
129
+ fq.append(f'metadata.name:"{name}"')
130
+
131
+ # Kind
132
+ if (kind := kwargs.pop("kind", None)) is not None:
133
+ fq.append(f'kind:"{kind}"')
134
+
135
+ # Time
136
+ created = kwargs.pop("created", None)
137
+ updated = kwargs.pop("updated", None)
138
+ created = created if created is not None else "*"
139
+ updated = updated if updated is not None else "*"
140
+ fq.append(f"metadata.updated:[{created} TO {updated}]")
141
+
142
+ # Description
143
+ if (description := kwargs.pop("description", None)) is not None:
144
+ fq.append(f'metadata.description:"{description}"')
145
+
146
+ # Labels
147
+ if (labels := kwargs.pop("labels", None)) is not None:
148
+ if len(labels) == 1:
149
+ labels = labels[0]
150
+ else:
151
+ labels = " AND ".join(labels)
152
+ fq.append(f"metadata.labels:({labels})")
153
+
154
+ # Add filters
155
+ kwargs["params"]["fq"] = fq
156
+
157
+ return kwargs
158
+
159
+ @staticmethod
160
+ def _set_params(**kwargs) -> dict:
161
+ """
162
+ Format params parameter.
163
+
164
+ Parameters
165
+ ----------
166
+ **kwargs : dict
167
+ Keyword arguments.
168
+
169
+ Returns
170
+ -------
171
+ dict
172
+ Parameters with initialized params.
173
+ """
174
+ if not kwargs:
175
+ kwargs = {}
176
+ if "params" not in kwargs:
177
+ kwargs["params"] = {}
178
+ return kwargs