digitalhub 0.11.0b7__py3-none-any.whl → 0.13.0b0__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 (270) hide show
  1. digitalhub/__init__.py +4 -1
  2. digitalhub/context/api.py +4 -0
  3. digitalhub/context/builder.py +4 -0
  4. digitalhub/context/context.py +4 -0
  5. digitalhub/entities/__init__.py +3 -0
  6. digitalhub/entities/_base/__init__.py +3 -0
  7. digitalhub/entities/_base/_base/__init__.py +3 -0
  8. digitalhub/entities/_base/_base/entity.py +4 -0
  9. digitalhub/entities/_base/context/__init__.py +3 -0
  10. digitalhub/entities/_base/context/entity.py +4 -0
  11. digitalhub/entities/_base/entity/__init__.py +3 -0
  12. digitalhub/entities/_base/entity/_constructors/__init__.py +3 -0
  13. digitalhub/entities/_base/entity/_constructors/metadata.py +4 -0
  14. digitalhub/entities/_base/entity/_constructors/name.py +4 -0
  15. digitalhub/entities/_base/entity/_constructors/spec.py +4 -0
  16. digitalhub/entities/_base/entity/_constructors/status.py +4 -0
  17. digitalhub/entities/_base/entity/_constructors/uuid.py +4 -0
  18. digitalhub/entities/_base/entity/builder.py +4 -0
  19. digitalhub/entities/_base/entity/entity.py +4 -0
  20. digitalhub/entities/_base/entity/metadata.py +4 -0
  21. digitalhub/entities/_base/entity/spec.py +4 -0
  22. digitalhub/entities/_base/entity/status.py +4 -0
  23. digitalhub/entities/_base/executable/__init__.py +3 -0
  24. digitalhub/entities/_base/executable/entity.py +11 -0
  25. digitalhub/entities/_base/material/__init__.py +3 -0
  26. digitalhub/entities/_base/material/entity.py +12 -15
  27. digitalhub/entities/_base/material/spec.py +4 -0
  28. digitalhub/entities/_base/material/status.py +4 -0
  29. digitalhub/entities/_base/material/utils.py +5 -1
  30. digitalhub/entities/_base/runtime_entity/__init__.py +3 -0
  31. digitalhub/entities/_base/runtime_entity/builder.py +4 -0
  32. digitalhub/entities/_base/unversioned/__init__.py +3 -0
  33. digitalhub/entities/_base/unversioned/builder.py +4 -0
  34. digitalhub/entities/_base/unversioned/entity.py +4 -0
  35. digitalhub/entities/_base/versioned/__init__.py +3 -0
  36. digitalhub/entities/_base/versioned/builder.py +4 -0
  37. digitalhub/entities/_base/versioned/entity.py +4 -0
  38. digitalhub/entities/_commons/__init__.py +3 -0
  39. digitalhub/entities/_commons/enums.py +4 -0
  40. digitalhub/entities/_commons/metrics.py +4 -0
  41. digitalhub/entities/_commons/types.py +4 -0
  42. digitalhub/entities/_commons/utils.py +4 -0
  43. digitalhub/entities/_processors/__init__.py +3 -0
  44. digitalhub/entities/_processors/base.py +4 -0
  45. digitalhub/entities/_processors/context.py +5 -1
  46. digitalhub/entities/_processors/utils.py +4 -0
  47. digitalhub/entities/artifact/__init__.py +3 -0
  48. digitalhub/entities/artifact/_base/__init__.py +3 -0
  49. digitalhub/entities/artifact/_base/builder.py +4 -0
  50. digitalhub/entities/artifact/_base/entity.py +4 -0
  51. digitalhub/entities/artifact/_base/spec.py +4 -0
  52. digitalhub/entities/artifact/_base/status.py +4 -0
  53. digitalhub/entities/artifact/artifact/__init__.py +3 -0
  54. digitalhub/entities/artifact/artifact/builder.py +4 -0
  55. digitalhub/entities/artifact/artifact/entity.py +4 -0
  56. digitalhub/entities/artifact/artifact/spec.py +4 -0
  57. digitalhub/entities/artifact/artifact/status.py +4 -0
  58. digitalhub/entities/artifact/crud.py +4 -0
  59. digitalhub/entities/artifact/utils.py +4 -0
  60. digitalhub/entities/builders.py +4 -0
  61. digitalhub/entities/dataitem/__init__.py +3 -0
  62. digitalhub/entities/dataitem/_base/__init__.py +3 -0
  63. digitalhub/entities/dataitem/_base/builder.py +4 -0
  64. digitalhub/entities/dataitem/_base/entity.py +4 -0
  65. digitalhub/entities/dataitem/_base/spec.py +4 -0
  66. digitalhub/entities/dataitem/_base/status.py +4 -0
  67. digitalhub/entities/dataitem/crud.py +4 -0
  68. digitalhub/entities/dataitem/dataitem/__init__.py +3 -0
  69. digitalhub/entities/dataitem/dataitem/builder.py +4 -0
  70. digitalhub/entities/dataitem/dataitem/entity.py +4 -0
  71. digitalhub/entities/dataitem/dataitem/spec.py +4 -0
  72. digitalhub/entities/dataitem/dataitem/status.py +4 -0
  73. digitalhub/entities/dataitem/iceberg/__init__.py +3 -0
  74. digitalhub/entities/dataitem/iceberg/builder.py +4 -0
  75. digitalhub/entities/dataitem/iceberg/entity.py +4 -0
  76. digitalhub/entities/dataitem/iceberg/spec.py +4 -0
  77. digitalhub/entities/dataitem/iceberg/status.py +4 -0
  78. digitalhub/entities/dataitem/table/__init__.py +3 -0
  79. digitalhub/entities/dataitem/table/builder.py +4 -0
  80. digitalhub/entities/dataitem/table/entity.py +4 -0
  81. digitalhub/entities/dataitem/table/models.py +4 -0
  82. digitalhub/entities/dataitem/table/spec.py +4 -0
  83. digitalhub/entities/dataitem/table/status.py +4 -0
  84. digitalhub/entities/dataitem/table/utils.py +4 -0
  85. digitalhub/entities/dataitem/utils.py +4 -0
  86. digitalhub/entities/function/__init__.py +3 -0
  87. digitalhub/entities/function/_base/__init__.py +3 -0
  88. digitalhub/entities/function/_base/builder.py +4 -0
  89. digitalhub/entities/function/_base/entity.py +4 -0
  90. digitalhub/entities/function/_base/spec.py +4 -0
  91. digitalhub/entities/function/_base/status.py +4 -0
  92. digitalhub/entities/function/crud.py +4 -0
  93. digitalhub/entities/model/__init__.py +3 -0
  94. digitalhub/entities/model/_base/__init__.py +3 -0
  95. digitalhub/entities/model/_base/builder.py +4 -0
  96. digitalhub/entities/model/_base/entity.py +4 -0
  97. digitalhub/entities/model/_base/spec.py +4 -0
  98. digitalhub/entities/model/_base/status.py +4 -0
  99. digitalhub/entities/model/crud.py +4 -0
  100. digitalhub/entities/model/huggingface/__init__.py +3 -0
  101. digitalhub/entities/model/huggingface/builder.py +4 -0
  102. digitalhub/entities/model/huggingface/entity.py +4 -0
  103. digitalhub/entities/model/huggingface/spec.py +4 -0
  104. digitalhub/entities/model/huggingface/status.py +4 -0
  105. digitalhub/entities/model/mlflow/__init__.py +3 -0
  106. digitalhub/entities/model/mlflow/builder.py +4 -0
  107. digitalhub/entities/model/mlflow/entity.py +4 -0
  108. digitalhub/entities/model/mlflow/models.py +4 -0
  109. digitalhub/entities/model/mlflow/spec.py +4 -0
  110. digitalhub/entities/model/mlflow/status.py +4 -0
  111. digitalhub/entities/model/mlflow/utils.py +4 -0
  112. digitalhub/entities/model/model/__init__.py +3 -0
  113. digitalhub/entities/model/model/builder.py +4 -0
  114. digitalhub/entities/model/model/entity.py +4 -0
  115. digitalhub/entities/model/model/spec.py +4 -0
  116. digitalhub/entities/model/model/status.py +4 -0
  117. digitalhub/entities/model/sklearn/__init__.py +3 -0
  118. digitalhub/entities/model/sklearn/builder.py +4 -0
  119. digitalhub/entities/model/sklearn/entity.py +4 -0
  120. digitalhub/entities/model/sklearn/spec.py +4 -0
  121. digitalhub/entities/model/sklearn/status.py +4 -0
  122. digitalhub/entities/model/utils.py +4 -0
  123. digitalhub/entities/project/__init__.py +3 -0
  124. digitalhub/entities/project/_base/__init__.py +3 -0
  125. digitalhub/entities/project/_base/builder.py +4 -0
  126. digitalhub/entities/project/_base/entity.py +4 -0
  127. digitalhub/entities/project/_base/models.py +4 -0
  128. digitalhub/entities/project/_base/spec.py +4 -0
  129. digitalhub/entities/project/_base/status.py +4 -0
  130. digitalhub/entities/project/crud.py +4 -0
  131. digitalhub/entities/project/utils.py +4 -0
  132. digitalhub/entities/run/__init__.py +3 -0
  133. digitalhub/entities/run/_base/__init__.py +3 -0
  134. digitalhub/entities/run/_base/builder.py +4 -0
  135. digitalhub/entities/run/_base/entity.py +4 -0
  136. digitalhub/entities/run/_base/spec.py +4 -0
  137. digitalhub/entities/run/_base/status.py +4 -0
  138. digitalhub/entities/run/crud.py +4 -0
  139. digitalhub/entities/secret/__init__.py +3 -0
  140. digitalhub/entities/secret/_base/__init__.py +3 -0
  141. digitalhub/entities/secret/_base/builder.py +4 -0
  142. digitalhub/entities/secret/_base/entity.py +4 -0
  143. digitalhub/entities/secret/_base/spec.py +4 -0
  144. digitalhub/entities/secret/_base/status.py +4 -0
  145. digitalhub/entities/secret/crud.py +4 -0
  146. digitalhub/entities/task/__init__.py +3 -0
  147. digitalhub/entities/task/_base/__init__.py +3 -0
  148. digitalhub/entities/task/_base/builder.py +4 -0
  149. digitalhub/entities/task/_base/entity.py +4 -0
  150. digitalhub/entities/task/_base/models.py +4 -0
  151. digitalhub/entities/task/_base/spec.py +4 -0
  152. digitalhub/entities/task/_base/status.py +4 -0
  153. digitalhub/entities/task/_base/utils.py +4 -0
  154. digitalhub/entities/task/crud.py +4 -0
  155. digitalhub/entities/trigger/__init__.py +3 -0
  156. digitalhub/entities/trigger/_base/__init__.py +3 -0
  157. digitalhub/entities/trigger/_base/builder.py +4 -0
  158. digitalhub/entities/trigger/_base/entity.py +4 -0
  159. digitalhub/entities/trigger/_base/spec.py +4 -0
  160. digitalhub/entities/trigger/_base/status.py +4 -0
  161. digitalhub/entities/trigger/crud.py +4 -0
  162. digitalhub/entities/trigger/lifecycle/__init__.py +3 -0
  163. digitalhub/entities/trigger/lifecycle/builder.py +4 -0
  164. digitalhub/entities/trigger/lifecycle/entity.py +4 -0
  165. digitalhub/entities/trigger/lifecycle/spec.py +4 -0
  166. digitalhub/entities/trigger/lifecycle/status.py +4 -0
  167. digitalhub/entities/trigger/scheduler/__init__.py +3 -0
  168. digitalhub/entities/trigger/scheduler/builder.py +4 -0
  169. digitalhub/entities/trigger/scheduler/entity.py +4 -0
  170. digitalhub/entities/trigger/scheduler/spec.py +4 -0
  171. digitalhub/entities/trigger/scheduler/status.py +4 -0
  172. digitalhub/entities/workflow/__init__.py +3 -0
  173. digitalhub/entities/workflow/_base/__init__.py +3 -0
  174. digitalhub/entities/workflow/_base/builder.py +4 -0
  175. digitalhub/entities/workflow/_base/entity.py +4 -0
  176. digitalhub/entities/workflow/_base/spec.py +4 -0
  177. digitalhub/entities/workflow/_base/status.py +4 -0
  178. digitalhub/entities/workflow/crud.py +4 -0
  179. digitalhub/factory/__init__.py +3 -0
  180. digitalhub/factory/factory.py +4 -0
  181. digitalhub/factory/utils.py +4 -0
  182. digitalhub/runtimes/__init__.py +3 -0
  183. digitalhub/runtimes/_base.py +4 -0
  184. digitalhub/runtimes/builder.py +4 -0
  185. digitalhub/runtimes/enums.py +4 -0
  186. digitalhub/stores/__init__.py +3 -0
  187. digitalhub/stores/client/__init__.py +3 -0
  188. digitalhub/stores/client/_base/__init__.py +3 -0
  189. digitalhub/stores/client/_base/api_builder.py +4 -0
  190. digitalhub/stores/client/_base/client.py +4 -0
  191. digitalhub/stores/client/_base/key_builder.py +4 -0
  192. digitalhub/stores/client/_base/params_builder.py +4 -0
  193. digitalhub/stores/client/api.py +4 -0
  194. digitalhub/stores/client/builder.py +4 -0
  195. digitalhub/stores/client/dhcore/__init__.py +3 -0
  196. digitalhub/stores/client/dhcore/api_builder.py +4 -0
  197. digitalhub/stores/client/dhcore/client.py +60 -19
  198. digitalhub/stores/client/dhcore/configurator.py +282 -183
  199. digitalhub/stores/client/dhcore/enums.py +6 -0
  200. digitalhub/stores/client/dhcore/error_parser.py +4 -0
  201. digitalhub/stores/client/dhcore/key_builder.py +4 -0
  202. digitalhub/stores/client/dhcore/models.py +4 -0
  203. digitalhub/stores/client/dhcore/params_builder.py +4 -0
  204. digitalhub/stores/client/dhcore/utils.py +12 -8
  205. digitalhub/stores/client/local/__init__.py +3 -0
  206. digitalhub/stores/client/local/api_builder.py +4 -0
  207. digitalhub/stores/client/local/client.py +4 -0
  208. digitalhub/stores/client/local/enums.py +4 -0
  209. digitalhub/stores/client/local/key_builder.py +4 -0
  210. digitalhub/stores/client/local/params_builder.py +4 -0
  211. digitalhub/stores/credentials/__init__.py +3 -0
  212. digitalhub/stores/{configurator → credentials}/api.py +7 -3
  213. digitalhub/stores/credentials/configurator.py +37 -0
  214. digitalhub/stores/credentials/enums.py +54 -0
  215. digitalhub/stores/credentials/handler.py +148 -0
  216. digitalhub/stores/{configurator → credentials}/ini_module.py +5 -1
  217. digitalhub/stores/credentials/store.py +49 -0
  218. digitalhub/stores/data/__init__.py +3 -0
  219. digitalhub/stores/data/_base/__init__.py +3 -0
  220. digitalhub/stores/data/_base/store.py +23 -6
  221. digitalhub/stores/data/api.py +41 -1
  222. digitalhub/stores/data/builder.py +50 -53
  223. digitalhub/stores/data/enums.py +4 -0
  224. digitalhub/stores/data/local/__init__.py +3 -0
  225. digitalhub/stores/data/local/store.py +8 -7
  226. digitalhub/stores/data/remote/__init__.py +3 -0
  227. digitalhub/stores/data/remote/store.py +8 -7
  228. digitalhub/stores/data/s3/__init__.py +3 -0
  229. digitalhub/stores/data/s3/configurator.py +40 -92
  230. digitalhub/stores/data/s3/store.py +46 -57
  231. digitalhub/stores/data/s3/utils.py +5 -1
  232. digitalhub/stores/data/sql/__init__.py +3 -0
  233. digitalhub/stores/data/sql/configurator.py +39 -83
  234. digitalhub/stores/data/sql/store.py +19 -15
  235. digitalhub/stores/readers/__init__.py +3 -0
  236. digitalhub/stores/readers/data/__init__.py +3 -0
  237. digitalhub/stores/readers/data/_base/__init__.py +3 -0
  238. digitalhub/stores/readers/data/_base/builder.py +4 -0
  239. digitalhub/stores/readers/data/_base/reader.py +4 -0
  240. digitalhub/stores/readers/data/api.py +4 -0
  241. digitalhub/stores/readers/data/factory.py +4 -0
  242. digitalhub/stores/readers/data/pandas/__init__.py +3 -0
  243. digitalhub/stores/readers/data/pandas/builder.py +4 -0
  244. digitalhub/stores/readers/data/pandas/reader.py +4 -0
  245. digitalhub/stores/readers/query/__init__.py +3 -0
  246. digitalhub/utils/__init__.py +3 -0
  247. digitalhub/utils/enums.py +4 -0
  248. digitalhub/utils/exceptions.py +4 -0
  249. digitalhub/utils/file_utils.py +4 -0
  250. digitalhub/utils/generic_utils.py +4 -0
  251. digitalhub/utils/git_utils.py +4 -0
  252. digitalhub/utils/io_utils.py +4 -0
  253. digitalhub/utils/logger.py +4 -0
  254. digitalhub/utils/types.py +4 -0
  255. digitalhub/utils/uri_utils.py +4 -0
  256. digitalhub-0.13.0b0.dist-info/METADATA +301 -0
  257. digitalhub-0.13.0b0.dist-info/RECORD +260 -0
  258. digitalhub-0.13.0b0.dist-info/licenses/AUTHORS +5 -0
  259. digitalhub-0.13.0b0.dist-info/licenses/LICENSE +201 -0
  260. digitalhub/stores/configurator/__init__.py +0 -0
  261. digitalhub/stores/configurator/configurator.py +0 -198
  262. digitalhub/stores/configurator/credentials_store.py +0 -65
  263. digitalhub/stores/configurator/enums.py +0 -21
  264. digitalhub/stores/data/s3/enums.py +0 -16
  265. digitalhub/stores/data/sql/enums.py +0 -16
  266. digitalhub/stores/data/utils.py +0 -34
  267. digitalhub-0.11.0b7.dist-info/METADATA +0 -259
  268. digitalhub-0.11.0b7.dist-info/RECORD +0 -261
  269. digitalhub-0.11.0b7.dist-info/licenses/LICENSE.txt +0 -216
  270. {digitalhub-0.11.0b7.dist-info → digitalhub-0.13.0b0.dist-info}/WHEEL +0 -0
