digitalhub 0.14.0b5__py3-none-any.whl → 0.14.9__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.
Files changed (93) hide show
  1. digitalhub/__init__.py +2 -2
  2. digitalhub/context/api.py +43 -6
  3. digitalhub/context/builder.py +1 -1
  4. digitalhub/context/context.py +3 -6
  5. digitalhub/entities/_base/context/entity.py +0 -3
  6. digitalhub/entities/_base/executable/entity.py +29 -11
  7. digitalhub/entities/_base/material/entity.py +2 -2
  8. digitalhub/entities/_base/material/utils.py +0 -4
  9. digitalhub/entities/_commons/enums.py +1 -0
  10. digitalhub/entities/_commons/utils.py +19 -0
  11. digitalhub/entities/_processors/base/crud.py +15 -24
  12. digitalhub/entities/_processors/base/import_export.py +3 -7
  13. digitalhub/entities/_processors/base/processor.py +4 -7
  14. digitalhub/entities/_processors/base/special_ops.py +4 -8
  15. digitalhub/entities/_processors/context/crud.py +27 -29
  16. digitalhub/entities/_processors/context/import_export.py +7 -7
  17. digitalhub/entities/_processors/context/material.py +2 -2
  18. digitalhub/entities/_processors/context/special_ops.py +25 -25
  19. digitalhub/entities/_processors/utils.py +7 -116
  20. digitalhub/entities/artifact/crud.py +3 -3
  21. digitalhub/entities/artifact/utils.py +2 -2
  22. digitalhub/entities/builders.py +2 -0
  23. digitalhub/entities/dataitem/crud.py +3 -3
  24. digitalhub/entities/dataitem/utils.py +10 -14
  25. digitalhub/entities/function/_base/entity.py +0 -3
  26. digitalhub/entities/function/crud.py +3 -3
  27. digitalhub/entities/model/crud.py +3 -3
  28. digitalhub/entities/model/mlflow/utils.py +29 -20
  29. digitalhub/entities/model/utils.py +2 -2
  30. digitalhub/entities/project/_base/builder.py +0 -6
  31. digitalhub/entities/project/_base/entity.py +264 -114
  32. digitalhub/entities/project/_base/spec.py +4 -4
  33. digitalhub/entities/project/crud.py +16 -51
  34. digitalhub/entities/project/utils.py +7 -3
  35. digitalhub/entities/secret/crud.py +2 -2
  36. digitalhub/entities/task/_base/models.py +13 -16
  37. digitalhub/entities/trigger/crud.py +28 -9
  38. digitalhub/entities/workflow/_base/entity.py +0 -5
  39. digitalhub/entities/workflow/crud.py +3 -6
  40. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
  41. digitalhub/stores/client/builder.py +20 -32
  42. digitalhub/stores/client/client.py +322 -0
  43. digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +148 -195
  44. digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
  45. digitalhub/stores/client/header_manager.py +61 -0
  46. digitalhub/stores/client/http_handler.py +152 -0
  47. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
  48. digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
  49. digitalhub/stores/client/response_processor.py +102 -0
  50. digitalhub/stores/client/utils.py +35 -0
  51. digitalhub/stores/{credentials → configurator}/api.py +5 -9
  52. digitalhub/stores/configurator/configurator.py +123 -0
  53. digitalhub/stores/{credentials → configurator}/enums.py +26 -10
  54. digitalhub/stores/configurator/handler.py +213 -0
  55. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -6
  56. digitalhub/stores/data/_base/store.py +0 -4
  57. digitalhub/stores/data/api.py +4 -6
  58. digitalhub/stores/data/builder.py +6 -38
  59. digitalhub/stores/data/s3/configurator.py +30 -114
  60. digitalhub/stores/data/s3/store.py +9 -22
  61. digitalhub/stores/data/sql/configurator.py +49 -71
  62. digitalhub/stores/data/sql/store.py +26 -61
  63. digitalhub/utils/generic_utils.py +0 -12
  64. digitalhub/utils/git_utils.py +0 -8
  65. digitalhub/utils/io_utils.py +0 -8
  66. digitalhub/utils/store_utils.py +1 -1
  67. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/METADATA +3 -3
  68. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/RECORD +73 -86
  69. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
  70. digitalhub/stores/client/_base/api_builder.py +0 -34
  71. digitalhub/stores/client/_base/client.py +0 -243
  72. digitalhub/stores/client/_base/params_builder.py +0 -82
  73. digitalhub/stores/client/api.py +0 -32
  74. digitalhub/stores/client/dhcore/__init__.py +0 -3
  75. digitalhub/stores/client/dhcore/client.py +0 -553
  76. digitalhub/stores/client/dhcore/enums.py +0 -18
  77. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  78. digitalhub/stores/client/dhcore/utils.py +0 -86
  79. digitalhub/stores/client/local/__init__.py +0 -3
  80. digitalhub/stores/client/local/api_builder.py +0 -116
  81. digitalhub/stores/client/local/client.py +0 -605
  82. digitalhub/stores/client/local/enums.py +0 -15
  83. digitalhub/stores/client/local/key_builder.py +0 -62
  84. digitalhub/stores/client/local/params_builder.py +0 -97
  85. digitalhub/stores/credentials/__init__.py +0 -3
  86. digitalhub/stores/credentials/configurator.py +0 -185
  87. digitalhub/stores/credentials/handler.py +0 -164
  88. digitalhub/stores/credentials/store.py +0 -77
  89. digitalhub/stores/data/enums.py +0 -15
  90. /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
  91. /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
  92. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
  93. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,213 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ from typing import Any
