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.
- digitalhub/__init__.py +2 -2
- digitalhub/context/api.py +43 -6
- digitalhub/context/builder.py +1 -1
- digitalhub/context/context.py +3 -6
- digitalhub/entities/_base/context/entity.py +0 -3
- digitalhub/entities/_base/executable/entity.py +29 -11
- digitalhub/entities/_base/material/entity.py +2 -2
- digitalhub/entities/_base/material/utils.py +0 -4
- digitalhub/entities/_commons/enums.py +1 -0
- digitalhub/entities/_commons/utils.py +19 -0
- digitalhub/entities/_processors/base/crud.py +15 -24
- digitalhub/entities/_processors/base/import_export.py +3 -7
- digitalhub/entities/_processors/base/processor.py +4 -7
- digitalhub/entities/_processors/base/special_ops.py +4 -8
- digitalhub/entities/_processors/context/crud.py +27 -29
- digitalhub/entities/_processors/context/import_export.py +7 -7
- digitalhub/entities/_processors/context/material.py +2 -2
- digitalhub/entities/_processors/context/special_ops.py +25 -25
- digitalhub/entities/_processors/utils.py +7 -116
- digitalhub/entities/artifact/crud.py +3 -3
- digitalhub/entities/artifact/utils.py +2 -2
- digitalhub/entities/builders.py +2 -0
- digitalhub/entities/dataitem/crud.py +3 -3
- digitalhub/entities/dataitem/utils.py +10 -14
- digitalhub/entities/function/_base/entity.py +0 -3
- digitalhub/entities/function/crud.py +3 -3
- digitalhub/entities/model/crud.py +3 -3
- digitalhub/entities/model/mlflow/utils.py +29 -20
- digitalhub/entities/model/utils.py +2 -2
- digitalhub/entities/project/_base/builder.py +0 -6
- digitalhub/entities/project/_base/entity.py +264 -114
- digitalhub/entities/project/_base/spec.py +4 -4
- digitalhub/entities/project/crud.py +16 -51
- digitalhub/entities/project/utils.py +7 -3
- digitalhub/entities/secret/crud.py +2 -2
- digitalhub/entities/task/_base/models.py +13 -16
- digitalhub/entities/trigger/crud.py +28 -9
- digitalhub/entities/workflow/_base/entity.py +0 -5
- digitalhub/entities/workflow/crud.py +3 -6
- digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
- digitalhub/stores/client/builder.py +20 -32
- digitalhub/stores/client/client.py +322 -0
- digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +148 -195
- digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
- digitalhub/stores/client/header_manager.py +61 -0
- digitalhub/stores/client/http_handler.py +152 -0
- digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
- digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
- digitalhub/stores/client/response_processor.py +102 -0
- digitalhub/stores/client/utils.py +35 -0
- digitalhub/stores/{credentials → configurator}/api.py +5 -9
- digitalhub/stores/configurator/configurator.py +123 -0
- digitalhub/stores/{credentials → configurator}/enums.py +26 -10
- digitalhub/stores/configurator/handler.py +213 -0
- digitalhub/stores/{credentials → configurator}/ini_module.py +31 -6
- digitalhub/stores/data/_base/store.py +0 -4
- digitalhub/stores/data/api.py +4 -6
- digitalhub/stores/data/builder.py +6 -38
- digitalhub/stores/data/s3/configurator.py +30 -114
- digitalhub/stores/data/s3/store.py +9 -22
- digitalhub/stores/data/sql/configurator.py +49 -71
- digitalhub/stores/data/sql/store.py +26 -61
- digitalhub/utils/generic_utils.py +0 -12
- digitalhub/utils/git_utils.py +0 -8
- digitalhub/utils/io_utils.py +0 -8
- digitalhub/utils/store_utils.py +1 -1
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/METADATA +3 -3
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/RECORD +73 -86
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
- digitalhub/stores/client/_base/api_builder.py +0 -34
- digitalhub/stores/client/_base/client.py +0 -243
- digitalhub/stores/client/_base/params_builder.py +0 -82
- digitalhub/stores/client/api.py +0 -32
- digitalhub/stores/client/dhcore/__init__.py +0 -3
- digitalhub/stores/client/dhcore/client.py +0 -553
- digitalhub/stores/client/dhcore/enums.py +0 -18
- digitalhub/stores/client/dhcore/key_builder.py +0 -62
- digitalhub/stores/client/dhcore/utils.py +0 -86
- digitalhub/stores/client/local/__init__.py +0 -3
- digitalhub/stores/client/local/api_builder.py +0 -116
- digitalhub/stores/client/local/client.py +0 -605
- digitalhub/stores/client/local/enums.py +0 -15
- digitalhub/stores/client/local/key_builder.py +0 -62
- digitalhub/stores/client/local/params_builder.py +0 -97
- digitalhub/stores/credentials/__init__.py +0 -3
- digitalhub/stores/credentials/configurator.py +0 -185
- digitalhub/stores/credentials/handler.py +0 -164
- digitalhub/stores/credentials/store.py +0 -77
- digitalhub/stores/data/enums.py +0 -15
- /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
- /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
- {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
|
##############################
|
digitalhub/stores/data/api.py
CHANGED
|
@@ -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.
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
|
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] =
|
|
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
|
-
|
|
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
|
|
140
|
-
store_builder.register(SchemeCategory.SQL.value, SqlStore
|
|
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.
|
|
12
|
-
from digitalhub.stores.
|
|
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
|
|
13
|
+
class S3StoreConfigurator:
|
|
17
14
|
"""
|
|
18
|
-
|
|
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
|
-
|
|
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[
|
|
88
|
-
"aws_access_key_id": creds[
|
|
89
|
-
"aws_secret_access_key": creds[
|
|
90
|
-
"aws_session_token": creds[
|
|
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[
|
|
93
|
-
signature_version=creds[
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
def _is_expired(timestamp: str | None) -> bool:
|
|
68
|
+
def eval_retry(self) -> bool:
|
|
142
69
|
"""
|
|
143
|
-
|
|
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
|
|
154
|
-
otherwise False.
|
|
75
|
+
True if a retry action was performed, otherwise False.
|
|
155
76
|
"""
|
|
156
|
-
|
|
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
|
|
42
|
-
super().__init__(
|
|
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
|
|
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
|
|
678
|
-
self.
|
|
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:
|