@@ -1,14 +1,17 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from __future__ import annotations
2
6
 
3
7
  import typing
4
- from warnings import warn
5
8
 
6
9
  from requests import request
7
10
 
8
- from digitalhub.stores.client.dhcore.enums import AuthType, DhcoreEnvVar
9
- from digitalhub.stores.configurator.configurator import configurator
10
- from digitalhub.stores.data.s3.enums import S3StoreEnv
11
- from digitalhub.stores.data.sql.enums import SqlStoreEnv
11
+ from digitalhub.stores.client.dhcore.enums import AuthType
12
+ from digitalhub.stores.credentials.configurator import Configurator
13
+ from digitalhub.stores.credentials.enums import CredsEnvVar, CredsOrigin
14
+ from digitalhub.stores.credentials.handler import creds_handler
12
15
  from digitalhub.utils.exceptions import ClientError
13
16
  from digitalhub.utils.generic_utils import list_enum
14
17
  from digitalhub.utils.uri_utils import has_remote_scheme
@@ -17,191 +20,282 @@ if typing.TYPE_CHECKING:
17
20
  from requests import Response
18
21
 
19
22
 
20
- # Default key used to store authentication information
21
- AUTH_KEY = "_auth"
22
-
23
- # API levels that are supported
24
- MAX_API_LEVEL = 20
25
- MIN_API_LEVEL = 11
26
- LIB_VERSION = 11
27
-
28
-
29
- class ClientDHCoreConfigurator:
23
+ class ClientDHCoreConfigurator(Configurator):
30
24
  """
31
25
  Configurator object used to configure the client.
26
+
27
+ The configurator starts reading the credentials from the
28
+ environment and from the ini file and stores them into the
29
+ creds_handler object.
30
+
31
+ While reading the credentials from the two sources (environment and file),
32
+ the configurator evaluate if the required keys are present in both sources.
33
+ If the required keys are not present in both sources, the configurator
34
+ will rise an error, otherwise decide which source to use.
35
+
36
+ Once the credentials are read, the configurator check the current profile
37
+ name from the ini file, and set it. The default one is __default. The
38
+ profile is used to discriminate a set of credentials inside the ini file.
39
+
40
+ The configurator finally set the authentication type based on the credentials.
41
+ The logic is the following:
42
+
43
+ 1. Check for a personal access token. Use it immediately to
44
+ require a timed access token in an exchange endpoint.
45
+ Switche then the origin to file and .
46
+ Set the auth type to EXCHANGE.
47
+ 2. Check for an access token and a refresh token.
48
+ Set the auth type to OAUTH2.
49
+ 3. Check for username and password.
50
+ Set the auth type to BASIC.
51
+ 4. If none of the above is true, leave the auth type to None.
32
52
  """