9
+
10
+ from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars, SetCreds
11
+ from digitalhub.stores.configurator.ini_module import (
12
+ load_file,
13
+ load_key,
14
+ load_profile,
15
+ set_current_profile,
16
+ write_file,
17
+ )
18
+ from digitalhub.utils.generic_utils import list_enum
19
+
20
+
21
+ class ConfigurationHandler:
22
+ """
23
+ Handler for loading and writing configuration variables.
24
+ """
25
+
26
+ def __init__(self):
27
+ self._current_profile = self._read_current_profile()
28
+ self._configuration: dict[str, Any] = self.load_configuration()
29
+ self._credentials: dict[str, Any] = self.load_credentials()
30
+
31
+ @staticmethod
32
+ def _read_env(variables: list) -> dict:
33
+ """
34
+ Read configuration variables from the .dhcore file.
35
+
36
+ Parameters
37
+ ----------
38
+ variables : list
39
+ List of environment variable names to read.
40
+
41
+ Returns
42
+ -------
43
+ dict
44
+ Dictionary of environment variables.
45
+ """
46
+ return {var: os.getenv(var) for var in variables}
47
+
48
+ @staticmethod
49
+ def _read_file(variables: list, profile: str) -> dict:
50
+ """
51
+ Read configuration variables from the .dhcore file.
52
+
53
+ Parameters
54
+ ----------
55
+ variables : list
56
+ List of environment variable names to read.
57
+ profile : str
58
+ Profile name to read from.
59
+
60
+ Returns
61
+ -------
62
+ dict
63
+ Dictionary of configuration variables.
64
+ """
65
+ file = load_file()
66
+ return {var: load_key(file, profile, var) for var in variables}
67
+
68
+ ##############################
69
+ # Configuration methods
70
+ ##############################
71
+
72
+ def load_configuration(self) -> dict[str, Any]:
73
+ """
74
+ Load configuration with env > file precedence.
75
+
76
+ Returns
77
+ -------
78
+ dict
79
+ Merged configuration dictionary.
80
+ """
81
+ profile = self.get_current_profile()
82
+ variables = list_enum(ConfigurationVars)
83
+ env_config = self._read_env(variables)
84
+ file_config = self._read_file(variables, profile)
85
+ return {**file_config, **{k: v for k, v in env_config.items() if v is not None}}
86
+
87
+ def reload_configuration(self) -> None:
88
+ """
89
+ Reload configuration from environment and file.
90
+ """
91
+ self._configuration = self.load_configuration()
92
+
93
+ def get_configuration(self) -> dict[str, Any]:
94
+ """
95
+ Get the merged configuration dictionary.
96
+
97
+ Returns
98
+ -------
99
+ dict[str, Any]
100
+ The configuration dictionary.
101
+ """
102
+ return self._configuration
103
+
104
+ ##############################
105
+ # Credentials methods
106
+ ##############################
107
+
108
+ def load_credentials(self) -> dict[str, Any]:
109
+ """
110
+ Load credentials with file > env precedence.
111
+
112
+ Parameters
113
+ ----------
114
+ profile : str
115
+ Profile name to load credentials from.
116
+
117
+ Returns
118
+ -------
119
+ dict
120
+ Merged credentials dictionary.
121
+ """
122
+ variables = list_enum(CredentialsVars)
123
+ env_config = self._read_env(variables)
124
+ file_config = self._read_file(variables, self.get_current_profile())
125
+ return {**env_config, **{k: v for k, v in file_config.items() if v is not None}}
126
+
127
+ def reload_credentials(self) -> None:
128
+ """
129
+ Reload credentials from environment and file.
130
+ """
131
+ self._credentials = self.load_credentials()
132
+
133
+ def reload_credentials_from_env(self) -> None:
134
+ """
135
+ Reload credentials from environment where env > file precedence.
136
+ Its a partial reload only from env variables used as fallback.
137
+ """
138
+ variables = list_enum(CredentialsVars)
139
+ env_config = self._read_env(variables)
140
+ file_config = self._read_file(variables, self.get_current_profile())
141
+ self._credentials = {**file_config, **{k: v for k, v in env_config.items() if v is not None}}
142
+
143
+ def get_credentials(self) -> dict[str, Any]:
144
+ """
145
+ Get the merged credentials dictionary.
146
+
147
+ Returns
148
+ -------
149
+ dict[str, Any]
150
+ The credentials dictionary.
151
+ """
152
+ return self._credentials
153
+
154
+ ##############################
155
+ # Profile Methods
156
+ ##############################
157
+
158
+ @staticmethod
159
+ def _read_current_profile() -> str:
160
+ """
161
+ Read the current credentials profile name.
162
+
163
+ Returns
164
+ -------
165
+ str
166
+ Name of the credentials profile.
167
+ """
168
+ profile = os.getenv(SetCreds.DH_PROFILE.value)
169
+ if profile is not None:
170
+ return profile
171
+ file = load_file()
172
+ profile = load_profile(file)
173
+ if profile is not None:
174
+ return profile
175
+ return SetCreds.DEFAULT.value
176
+
177
+ def set_current_profile(self, profile: str) -> None:
178
+ """
179
+ Set the current credentials profile name.
180
+
181
+ Parameters
182
+ ----------
183
+ profile : str
184
+ Name of the credentials profile to set.
185
+ """
186
+ self._current_profile = profile
187
+ set_current_profile(profile)
188
+ self.reload_configuration()
189
+ self.reload_credentials()
190
+
191
+ def get_current_profile(self) -> str:
192
+ """
193
+ Get the current credentials profile name.
194
+
195
+ Returns
196
+ -------
197
+ str
198
+ Name of the current credentials profile.
199
+ """
200
+ return self._current_profile
201
+
202
+ def write_file(self, variables: dict) -> None:
203
+ """
204
+ Write variables to the .dhcore file for the current profile.
205
+
206
+ Parameters
207
+ ----------
208
+ infos : dict
209
+ Information to write.
210
+ profile : str
211
+ Profile name to write to.
212
+ """
213
+ write_file(variables, self._current_profile)
@@ -92,9 +92,6 @@ def write_config(creds: dict, environment: str) -> None:
92
92
  environment : str
