digitalhub 0.8.0b15__py3-none-any.whl → 0.9.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 (135) hide show
  1. digitalhub/__init__.py +19 -2
  2. digitalhub/client/_base/api_builder.py +16 -0
  3. digitalhub/client/_base/client.py +67 -0
  4. digitalhub/client/_base/key_builder.py +52 -0
  5. digitalhub/client/api.py +2 -38
  6. digitalhub/client/dhcore/api_builder.py +100 -0
  7. digitalhub/client/dhcore/client.py +100 -48
  8. digitalhub/client/dhcore/enums.py +27 -0
  9. digitalhub/client/dhcore/env.py +4 -2
  10. digitalhub/client/dhcore/key_builder.py +58 -0
  11. digitalhub/client/dhcore/utils.py +17 -17
  12. digitalhub/client/local/api_builder.py +100 -0
  13. digitalhub/client/local/client.py +22 -0
  14. digitalhub/client/local/key_builder.py +58 -0
  15. digitalhub/context/api.py +3 -38
  16. digitalhub/context/builder.py +10 -23
  17. digitalhub/context/context.py +20 -92
  18. digitalhub/entities/_base/context/entity.py +30 -22
  19. digitalhub/entities/_base/entity/_constructors/metadata.py +12 -1
  20. digitalhub/entities/_base/entity/_constructors/name.py +1 -1
  21. digitalhub/entities/_base/entity/_constructors/spec.py +1 -1
  22. digitalhub/entities/_base/entity/_constructors/status.py +3 -2
  23. digitalhub/entities/_base/entity/_constructors/uuid.py +1 -1
  24. digitalhub/entities/_base/entity/builder.py +6 -1
  25. digitalhub/entities/_base/entity/entity.py +32 -10
  26. digitalhub/entities/_base/entity/metadata.py +22 -0
  27. digitalhub/entities/_base/entity/spec.py +7 -2
  28. digitalhub/entities/_base/executable/entity.py +8 -8
  29. digitalhub/entities/_base/material/entity.py +49 -17
  30. digitalhub/entities/_base/material/status.py +0 -31
  31. digitalhub/entities/_base/material/utils.py +106 -0
  32. digitalhub/entities/_base/project/entity.py +341 -0
  33. digitalhub/entities/_base/unversioned/entity.py +3 -24
  34. digitalhub/entities/_base/versioned/entity.py +2 -26
  35. digitalhub/entities/_commons/enums.py +103 -0
  36. digitalhub/entities/_commons/utils.py +83 -0
  37. digitalhub/entities/_operations/processor.py +1873 -0
  38. digitalhub/entities/artifact/_base/builder.py +1 -1
  39. digitalhub/entities/artifact/_base/entity.py +1 -1
  40. digitalhub/entities/artifact/artifact/builder.py +2 -1
  41. digitalhub/entities/artifact/crud.py +46 -29
  42. digitalhub/entities/artifact/utils.py +62 -0
  43. digitalhub/entities/dataitem/_base/builder.py +1 -1
  44. digitalhub/entities/dataitem/_base/entity.py +6 -6
  45. digitalhub/entities/dataitem/crud.py +50 -66
  46. digitalhub/entities/dataitem/dataitem/builder.py +2 -1
  47. digitalhub/entities/dataitem/iceberg/builder.py +2 -1
  48. digitalhub/entities/dataitem/table/builder.py +2 -1
  49. digitalhub/entities/dataitem/table/entity.py +5 -10
  50. digitalhub/entities/dataitem/table/models.py +4 -5
  51. digitalhub/entities/dataitem/utils.py +137 -0
  52. digitalhub/entities/function/_base/builder.py +1 -1
  53. digitalhub/entities/function/_base/entity.py +6 -2
  54. digitalhub/entities/function/crud.py +36 -17
  55. digitalhub/entities/model/_base/builder.py +1 -1
  56. digitalhub/entities/model/_base/entity.py +1 -1
  57. digitalhub/entities/model/crud.py +46 -29
  58. digitalhub/entities/model/huggingface/builder.py +2 -1
  59. digitalhub/entities/model/huggingface/spec.py +4 -2
  60. digitalhub/entities/model/mlflow/builder.py +2 -1
  61. digitalhub/entities/model/mlflow/models.py +17 -9
  62. digitalhub/entities/model/mlflow/spec.py +6 -1
  63. digitalhub/entities/model/mlflow/utils.py +4 -2
  64. digitalhub/entities/model/model/builder.py +2 -1
  65. digitalhub/entities/model/sklearn/builder.py +2 -1
  66. digitalhub/entities/model/utils.py +62 -0
  67. digitalhub/entities/project/_base/builder.py +2 -2
  68. digitalhub/entities/project/_base/entity.py +82 -272
  69. digitalhub/entities/project/crud.py +110 -89
  70. digitalhub/entities/project/utils.py +35 -0
  71. digitalhub/entities/run/_base/builder.py +3 -1
  72. digitalhub/entities/run/_base/entity.py +52 -54
  73. digitalhub/entities/run/_base/spec.py +15 -7
  74. digitalhub/entities/run/crud.py +35 -17
  75. digitalhub/entities/secret/_base/builder.py +2 -2
  76. digitalhub/entities/secret/_base/entity.py +4 -10
  77. digitalhub/entities/secret/crud.py +36 -21
  78. digitalhub/entities/task/_base/builder.py +14 -14
  79. digitalhub/entities/task/_base/entity.py +21 -14
  80. digitalhub/entities/task/_base/models.py +35 -6
  81. digitalhub/entities/task/_base/spec.py +50 -13
  82. digitalhub/entities/task/_base/utils.py +18 -0
  83. digitalhub/entities/task/crud.py +35 -15
  84. digitalhub/entities/workflow/_base/builder.py +1 -1
  85. digitalhub/entities/workflow/_base/entity.py +22 -6
  86. digitalhub/entities/workflow/crud.py +36 -17
  87. digitalhub/factory/utils.py +1 -1
  88. digitalhub/readers/_base/reader.py +2 -2
  89. digitalhub/readers/_commons/enums.py +13 -0
  90. digitalhub/readers/api.py +3 -2
  91. digitalhub/readers/factory.py +12 -6
  92. digitalhub/readers/pandas/reader.py +20 -8
  93. digitalhub/runtimes/_base.py +0 -7
  94. digitalhub/runtimes/enums.py +12 -0
  95. digitalhub/stores/_base/store.py +59 -11
  96. digitalhub/stores/builder.py +5 -5
  97. digitalhub/stores/local/store.py +43 -4
  98. digitalhub/stores/remote/store.py +31 -5
  99. digitalhub/stores/s3/store.py +136 -57
  100. digitalhub/stores/sql/store.py +122 -47
  101. digitalhub/utils/exceptions.py +6 -0
  102. digitalhub/utils/file_utils.py +60 -2
  103. digitalhub/utils/generic_utils.py +45 -4
  104. digitalhub/utils/io_utils.py +18 -0
  105. digitalhub/utils/s3_utils.py +17 -0
  106. digitalhub/utils/uri_utils.py +153 -15
  107. {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/LICENSE.txt +1 -1
  108. {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/METADATA +11 -11
  109. {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/RECORD +117 -115
  110. {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/WHEEL +1 -1
  111. test/local/instances/test_validate.py +55 -0
  112. test/testkfp.py +4 -1
  113. digitalhub/datastores/_base/datastore.py +0 -85
  114. digitalhub/datastores/api.py +0 -37
  115. digitalhub/datastores/builder.py +0 -110
  116. digitalhub/datastores/local/datastore.py +0 -50
  117. digitalhub/datastores/remote/__init__.py +0 -0
  118. digitalhub/datastores/remote/datastore.py +0 -31
  119. digitalhub/datastores/s3/__init__.py +0 -0
  120. digitalhub/datastores/s3/datastore.py +0 -46
  121. digitalhub/datastores/sql/__init__.py +0 -0
  122. digitalhub/datastores/sql/datastore.py +0 -68
  123. digitalhub/entities/_base/api_utils.py +0 -620
  124. digitalhub/entities/_base/crud.py +0 -468
  125. digitalhub/entities/function/_base/models.py +0 -118
  126. digitalhub/entities/utils/__init__.py +0 -0
  127. digitalhub/entities/utils/api.py +0 -346
  128. digitalhub/entities/utils/entity_types.py +0 -19
  129. digitalhub/entities/utils/state.py +0 -31
  130. digitalhub/entities/utils/utils.py +0 -202
  131. /digitalhub/{context → entities/_base/project}/__init__.py +0 -0
  132. /digitalhub/{datastores → entities/_commons}/__init__.py +0 -0
  133. /digitalhub/{datastores/_base → entities/_operations}/__init__.py +0 -0
  134. /digitalhub/{datastores/local → readers/_commons}/__init__.py +0 -0
  135. {digitalhub-0.8.0b15.dist-info → digitalhub-0.9.0.dist-info}/top_level.txt +0 -0
digitalhub/__init__.py CHANGED
@@ -1,9 +1,11 @@
1
+ __version__ = "0.9.0b0"
1
2
  from digitalhub.entities.artifact.crud import (
2
3
  delete_artifact,
3
4
  get_artifact,
4
5
  get_artifact_versions,
5
6
  import_artifact,
6
7
  list_artifacts,
8
+ load_artifact,
7
9
  log_artifact,
8
10
  new_artifact,
9
11
  update_artifact,
@@ -14,6 +16,7 @@ from digitalhub.entities.dataitem.crud import (
14
16
  get_dataitem_versions,
15
17
  import_dataitem,
16
18
  list_dataitems,
19
+ load_dataitem,
17
20
  log_dataitem,
18
21
  new_dataitem,
19
22
  update_dataitem,
@@ -24,6 +27,7 @@ from digitalhub.entities.function.crud import (
24
27
  get_function_versions,
25
28
  import_function,
26
29
  list_functions,
30
+ load_function,
27
31
  new_function,
28
32
  update_function,
29
33
  )
@@ -33,6 +37,7 @@ from digitalhub.entities.model.crud import (
33
37
  get_model_versions,
34
38
  import_model,
35
39
  list_models,
40
+ load_model,
36
41
  log_model,
37
42
  new_model,
38
43
  update_model,
@@ -42,27 +47,39 @@ from digitalhub.entities.project.crud import (
42
47
  get_or_create_project,
43
48
  get_project,
44
49
  import_project,
50
+ list_projects,
45
51
  load_project,
46
52
  new_project,
53
+ search_entity,
47
54
  update_project,
48
55
  )
49
- from digitalhub.entities.run.crud import delete_run, get_run, import_run, list_runs, new_run, update_run
56
+ from digitalhub.entities.run.crud import delete_run, get_run, import_run, list_runs, load_run, new_run, update_run
50
57
  from digitalhub.entities.secret.crud import (
51
58
  delete_secret,
52
59
  get_secret,
53
60
  get_secret_versions,
54
61
  import_secret,
55
62
  list_secrets,
63
+ load_secret,
56
64
  new_secret,
57
65
  update_secret,
58
66
  )
59
- from digitalhub.entities.task.crud import delete_task, get_task, import_task, list_tasks, new_task, update_task
67
+ from digitalhub.entities.task.crud import (
68
+ delete_task,
69
+ get_task,
70
+ import_task,
71
+ list_tasks,
72
+ load_task,
73
+ new_task,
74
+ update_task,
75
+ )
60
76
  from digitalhub.entities.workflow.crud import (
61
77
  delete_workflow,
62
78
  get_workflow,
63
79
  get_workflow_versions,
64
80
  import_workflow,
65
81
  list_workflows,
82
+ load_workflow,
66
83
  new_workflow,
67
84
  update_workflow,
68
85
  )
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+
5
+
6
+ class ClientApiBuilder:
7
+ """
8
+ This class is used to build the API for the client.
9
+ Depending on the client, the API is built differently.
10
+ """
11
+
12
+ @abstractmethod
13
+ def build_api(self) -> str:
14
+ """
15
+ Build the API for the client.
16
+ """
@@ -1,7 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import typing
3
4
  from abc import abstractmethod
4
5
 
6
+ if typing.TYPE_CHECKING:
7
+ from digitalhub.client._base.api_builder import ClientApiBuilder
8
+ from digitalhub.client._base.key_builder import ClientKeyBuilder
9
+
5
10
 
6
11
  class Client:
7
12
  """
@@ -12,6 +17,14 @@ class Client:
12
17
  listing of objects and comes into two subclasses: Local and DHCore.
13
18
  """
14
19
 
20
+ def __init__(self) -> None:
21
+ self._api_builder: ClientApiBuilder = None
22
+ self._key_builder: ClientKeyBuilder = None
23
+
24
+ ##############################
25
+ # CRUD methods
26
+ ##############################
27
+
15
28
  @abstractmethod
16
29
  def create_object(self, api: str, obj: dict, **kwargs) -> dict:
17
30
  """
@@ -48,6 +61,60 @@ class Client:
48
61
  Read first object method.
49
62
  """
50
63
 
64
+ @abstractmethod
65
+ def search_objects(self, api: str, **kwargs) -> dict:
66
+ """
67
+ Search objects method.
68
+ """
69
+
70
+ ##############################
71
+ # Build methods
72
+ ##############################
73
+
74
+ def build_api(self, category: str, operation: str, **kwargs) -> str:
75
+ """
76
+ Build the API for the client.
77
+
78
+ Parameters
79
+ ----------
80
+ category : str
81
+ API category.
82
+ operation : str
83
+ API operation.
84
+ **kwargs : dict
85
+ Additional parameters.
86
+
87
+ Returns
88
+ -------
89
+ str
90
+ API formatted.
91
+ """
92
+ return self._api_builder.build_api(category, operation, **kwargs)
93
+
94
+ def build_key(self, category: str, *args, **kwargs) -> str:
95
+ """
96
+ Build the key for the client.
97
+
98
+ Parameters
99
+ ----------
100
+ category : str
101
+ Key category.
102
+ *args : tuple
103
+ Additional arguments.
104
+ **kwargs : dict
105
+ Additional parameters.
106
+
107
+ Returns
108
+ -------
109
+ str
110
+ Key formatted.
111
+ """
112
+ return self._key_builder.build_key(category, *args, **kwargs)
113
+
114
+ ##############################
115
+ # Interface methods
116
+ ##############################
117
+
51
118
  @staticmethod
52
119
  @abstractmethod
53
120
  def is_local() -> bool:
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+
5
+ from digitalhub.entities._commons.enums import ApiCategories
6
+
7
+
8
+ class ClientKeyBuilder:
9
+ """
10
+ Class that build the key of entities.
11
+ """
12
+
13
+ def build_key(self, category: str, *args, **kwargs) -> str:
14
+ """
15
+ Build key.
16
+
17
+ Parameters
18
+ ----------
19
+ category : str
20
+ Key category.
21
+ *args : tuple
22
+ Positional arguments.
23
+ **kwargs : dict
24
+ Keyword arguments.
25
+
26
+ Returns
27
+ -------
28
+ str
29
+ Key.
30
+ """
31
+ if category == ApiCategories.BASE.value:
32
+ return self.base_entity_key(*args, **kwargs)
33
+ return self.context_entity_key(*args, **kwargs)
34
+
35
+ @abstractmethod
36
+ def base_entity_key(self, entity_id: str) -> str:
37
+ """
38
+ Build for base entity key.
39
+ """
40
+
41
+ @abstractmethod
42
+ def context_entity_key(
43
+ self,
44
+ project: str,
45
+ entity_type: str,
46
+ entity_kind: str,
47
+ entity_name: str,
48
+ entity_id: str | None = None,
49
+ ) -> str:
50
+ """
51
+ Build for context entity key.
52
+ """
digitalhub/client/api.py CHANGED
@@ -8,26 +8,7 @@ if typing.TYPE_CHECKING:
8
8
  from digitalhub.client._base.client import Client
9
9
 
10
10
 
11
- def check_client_exists(local: bool = False) -> bool:
12
- """
13
- Check if client exists.
14
-
15
- Parameters
16
- ----------
17
- local : bool
18
- Check client existence by local.
19
-
20
- Returns
21
- -------
22
- bool
23
- True if client exists, False otherwise.
24
- """
25
- if local:
26
- return client_builder._local is not None
27
- return client_builder._dhcore is not None
28
-
29
-
30
- def build_client(local: bool = False, config: dict | None = None) -> None:
11
+ def get_client(local: bool = False, config: dict | None = None) -> Client:
31
12
  """
32
13
  Wrapper around ClientBuilder.build.
33
14
 
@@ -43,21 +24,4 @@ def build_client(local: bool = False, config: dict | None = None) -> None:
43
24
  Client
44
25
  The client instance.
45
26
  """
46
- client_builder.build(local, config)
47
-
48
-
49
- def get_client(local: bool = False) -> Client:
50
- """
51
- Wrapper around ClientBuilder.build.
52
-
53
- Parameters
54
- ----------
55
- local : bool
56
- Whether to create a local client or not.
57
-
58
- Returns
59
- -------
60
- Client
61
- The client instance.
62
- """
63
- return client_builder.build(local)
27
+ return client_builder.build(local, config)
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.client._base.api_builder import ClientApiBuilder
4
+ from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
5
+ from digitalhub.utils.exceptions import BackendError
6
+
7
+ API_BASE = "/api/v1"
8
+ API_CONTEXT = f"{API_BASE}/-"
9
+
10
+
11
+ class ClientDHCoreApiBuilder(ClientApiBuilder):
12
+ """
13
+ This class is used to build the API for the DHCore client.
14
+ """
15
+
16
+ def build_api(self, category: str, operation: str, **kwargs) -> str:
17
+ """
18
+ Build the API for the client.
19
+
20
+ Parameters
21
+ ----------
22
+ category : str
23
+ API category.
24
+ operation : str
25
+ API operation.
26
+ **kwargs : dict
27
+ Additional parameters.
28
+
29
+ Returns
30
+ -------
31
+ str
32
+ API formatted.
33
+ """
34
+ if category == ApiCategories.BASE.value:
35
+ return self.build_api_base(operation, **kwargs)
36
+ return self.build_api_context(operation, **kwargs)
37
+
38
+ def build_api_base(self, operation: str, **kwargs) -> str:
39
+ """
40
+ Build the base API for the client.
41
+
42
+ Parameters
43
+ ----------
44
+ operation : str
45
+ API operation.
46
+ **kwargs : dict
47
+ Additional parameters.
48
+
49
+ Returns
50
+ -------
51
+ str
52
+ API formatted.
53
+ """
54
+ entity_type = kwargs["entity_type"] + "s"
55
+ if operation in (
56
+ BackendOperations.CREATE.value,
57
+ BackendOperations.LIST.value,
58
+ ):
59
+ return f"{API_BASE}/{entity_type}"
60
+ elif operation in (
61
+ BackendOperations.READ.value,
62
+ BackendOperations.UPDATE.value,
63
+ BackendOperations.DELETE.value,
64
+ ):
65
+ return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}"
66
+ elif operation == BackendOperations.SHARE.value:
67
+ return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}/share"
68
+ raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
69
+
70
+ def build_api_context(self, operation: str, **kwargs) -> str:
71
+ """
72
+ Build the context API for the client.
73
+ """
74
+ entity_type = kwargs["entity_type"] + "s"
75
+ project = kwargs["project"]
76
+ if operation in (
77
+ BackendOperations.CREATE.value,
78
+ BackendOperations.LIST.value,
79
+ ):
80
+ return f"{API_CONTEXT}/{project}/{entity_type}"
81
+ elif operation in (
82
+ BackendOperations.READ.value,
83
+ BackendOperations.UPDATE.value,
84
+ BackendOperations.DELETE.value,
85
+ ):
86
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}"
87
+ elif operation == BackendOperations.LOGS.value:
88
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/logs"
89
+ elif operation == BackendOperations.STOP.value:
90
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/stop"
91
+ elif operation == BackendOperations.RESUME.value:
92
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/resume"
93
+ elif operation == BackendOperations.DATA.value:
94
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}/data"
95
+ elif operation == BackendOperations.FILES.value:
96
+ 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"
99
+
100
+ raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in DHCore.")
@@ -4,14 +4,17 @@ import datetime
4
4
  import json
5
5
  import os
6
6
  import typing
7
- from urllib.parse import urlparse
7
+ from warnings import warn
8
8
 
9
- from dotenv import load_dotenv, set_key
9
+ from dotenv import get_key, set_key
10
10
  from requests import request
11
11
  from requests.exceptions import HTTPError, JSONDecodeError, RequestException
12
12
 
13
13
  from digitalhub.client._base.client import Client
14
- from digitalhub.client.dhcore.env import ENV_FILE, FALLBACK_USER, MAX_API_LEVEL, MIN_API_LEVEL
14
+ 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
17
+ from digitalhub.client.dhcore.key_builder import ClientDHCoreKeyBuilder
15
18
  from digitalhub.client.dhcore.models import BasicAuth, OAuth2TokenAuth
16
19
  from digitalhub.utils.exceptions import (
17
20
  BackendError,
@@ -22,6 +25,7 @@ from digitalhub.utils.exceptions import (
22
25
  MissingSpecError,
23
26
  UnauthorizedError,
24
27
  )
28
+ from digitalhub.utils.uri_utils import has_remote_scheme
25
29
 
26
30
  if typing.TYPE_CHECKING:
27
31
  from requests import Response
@@ -46,6 +50,12 @@ class ClientDHCore(Client):
46
50
  def __init__(self, config: dict | None = None) -> None:
47
51
  super().__init__()
48
52
 
53
+ # API builder
54
+ self._api_builder = ClientDHCoreApiBuilder()
55
+
56
+ # Key builder
57
+ self._key_builder = ClientDHCoreKeyBuilder()
58
+
49
59
  # Endpoints
50
60
  self._endpoint_core: str | None = None
51
61
  self._endpoint_issuer: str | None = None
@@ -213,6 +223,56 @@ class ClientDHCore(Client):
213
223
  except IndexError:
214
224
  raise BackendError("No object found.")
215
225
 
226
+ def search_objects(self, api: str, **kwargs) -> list[dict]:
227
+ """
228
+ Search objects from DHCore.
229
+
230
+ Parameters
231
+ ----------
232
+ api : str
233
+ Search API.
234
+ **kwargs : dict
235
+ Keyword arguments to pass to the request.
236
+
237
+ Returns
238
+ -------
239
+ list[dict]
240
+ Response objects.
241
+ """
242
+ if kwargs is None:
243
+ kwargs = {}
244
+
245
+ if "params" not in kwargs:
246
+ kwargs["params"] = {}
247
+
248
+ start_page = 0
249
+ if "page" not in kwargs["params"]:
250
+ kwargs["params"]["page"] = start_page
251
+
252
+ if "size" not in kwargs["params"]:
253
+ kwargs["params"]["size"] = 10
254
+
255
+ # Add sorting
256
+ if "sort" not in kwargs["params"]:
257
+ kwargs["params"]["sort"] = "metadata.updated,DESC"
258
+
259
+ objects_with_highlights = []
260
+ while True:
261
+ resp = self._prepare_call("GET", api, **kwargs)
262
+ contents = resp["content"]
263
+ total_pages = resp["totalPages"]
264
+ if not contents or kwargs["params"]["page"] >= total_pages:
265
+ break
266
+ objects_with_highlights.extend(contents)
267
+ kwargs["params"]["page"] += 1
268
+
269
+ objects = []
270
+ for obj in objects_with_highlights:
271
+ obj.pop("highlights", None)
272
+ objects.append(obj)
273
+
274
+ return objects
275
+
216
276
  ##############################
217
277
  # Call methods
218
278
  ##############################
@@ -274,9 +334,9 @@ class ClientDHCore(Client):
274
334
  dict
275
335
  Keyword arguments with the authentication parameters.
276
336
  """
277
- if self._auth_type == "basic":
337
+ if self._auth_type == AuthType.BASIC.value:
278
338
  kwargs["auth"] = self._user, self._password
279
- elif self._auth_type == "oauth2":
339
+ elif self._auth_type == AuthType.OAUTH2.value:
280
340
  if "headers" not in kwargs:
281
341
  kwargs["headers"] = {}
282
342
  kwargs["headers"]["Authorization"] = f"Bearer {self._access_token}"
@@ -332,6 +392,8 @@ class ClientDHCore(Client):
332
392
  core_api_level = int(response.headers["X-Api-Level"])
333
393
  if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
334
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.")
335
397
 
336
398
  def _raise_for_error(self, response: Response) -> None:
337
399
  """
@@ -462,9 +524,6 @@ class ClientDHCore(Client):
462
524
  -------
463
525
  None
464
526
  """
465
- # Load env from file
466
- self._load_env()
467
-
468
527
  self._get_endpoints_from_env()
469
528
 
470
529
  if config is not None:
@@ -474,13 +533,13 @@ class ClientDHCore(Client):
474
533
  self._access_token = config.access_token
475
534
  self._refresh_token = config.refresh_token
476
535
  self._client_id = config.client_id
477
- self._auth_type = "oauth2"
536
+ self._auth_type = AuthType.OAUTH2.value
478
537
 
479
538
  elif config.get("user") is not None and config.get("password") is not None:
480
539
  config = BasicAuth(**config)
481
540
  self._user = config.user
482
541
  self._password = config.password
483
- self._auth_type = "basic"
542
+ self._auth_type = AuthType.BASIC.value
484
543
 
485
544
  return
486
545
 
@@ -502,12 +561,12 @@ class ClientDHCore(Client):
502
561
  Exception
503
562
  If the endpoint of DHCore is not set in the env variables.
504
563
  """
505
- core_endpt = os.getenv("DHCORE_ENDPOINT")
564
+ core_endpt = os.getenv(DhcoreEnvVar.ENDPOINT.value)
506
565
  if core_endpt is None:
507
566
  raise BackendError("Endpoint not set as environment variables.")
508
567
  self._endpoint_core = self._sanitize_endpoint(core_endpt)
509
568
 
510
- issr_endpt = os.getenv("DHCORE_ISSUER")
569
+ issr_endpt = os.getenv(DhcoreEnvVar.ISSUER.value)
511
570
  if issr_endpt is not None:
512
571
  self._endpoint_issuer = self._sanitize_endpoint(issr_endpt)
513
572
 
@@ -519,9 +578,8 @@ class ClientDHCore(Client):
519
578
  -------
520
579
  None
521
580
  """
522
- parsed = urlparse(endpoint)
523
- if parsed.scheme not in ["http", "https"]:
524
- raise BackendError("Invalid endpoint scheme.")
581
+ if not has_remote_scheme(endpoint):
582
+ raise BackendError("Invalid endpoint scheme. Must start with http:// or https://.")
525
583
 
526
584
  endpoint = endpoint.strip()
527
585
  return endpoint.removesuffix("/")
@@ -534,19 +592,19 @@ class ClientDHCore(Client):
534
592
  -------
535
593
  None
536
594
  """
537
- self._user = os.getenv("DHCORE_USER", FALLBACK_USER)
538
- self._refresh_token = os.getenv("DHCORE_REFRESH_TOKEN")
539
- self._client_id = os.getenv("DHCORE_CLIENT_ID")
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)
540
598
 
541
- token = os.getenv("DHCORE_ACCESS_TOKEN")
599
+ token = os.getenv(DhcoreEnvVar.ACCESS_TOKEN.value)
542
600
  if token is not None and token != "":
543
- self._auth_type = "oauth2"
601
+ self._auth_type = AuthType.OAUTH2.value
544
602
  self._access_token = token
545
603
  return
546
604
 
547
- password = os.getenv("DHCORE_PASSWORD")
605
+ password = os.getenv(DhcoreEnvVar.PASSWORD.value)
548
606
  if self._user is not None and password is not None:
549
- self._auth_type = "basic"
607
+ self._auth_type = AuthType.BASIC.value
550
608
  self._password = password
551
609
  return
552
610
 
@@ -563,11 +621,21 @@ class ClientDHCore(Client):
563
621
  url = self._get_refresh_endpoint()
564
622
 
565
623
  # Call refresh token endpoint
566
- response = self._call_refresh_token_endpoint(url)
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()
567
635
 
568
636
  # Read new access token and refresh token
569
- self._access_token = response["access_token"]
570
- self._refresh_token = response["refresh_token"]
637
+ self._access_token = dict_response["access_token"]
638
+ self._refresh_token = dict_response["refresh_token"]
571
639
 
572
640
  # Propagate new access token to env
573
641
  self._write_env()
@@ -593,7 +661,7 @@ class ClientDHCore(Client):
593
661
  self._raise_for_error(r)
594
662
  return r.json().get("token_endpoint")
595
663
 
596
- def _call_refresh_token_endpoint(self, url: str) -> dict:
664
+ def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
597
665
  """
598
666
  Call the refresh token endpoint.
599
667
 
@@ -601,17 +669,14 @@ class ClientDHCore(Client):
601
669
  ----------
602
670
  url : str
603
671
  Refresh token endpoint.
672
+ refresh_token : str
673
+ Refresh token.
604
674
 
605
675
  Returns
606
676
  -------
607
- dict
677
+ Response
608
678
  Response object.
609
679
  """
610
- # Get refersh token from .core file to avoid concurrency
611
- # in a shared workspace
612
- self._load_env()
613
- refresh_token = os.getenv("DHCORE_REFRESH_TOKEN")
614
-
615
680
  # Send request to get new access token
616
681
  payload = {
617
682
  "grant_type": "refresh_token",
@@ -619,20 +684,7 @@ class ClientDHCore(Client):
619
684
  "refresh_token": refresh_token,
620
685
  }
621
686
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
622
- r = request("POST", url, data=payload, headers=headers, timeout=60)
623
- self._raise_for_error(r)
624
- return r.json()
625
-
626
- @staticmethod
627
- def _load_env() -> None:
628
- """
629
- Load the env variables from the .dhcore file.
630
-
631
- Returns
632
- -------
633
- None
634
- """
635
- load_dotenv(dotenv_path=ENV_FILE, override=True)
687
+ return request("POST", url, data=payload, headers=headers, timeout=60)
636
688
 
637
689
  def _write_env(self) -> None:
638
690
  """
@@ -645,9 +697,9 @@ class ClientDHCore(Client):
645
697
  """
646
698
  keys = {}
647
699
  if self._access_token is not None:
648
- keys["DHCORE_ACCESS_TOKEN"] = self._access_token
700
+ keys[DhcoreEnvVar.ACCESS_TOKEN.value] = self._access_token
649
701
  if self._refresh_token is not None:
650
- keys["DHCORE_REFRESH_TOKEN"] = self._refresh_token
702
+ keys[DhcoreEnvVar.REFRESH_TOKEN.value] = self._refresh_token
651
703
 
652
704
  for k, v in keys.items():
653
705
  set_key(dotenv_path=ENV_FILE, key_to_set=k, value_to_set=v)
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class DhcoreEnvVar(Enum):
7
+ """
8
+ Environment variables.
9
+ """
10
+
11
+ ENDPOINT = "DHCORE_ENDPOINT"
12
+ ISSUER = "DHCORE_ISSUER"
13
+ USER = "DHCORE_USER"
14
+ PASSWORD = "DHCORE_PASSWORD"
15
+ CLIENT_ID = "DHCORE_CLIENT_ID"
16
+ ACCESS_TOKEN = "DHCORE_ACCESS_TOKEN"
17
+ REFRESH_TOKEN = "DHCORE_REFRESH_TOKEN"
18
+ WORKFLOW_IMAGE = "DHCORE_WORKFLOW_IMAGE"
19
+
20
+
21
+ class AuthType(Enum):
22
+ """
23
+ Authentication types.
24
+ """
25
+
26
+ BASIC = "basic"
27
+ OAUTH2 = "oauth2"