33
53
 
54
+ keys = [*list_enum(CredsEnvVar)]
55
+ required_keys = [CredsEnvVar.DHCORE_ENDPOINT.value]
56
+ keys_to_unprefix = [
57
+ CredsEnvVar.DHCORE_REFRESH_TOKEN.value,
58
+ CredsEnvVar.DHCORE_ACCESS_TOKEN.value,
59
+ CredsEnvVar.DHCORE_ISSUER.value,
60
+ CredsEnvVar.DHCORE_CLIENT_ID.value,
61
+ ]
62
+
34
63
  def __init__(self) -> None:
35
- self._current_env = configurator.get_current_env()
64
+ super().__init__()
65
+ self.load_configs()
66
+ self._origin = self.set_origin()
67
+ self._current_profile = creds_handler.get_current_env()
68
+ self._auth_type: str | None = None
69
+ self.set_auth_type()
36
70
 
37
71
  ##############################
38
- # Configuration methods
72
+ # Credentials methods
39
73
  ##############################
40
74
 
41
- def check_config(self) -> None:
75
+ def load_configs(self) -> str:
42
76
  """
43
- Check if the config is valid.
44
-
45
- Parameters
46
- ----------
47
- config : dict
48
- Configuration dictionary.
77
+ Load the configuration from the environment and from the file.
78
+ """
79
+ self.load_env_vars()
80
+ self.load_file_vars()
49
81
 