93
93
  Name of the credentials profile/environment.
94
94
 
95
- Returns
96
- -------
97
- None
98
95
 
99
96
  Raises
100
97
  ------
@@ -120,6 +117,37 @@ def write_config(creds: dict, environment: str) -> None:
120
117
  raise ClientError(f"Failed to write env file: {e}")
121
118
 
122
119
 
120
+ def write_file(variables: dict, profile: str) -> None:
121
+ """
122
+ Write variables to the .dhcore.ini file for the specified profile.
123
+ Overwrites any existing values for that profile.
124
+
125
+ Parameters
126
+ ----------
127
+ variables : dict
128
+ Dictionary of variables to write.
129
+ profile : str
130
+ Name of the credentials profile to write to.
131
+ """
132
+ try:
133
+ cfg = load_file()
134
+
135
+ sections = cfg.sections()
136
+ if profile not in sections:
137
+ cfg.add_section(profile)
138
+
139
+ cfg["DEFAULT"]["current_environment"] = profile
140
+ for k, v in variables.items():
141
+ cfg[profile][k] = str(v)
142
+
143
+ ENV_FILE.touch(exist_ok=True)
144
+ with open(ENV_FILE, "w") as inifile:
145
+ cfg.write(inifile)
146
+
147
+ except Exception as e:
148
+ raise ClientError(f"Failed to write env file: {e}")
149
+
150
+
123
151
  def set_current_profile(environment: str) -> None:
124
152
  """
125
153
  Set the current credentials profile in the .dhcore.ini file.
@@ -129,9 +157,6 @@ def set_current_profile(environment: str) -> None:
129
157
  environment : str
130
158
  Name of the credentials profile to set as current.
131
159
 
132
- Returns
133
- -------
134
- None
135
160
 
136
161
  Raises
137
162
  ------
@@ -16,7 +16,6 @@ from digitalhub.utils.types import SourcesOrListOfSources
16
16
  from digitalhub.utils.uri_utils import has_local_scheme
17
17
 
18
18
  if typing.TYPE_CHECKING:
19
- from digitalhub.stores.credentials.configurator import Configurator
20
19
  from digitalhub.stores.readers.data._base.reader import DataframeReader
21
20
 
22
21
 
@@ -25,9 +24,6 @@ class Store:
25
24
  Store abstract class.
26
25
  """
27
26
 
28
- def __init__(self, configurator: Configurator | None = None) -> None:
29
- self._configurator = configurator
30
-
31
27
  ##############################
32
28
  # I/O methods
33
29
  ##############################
@@ -7,9 +7,9 @@ from __future__ import annotations
7
7
  import typing
8
8
 
9
9
  from digitalhub.context.api import get_context
10
- from digitalhub.stores.credentials.handler import creds_handler
10
+ from digitalhub.stores.configurator.configurator import configurator
11
+ from digitalhub.stores.configurator.enums import ConfigurationVars
11
12
  from digitalhub.stores.data.builder import store_builder
12
- from digitalhub.stores.data.enums import StoreEnv
13
13
 
14
14
  if typing.TYPE_CHECKING:
15
15
  from digitalhub.stores.data._base.store import Store
@@ -34,16 +34,14 @@ def get_default_store(project: str) -> str:
34
34
  ValueError
35
35
  If no default store is found.
36
36
  """
37
- var = StoreEnv.DEFAULT_FILES_STORE.value
37
+ var = ConfigurationVars.DEFAULT_FILES_STORE.value
38
38
 
39
39
  context = get_context(project)
40
40
  store = context.config.get(var.lower().replace("dhcore_", ""))
41
41
  if store is not None:
42
42
  return store
43
43
 
44
- store = creds_handler.load_from_env([var]).get(var)
45
- if store is None:
46
- store = creds_handler.load_from_file([var]).get(var)
44
+ store = configurator.get_configuration().get(var)
47
45
 
48
46
  if store is None or store == "":
49
47
  raise ValueError(
@@ -8,38 +8,15 @@ import typing
8
8
 
9
9
  from digitalhub.stores.data.local.store import LocalStore
10
10
  from digitalhub.stores.data.remote.store import RemoteStore
11
- from digitalhub.stores.data.s3.configurator import S3StoreConfigurator
12
11
  from digitalhub.stores.data.s3.store import S3Store
13
- from digitalhub.stores.data.sql.configurator import SqlStoreConfigurator
14
12
  from digitalhub.stores.data.sql.store import SqlStore
15
13
  from digitalhub.utils.uri_utils import SchemeCategory, map_uri_scheme
16
14
 
17
15
  if typing.TYPE_CHECKING:
18
- from digitalhub.stores.credentials.configurator import Configurator
19
16
  from digitalhub.stores.data._base.store import Store
20
17
  from digitalhub.utils.exceptions import StoreError
21
18
 
22
19
 
23
- class StoreInfo:
24
- """
25
- Container for store class and configurator information.
26
-
27
- Holds store class references and their associated configurators
28
- for registration and instantiation in the store builder system.
29
-
30
- Attributes
31
- ----------
32
- _store : Store
33
- The store class to be instantiated.
34
- _configurator : Configurator or None
35
- The configurator class for store configuration, if required.
36
- """
37
-
38
- def __init__(self, store: Store, configurator: Configurator | None = None) -> None:
39
- self._store = store
40
- self._configurator = configurator
41
-
42
-
43
20
  class StoreBuilder:
44
21
  """
45
22
  Store factory and registry for managing data store instances.
@@ -58,14 +35,13 @@ class StoreBuilder:
58
35
  """
59
36
 
60
37
  def __init__(self) -> None:
61
- self._builders: dict[str, StoreInfo] = {}
38
+ self._builders: dict[str, Store] = {}
62
39
  self._instances: dict[str, dict[str, Store]] = {}
63
40
 
64
41
  def register(
65
42
  self,
66
43
  store_type: str,
67
44
  store: Store,
68
- configurator: Configurator | None = None,
69
45
  ) -> None:
70
46
  """
