digitalhub 0.9.2__py3-none-any.whl → 0.10.0b1__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 -3
  2. digitalhub/client/_base/client.py +3 -2
  3. digitalhub/client/dhcore/api_builder.py +5 -0
  4. digitalhub/client/dhcore/client.py +27 -399
  5. digitalhub/client/dhcore/configurator.py +339 -0
  6. digitalhub/client/dhcore/error_parser.py +107 -0
  7. digitalhub/client/dhcore/models.py +13 -23
  8. digitalhub/client/dhcore/utils.py +4 -44
  9. digitalhub/client/local/api_builder.py +9 -17
  10. digitalhub/client/local/client.py +12 -2
  11. digitalhub/client/local/enums.py +11 -0
  12. digitalhub/configurator/api.py +31 -0
  13. digitalhub/configurator/configurator.py +194 -0
  14. digitalhub/configurator/credentials_store.py +65 -0
  15. digitalhub/configurator/ini_module.py +74 -0
  16. digitalhub/entities/_base/_base/entity.py +2 -2
  17. digitalhub/entities/_base/material/entity.py +19 -6
  18. digitalhub/entities/_base/material/utils.py +2 -2
  19. digitalhub/entities/_commons/enums.py +1 -0
  20. digitalhub/entities/_commons/models.py +9 -0
  21. digitalhub/entities/_commons/utils.py +25 -0
  22. digitalhub/entities/_operations/processor.py +103 -107
  23. digitalhub/entities/artifact/crud.py +3 -3
  24. digitalhub/entities/artifact/utils.py +1 -1
  25. digitalhub/entities/dataitem/_base/entity.py +2 -2
  26. digitalhub/entities/dataitem/crud.py +3 -3
  27. digitalhub/entities/dataitem/table/entity.py +2 -2
  28. digitalhub/{utils/data_utils.py → entities/dataitem/table/utils.py} +43 -51
  29. digitalhub/entities/dataitem/utils.py +6 -3
  30. digitalhub/entities/model/_base/entity.py +172 -0
  31. digitalhub/entities/model/_base/spec.py +0 -10
  32. digitalhub/entities/model/_base/status.py +10 -0
  33. digitalhub/entities/model/crud.py +3 -3
  34. digitalhub/entities/model/huggingface/spec.py +6 -3
  35. digitalhub/entities/model/mlflow/models.py +2 -2
  36. digitalhub/entities/model/mlflow/spec.py +1 -3
  37. digitalhub/entities/model/mlflow/utils.py +44 -5
  38. digitalhub/entities/run/_base/entity.py +149 -0
  39. digitalhub/entities/run/_base/status.py +12 -0
  40. digitalhub/entities/task/_base/spec.py +2 -0
  41. digitalhub/entities/task/crud.py +4 -0
  42. digitalhub/readers/{_commons → pandas}/enums.py +4 -0
  43. digitalhub/readers/pandas/reader.py +58 -10
  44. digitalhub/stores/_base/store.py +1 -49
  45. digitalhub/stores/api.py +8 -33
  46. digitalhub/stores/builder.py +44 -161
  47. digitalhub/stores/local/store.py +4 -18
  48. digitalhub/stores/remote/store.py +3 -10
  49. digitalhub/stores/s3/configurator.py +107 -0
  50. digitalhub/stores/s3/enums.py +17 -0
  51. digitalhub/stores/s3/models.py +21 -0
  52. digitalhub/stores/s3/store.py +8 -28
  53. digitalhub/{utils/s3_utils.py → stores/s3/utils.py} +7 -3
  54. digitalhub/stores/sql/configurator.py +88 -0
  55. digitalhub/stores/sql/enums.py +16 -0
  56. digitalhub/stores/sql/models.py +24 -0
  57. digitalhub/stores/sql/store.py +14 -57
  58. digitalhub/utils/exceptions.py +6 -0
  59. digitalhub/utils/generic_utils.py +9 -8
  60. digitalhub/utils/uri_utils.py +1 -1
  61. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0b1.dist-info}/METADATA +5 -6
  62. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0b1.dist-info}/RECORD +67 -54
  63. test/local/imports/test_imports.py +0 -1
  64. digitalhub/client/dhcore/env.py +0 -23
  65. /digitalhub/{readers/_commons → configurator}/__init__.py +0 -0
  66. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0b1.dist-info}/LICENSE.txt +0 -0
  67. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0b1.dist-info}/WHEEL +0 -0
  68. {digitalhub-0.9.2.dist-info → digitalhub-0.10.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,339 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from warnings import warn
5
+
6
+ from requests import request
7
+
8
+ from digitalhub.client.dhcore.enums import AuthType, DhcoreEnvVar
9
+ from digitalhub.client.dhcore.models import BasicAuth, OAuth2TokenAuth
10
+ from digitalhub.configurator.configurator import configurator
11
+ from digitalhub.utils.exceptions import ClientError
12
+ from digitalhub.utils.uri_utils import has_remote_scheme
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from requests import Response
16
+
17
+
18
+ # Default key used to store authentication information
19
+ AUTH_KEY = "_auth"
20
+
21
+ # API levels that are supported
22
+ MAX_API_LEVEL = 20
23
+ MIN_API_LEVEL = 10
24
+ LIB_VERSION = 10
25
+
26
+
27
+ class ClientDHCoreConfigurator:
28
+ """
29
+ Configurator object used to configure the client.
30
+ """
31
+
32
+ ##############################
33
+ # Configuration methods
34
+ ##############################
35
+
36
+ def configure(self, config: dict | None = None) -> None:
37
+ """
38
+ Configure the client attributes with config (given or from
39
+ environment).
40
+ Regarding authentication parameters, the config parameter
41
+ takes precedence over the env variables, and the token
42
+ over the basic auth. Furthermore, the config parameter is
43
+ validated against the proper pydantic model.
44
+
45
+ Parameters
46
+ ----------
47
+ config : dict
48
+ Configuration dictionary.
49
+
50
+ Returns
51
+ -------
52
+ None
53
+ """
54
+ if config is None:
55
+ self._get_core_endpoint()
56
+ self._get_auth_vars()
57
+ return
58
+
59
+ # Read passed config
60
+ # Validate and save credentials
61
+ if config.get("access_token") is not None:
62
+ config = OAuth2TokenAuth(**config)
63
+ for pair in [
64
+ (AUTH_KEY, AuthType.OAUTH2.value),
65
+ (DhcoreEnvVar.ENDPOINT.value, config.endpoint),
66
+ (DhcoreEnvVar.ISSUER.value, config.issuer),
67
+ (DhcoreEnvVar.ACCESS_TOKEN.value, config.access_token),
68
+ (DhcoreEnvVar.REFRESH_TOKEN.value, config.refresh_token),
69
+ (DhcoreEnvVar.CLIENT_ID.value, config.client_id),
70
+ ]:
71
+ configurator.set_credential(*pair)
72
+
73
+ elif config.get("user") is not None and config.get("password") is not None:
74
+ config = BasicAuth(**config)
75
+ for pair in [
76
+ (AUTH_KEY, AuthType.BASIC.value),
77
+ (DhcoreEnvVar.ENDPOINT.value, config.endpoint),
78
+ (DhcoreEnvVar.USER.value, config.user),
79
+ (DhcoreEnvVar.PASSWORD.value, config.password),
80
+ ]:
81
+ configurator.set_credential(*pair)
82
+
83
+ else:
84
+ raise ClientError("Invalid credentials format.")
85
+
86
+ def check_core_version(self, response: Response) -> None:
87
+ """
88
+ Raise an exception if DHCore API version is not supported.
89
+
90
+ Parameters
91
+ ----------
92
+ response : Response
93
+ The response object.
94
+
95
+ Returns
96
+ -------
97
+ None
98
+ """
99
+ if "X-Api-Level" in response.headers:
100
+ core_api_level = int(response.headers["X-Api-Level"])
101
+ if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
102
+ raise ClientError("Backend API level not supported.")
103
+ if LIB_VERSION < core_api_level:
104
+ warn("Backend API level is higher than library version. You should consider updating the library.")
105
+
106
+ def build_url(self, api: str) -> str:
107
+ """
108
+ Build the url.
109
+
110
+ Parameters
111
+ ----------
112
+ api : str
113
+ The api to call.
114
+
115
+ Returns
116
+ -------
117
+ str
118
+ The url.
119
+ """
120
+ api = api.removeprefix("/")
121
+ return f"{configurator.get_credentials(DhcoreEnvVar.ENDPOINT.value)}/{api}"
122
+
123
+ ##############################
124
+ # Private methods
125
+ ##############################
126
+
127
+ @staticmethod
128
+ def _sanitize_endpoint(endpoint: str) -> str:
129
+ """
130
+ Sanitize the endpoint.
131
+
132
+ Returns
133
+ -------
134
+ None
135
+ """
136
+ if not has_remote_scheme(endpoint):
137
+ raise ClientError("Invalid endpoint scheme. Must start with http:// or https://.")
138
+
139
+ endpoint = endpoint.strip()
140
+ return endpoint.removesuffix("/")
141
+
142
+ def _get_core_endpoint(self) -> None:
143
+ """
144
+ Get the DHCore endpoint from env.
145
+
146
+ Returns
147
+ -------
148
+ None
149
+
150
+ Raises
151
+ ------
152
+ Exception
153
+ If the endpoint of DHCore is not set in the env variables.
154
+ """
155
+ endpoint = configurator.load_var(DhcoreEnvVar.ENDPOINT.value)
156
+ if endpoint is None:
157
+ raise ClientError("Endpoint not set as environment variables.")
158
+ endpoint = self._sanitize_endpoint(endpoint)
159
+ configurator.set_credential(DhcoreEnvVar.ENDPOINT.value, endpoint)
160
+
161
+ def _get_auth_vars(self) -> None:
162
+ """
163
+ Get authentication parameters from the env.
164
+
165
+ Returns
166
+ -------
167
+ None
168
+ """
169
+ # Give priority to access token
170
+ access_token = configurator.load_var(DhcoreEnvVar.ACCESS_TOKEN.value)
171
+ if access_token is not None:
172
+ configurator.set_credential(AUTH_KEY, AuthType.OAUTH2.value)
173
+ configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value, access_token)
174
+
175
+ # Fallback to basic
176
+ else:
177
+ user = configurator.load_var(DhcoreEnvVar.USER.value)
178
+ password = configurator.load_var(DhcoreEnvVar.PASSWORD.value)
179
+ if user is not None and password is not None:
180
+ configurator.set_credential(AUTH_KEY, AuthType.BASIC.value)
181
+ configurator.set_credential(DhcoreEnvVar.USER.value, user)
182
+ configurator.set_credential(DhcoreEnvVar.PASSWORD.value, password)
183
+
184
+ ##############################
185
+ # Auth methods
186
+ ##############################
187
+
188
+ def basic_auth(self) -> bool:
189
+ """
190
+ Get basic auth.
191
+
192
+ Returns
193
+ -------
194
+ bool
195
+ """
196
+ auth_type = configurator.get_credentials(AUTH_KEY)
197
+ return auth_type == AuthType.BASIC.value
198
+
199
+ def oauth2_auth(self) -> bool:
200
+ """
201
+ Get oauth2 auth.
202
+
203
+ Returns
204
+ -------
205
+ bool
206
+ """
207
+ auth_type = configurator.get_credentials(AUTH_KEY)
208
+ return auth_type == AuthType.OAUTH2.value
209
+
210
+ def set_request_auth(self, kwargs: dict) -> dict:
211
+ """
212
+ Get the authentication header.
213
+
214
+ Parameters
215
+ ----------
216
+ kwargs : dict
217
+ Keyword arguments to pass to the request.
218
+
219
+ Returns
220
+ -------
221
+ dict
222
+ Authentication header.
223
+ """
224
+ creds = configurator.get_all_cred()
225
+ if AUTH_KEY not in creds:
226
+ return kwargs
227
+ if self.basic_auth():
228
+ user = creds[DhcoreEnvVar.USER.value]
229
+ password = creds[DhcoreEnvVar.PASSWORD.value]
230
+ kwargs["auth"] = (user, password)
231
+ elif self.oauth2_auth():
232
+ if "headers" not in kwargs:
233
+ kwargs["headers"] = {}
234
+ access_token = creds[DhcoreEnvVar.ACCESS_TOKEN.value]
235
+ kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
236
+ return kwargs
237
+
238
+ def get_new_access_token(self) -> None:
239
+ """
240
+ Get a new access token.
241
+
242
+ Returns
243
+ -------
244
+ None
245
+ """
246
+ # Call issuer and get endpoint for
247
+ # refreshing access token
248
+ url = self._get_refresh_endpoint()
249
+
250
+ # Call refresh token endpoint
251
+ # Try token from env
252
+ refresh_token = configurator.load_from_env(DhcoreEnvVar.REFRESH_TOKEN.value)
253
+ response = self._call_refresh_token_endpoint(url, refresh_token)
254
+
255
+ # Otherwise try token from file
256
+ if response.status_code in (400, 401, 403):
257
+ refresh_token = configurator.load_from_config(DhcoreEnvVar.REFRESH_TOKEN.value)
258
+ response = self._call_refresh_token_endpoint(url, refresh_token)
259
+
260
+ response.raise_for_status()
261
+ dict_response = response.json()
262
+
263
+ # Read new access token and refresh token
264
+ configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value, dict_response["access_token"])
265
+ configurator.set_credential(DhcoreEnvVar.REFRESH_TOKEN.value, dict_response["refresh_token"])
266
+
267
+ # Propagate new access token to config file
268
+ self._write_env()
269
+
270
+ def _write_env(self) -> None:
271
+ """
272
+ Write the env variables to the .dhcore.ini file.
273
+ It will overwrite any existing env variables.
274
+
275
+ Returns
276
+ -------
277
+ None
278
+ """
279
+ configurator.write_env(
280
+ [
281
+ DhcoreEnvVar.ACCESS_TOKEN.value,
282
+ DhcoreEnvVar.REFRESH_TOKEN.value,
283
+ ]
284
+ )
285
+
286
+ def _get_refresh_endpoint(self) -> str:
287
+ """
288
+ Get the refresh endpoint.
289
+
290
+ Returns
291
+ -------
292
+ str
293
+ Refresh endpoint.
294
+ """
295
+ # Get issuer endpoint
296
+ endpoint_issuer = configurator.load_var(DhcoreEnvVar.ISSUER.value)
297
+ if endpoint_issuer is not None:
298
+ endpoint_issuer = self._sanitize_endpoint(endpoint_issuer)
299
+ configurator.set_credential(DhcoreEnvVar.ISSUER.value, endpoint_issuer)
300
+ else:
301
+ raise ClientError("Issuer endpoint not set.")
302
+
303
+ # Standard issuer endpoint path
304
+ url = endpoint_issuer + "/.well-known/openid-configuration"
305
+
306
+ # Call issuer to get refresh endpoint
307
+ r = request("GET", url, timeout=60)
308
+ r.raise_for_status()
309
+ return r.json().get("token_endpoint")
310
+
311
+ def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
312
+ """
313
+ Call the refresh token endpoint.
314
+
315
+ Parameters
316
+ ----------
317
+ url : str
318
+ Refresh token endpoint.
319
+ refresh_token : str
320
+ Refresh token.
321
+
322
+ Returns
323
+ -------
324
+ Response
325
+ Response object.
326
+ """
327
+ # Get client id
328
+ client_id = configurator.load_var(DhcoreEnvVar.CLIENT_ID.value)
329
+ if client_id is None:
330
+ raise ClientError("Client id not set.")
331
+
332
+ # Send request to get new access token
333
+ payload = {
334
+ "grant_type": "refresh_token",
335
+ "client_id": client_id,
336
+ "refresh_token": refresh_token,
337
+ }
338
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
339
+ 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."""
@@ -4,7 +4,7 @@ import os
4
4
  import typing
5
5
 
6
6
  from digitalhub.client.api import get_client
7
- from digitalhub.client.dhcore.enums import AuthType, DhcoreEnvVar
7
+ from digitalhub.client.dhcore.enums import DhcoreEnvVar
8
8
 
9
9
  if typing.TYPE_CHECKING:
10
10
  from digitalhub.client.dhcore.client import ClientDHCore
@@ -21,8 +21,7 @@ def set_dhcore_env(
21
21
  """
22
22
  Function to set environment variables for DHCore config.
23
23
  Note that if the environment variable is already set, it
24
- will be overwritten. It also ovverides the remote client
25
- configuration.
24
+ will be overwritten.
26
25
 
27
26
  Parameters
28
27
  ----------
@@ -56,47 +55,8 @@ def set_dhcore_env(
56
55
  if client_id is not None:
57
56
  os.environ[DhcoreEnvVar.CLIENT_ID.value] = client_id
58
57
 
59
- update_client_from_env()
60
-
61
-
62
- def update_client_from_env() -> None:
63
- """
64
- Function to update client from environment variables.
65
-
66
- Returns
67
- -------
68
- None
69
- """
70
58
  client: ClientDHCore = get_client(local=False)
71
-
72
- # Update endpoint
73
- endpoint = os.getenv(DhcoreEnvVar.ENDPOINT.value)
74
- if endpoint is not None:
75
- client._endpoint_core = endpoint
76
-
77
- # Update auth
78
-
79
- # If token is set, it will override the other auth options
80
- access_token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
81
- refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
82
- client_id = os.getenv(DhcoreEnvVar.CLIENT_ID.value)
83
-
84
- if access_token is not None:
85
- if refresh_token is not None:
86
- client._refresh_token = refresh_token
87
- if client_id is not None:
88
- client._client_id = client_id
89
- client._access_token = access_token
90
- client._auth_type = AuthType.OAUTH2.value
91
- return
92
-
93
- # Otherwise, if user and password are set, basic auth will be used
94
- username = os.getenv(DhcoreEnvVar.USER.value)
95
- password = os.getenv(DhcoreEnvVar.PASSWORD.value)
96
- if username is not None and password is not None:
97
- client._user = username
98
- client._password = password
99
- client._auth_type = AuthType.BASIC.value
59
+ client._configurator.configure()
100
60
 
101
61
 
102
62
  def refresh_token() -> None:
@@ -108,4 +68,4 @@ def refresh_token() -> None:
108
68
  None
109
69
  """
110
70
  client: ClientDHCore = get_client(local=False)
111
- client._get_new_access_token()
71
+ client._configurator.get_new_access_token()
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from digitalhub.client._base.api_builder import ClientApiBuilder
4
+ from digitalhub.client.local.enums import LocalClientVar
4
5
  from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
5
6
  from digitalhub.utils.exceptions import BackendError
6
7
 
@@ -63,9 +64,7 @@ class ClientLocalApiBuilder(ClientApiBuilder):
63
64
  BackendOperations.DELETE.value,
64
65
  ):
65
66
  return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}"
66
- elif operation == BackendOperations.SHARE.value:
67
- raise BackendError("Share API not implemented for Local.")
68
- raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
67
+ raise BackendError(f"API for operation '{operation}' for entity type '{entity_type}' not implemented in Local.")
69
68
 
70
69
  def build_api_context(self, operation: str, **kwargs) -> str:
71
70
  """
@@ -84,17 +83,10 @@ class ClientLocalApiBuilder(ClientApiBuilder):
84
83
  BackendOperations.DELETE.value,
85
84
  ):
86
85
  return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}"
87
- elif operation == BackendOperations.LOGS.value:
88
- raise BackendError("Logs run API not implemented for Local.")
89
- elif operation == BackendOperations.STOP.value:
90
- raise BackendError("Stop run API not implemented for Local.")
91
- elif operation == BackendOperations.RESUME.value:
92
- raise BackendError("Resume run API not implemented for Local.")
93
- elif operation == BackendOperations.DATA.value:
94
- raise BackendError("Secret API (read/set value) not implemented for Local.")
95
- elif operation == BackendOperations.FILES.value:
96
- raise BackendError("Files API not implemented for Local.")
97
- elif operation == BackendOperations.SEARCH.value:
98
- raise BackendError("Search API not implemented for Local.")
99
-
100
- raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
86
+ elif operation in (
87
+ BackendOperations.LOGS.value,
88
+ BackendOperations.FILES.value,
89
+ BackendOperations.METRICS.value,
90
+ ):
91
+ return LocalClientVar.EMPTY.value
92
+ raise BackendError(f"API for operation '{operation}' for entity type '{entity_type}' not implemented in Local.")
@@ -2,9 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from copy import deepcopy
4
4
  from datetime import datetime, timezone
5
+ from typing import Any
5
6
 
6
7
  from digitalhub.client._base.client import Client
7
8
  from digitalhub.client.local.api_builder import ClientLocalApiBuilder
9
+ from digitalhub.client.local.enums import LocalClientVar
8
10
  from digitalhub.client.local.key_builder import ClientLocalKeyBuilder
9
11
  from digitalhub.utils.exceptions import BackendError
10
12
 
@@ -32,7 +34,7 @@ class ClientLocal(Client):
32
34
  # CRUD
33
35
  ##############################
34
36
 
35
- def create_object(self, api: str, obj: dict, **kwargs) -> dict:
37
+ def create_object(self, api: str, obj: Any, **kwargs) -> dict:
36
38
  """
37
39
  Create an object in local.
38
40
 
@@ -48,6 +50,9 @@ class ClientLocal(Client):
48
50
  dict
49
51
  The created object.
50
52
  """
53
+ if not isinstance(obj, dict):
54
+ raise TypeError("Object must be a dictionary")
55
+
51
56
  entity_type, _, context_api = self._parse_api(api)
52
57
  try:
53
58
  # Check if entity_type is valid
@@ -114,6 +119,8 @@ class ClientLocal(Client):
114
119
  dict
115
120
  The read object.
116
121
  """
122
+ if api == LocalClientVar.EMPTY.value:
123
+ return {}
117
124
  entity_type, entity_id, context_api = self._parse_api(api)
118
125
  if entity_id is None:
119
126
  msg = self._format_msg(4)
@@ -155,7 +162,7 @@ class ClientLocal(Client):
155
162
  msg = self._format_msg(3, entity_type=entity_type, entity_id=entity_id)
156
163
  raise BackendError(msg)
157
164
 
158
- def update_object(self, api: str, obj: dict, **kwargs) -> dict:
165
+ def update_object(self, api: str, obj: Any, **kwargs) -> dict:
159
166
  """
160
167
  Update an object in local.
161
168
 
@@ -171,6 +178,9 @@ class ClientLocal(Client):
171
178
  dict
172
179
  The updated object.
173
180
  """
181
+ if not isinstance(obj, dict):
182
+ raise TypeError("Object must be a dictionary")
183
+
174
184
  entity_type, entity_id, context_api = self._parse_api(api)
175
185
  try:
176
186
  # API example
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class LocalClientVar(Enum):
7
+ """
8
+ Variables for Local.
9
+ """
10
+
11
+ EMPTY = "EMPTY"