50
- Returns
51
- -------
52
- None
82
+ def load_env_vars(self) -> None:
83
+ """
84
+ Load the credentials from the environment.
53
85
  """
54
- if configurator.get_current_env() != self._current_env:
55
- self.configure()
86
+ env_creds = {var: self._creds_handler.load_from_env(var) for var in self.keys}
87
+ env_creds = self._sanitize_env_vars(env_creds)
88
+ self._creds_handler.set_credentials(self._env, env_creds)
56
89
 
57
- def configure(self, config: dict | None = None) -> None:
90
+ def _sanitize_env_vars(self, creds: dict) -> dict:
58
91
  """
59
- Configure the client attributes with config (given or from
60
- environment).
61
- Regarding authentication parameters, the config parameter
62
- takes precedence over the env variables, and the token
63
- over the basic auth. Furthermore, the config parameter is
64
- validated against the proper pydantic model.
92
+ Sanitize the env vars. We expect issuer to have the
93
+ form "DHCORE_ISSUER" in env.
65
94
 
66
95
  Parameters
67
96
  ----------
68
- config : dict
69
- Configuration dictionary.
97
+ creds : dict
98
+ Credentials dictionary.
70
99
 
71
100
  Returns
72
101
  -------
73
- None
102
+ dict
74
103
  """