71
47
  Register a store type with its class and optional configurator.
@@ -79,7 +55,7 @@ class StoreBuilder:
79
55
  The unique identifier for the store type (e.g., 's3', 'sql').
80
56
  store : Store
81
57
  The store class to register for this type.
82
- configurator : Configurator, optional
58
+ configurator : Configurator
83
59
  The configurator class for store configuration.
84
60
  If None, the store will be instantiated without configuration.
85
61
 
@@ -89,7 +65,7 @@ class StoreBuilder:
89
65
  If the store type is already registered in the builder.
90
66
  """
91
67
  if store_type not in self._builders:
92
- self._builders[store_type] = StoreInfo(store, configurator)
68
+ self._builders[store_type] = store
93
69
  else:
94
70
  raise StoreError(f"Store type {store_type} already registered")
95
71
 
@@ -122,21 +98,13 @@ class StoreBuilder:
122
98
 
123
99
  # Build the store instance if not already present
124
100
  if store_type not in self._instances:
125
- store_info = self._builders[store_type]
126
- store_cls = store_info._store
127
- cfgrt_cls = store_info._configurator
128
-
129
- if cfgrt_cls is None:
130
- store = store_cls()
131
- else:
132
- store = store_cls(cfgrt_cls())
133
- self._instances[store_type] = store
101
+ self._instances[store_type] = self._builders[store_type]()
134
102
 
135
103
  return self._instances[store_type]
136
104
 
137
105
 
138
106
  store_builder = StoreBuilder()
139
- store_builder.register(SchemeCategory.S3.value, S3Store, S3StoreConfigurator)
140
- store_builder.register(SchemeCategory.SQL.value, SqlStore, SqlStoreConfigurator)
107
+ store_builder.register(SchemeCategory.S3.value, S3Store)
108
+ store_builder.register(SchemeCategory.SQL.value, SqlStore)
141
109
  store_builder.register(SchemeCategory.LOCAL.value, LocalStore)
142
110
  store_builder.register(SchemeCategory.REMOTE.value, RemoteStore)
@@ -4,75 +4,28 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from datetime import datetime, timedelta, timezone
8
-
9
7
  from botocore.config import Config
10
8
 
11
- from digitalhub.stores.client.dhcore.utils import refresh_token
12
- from digitalhub.stores.credentials.configurator import Configurator
13
- from digitalhub.stores.credentials.enums import CredsEnvVar
9
+ from digitalhub.stores.configurator.configurator import configurator
10
+ from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
14
11
 
15
12
 
16
- class S3StoreConfigurator(Configurator):
13
+ class S3StoreConfigurator:
17
14
  """
18
- Configure the store by getting the credentials from user
19
- provided config or from environment.
15
+ Configurator class for S3 store configuration and credentials management.
20
16
  """
21
17
 
22
- keys = [
23
- CredsEnvVar.S3_ENDPOINT_URL.value,
24
- CredsEnvVar.S3_ACCESS_KEY_ID.value,
25
- CredsEnvVar.S3_SECRET_ACCESS_KEY.value,
26
- CredsEnvVar.S3_REGION.value,
27
- CredsEnvVar.S3_SIGNATURE_VERSION.value,
28
- CredsEnvVar.S3_SESSION_TOKEN.value,
29
- CredsEnvVar.S3_PATH_STYLE.value,
30
- CredsEnvVar.S3_CREDENTIALS_EXPIRATION.value,
31
- ]
32
- required_keys = [
33
- CredsEnvVar.S3_ENDPOINT_URL.value,
34
- CredsEnvVar.S3_ACCESS_KEY_ID.value,
35
- CredsEnvVar.S3_SECRET_ACCESS_KEY.value,
36
- ]
37
-
38
18
  def __init__(self):
39
- super().__init__()
40
- self.load_configs()
19
+ self._validate()
41
20
 
42
21
  ##############################
43
22
  # Configuration methods
44
23
  ##############################
45
24
 
46
- def load_env_vars(self) -> None:
47
- """
48
- Loads the credentials from the environment variables.
49
- """
50
- env_creds = self._creds_handler.load_from_env(self.keys)
51
- self._creds_handler.set_credentials(self._env, env_creds)
52
-
53
- def load_file_vars(self) -> None:
54
- """
55
- Loads the credentials from a file.
56
- """
57
- file_creds = self._creds_handler.load_from_file(self.keys)
58
- self._creds_handler.set_credentials(self._file, file_creds)
59
-
60
25
  def get_client_config(self) -> dict:
61
26
  """
