digitalhub 0.9.1__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of digitalhub might be problematic. Click here for more details.
- digitalhub/__init__.py +2 -3
- digitalhub/client/_base/api_builder.py +1 -1
- digitalhub/client/_base/client.py +25 -2
- digitalhub/client/_base/params_builder.py +16 -0
- digitalhub/client/dhcore/api_builder.py +10 -4
- digitalhub/client/dhcore/client.py +30 -398
- digitalhub/client/dhcore/configurator.py +361 -0
- digitalhub/client/dhcore/error_parser.py +107 -0
- digitalhub/client/dhcore/models.py +13 -23
- digitalhub/client/dhcore/params_builder.py +178 -0
- digitalhub/client/dhcore/utils.py +4 -44
- digitalhub/client/local/api_builder.py +13 -18
- digitalhub/client/local/client.py +18 -2
- digitalhub/client/local/enums.py +11 -0
- digitalhub/client/local/params_builder.py +116 -0
- digitalhub/configurator/api.py +31 -0
- digitalhub/configurator/configurator.py +195 -0
- digitalhub/configurator/credentials_store.py +65 -0
- digitalhub/configurator/ini_module.py +74 -0
- digitalhub/entities/_base/_base/entity.py +2 -2
- digitalhub/entities/_base/context/entity.py +4 -4
- digitalhub/entities/_base/entity/builder.py +5 -5
- digitalhub/entities/_base/executable/entity.py +2 -2
- digitalhub/entities/_base/material/entity.py +12 -12
- digitalhub/entities/_base/material/status.py +1 -1
- digitalhub/entities/_base/material/utils.py +2 -2
- digitalhub/entities/_base/unversioned/entity.py +2 -2
- digitalhub/entities/_base/versioned/entity.py +2 -2
- digitalhub/entities/_commons/enums.py +2 -0
- digitalhub/entities/_commons/metrics.py +164 -0
- digitalhub/entities/_commons/types.py +5 -0
- digitalhub/entities/_commons/utils.py +2 -2
- digitalhub/entities/_processors/base.py +527 -0
- digitalhub/entities/{_operations/processor.py → _processors/context.py} +212 -837
- digitalhub/entities/_processors/utils.py +158 -0
- digitalhub/entities/artifact/artifact/spec.py +3 -1
- digitalhub/entities/artifact/crud.py +13 -12
- digitalhub/entities/artifact/utils.py +1 -1
- digitalhub/entities/builders.py +6 -18
- digitalhub/entities/dataitem/_base/entity.py +0 -41
- digitalhub/entities/dataitem/crud.py +27 -15
- digitalhub/entities/dataitem/table/entity.py +49 -35
- digitalhub/entities/dataitem/table/models.py +4 -3
- digitalhub/{utils/data_utils.py → entities/dataitem/table/utils.py} +46 -54
- digitalhub/entities/dataitem/utils.py +58 -10
- digitalhub/entities/function/crud.py +9 -9
- digitalhub/entities/model/_base/entity.py +120 -0
- digitalhub/entities/model/_base/spec.py +6 -17
- digitalhub/entities/model/_base/status.py +10 -0
- digitalhub/entities/model/crud.py +13 -12
- digitalhub/entities/model/huggingface/spec.py +9 -4
- digitalhub/entities/model/mlflow/models.py +2 -2
- digitalhub/entities/model/mlflow/spec.py +7 -7
- digitalhub/entities/model/mlflow/utils.py +44 -5
- digitalhub/entities/project/_base/entity.py +317 -9
- digitalhub/entities/project/_base/spec.py +8 -6
- digitalhub/entities/project/crud.py +12 -11
- digitalhub/entities/run/_base/entity.py +103 -6
- digitalhub/entities/run/_base/spec.py +4 -2
- digitalhub/entities/run/_base/status.py +12 -0
- digitalhub/entities/run/crud.py +8 -8
- digitalhub/entities/secret/_base/entity.py +3 -3
- digitalhub/entities/secret/_base/spec.py +4 -2
- digitalhub/entities/secret/crud.py +11 -9
- digitalhub/entities/task/_base/entity.py +4 -4
- digitalhub/entities/task/_base/models.py +51 -40
- digitalhub/entities/task/_base/spec.py +2 -0
- digitalhub/entities/task/_base/utils.py +2 -2
- digitalhub/entities/task/crud.py +12 -8
- digitalhub/entities/workflow/crud.py +9 -9
- digitalhub/factory/utils.py +9 -9
- digitalhub/readers/{_base → data/_base}/builder.py +1 -1
- digitalhub/readers/{_base → data/_base}/reader.py +16 -4
- digitalhub/readers/{api.py → data/api.py} +2 -2
- digitalhub/readers/{factory.py → data/factory.py} +3 -3
- digitalhub/readers/{pandas → data/pandas}/builder.py +2 -2
- digitalhub/readers/{pandas → data/pandas}/reader.py +110 -30
- digitalhub/readers/query/__init__.py +0 -0
- digitalhub/stores/_base/store.py +59 -69
- digitalhub/stores/api.py +8 -33
- digitalhub/stores/builder.py +44 -161
- digitalhub/stores/local/store.py +106 -89
- digitalhub/stores/remote/store.py +86 -11
- digitalhub/stores/s3/configurator.py +108 -0
- digitalhub/stores/s3/enums.py +17 -0
- digitalhub/stores/s3/models.py +21 -0
- digitalhub/stores/s3/store.py +154 -70
- digitalhub/{utils/s3_utils.py → stores/s3/utils.py} +7 -3
- digitalhub/stores/sql/configurator.py +88 -0
- digitalhub/stores/sql/enums.py +16 -0
- digitalhub/stores/sql/models.py +24 -0
- digitalhub/stores/sql/store.py +106 -85
- digitalhub/{readers/_commons → utils}/enums.py +5 -1
- digitalhub/utils/exceptions.py +6 -0
- digitalhub/utils/file_utils.py +8 -7
- digitalhub/utils/generic_utils.py +28 -15
- digitalhub/utils/git_utils.py +16 -9
- digitalhub/utils/types.py +5 -0
- digitalhub/utils/uri_utils.py +2 -2
- {digitalhub-0.9.1.dist-info → digitalhub-0.10.0.dist-info}/METADATA +25 -31
- {digitalhub-0.9.1.dist-info → digitalhub-0.10.0.dist-info}/RECORD +108 -99
- {digitalhub-0.9.1.dist-info → digitalhub-0.10.0.dist-info}/WHEEL +1 -2
- digitalhub/client/dhcore/env.py +0 -23
- digitalhub/entities/_base/project/entity.py +0 -341
- digitalhub-0.9.1.dist-info/top_level.txt +0 -2
- test/local/CRUD/test_artifacts.py +0 -96
- test/local/CRUD/test_dataitems.py +0 -96
- test/local/CRUD/test_models.py +0 -95
- test/local/imports/test_imports.py +0 -66
- test/local/instances/test_validate.py +0 -55
- test/test_crud_functions.py +0 -109
- test/test_crud_runs.py +0 -86
- test/test_crud_tasks.py +0 -81
- test/testkfp.py +0 -37
- test/testkfp_pipeline.py +0 -22
- /digitalhub/{entities/_base/project → configurator}/__init__.py +0 -0
- /digitalhub/entities/{_operations → _processors}/__init__.py +0 -0
- /digitalhub/readers/{_base → data}/__init__.py +0 -0
- /digitalhub/readers/{_commons → data/_base}/__init__.py +0 -0
- /digitalhub/readers/{pandas → data/pandas}/__init__.py +0 -0
- {digitalhub-0.9.1.dist-info → digitalhub-0.10.0.dist-info/licenses}/LICENSE.txt +0 -0
digitalhub/stores/local/store.py
CHANGED
|
@@ -4,19 +4,11 @@ import shutil
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from digitalhub.readers.api import get_reader_by_object
|
|
8
|
-
from digitalhub.stores._base.store import Store
|
|
7
|
+
from digitalhub.readers.data.api import get_reader_by_object
|
|
8
|
+
from digitalhub.stores._base.store import Store
|
|
9
9
|
from digitalhub.utils.exceptions import StoreError
|
|
10
10
|
from digitalhub.utils.file_utils import get_file_info_from_local
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class LocalStoreConfig(StoreConfig):
|
|
14
|
-
"""
|
|
15
|
-
Local store configuration class.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
path: str
|
|
19
|
-
"""Local path."""
|
|
11
|
+
from digitalhub.utils.types import SourcesOrListOfSources
|
|
20
12
|
|
|
21
13
|
|
|
22
14
|
class LocalStore(Store):
|
|
@@ -25,9 +17,8 @@ class LocalStore(Store):
|
|
|
25
17
|
artifacts on local filesystem based storage.
|
|
26
18
|
"""
|
|
27
19
|
|
|
28
|
-
def __init__(self,
|
|
29
|
-
super().__init__(
|
|
30
|
-
self.config = config
|
|
20
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
21
|
+
super().__init__()
|
|
31
22
|
|
|
32
23
|
##############################
|
|
33
24
|
# I/O methods
|
|
@@ -61,7 +52,7 @@ class LocalStore(Store):
|
|
|
61
52
|
"""
|
|
62
53
|
raise StoreError("Local store does not support download.")
|
|
63
54
|
|
|
64
|
-
def upload(self, src:
|
|
55
|
+
def upload(self, src: SourcesOrListOfSources, dst: str) -> list[tuple[str, str]]:
|
|
65
56
|
"""
|
|
66
57
|
Upload an artifact to storage.
|
|
67
58
|
|
|
@@ -92,6 +83,106 @@ class LocalStore(Store):
|
|
|
92
83
|
"""
|
|
93
84
|
return [get_file_info_from_local(p) for p in paths]
|
|
94
85
|
|
|
86
|
+
##############################
|
|
87
|
+
# Datastore methods
|
|
88
|
+
##############################
|
|
89
|
+
|
|
90
|
+
def read_df(
|
|
91
|
+
self,
|
|
92
|
+
path: SourcesOrListOfSources,
|
|
93
|
+
file_format: str | None = None,
|
|
94
|
+
engine: str | None = None,
|
|
95
|
+
**kwargs,
|
|
96
|
+
) -> Any:
|
|
97
|
+
"""
|
|
98
|
+
Read DataFrame from path.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
path : SourcesOrListOfSources
|
|
103
|
+
Path(s) to read DataFrame from.
|
|
104
|
+
file_format : str
|
|
105
|
+
Extension of the file.
|
|
106
|
+
engine : str
|
|
107
|
+
Dataframe engine (pandas, polars, etc.).
|
|
108
|
+
**kwargs : dict
|
|
109
|
+
Keyword arguments.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Any
|
|
114
|
+
DataFrame.
|
|
115
|
+
"""
|
|
116
|
+
reader = self._get_reader(engine)
|
|
117
|
+
|
|
118
|
+
dfs = []
|
|
119
|
+
if isinstance(path, list):
|
|
120
|
+
for p in path:
|
|
121
|
+
file_format = self._get_extension(file_format, p)
|
|
122
|
+
dfs.append(reader.read_df(p, file_format, **kwargs))
|
|
123
|
+
elif Path(path).is_dir():
|
|
124
|
+
import glob
|
|
125
|
+
|
|
126
|
+
paths = glob.glob(f"{path}/*")
|
|
127
|
+
for p in paths:
|
|
128
|
+
file_format = self._get_extension(file_format, p)
|
|
129
|
+
dfs.append(reader.read_df(p, file_format, **kwargs))
|
|
130
|
+
else:
|
|
131
|
+
file_format = self._get_extension(file_format, path)
|
|
132
|
+
dfs.append(reader.read_df(path, file_format, **kwargs))
|
|
133
|
+
|
|
134
|
+
if len(dfs) == 1:
|
|
135
|
+
return dfs[0]
|
|
136
|
+
|
|
137
|
+
return reader.concat_dfs(dfs)
|
|
138
|
+
|
|
139
|
+
def query(
|
|
140
|
+
self,
|
|
141
|
+
query: str,
|
|
142
|
+
engine: str | None = None,
|
|
143
|
+
) -> Any:
|
|
144
|
+
"""
|
|
145
|
+
Query data from database.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
query : str
|
|
150
|
+
The query to execute.
|
|
151
|
+
engine : str
|
|
152
|
+
Dataframe engine (pandas, polars, etc.).
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
Any
|
|
157
|
+
DataFrame.
|
|
158
|
+
"""
|
|
159
|
+
raise StoreError("Local store does not support query.")
|
|
160
|
+
|
|
161
|
+
def write_df(self, df: Any, dst: str, extension: str | None = None, **kwargs) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Method to write a dataframe to a file. Kwargs are passed to df.to_parquet().
|
|
164
|
+
If destination is not provided, the dataframe is written to the default
|
|
165
|
+
store path with generated name.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
df : Any
|
|
170
|
+
The dataframe to write.
|
|
171
|
+
dst : str
|
|
172
|
+
The destination of the dataframe.
|
|
173
|
+
**kwargs : dict
|
|
174
|
+
Keyword arguments.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
str
|
|
179
|
+
Path of written dataframe.
|
|
180
|
+
"""
|
|
181
|
+
self._check_local_dst(dst)
|
|
182
|
+
reader = get_reader_by_object(df)
|
|
183
|
+
reader.write_df(df, dst, extension=extension, **kwargs)
|
|
184
|
+
return dst
|
|
185
|
+
|
|
95
186
|
##############################
|
|
96
187
|
# Private I/O methods
|
|
97
188
|
##############################
|
|
@@ -193,77 +284,3 @@ class LocalStore(Store):
|
|
|
193
284
|
dst = dst / src
|
|
194
285
|
self._build_path(dst)
|
|
195
286
|
return dst
|
|
196
|
-
|
|
197
|
-
##############################
|
|
198
|
-
# Datastore methods
|
|
199
|
-
##############################
|
|
200
|
-
|
|
201
|
-
def write_df(self, df: Any, dst: str, extension: str | None = None, **kwargs) -> str:
|
|
202
|
-
"""
|
|
203
|
-
Method to write a dataframe to a file. Kwargs are passed to df.to_parquet().
|
|
204
|
-
If destination is not provided, the dataframe is written to the default
|
|
205
|
-
store path with generated name.
|
|
206
|
-
|
|
207
|
-
Parameters
|
|
208
|
-
----------
|
|
209
|
-
df : Any
|
|
210
|
-
The dataframe to write.
|
|
211
|
-
dst : str
|
|
212
|
-
The destination of the dataframe.
|
|
213
|
-
**kwargs : dict
|
|
214
|
-
Keyword arguments.
|
|
215
|
-
|
|
216
|
-
Returns
|
|
217
|
-
-------
|
|
218
|
-
str
|
|
219
|
-
Path of written dataframe.
|
|
220
|
-
"""
|
|
221
|
-
self.store._check_local_dst(dst)
|
|
222
|
-
self._validate_extension(Path(dst).suffix.removeprefix("."))
|
|
223
|
-
|
|
224
|
-
# Write dataframe
|
|
225
|
-
reader = get_reader_by_object(df)
|
|
226
|
-
reader.write_df(df, dst, extension=extension, **kwargs)
|
|
227
|
-
|
|
228
|
-
return dst
|
|
229
|
-
|
|
230
|
-
##############################
|
|
231
|
-
# Helper methods
|
|
232
|
-
##############################
|
|
233
|
-
|
|
234
|
-
@staticmethod
|
|
235
|
-
def is_partition_or_dir(path: str) -> bool:
|
|
236
|
-
"""
|
|
237
|
-
Check if path is a directory or a partition.
|
|
238
|
-
|
|
239
|
-
Parameters
|
|
240
|
-
----------
|
|
241
|
-
path : str
|
|
242
|
-
The path to check.
|
|
243
|
-
|
|
244
|
-
Returns
|
|
245
|
-
-------
|
|
246
|
-
bool
|
|
247
|
-
"""
|
|
248
|
-
return Path(path).is_dir()
|
|
249
|
-
|
|
250
|
-
@staticmethod
|
|
251
|
-
def build_object_path(root: str, paths: str | list[str]) -> list[str]:
|
|
252
|
-
"""
|
|
253
|
-
Method to build object path.
|
|
254
|
-
|
|
255
|
-
Parameters
|
|
256
|
-
----------
|
|
257
|
-
root : str
|
|
258
|
-
The root of the object path.
|
|
259
|
-
paths : str | list[str]
|
|
260
|
-
The path to build.
|
|
261
|
-
|
|
262
|
-
Returns
|
|
263
|
-
-------
|
|
264
|
-
list[str]
|
|
265
|
-
Returns the path of the object.
|
|
266
|
-
"""
|
|
267
|
-
if isinstance(paths, str):
|
|
268
|
-
paths = [paths]
|
|
269
|
-
return [str(Path(root) / path) for path in paths]
|
|
@@ -5,14 +5,9 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
-
from digitalhub.stores._base.store import Store
|
|
8
|
+
from digitalhub.stores._base.store import Store
|
|
9
9
|
from digitalhub.utils.exceptions import StoreError
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class RemoteStoreConfig(StoreConfig):
|
|
13
|
-
"""
|
|
14
|
-
Remote store configuration class.
|
|
15
|
-
"""
|
|
10
|
+
from digitalhub.utils.types import SourcesOrListOfSources
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
class RemoteStore(Store):
|
|
@@ -21,9 +16,8 @@ class RemoteStore(Store):
|
|
|
21
16
|
artifacts from remote HTTP based storage.
|
|
22
17
|
"""
|
|
23
18
|
|
|
24
|
-
def __init__(self,
|
|
25
|
-
super().__init__(
|
|
26
|
-
self.config = config
|
|
19
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
20
|
+
super().__init__()
|
|
27
21
|
|
|
28
22
|
##############################
|
|
29
23
|
# I/O methods
|
|
@@ -69,7 +63,7 @@ class RemoteStore(Store):
|
|
|
69
63
|
|
|
70
64
|
return self._download_file(root, dst, overwrite)
|
|
71
65
|
|
|
72
|
-
def upload(self, src:
|
|
66
|
+
def upload(self, src: SourcesOrListOfSources, dst: str) -> list[tuple[str, str]]:
|
|
73
67
|
"""
|
|
74
68
|
Upload an artifact to storage.
|
|
75
69
|
|
|
@@ -104,6 +98,58 @@ class RemoteStore(Store):
|
|
|
104
98
|
# Datastore methods
|
|
105
99
|
##############################
|
|
106
100
|
|
|
101
|
+
def read_df(
|
|
102
|
+
self,
|
|
103
|
+
path: SourcesOrListOfSources,
|
|
104
|
+
file_format: str | None = None,
|
|
105
|
+
engine: str | None = None,
|
|
106
|
+
**kwargs,
|
|
107
|
+
) -> Any:
|
|
108
|
+
"""
|
|
109
|
+
Read DataFrame from path.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
path : SourcesOrListOfSources
|
|
114
|
+
Path(s) to read DataFrame from.
|
|
115
|
+
file_format : str
|
|
116
|
+
Extension of the file.
|
|
117
|
+
engine : str
|
|
118
|
+
Dataframe engine (pandas, polars, etc.).
|
|
119
|
+
**kwargs : dict
|
|
120
|
+
Keyword arguments.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
Any
|
|
125
|
+
DataFrame.
|
|
126
|
+
"""
|
|
127
|
+
reader = self._get_reader(engine)
|
|
128
|
+
extension = self._head_extension(path, file_format)
|
|
129
|
+
return reader.read_df(path, extension, **kwargs)
|
|
130
|
+
|
|
131
|
+
def query(
|
|
132
|
+
self,
|
|
133
|
+
query: str,
|
|
134
|
+
engine: str | None = None,
|
|
135
|
+
) -> Any:
|
|
136
|
+
"""
|
|
137
|
+
Query data from database.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
query : str
|
|
142
|
+
The query to execute.
|
|
143
|
+
engine : str
|
|
144
|
+
Dataframe engine (pandas, polars, etc.).
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
Any
|
|
149
|
+
DataFrame.
|
|
150
|
+
"""
|
|
151
|
+
raise StoreError("Remote store does not support query.")
|
|
152
|
+
|
|
107
153
|
def write_df(self, df: Any, dst: str, extension: str | None = None, **kwargs) -> str:
|
|
108
154
|
"""
|
|
109
155
|
Method to write a dataframe to a file. Note that this method is not implemented
|
|
@@ -167,3 +213,32 @@ class RemoteStore(Store):
|
|
|
167
213
|
for chunk in r.iter_content(chunk_size=8192):
|
|
168
214
|
f.write(chunk)
|
|
169
215
|
return str(dst)
|
|
216
|
+
|
|
217
|
+
def _head_extension(self, url: str, file_format: str | None = None) -> str:
|
|
218
|
+
"""
|
|
219
|
+
Method to get the extension of a file from a given url.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
url : str
|
|
224
|
+
The url of the file to get the extension.
|
|
225
|
+
file_format : str
|
|
226
|
+
The file format to check.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
str
|
|
231
|
+
File extension.
|
|
232
|
+
"""
|
|
233
|
+
if file_format is not None:
|
|
234
|
+
return file_format
|
|
235
|
+
try:
|
|
236
|
+
r = requests.head(url, timeout=60)
|
|
237
|
+
r.raise_for_status()
|
|
238
|
+
content_type = r.headers["content-type"]
|
|
239
|
+
if "text" in content_type:
|
|
240
|
+
return "csv"
|
|
241
|
+
else:
|
|
242
|
+
raise ValueError("Content type not supported.")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
raise e
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from botocore.config import Config
|
|
4
|
+
|
|
5
|
+
from digitalhub.configurator.configurator import configurator
|
|
6
|
+
from digitalhub.stores.s3.enums import S3StoreEnv
|
|
7
|
+
from digitalhub.stores.s3.models import S3StoreConfig
|
|
8
|
+
from digitalhub.utils.exceptions import StoreError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class S3StoreConfigurator:
|
|
12
|
+
"""
|
|
13
|
+
Configure the store by getting the credentials from user
|
|
14
|
+
provided config or from environment.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
18
|
+
self.configure(config)
|
|
19
|
+
|
|
20
|
+
##############################
|
|
21
|
+
# Configuration methods
|
|
22
|
+
##############################
|
|
23
|
+
|
|
24
|
+
def configure(self, config: dict | None = None) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Configure the store by getting the credentials from user
|
|
27
|
+
provided config or from environment.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
config : dict
|
|
32
|
+
Configuration dictionary.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
None
|
|
37
|
+
"""
|
|
38
|
+
# Validate config
|
|
39
|
+
if config is None:
|
|
40
|
+
self._get_config()
|
|
41
|
+
else:
|
|
42
|
+
config = S3StoreConfig(**config)
|
|
43
|
+
for pair in [
|
|
44
|
+
(S3StoreEnv.ENDPOINT_URL.value, config.endpoint),
|
|
45
|
+
(S3StoreEnv.ACCESS_KEY_ID.value, config.access_key),
|
|
46
|
+
(S3StoreEnv.SECRET_ACCESS_KEY.value, config.secret_key),
|
|
47
|
+
(S3StoreEnv.SESSION_TOKEN.value, config.session_token),
|
|
48
|
+
(S3StoreEnv.BUCKET_NAME.value, config.bucket_name),
|
|
49
|
+
(S3StoreEnv.REGION.value, config.region),
|
|
50
|
+
(S3StoreEnv.SIGNATURE_VERSION.value, config.signature_version),
|
|
51
|
+
]:
|
|
52
|
+
configurator.set_credential(*pair)
|
|
53
|
+
|
|
54
|
+
def get_boto3_client_config(self) -> dict:
|
|
55
|
+
"""
|
|
56
|
+
Get S3 credentials (access key, secret key,
|
|
57
|
+
session token and other config).
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
dict
|
|
62
|
+
The credentials.
|
|
63
|
+
"""
|
|
64
|
+
creds = configurator.get_all_credentials()
|
|
65
|
+
try:
|
|
66
|
+
return {
|
|
67
|
+
"endpoint_url": creds[S3StoreEnv.ENDPOINT_URL.value],
|
|
68
|
+
"aws_access_key_id": creds[S3StoreEnv.ACCESS_KEY_ID.value],
|
|
69
|
+
"aws_secret_access_key": creds[S3StoreEnv.SECRET_ACCESS_KEY.value],
|
|
70
|
+
"aws_session_token": creds[S3StoreEnv.SESSION_TOKEN.value],
|
|
71
|
+
"config": Config(
|
|
72
|
+
region_name=creds[S3StoreEnv.REGION.value],
|
|
73
|
+
signature_version=creds[S3StoreEnv.SIGNATURE_VERSION.value],
|
|
74
|
+
),
|
|
75
|
+
}
|
|
76
|
+
except KeyError as e:
|
|
77
|
+
raise StoreError(f"Missing credentials for S3 store. {str(e)}")
|
|
78
|
+
|
|
79
|
+
def _get_config(self) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Get the store configuration.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
None
|
|
86
|
+
"""
|
|
87
|
+
required_vars = [
|
|
88
|
+
S3StoreEnv.ENDPOINT_URL,
|
|
89
|
+
S3StoreEnv.ACCESS_KEY_ID,
|
|
90
|
+
S3StoreEnv.SECRET_ACCESS_KEY,
|
|
91
|
+
S3StoreEnv.BUCKET_NAME,
|
|
92
|
+
]
|
|
93
|
+
optional_vars = [S3StoreEnv.REGION, S3StoreEnv.SIGNATURE_VERSION, S3StoreEnv.SESSION_TOKEN]
|
|
94
|
+
|
|
95
|
+
# Load required environment variables
|
|
96
|
+
credentials = {var.value: configurator.load_var(var.value) for var in required_vars}
|
|
97
|
+
|
|
98
|
+
# Check for missing required credentials
|
|
99
|
+
missing_vars = [key for key, value in credentials.items() if value is None]
|
|
100
|
+
if missing_vars:
|
|
101
|
+
raise StoreError(f"Missing credentials for S3 store: {', '.join(missing_vars)}")
|
|
102
|
+
|
|
103
|
+
# Load optional environment variables
|
|
104
|
+
credentials.update({var.value: configurator.load_var(var.value) for var in optional_vars})
|
|
105
|
+
|
|
106
|
+
# Set credentials
|
|
107
|
+
for key, value in credentials.items():
|
|
108
|
+
configurator.set_credential(key, value)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class S3StoreEnv(Enum):
|
|
7
|
+
"""
|
|
8
|
+
S3Store environment
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
ENDPOINT_URL = "S3_ENDPOINT_URL"
|
|
12
|
+
ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
|
|
13
|
+
SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
|
|
14
|
+
SESSION_TOKEN = "AWS_SESSION_TOKEN"
|
|
15
|
+
BUCKET_NAME = "S3_BUCKET"
|
|
16
|
+
REGION = "S3_REGION"
|
|
17
|
+
SIGNATURE_VERSION = "S3_SIGNATURE_VERSION"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class S3StoreConfig(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
S3 store configuration class.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
endpoint_url: str
|
|
12
|
+
"""S3 endpoint URL."""
|
|
13
|
+
|
|
14
|
+
aws_access_key_id: str
|
|
15
|
+
"""AWS access key ID."""
|
|
16
|
+
|
|
17
|
+
aws_secret_access_key: str
|
|
18
|
+
"""AWS secret access key."""
|
|
19
|
+
|
|
20
|
+
bucket_name: str
|
|
21
|
+
"""S3 bucket name."""
|