75
- self._get_core_endpoint()
76
- self._get_auth_vars()
104
+ creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
105
+ creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
106
+ return creds
77
107
 
78
- def check_core_version(self, response: Response) -> None:
108
+ def load_file_vars(self) -> None:
79
109
  """
80
- Raise an exception if DHCore API version is not supported.
110
+ Load the credentials from the file.
111
+ """
112
+ keys = [*self._remove_prefix_dhcore()]
113
+ file_creds = {var: self._creds_handler.load_from_file(var) for var in keys}
81
114
 
82
- Parameters
83
- ----------
84
- response : Response
85
- The response object.
115
+ # Because in the response there is no endpoint
116
+ if file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] is None:
117
+ file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._creds_handler.load_from_env(
118
+ CredsEnvVar.DHCORE_ENDPOINT.value
119
+ )
86
120
 
87
- Returns
88
- -------
89
- None
90
- """
91
- if "X-Api-Level" in response.headers:
92
- core_api_level = int(response.headers["X-Api-Level"])
93
- if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
94
- raise ClientError("Backend API level not supported.")
95
- if LIB_VERSION < core_api_level:
96
- warn("Backend API level is higher than library version. You should consider updating the library.")
121
+ # Because in the response there is no personal access token
122
+ if file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is None:
123
+ file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] = self._creds_handler.load_from_env(
124
+ CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
125
+ )
97
126
 
98
- def build_url(self, api: str) -> str:
127
+ file_creds = self._sanitize_file_vars(file_creds)
128
+ self._creds_handler.set_credentials(self._file, file_creds)
129
+
130
+ def _sanitize_file_vars(self, creds: dict) -> dict:
99
131
  """
100
- Build the url.
132
+ Sanitize the file vars. We expect issuer, client_id and access_token and
133
+ refresh_token to not have the form "DHCORE_" in the file.
101
134
 
102
135
  Parameters
103
136
  ----------
104
- api : str
105
- The api to call.
137
+ creds : dict
138
+ Credentials dictionary.
106
139
 
107
140
  Returns
108
141
  -------
109
- str
110
- The url.
142
+ dict
111
143
  """
112
- api = api.removeprefix("/")
113
- return f"{configurator.get_credential(DhcoreEnvVar.ENDPOINT.value)}/{api}"
114
-
115
- ##############################
116
- # Private methods
117
- ##############################
144
+ creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
145
+ creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(
146
+ creds[CredsEnvVar.DHCORE_ISSUER.value.removeprefix("DHCORE_")]
147
+ )
148
+ creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] = creds[
149
+ CredsEnvVar.DHCORE_REFRESH_TOKEN.value.removeprefix("DHCORE_")
150
+ ]
151
+ creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] = creds[
152
+ CredsEnvVar.DHCORE_ACCESS_TOKEN.value.removeprefix("DHCORE_")
153
+ ]
154
+ creds[CredsEnvVar.DHCORE_CLIENT_ID.value] = creds[CredsEnvVar.DHCORE_CLIENT_ID.value.removeprefix("DHCORE_")]
155
+ return {k: v for k, v in creds.items() if k in self.keys}
118
156
 
119
157
  @staticmethod
120
- def _sanitize_endpoint(endpoint: str) -> str:
158
+ def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
121
159
  """
122
160
  Sanitize the endpoint.
123
161
 
124
162
  Returns
125
163
  -------
126
- None
164
+ str | None
165
+ The sanitized endpoint.
127
166
  """
167
+ if endpoint is None:
168
+ return
128
169
  if not has_remote_scheme(endpoint):
129
170
  raise ClientError("Invalid endpoint scheme. Must start with http:// or https://.")
130
171
 
131
172
  endpoint = endpoint.strip()
132
173
  return endpoint.removesuffix("/")
133
174
 
134
- def _get_core_endpoint(self) -> None:
175
+ def check_config(self) -> None:
135
176
  """