62
27
  Gets S3 credentials (access key, secret key, session token, and other config).
63
28
 
64
- Returns
65
- -------
66
- dict
67
- Dictionary containing S3 credentials and configuration.
68
- """
69
- creds = self.evaluate_credentials()
70
- return self.get_creds_dict(creds)
71
-
72
- def get_creds_dict(self, creds: dict) -> dict:
73
- """
74
- Returns a dictionary containing the S3 credentials.
75
-
76
29
  Parameters
77
30
  ----------
78
31
  creds : dict
@@ -83,79 +36,42 @@ class S3StoreConfigurator(Configurator):
83
36
  dict
84
37
  A dictionary containing the S3 credentials.
85
38
  """
39
+ creds = configurator.get_config_creds()
86
40
  return {
87
- "endpoint_url": creds[CredsEnvVar.S3_ENDPOINT_URL.value],
88
- "aws_access_key_id": creds[CredsEnvVar.S3_ACCESS_KEY_ID.value],
89
- "aws_secret_access_key": creds[CredsEnvVar.S3_SECRET_ACCESS_KEY.value],
90
- "aws_session_token": creds[CredsEnvVar.S3_SESSION_TOKEN.value],
41
+ "endpoint_url": creds[ConfigurationVars.S3_ENDPOINT_URL.value],
42
+ "aws_access_key_id": creds[CredentialsVars.S3_ACCESS_KEY_ID.value],
43
+ "aws_secret_access_key": creds[CredentialsVars.S3_SECRET_ACCESS_KEY.value],
44
+ "aws_session_token": creds[CredentialsVars.S3_SESSION_TOKEN.value],
91
45
  "config": Config(
92
- region_name=creds[CredsEnvVar.S3_REGION.value],
93
- signature_version=creds[CredsEnvVar.S3_SIGNATURE_VERSION.value],
46
+ region_name=creds[ConfigurationVars.S3_REGION.value],
47
+ signature_version=creds[ConfigurationVars.S3_SIGNATURE_VERSION.value],
94
48
  ),
95
49
  }
96
50
 
97
- def evaluate_credentials(self) -> dict:
98
- """
99
- Evaluates and returns the current valid credentials.
100
- If the credentials are expired and were loaded from file,
101
- it refreshes them.
102
-
103
- Returns
104
- -------
105
- dict
106
- The current valid credentials.
107
- """
108
- creds = self.get_credentials(self._origin)
109
- expired = creds[CredsEnvVar.S3_CREDENTIALS_EXPIRATION.value]
110
- if self._origin == self._file and self._is_expired(expired):
111
- refresh_token()
112
- self.load_file_vars()
113
- creds = self.get_credentials(self._origin)
114
- return creds
115
-
116
- def get_file_config(self) -> dict:
117
- """
118
- Returns the credentials loaded from file.
119
-
120
- Returns
121
- -------
122
- dict
123
- The credentials loaded from file.
124
- """
125
- creds = self.get_credentials(self._file)
126
- return self.get_creds_dict(creds)
127
-
128
- def get_env_config(self) -> dict:
51
+ def _validate(self) -> None:
129
52
  """
130
- Returns the credentials loaded from environment variables.
131
-
132
- Returns
133
- -------
134
- dict
135
- The credentials loaded from environment variables.
53
+ Validate if all required keys are present in the configuration.
136
54
  """
137
- creds = self.get_credentials(self._env)
138
- return self.get_creds_dict(creds)
55
+ required_keys = [
56
+ ConfigurationVars.S3_ENDPOINT_URL.value,
57
+ CredentialsVars.S3_ACCESS_KEY_ID.value,
58
+ CredentialsVars.S3_SECRET_ACCESS_KEY.value,
59
+ ]
60
+ current_keys = configurator.get_config_creds()
61
+ missing_keys = []
62
+ for key in required_keys:
63
+ if key not in current_keys or current_keys[key] is None:
64
+ missing_keys.append(key)
65
+ if missing_keys:
66
+ raise ValueError(f"Missing required variables for S3 store: {', '.join(missing_keys)}")
139
67
 
140
- @staticmethod
141
- def _is_expired(timestamp: str | None) -> bool:
68
+ def eval_retry(self) -> bool:
142
69
  """
