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
digitalhub/__init__.py CHANGED
@@ -1,4 +1,3 @@
1
- __version__ = "0.9.0b0"
2
1
  from digitalhub.entities.artifact.crud import (
3
2
  delete_artifact,
4
3
  get_artifact,
@@ -85,15 +84,15 @@ from digitalhub.entities.workflow.crud import (
85
84
  )
86
85
 
87
86
  try:
88
- from digitalhub.entities.model.mlflow.utils import from_mlflow_run
87
+ from digitalhub.entities.model.mlflow.utils import from_mlflow_run, get_mlflow_model_metrics
89
88
  except ImportError:
90
89
  ...
91
90
 
92
91
  from digitalhub.client.dhcore.utils import refresh_token, set_dhcore_env
92
+ from digitalhub.configurator.api import get_current_env, set_current_env
93
93
 
94
94
  # Register entities into registry
95
95
  from digitalhub.factory.utils import register_entities, register_runtimes_entities
96
- from digitalhub.stores.api import set_store
97
96
 
98
97
  register_entities()
99
98
  register_runtimes_entities()
@@ -10,7 +10,7 @@ class ClientApiBuilder:
10
10
  """
11
11
 
12
12
  @abstractmethod
13
- def build_api(self) -> str:
13
+ def build_api(self, category: str, operation: str, **kwargs) -> str:
14
14
  """
15
15
  Build the API for the client.
16
16
  """
@@ -2,10 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
  from abc import abstractmethod
5
+ from typing import Any
5
6
 
6
7
  if typing.TYPE_CHECKING:
7
8
  from digitalhub.client._base.api_builder import ClientApiBuilder
8
9
  from digitalhub.client._base.key_builder import ClientKeyBuilder
10
+ from digitalhub.client._base.params_builder import ClientParametersBuilder
9
11
 
10
12
 
11
13
  class Client:
@@ -20,13 +22,14 @@ class Client:
20
22
  def __init__(self) -> None:
21
23
  self._api_builder: ClientApiBuilder = None
22
24
  self._key_builder: ClientKeyBuilder = None
25
+ self._params_builder: ClientParametersBuilder = None
23
26
 
24
27
  ##############################
25
28
  # CRUD methods
26
29
  ##############################
27
30
 
28
31
  @abstractmethod
29
- def create_object(self, api: str, obj: dict, **kwargs) -> dict:
32
+ def create_object(self, api: str, obj: Any, **kwargs) -> dict:
30
33
  """
31
34
  Create object method.
32
35
  """
@@ -38,7 +41,7 @@ class Client:
38
41
  """
39
42
 
40
43
  @abstractmethod
41
- def update_object(self, api: str, obj: dict, **kwargs) -> dict:
44
+ def update_object(self, api: str, obj: Any, **kwargs) -> dict:
42
45
  """
43
46
  Update object method.
44
47
  """
@@ -111,6 +114,26 @@ class Client:
111
114
  """
112
115
  return self._key_builder.build_key(category, *args, **kwargs)
113
116
 
117
+ def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
118
+ """
119
+ Build the parameters for the client call.
120
+
121
+ Parameters
122
+ ----------
123
+ category : str
124
+ API category.
125
+ operation : str
126
+ API operation.
127
+ **kwargs : dict
128
+ Parameters to build.
129
+
130
+ Returns
131
+ -------
132
+ dict
133
+ Parameters formatted.
134
+ """
135
+ return self._params_builder.build_parameters(category, operation, **kwargs)
136
+
114
137
  ##############################
115
138
  # Interface methods
116
139
  ##############################
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+
5
+
6
+ class ClientParametersBuilder:
7
+ """
8
+ This class is used to build the parameters for the client call.
9
+ Depending on the client, the parameters are built differently.
10
+ """
11
+
12
+ @abstractmethod
13
+ def build_parameters(self, category: str, operation: str, **kwargs) -> dict:
14
+ """
15
+ Build the parameters for the client call.
16
+ """
@@ -71,8 +71,11 @@ class ClientDHCoreApiBuilder(ClientApiBuilder):
71
71
  """
72
72
  Build the context API for the client.
73
73
  """
74
- entity_type = kwargs["entity_type"] + "s"
75
74
  project = kwargs["project"]
75
+ if operation == BackendOperations.SEARCH.value:
76
+ return f"{API_CONTEXT}/{project}/solr/search/item"
77
+
78
+ entity_type = kwargs["entity_type"] + "s"
76
79
  if operation in (
77
80
  BackendOperations.CREATE.value,
78
81
  BackendOperations.LIST.value,
@@ -94,7 +97,10 @@ class ClientDHCoreApiBuilder(ClientApiBuilder):
94
97
  return f"{API_CONTEXT}/{project}/{entity_type}/data"
95
98
  elif operation == BackendOperations.FILES.value:
96
99
  return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/files/info"
97
- elif operation == BackendOperations.SEARCH.value:
98
- return f"{API_CONTEXT}/{project}/solr/search/item"
100
+ elif operation == BackendOperations.METRICS.value:
101
+ if kwargs["metric_name"] is not None:
102
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/metrics/{kwargs['metric_name']}"
103
+ else:
104
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/metrics"
99
105
 
100
106
  raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
@@ -1,31 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime
4
- import json
5
- import os
6
3
  import typing
7
- from warnings import warn
4
+ from typing import Any
8
5
 
9
- from dotenv import get_key, set_key
10
6
  from requests import request
11
- from requests.exceptions import HTTPError, JSONDecodeError, RequestException
7
+ from requests.exceptions import JSONDecodeError
12
8
 
13
9
  from digitalhub.client._base.client import Client
14
10
  from digitalhub.client.dhcore.api_builder import ClientDHCoreApiBuilder
15
- from digitalhub.client.dhcore.enums import AuthType, DhcoreEnvVar
16
- from digitalhub.client.dhcore.env import ENV_FILE, FALLBACK_USER, LIB_VERSION, MAX_API_LEVEL, MIN_API_LEVEL
11
+ from digitalhub.client.dhcore.configurator import ClientDHCoreConfigurator
12
+ from digitalhub.client.dhcore.error_parser import ErrorParser
17
13
  from digitalhub.client.dhcore.key_builder import ClientDHCoreKeyBuilder
18
- from digitalhub.client.dhcore.models import BasicAuth, OAuth2TokenAuth
19
- from digitalhub.utils.exceptions import (
20
- BackendError,
21
- BadRequestError,
22
- EntityAlreadyExistsError,
23
- EntityNotExistsError,
24
- ForbiddenError,
25
- MissingSpecError,
26
- UnauthorizedError,
27
- )
28
- from digitalhub.utils.uri_utils import has_remote_scheme
14
+ from digitalhub.client.dhcore.params_builder import ClientDHCoreParametersBuilder
15
+ from digitalhub.utils.exceptions import BackendError
16
+ from digitalhub.utils.generic_utils import dump_json
29
17
 
30
18
  if typing.TYPE_CHECKING:
31
19
  from requests import Response
@@ -56,28 +44,21 @@ class ClientDHCore(Client):
56
44
  # Key builder
57
45
  self._key_builder = ClientDHCoreKeyBuilder()
58
46
 
59
- # Endpoints
60
- self._endpoint_core: str | None = None
61
- self._endpoint_issuer: str | None = None
47
+ # Parameters builder
48
+ self._params_builder = ClientDHCoreParametersBuilder()
62
49
 
63
- # Authentication
64
- self._auth_type: str | None = None
50
+ # Error parser
51
+ self._error_parser = ErrorParser()
65
52
 
66
- # Basic
67
- self._user: str | None = None
68
- self._password: str | None = None
69
-
70
- # OAuth2
71
- self._access_token: str | None = None
72
- self._refresh_token: str | None = None
73
-
74
- self._configure(config)
53
+ # Client Configurator
54
+ self._configurator = ClientDHCoreConfigurator()
55
+ self._configurator.configure(config)
75
56
 
76
57
  ##############################
77
58
  # CRUD methods
78
59
  ##############################
79
60
 
80
- def create_object(self, api: str, obj: dict, **kwargs) -> dict:
61
+ def create_object(self, api: str, obj: Any, **kwargs) -> dict:
81
62
  """
82
63
  Create an object in DHCore.
83
64
 
@@ -85,7 +66,7 @@ class ClientDHCore(Client):
85
66
  ----------
86
67
  api : str
87
68
  Create API.
88
- obj : dict
69
+ obj : Any
89
70
  Object to create.
90
71
  **kwargs : dict
91
72
  Keyword arguments to pass to the request.
@@ -98,7 +79,7 @@ class ClientDHCore(Client):
98
79
  if "headers" not in kwargs:
99
80
  kwargs["headers"] = {}
100
81
  kwargs["headers"]["Content-Type"] = "application/json"
101
- kwargs["data"] = json.dumps(obj, default=ClientDHCore._json_serialize)
82
+ kwargs["data"] = dump_json(obj)
102
83
  return self._prepare_call("POST", api, **kwargs)
103
84
 
104
85
  def read_object(self, api: str, **kwargs) -> dict:
@@ -119,7 +100,7 @@ class ClientDHCore(Client):
119
100
  """
120
101
  return self._prepare_call("GET", api, **kwargs)
121
102
 
122
- def update_object(self, api: str, obj: dict, **kwargs) -> dict:
103
+ def update_object(self, api: str, obj: Any, **kwargs) -> dict:
123
104
  """
124
105
  Update an object in DHCore.
125
106
 
@@ -140,7 +121,7 @@ class ClientDHCore(Client):
140
121
  if "headers" not in kwargs:
141
122
  kwargs["headers"] = {}
142
123
  kwargs["headers"]["Content-Type"] = "application/json"
143
- kwargs["data"] = json.dumps(obj, default=ClientDHCore._json_serialize)
124
+ kwargs["data"] = dump_json(obj)
144
125
  return self._prepare_call("PUT", api, **kwargs)
145
126
 
146
127
  def delete_object(self, api: str, **kwargs) -> dict:
@@ -256,7 +237,7 @@ class ClientDHCore(Client):
256
237
  if "sort" not in kwargs["params"]:
257
238
  kwargs["params"]["sort"] = "metadata.updated,DESC"
258
239
 
259
- objects_with_highlights = []
240
+ objects_with_highlights: list[dict] = []
260
241
  while True:
261
242
  resp = self._prepare_call("GET", api, **kwargs)
262
243
  contents = resp["content"]
@@ -277,25 +258,6 @@ class ClientDHCore(Client):
277
258
  # Call methods
278
259
  ##############################
279
260
 
280
- @staticmethod
281
- def _json_serialize(obj: dict) -> dict:
282
- """
283
- JSON datetime to ISO format serializer.
284
-
285
- Parameters
286
- ----------
287
- obj : dict
288
- The object to serialize.
289
-
290
- Returns
291
- -------
292
- dict
293
- The serialized object.
294
- """
295
- if isinstance(obj, (datetime.datetime, datetime.date)):
296
- return obj.isoformat()
297
- raise TypeError("Type %s not serializable" % type(obj))
298
-
299
261
  def _prepare_call(self, call_type: str, api: str, **kwargs) -> dict:
300
262
  """
301
263
  Prepare a call to the DHCore API.
@@ -316,32 +278,10 @@ class ClientDHCore(Client):
316
278
  """
317
279
  if kwargs is None:
318
280
  kwargs = {}
319
- url = self._endpoint_core + api
320
- kwargs = self._set_auth(kwargs)
281
+ url = self._configurator.build_url(api)
282
+ kwargs = self._configurator.set_request_auth(kwargs)
321
283
  return self._make_call(call_type, url, **kwargs)
322
284
 
323
- def _set_auth(self, kwargs: dict) -> dict:
324
- """
325
- Set the authentication type.
326
-
327
- Parameters
328
- ----------
329
- kwargs : dict
330
- Keyword arguments to pass to the request.
331
-
332
- Returns
333
- -------
334
- dict
335
- Keyword arguments with the authentication parameters.
336
- """
337
- if self._auth_type == AuthType.BASIC.value:
338
- kwargs["auth"] = self._user, self._password
339
- elif self._auth_type == AuthType.OAUTH2.value:
340
- if "headers" not in kwargs:
341
- kwargs["headers"] = {}
342
- kwargs["headers"]["Authorization"] = f"Bearer {self._access_token}"
343
- return kwargs
344
-
345
285
  def _make_call(self, call_type: str, url: str, refresh_token: bool = True, **kwargs) -> dict:
346
286
  """
347
287
  Make a call to the DHCore API.
@@ -364,126 +304,20 @@ class ClientDHCore(Client):
364
304
  response = request(call_type, url, timeout=60, **kwargs)
365
305
 
366
306
  # Evaluate DHCore API version
367
- self._check_core_version(response)
307
+ self._configurator.check_core_version(response)
368
308
 
369
309
  # Handle token refresh
370
- if response.status_code in [401] and refresh_token:
371
- self._get_new_access_token()
372
- kwargs = self._set_auth(kwargs)
310
+ if response.status_code in [401] and refresh_token and self._configurator.oauth2_auth():
311
+ self._configurator.get_new_access_token()
312
+ kwargs = self._configurator.set_request_auth(kwargs)
373
313
  return self._make_call(call_type, url, refresh_token=False, **kwargs)
374
314
 
375
- self._raise_for_error(response)
376
- return self._parse_response(response)
377
-
378
- def _check_core_version(self, response: Response) -> None:
379
- """
380
- Raise an exception if DHCore API version is not supported.
381
-
382
- Parameters
383
- ----------
384
- response : Response
385
- The response object.
386
-
387
- Returns
388
- -------
389
- None
390
- """
391
- if "X-Api-Level" in response.headers:
392
- core_api_level = int(response.headers["X-Api-Level"])
393
- if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
394
- raise BackendError("Backend API level not supported.")
395
- if LIB_VERSION < core_api_level:
396
- warn("Backend API level is higher than library version. You should consider updating the library.")
397
-
398
- def _raise_for_error(self, response: Response) -> None:
399
- """
400
- Handle DHCore API errors.
315
+ self._error_parser.parse(response)
316
+ return self._dictify_response(response)
401
317
 
402
- Parameters
403
- ----------
404
- response : Response
405
- The response object.
406
-
407
- Returns
408
- -------
409
- None
318
+ def _dictify_response(self, response: Response) -> dict:
410
319
  """
411
- try:
412
- response.raise_for_status()
413
-
414
- # Backend errors
415
- except RequestException as e:
416
- # Handle timeout
417
- if isinstance(e, TimeoutError):
418
- msg = "Request to DHCore backend timed out."
419
- raise TimeoutError(msg)
420
-
421
- # Handle connection error
422
- elif isinstance(e, ConnectionError):
423
- msg = "Unable to connect to DHCore backend."
424
- raise ConnectionError(msg)
425
-
426
- # Handle HTTP errors
427
- elif isinstance(e, HTTPError):
428
- txt_resp = f"Response: {response.text}."
429
-
430
- # Bad request
431
- if response.status_code == 400:
432
- # Missing spec in backend
433
- if "missing spec" in response.text:
434
- msg = f"Missing spec in backend. {txt_resp}"
435
- raise MissingSpecError(msg)
436
-
437
- # Duplicated entity
438
- elif "Duplicated entity" in response.text:
439
- msg = f"Entity already exists. {txt_resp}"
440
- raise EntityAlreadyExistsError(msg)
441
-
442
- # Other errors
443
- else:
444
- msg = f"Bad request. {txt_resp}"
445
- raise BadRequestError(msg)
446
-
447
- # Unauthorized errors
448
- elif response.status_code == 401:
449
- msg = f"Unauthorized. {txt_resp}"
450
- raise UnauthorizedError(msg)
451
-
452
- # Forbidden errors
453
- elif response.status_code == 403:
454
- msg = f"Forbidden. {txt_resp}"
455
- raise ForbiddenError(msg)
456
-
457
- # Entity not found
458
- elif response.status_code == 404:
459
- # Put with entity not found
460
- if "No such EntityName" in response.text:
461
- msg = f"Entity does not exists. {txt_resp}"
462
- raise EntityNotExistsError(msg)
463
-
464
- # Other cases
465
- else:
466
- msg = f"Not found. {txt_resp}"
467
- raise BackendError(msg)
468
-
469
- # Other errors
470
- else:
471
- msg = f"Backend error. {txt_resp}"
472
- raise BackendError(msg) from e
473
-
474
- # Other requests errors
475
- else:
476
- msg = f"Some error occurred. {e}"
477
- raise BackendError(msg) from e
478
-
479
- # Other generic errors
480
- except Exception as e:
481
- msg = f"Some error occurred: {e}"
482
- raise RuntimeError(msg) from e
483
-
484
- def _parse_response(self, response: Response) -> dict:
485
- """
486
- Parse the response object.
320
+ Return dict from response.
487
321
 
488
322
  Parameters
489
323
  ----------
@@ -502,208 +336,6 @@ class ClientDHCore(Client):
502
336
  return {}
503
337
  raise BackendError("Backend response could not be parsed.")
504
338
 
505
- ##############################
506
- # Configuration methods
507
- ##############################
508
-
509
- def _configure(self, config: dict | None = None) -> None:
510
- """
511
- Configure the client attributes with config (given or from
512
- environment).
513
- Regarding authentication parameters, the config parameter
514
- takes precedence over the env variables, and the token
515
- over the basic auth. Furthermore, the config parameter is
516
- validated against the proper pydantic model.
517
-
518
- Parameters
519
- ----------
520
- config : dict
521
- Configuration dictionary.
522
-
523
- Returns
524
- -------
525
- None
526
- """
527
- self._get_endpoints_from_env()
528
-
529
- if config is not None:
530
- if config.get("access_token") is not None:
531
- config = OAuth2TokenAuth(**config)
532
- self._user = config.user
533
- self._access_token = config.access_token
534
- self._refresh_token = config.refresh_token
535
- self._client_id = config.client_id
536
- self._auth_type = AuthType.OAUTH2.value
537
-
538
- elif config.get("user") is not None and config.get("password") is not None:
539
- config = BasicAuth(**config)
540
- self._user = config.user
541
- self._password = config.password
542
- self._auth_type = AuthType.BASIC.value
543
-
544
- return
545
-
546
- self._get_auth_from_env()
547
-
548
- # Propagate access and refresh token to env file
549
- self._write_env()
550
-
551
- def _get_endpoints_from_env(self) -> None:
552
- """
553
- Get the DHCore endpoint and token issuer endpoint from env.
554
-
555
- Returns
556
- -------
557
- None
558
-
559
- Raises
560
- ------
561
- Exception
562
- If the endpoint of DHCore is not set in the env variables.
563
- """
564
- core_endpt = os.getenv(DhcoreEnvVar.ENDPOINT.value)
565
- if core_endpt is None:
566
- raise BackendError("Endpoint not set as environment variables.")
567
- self._endpoint_core = self._sanitize_endpoint(core_endpt)
568
-
569
- issr_endpt = os.getenv(DhcoreEnvVar.ISSUER.value)
570
- if issr_endpt is not None:
571
- self._endpoint_issuer = self._sanitize_endpoint(issr_endpt)
572
-
573
- def _sanitize_endpoint(self, endpoint: str) -> str:
574
- """
575
- Sanitize the endpoint.
576
-
577
- Returns
578
- -------
579
- None
580
- """
581
- if not has_remote_scheme(endpoint):
582
- raise BackendError("Invalid endpoint scheme. Must start with http:// or https://.")
583
-
584
- endpoint = endpoint.strip()
585
- return endpoint.removesuffix("/")
586
-
587
- def _get_auth_from_env(self) -> None:
588
- """
589
- Get authentication parameters from the env.
590
-
591
- Returns
592
- -------
593
- None
594
- """
595
- self._user = os.getenv(DhcoreEnvVar.USER.value, FALLBACK_USER)
596
- self._refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
597
- self._client_id = os.getenv(DhcoreEnvVar.CLIENT_ID.value)
598
-
599
- token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
600
- if token is not None and token != "":
601
- self._auth_type = AuthType.OAUTH2.value
602
- self._access_token = token
603
- return
604
-
605
- password = os.getenv(DhcoreEnvVar.PASSWORD.value)
606
- if self._user is not None and password is not None:
607
- self._auth_type = AuthType.BASIC.value
608
- self._password = password
609
- return
610
-
611
- def _get_new_access_token(self) -> None:
612
- """
613
- Get a new access token.
614
-
615
- Returns
616
- -------
617
- None
618
- """
619
- # Call issuer and get endpoint for
620
- # refreshing access token
621
- url = self._get_refresh_endpoint()
622
-
623
- # Call refresh token endpoint
624
- # Try token from env
625
- refresh_token = os.getenv(DhcoreEnvVar.REFRESH_TOKEN.value)
626
- response = self._call_refresh_token_endpoint(url, refresh_token)
627
-
628
- # Otherwise try token from file
629
- if response.status_code in (400, 401, 403):
630
- refresh_token = get_key(ENV_FILE, DhcoreEnvVar.REFRESH_TOKEN.value)
631
- response = self._call_refresh_token_endpoint(url, refresh_token)
632
-
633
- response.raise_for_status()
634
- dict_response = response.json()
635
-
636
- # Read new access token and refresh token
637
- self._access_token = dict_response["access_token"]
638
- self._refresh_token = dict_response["refresh_token"]
639
-
640
- # Propagate new access token to env
641
- self._write_env()
642
-
643
- def _get_refresh_endpoint(self) -> str:
644
- """
645
- Get the refresh endpoint.
646
-
647
- Returns
648
- -------
649
- str
650
- Refresh endpoint.
651
- """
652
- # Get issuer endpoint
653
- if self._endpoint_issuer is None:
654
- raise BackendError("Issuer endpoint not set.")
655
-
656
- # Standard issuer endpoint path
657
- url = self._endpoint_issuer + "/.well-known/openid-configuration"
658
-
659
- # Call
660
- r = request("GET", url, timeout=60)
661
- self._raise_for_error(r)
662
- return r.json().get("token_endpoint")
663
-
664
- def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
665
- """
666
- Call the refresh token endpoint.
667
-
668
- Parameters
669
- ----------
670
- url : str
671
- Refresh token endpoint.
672
- refresh_token : str
673
- Refresh token.
674
-
675
- Returns
676
- -------
677
- Response
678
- Response object.
679
- """
680
- # Send request to get new access token
681
- payload = {
682
- "grant_type": "refresh_token",
683
- "client_id": self._client_id,
684
- "refresh_token": refresh_token,
685
- }
686
- headers = {"Content-Type": "application/x-www-form-urlencoded"}
687
- return request("POST", url, data=payload, headers=headers, timeout=60)
688
-
689
- def _write_env(self) -> None:
690
- """
691
- Write the env variables to the .dhcore file.
692
- It will overwrite any existing env variables.
693
-
694
- Returns
695
- -------
696
- None
697
- """
698
- keys = {}
699
- if self._access_token is not None:
700
- keys[DhcoreEnvVar.ACCESS_TOKEN.value] = self._access_token
701
- if self._refresh_token is not None:
702
- keys[DhcoreEnvVar.REFRESH_TOKEN.value] = self._refresh_token
703
-
704
- for k, v in keys.items():
705
- set_key(dotenv_path=ENV_FILE, key_to_set=k, value_to_set=v)
706
-
707
339
  ##############################
708
340
  # Interface methods
709
341
  ##############################