136
- Get the DHCore endpoint from env.
177
+ Check if the config is valid.
178
+
179
+ Parameters
180
+ ----------
181
+ config : dict
182
+ Configuration dictionary.
137
183
 
138
184
  Returns
139
185
  -------
140
186
  None
187
+ """
188
+ if (current := creds_handler.get_current_env()) != self._current_profile:
189
+ self.load_file_vars()
190
+ self._current_profile = current
141
191
 
142
- Raises
143
- ------
144
- Exception
145
- If the endpoint of DHCore is not set in the env variables.
192
+ def get_endpoint(self) -> str:
146
193
  """
147
- endpoint = configurator.load_var(DhcoreEnvVar.ENDPOINT.value)
148
- if endpoint is None:
149
- raise ClientError("Endpoint not set as environment variables.")
150
- endpoint = self._sanitize_endpoint(endpoint)
151
- configurator.set_credential(DhcoreEnvVar.ENDPOINT.value, endpoint)
194
+ Get the DHCore endpoint.
195
+
196
+ Returns
197
+ -------
198
+ str
199
+ The endpoint.
200
+ """
201
+ creds = self._creds_handler.get_credentials(self._origin)
202
+ return creds[CredsEnvVar.DHCORE_ENDPOINT.value]
152
203
 
153
- def _get_auth_vars(self) -> None:
204
+ ##############################
205
+ # Origin methods
206
+ ##############################
207
+
208
+ def set_origin(self) -> str:
154
209
  """
155
- Get authentication parameters from the env.
210
+ Evaluate the default origin from the credentials.
156
211
 
157
212
  Returns
158
213
  -------
159
- None
214
+ str
215
+ The origin.
160
216
  """
161
- # Give priority to access token
162
- access_token = self._load_dhcore_oauth_vars(DhcoreEnvVar.ACCESS_TOKEN.value)
163
- if access_token is not None:
164
- configurator.set_credential(AUTH_KEY, AuthType.OAUTH2.value)
165
- configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value.removeprefix("DHCORE_"), access_token)
217
+ origin = CredsOrigin.ENV.value
218
+
219
+ env_creds = self._creds_handler.get_credentials(self._env)
220
+ missing_env = self._check_credentials(env_creds)
221
+
222
+ file_creds = self._creds_handler.get_credentials(self._file)
223
+ missing_file = self._check_credentials(file_creds)
224
+
225
+ msg = ""
226
+ if missing_env:
227
+ msg = f"Missing required vars in env: {', '.join(missing_env)}"
228
+ origin = CredsOrigin.FILE.value
229
+ elif missing_file:
230
+ msg += f"Missing required vars in .dhcore.ini file: {', '.join(missing_file)}"
231
+
232
+ if missing_env and missing_file:
233
+ raise ClientError(msg)
234
+
235
+ return origin
166
236
 
167
- # Fallback to basic
237
+ def change_origin(self) -> None:
238
+ """
239
+ Change the origin of the credentials.
240
+ """
241
+ if self._origin == CredsOrigin.ENV.value:
242
+ self.change_to_file()
168
243
  else:
169
- user = configurator.load_var(DhcoreEnvVar.USER.value)
170
- password = configurator.load_var(DhcoreEnvVar.PASSWORD.value)
171
- if user is not None and password is not None:
172
- configurator.set_credential(AUTH_KEY, AuthType.BASIC.value)
173
- configurator.set_credential(DhcoreEnvVar.USER.value, user)
174
- configurator.set_credential(DhcoreEnvVar.PASSWORD.value, password)
244
+ self.change_to_env()
245
+
246
+ # Re-evaluate the auth type
247
+ self.set_auth_type()
248
+
249
+ def change_to_file(self) -> None:
250
+ """
251
+ Change the origin to file. Re-evaluate the auth type.
252
+ """
253
+ self._origin = CredsOrigin.FILE.value
254
+
255
+ def change_to_env(self) -> None:
256
+ """
257
+ Change the origin to env. Re-evaluate the auth type.
258
+ """
259
+ self._origin = CredsOrigin.ENV.value
175
260
 
176
261
  ##############################
177
262
  # Auth methods
178
263
  ##############################
179
264
 
180
- def basic_auth(self) -> bool:
265
+ def set_auth_type(self) -> None:
181
266
  """
182
- Get basic auth.
267
+ Evaluate the auth type from the credentials.
183
268
 
184
269
  Returns
185
270
  -------
186
- bool
271
+ None
187
272
  """
188
- auth_type = configurator.get_credential(AUTH_KEY)
189
- return auth_type == AuthType.BASIC.value
273
+ creds = creds_handler.get_credentials(self._origin)
274
+ self._auth_type = self._eval_auth_type(creds)
275
+ # If we have an exchange token, we need to get a new access token.
276
+ # Therefore, we change the origin to file, where the refresh token is written.
277
+ # We also try to fetch the PAT from both env and file
278
+ if self._auth_type == AuthType.EXCHANGE.value:
279
+ self.get_new_access_token(change_origin=True)
280
+ # Just to ensure we get the right source from file
281
+ self.change_to_file()
190
282
 
191
- def oauth2_auth(self) -> bool:
283
+ def refreshable_auth_types(self) -> bool:
192
284
  """
193
- Get oauth2 auth.
285
+ Check if the auth type is refreshable.
194
286
 
195
287
  Returns
196
288
  -------
197
289
  bool
290
+ True if the auth type is refreshable, False otherwise.
198
291
  """
199
- auth_type = configurator.get_credential(AUTH_KEY)
200
- return auth_type == AuthType.OAUTH2.value
292
+ return self._auth_type in [AuthType.OAUTH2.value, AuthType.EXCHANGE.value]
201
293
 
202
- def set_request_auth(self, kwargs: dict) -> dict:
294
+ def get_auth_parameters(self, kwargs: dict) -> dict:
203
295
  """
204
- Get the authentication header.
296
+ Get the authentication header for the request.
297
+ It is given for granted that the auth type is set and that,
298
+ if the auth type is EXCHANGE, the refresh token is set.
205
299
 
206
300
  Parameters
207
301
  ----------
@@ -211,50 +305,78 @@ class ClientDHCoreConfigurator:
211
305
  Returns
212
306
  -------
213
307
  dict
214
- Authentication header.
215
- """
216
- creds = configurator.get_all_credentials()
217
- if AUTH_KEY not in creds:
218
- return kwargs
219
- if self.basic_auth():
220
- user = creds[DhcoreEnvVar.USER.value]
221
- password = creds[DhcoreEnvVar.PASSWORD.value]
222
- kwargs["auth"] = (user, password)
223
- elif self.oauth2_auth():
308
+ Authentication parameters.
309
+ """
310
+ creds = creds_handler.get_credentials(self._origin)
311
+ if self._auth_type in (AuthType.EXCHANGE.value, AuthType.OAUTH2.value):
312
+ access_token = creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value]
224
313
  if "headers" not in kwargs:
225
314
  kwargs["headers"] = {}
226
- access_token = creds[DhcoreEnvVar.ACCESS_TOKEN.value.removeprefix("DHCORE_")]
227
315
  kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
316
+ elif self._auth_type == AuthType.BASIC.value:
317
+ user = creds[CredsEnvVar.DHCORE_USER.value]
318
+ password = creds[CredsEnvVar.DHCORE_PASSWORD.value]
319
+ kwargs["auth"] = (user, password)
228
320
  return kwargs
229
321
 
230
- def get_new_access_token(self) -> None:
322
+ def get_new_access_token(self, change_origin: bool = False) -> None:
231
323
  """
232
324
  Get a new access token.
233
325
 
326
+ Parameters
327
+ ----------
328
+ change_origin : bool, optional
329
+ Whether to change the origin of the credentials, by default False
330
+
234
331
  Returns
235
332
  -------
236
333
  None
237
334
  """
238
- # Call issuer and get endpoint for
239
- # refreshing access token
335
+ if not self.refreshable_auth_types():
336
+ raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
337
+
338
+ # Get refresh endpoint
240
339
  url = self._get_refresh_endpoint()
241
340
 
242
- # Call refresh token endpoint
243
- # Try token from env
244
- refresh_token = configurator.load_from_env(DhcoreEnvVar.REFRESH_TOKEN.value)
245
- response = self._call_refresh_token_endpoint(url, refresh_token)
341
+ # Get credentials
342
+ creds = self._creds_handler.get_credentials(self._origin)
246
343
 
247
- # Otherwise try token from file
344
+ # Get client id
345
+ if (client_id := creds.get(CredsEnvVar.DHCORE_CLIENT_ID.value)) is None:
346
+ raise ClientError("Client id not set.")
347
+
348
+ # Handling of token exchange or refresh
349
+ if self._auth_type == AuthType.OAUTH2.value:
350
+ response = self._call_refresh_token_endpoint(
351
+ url,
352
+ client_id=client_id,
353
+ refresh_token=creds.get(CredsEnvVar.DHCORE_REFRESH_TOKEN.value),
354
+ grant_type="refresh_token",
355
+ scope="credentials",
356
+ )
357
+ elif self._auth_type == AuthType.EXCHANGE.value:
358
+ response = self._call_refresh_token_endpoint(
359
+ url,
360
+ client_id=client_id,
361
+ subject_token=creds.get(CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value),
362
+ subject_token_type="urn:ietf:params:oauth:token-type:pat",
363
+ grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
364
+ scope="credentials",
365
+ )
366
+
367
+ # Change origin of creds if needed
248
368
  if response.status_code in (400, 401, 403):
249
- refresh_token = configurator.load_from_file(DhcoreEnvVar.REFRESH_TOKEN.value.removeprefix("DHCORE_"))
250
- response = self._call_refresh_token_endpoint(url, refresh_token)
369
+ if not change_origin:
370
+ raise ClientError("Unable to refresh token. Please check your credentials.")
371
+ self.change_origin()
372
+ self.get_new_access_token(change_origin=False)
251
373
 
252
374
  response.raise_for_status()
253
375
 
254
376
  # Read new credentials and propagate to config file
255
- self._set_creds(response.json())
377
+ self._export_new_creds(response.json())
256
378
 
257
- def _set_creds(self, response: dict) -> None:
379
+ def _export_new_creds(self, response: dict) -> None:
258
380
  """
259
381
  Set new credentials.
260
382
 
@@ -267,17 +389,13 @@ class ClientDHCoreConfigurator:
267
389
  -------
268
390
  None
269
391
  """
270
- keys = [
271
- *self._remove_prefix_dhcore(list_enum(DhcoreEnvVar)),
272
- *list_enum(S3StoreEnv),
273
- *list_enum(SqlStoreEnv),
274
- ]
275
- for key in keys:
276
- if (value := response.get(key.lower())) is not None:
277
- configurator.set_credential(key, value)
278
- configurator.write_env(keys)
392
+ creds_handler.write_env(response)
393
+ self.load_file_vars()
279
394
 
280
- def _remove_prefix_dhcore(self, keys: list[str]) -> list[str]:
395
+ # Change current origin to file because of refresh
396
+ self._origin = CredsOrigin.FILE.value
397
+
398
+ def _remove_prefix_dhcore(self) -> list[str]:
281
399
  """
282
400
  Remove prefix from selected keys. (Compatibility with CLI)
283
401
 
@@ -292,13 +410,8 @@ class ClientDHCoreConfigurator:
292
410
  List of keys without prefix.
293
411
  """
294
412
  new_list = []
295
- for key in keys:
296
- if key in (
297
- DhcoreEnvVar.REFRESH_TOKEN.value,
298
- DhcoreEnvVar.ACCESS_TOKEN.value,
299
- DhcoreEnvVar.ISSUER.value,
300
- DhcoreEnvVar.CLIENT_ID.value,
301
- ):
413
+ for key in self.keys:
414
+ if key in self.keys_to_unprefix:
302
415
  new_list.append(key.removeprefix("DHCORE_"))
303
416
  else:
304
417
  new_list.append(key)
@@ -314,11 +427,10 @@ class ClientDHCoreConfigurator:
314
427
  Refresh endpoint.
315
428
  """
316
429
  # Get issuer endpoint
317
- endpoint_issuer = self._load_dhcore_oauth_vars(DhcoreEnvVar.ISSUER.value)
430
+ creds = self._creds_handler.get_credentials(self._origin)
431
+ endpoint_issuer = creds.get(CredsEnvVar.DHCORE_ISSUER.value)
318
432
  if endpoint_issuer is None:
319
433
  raise ClientError("Issuer endpoint not set.")
320
- endpoint_issuer = self._sanitize_endpoint(endpoint_issuer)
321
- configurator.set_credential(DhcoreEnvVar.ISSUER.value.removeprefix("DHCORE_"), endpoint_issuer)
322
434
 
323
435
  # Standard issuer endpoint path
324
436
  url = endpoint_issuer + "/.well-known/openid-configuration"
@@ -328,7 +440,11 @@ class ClientDHCoreConfigurator:
328
440
  r.raise_for_status()
329
441
  return r.json().get("token_endpoint")
330
442
 
331
- def _call_refresh_token_endpoint(self, url: str, refresh_token: str) -> Response:
443
+ def _call_refresh_token_endpoint(
444
+ self,
445
+ url: str,
446
+ **kwargs,
447
+ ) -> Response:
332
448
  """
333
449
  Call the refresh token endpoint.
334
450
 
@@ -336,44 +452,27 @@ class ClientDHCoreConfigurator:
336
452
  ----------
337
453
  url : str
338
454
  Refresh token endpoint.
339
- refresh_token : str
340
- Refresh token.
455
+ kwargs : dict
456
+ Keyword arguments to pass to the request.
341
457
 
342
458
  Returns
343
459
  -------
344
460
  Response
345
461
  Response object.
346
462
  """
347
- # Get client id
348
- client_id = self._load_dhcore_oauth_vars(DhcoreEnvVar.CLIENT_ID.value)
349
- if client_id is None:
350
- raise ClientError("Client id not set.")
351
-
352
463
  # Send request to get new access token
353
- payload = {
354
- "grant_type": "refresh_token",
355
- "client_id": client_id,
356
- "refresh_token": refresh_token,
357
- "scope": "openid credentials offline_access",
358
- }
464
+ payload = {**kwargs}
359
465
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
360
466
  return request("POST", url, data=payload, headers=headers, timeout=60)
361
467
 
362
- def _load_dhcore_oauth_vars(self, oauth_var: str) -> str | None:
363
- """
364
- Load DHCore oauth variables.
365
-
366
- Parameters
367
- ----------
368
- oauth_var : str
369
- The oauth variable to load.
370
-
371
- Returns
372
- -------
373
- str
374
- The oauth variable.
375
- """
376
- read_var = configurator.load_from_env(oauth_var)
377
- if read_var is None:
378
- read_var = configurator.load_from_file(oauth_var.removeprefix("DHCORE_"))
379
- return read_var
468
+ def _eval_auth_type(self, creds: dict) -> str | None:
469
+ if creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
470
+ return AuthType.EXCHANGE.value
471
+ if (
472
+ creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None
473
+ and creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] is not None
474
+ ):
475
+ return AuthType.OAUTH2.value
476
+ if creds[CredsEnvVar.DHCORE_USER.value] is not None and creds[CredsEnvVar.DHCORE_PASSWORD.value] is not None:
477
+ return AuthType.BASIC.value
478
+ return None
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from __future__ import annotations
2
6
 
3
7
  from enum import Enum
@@ -15,6 +19,7 @@ class DhcoreEnvVar(Enum):
15
19
  CLIENT_ID = "DHCORE_CLIENT_ID"
16
20
  ACCESS_TOKEN = "DHCORE_ACCESS_TOKEN"
17
21
  REFRESH_TOKEN = "DHCORE_REFRESH_TOKEN"
22
+ PERSONAL_ACCESS_TOKEN = "DHCORE_PERSONAL_ACCESS_TOKEN"
18
23
  WORKFLOW_IMAGE = "DHCORE_WORKFLOW_IMAGE"
19
24
 
20
25
 
@@ -25,3 +30,4 @@ class AuthType(Enum):
25
30
 
26
31
  BASIC = "basic"
27
32
  OAUTH2 = "oauth2"
33
+ EXCHANGE = "exchange"
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from __future__ import annotations
2
6
 
3
7
  import typing
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from __future__ import annotations
2
6
 
3
7
  from digitalhub.stores.client._base.key_builder import ClientKeyBuilder