143
- Determines whether a given timestamp is after the current UTC time.
144
-
145
- Parameters
146
- ----------
147
- timestamp : str or None
148
- Timestamp string in the format 'YYYY-MM-DDTHH:MM:SSZ'.
70
+ Evaluate the status of retry lifecycle.
149
71
 
150
72
  Returns
151
73
  -------
152
74
  bool
153
- True if the given timestamp is later than the current UTC time,
154
- otherwise False.
75
+ True if a retry action was performed, otherwise False.
155
76
  """
156
- if timestamp is None:
157
- return False
158
- dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
159
- dt = dt.replace(tzinfo=timezone.utc)
160
- now = datetime.now(timezone.utc) + timedelta(seconds=120)
161
- return dt < now
77
+ return configurator.eval_retry()
@@ -4,7 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import typing
8
7
  from io import BytesIO
9
8
  from pathlib import Path
10
9
  from typing import Any, Type
@@ -16,16 +15,12 @@ from boto3.s3.transfer import TransferConfig
16
15
  from botocore.exceptions import ClientError, NoCredentialsError
17
16
 
18
17
  from digitalhub.stores.data._base.store import Store
18
+ from digitalhub.stores.data.s3.configurator import S3StoreConfigurator
19
19
  from digitalhub.stores.readers.data.api import get_reader_by_object
20
20
  from digitalhub.utils.exceptions import ConfigError, StoreError
21
21
  from digitalhub.utils.file_utils import get_file_info_from_s3, get_file_mime_type
22
22
  from digitalhub.utils.types import SourcesOrListOfSources
23
23
 
24
- if typing.TYPE_CHECKING:
25
- from digitalhub.stores.credentials.configurator import Configurator
26
- from digitalhub.stores.data.s3.configurator import S3StoreConfigurator
27
-
28
-
29
24
  # Type aliases
30
25
  S3Client = Type["botocore.client.S3"]
31
26
 
@@ -38,9 +33,9 @@ class S3Store(Store):
38
33
  artifacts on S3 based storage.
39
34
  """
40
35
 
41
- def __init__(self, configurator: Configurator | None = None) -> None:
42
- super().__init__(configurator)
43
- self._configurator: S3StoreConfigurator
36
+ def __init__(self) -> None:
37
+ super().__init__()
38
+ self._configurator: S3StoreConfigurator = S3StoreConfigurator()
44
39
 
45
40
  ##############################
46
41
  # I/O methods
@@ -646,7 +641,7 @@ class S3Store(Store):
646
641
  """
647
642
  return boto3.client("s3", **cfg)
648
643
 
649
- def _check_factory(self, s3_path: str, retry: bool = True) -> tuple[S3Client, str]:
644
+ def _check_factory(self, s3_path: str) -> tuple[S3Client, str]:
650
645
  """
651
646
  Checks if the S3 bucket collected from the URI is accessible.
652
647
 
@@ -654,29 +649,21 @@ class S3Store(Store):
654
649
  ----------
655
650
  s3_path : str
656
651
  Path to the S3 bucket (e.g., 's3://bucket/path').
657
- retry : bool, optional
658
- Whether to retry the operation if a ConfigError is raised. Default is True.
659
652
 
660
653
  Returns
661
654
  -------
662
655
  tuple of S3Client and str
663
656
  Tuple containing the S3 client object and the name of the S3 bucket.
664
-
665
- Raises
666
- ------
667
- ConfigError
668
- If access to the specified bucket is not available and retry is False.
669
657
  """
670
658
  bucket = self._get_bucket(s3_path)
659
+ cfg = self._configurator.get_client_config()
660
+ client = self._get_client(cfg)
671
661
  try:
672
- cfg = self._configurator.get_client_config()
673
- client = self._get_client(cfg)
674
662
  self._check_access_to_storage(client, bucket)
675
663
  return client, bucket
676
664
  except ConfigError as e:
677
- if retry:
678
- self._configurator.eval_change_origin()
679
- return self._check_factory(s3_path, False)
665
+ if self._configurator.eval_retry():
666
+ return self._check_factory(s3_path)
680
667
  raise e
681
668
 
682
669
  def _check_access_to_storage(self, client: S3Client, bucket: str) -> None: