digitalhub 0.14.0b7__py3-none-any.whl → 0.14.1b0__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 -2
- digitalhub/context/api.py +42 -1
- digitalhub/context/context.py +3 -6
- digitalhub/entities/_base/context/entity.py +0 -3
- digitalhub/entities/_base/material/entity.py +2 -2
- digitalhub/entities/_processors/base/crud.py +14 -23
- digitalhub/entities/_processors/base/import_export.py +0 -5
- digitalhub/entities/_processors/base/processor.py +1 -4
- digitalhub/entities/_processors/base/special_ops.py +4 -8
- digitalhub/entities/_processors/context/crud.py +5 -5
- digitalhub/entities/_processors/context/import_export.py +5 -5
- digitalhub/entities/_processors/context/material.py +2 -2
- digitalhub/entities/_processors/context/special_ops.py +13 -13
- digitalhub/entities/_processors/utils.py +2 -111
- digitalhub/entities/function/_base/entity.py +0 -3
- digitalhub/entities/project/_base/builder.py +0 -6
- digitalhub/entities/project/_base/entity.py +4 -12
- digitalhub/entities/project/_base/spec.py +4 -4
- digitalhub/entities/project/crud.py +9 -44
- digitalhub/entities/project/utils.py +7 -3
- digitalhub/entities/workflow/_base/entity.py +0 -5
- digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
- digitalhub/stores/client/builder.py +20 -32
- digitalhub/stores/client/{dhcore/client.py → client.py} +64 -23
- digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +122 -176
- digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
- digitalhub/stores/client/{dhcore/http_handler.py → http_handler.py} +4 -5
- digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +13 -13
- digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
- digitalhub/stores/client/{dhcore/response_processor.py → response_processor.py} +1 -1
- digitalhub/stores/client/{dhcore/utils.py → utils.py} +2 -7
- digitalhub/stores/{credentials → configurator}/api.py +5 -5
- digitalhub/stores/configurator/configurator.py +123 -0
- digitalhub/stores/{credentials → configurator}/enums.py +25 -10
- digitalhub/stores/configurator/handler.py +213 -0
- digitalhub/stores/{credentials → configurator}/ini_module.py +31 -0
- digitalhub/stores/data/_base/store.py +0 -4
- digitalhub/stores/data/api.py +2 -4
- digitalhub/stores/data/builder.py +5 -37
- 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 +20 -55
- {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/METADATA +1 -1
- {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/RECORD +51 -66
- 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/enums.py +0 -18
- digitalhub/stores/client/dhcore/key_builder.py +0 -62
- 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/client/{dhcore/error_parser.py → error_parser.py} +0 -0
- /digitalhub/stores/client/{dhcore/header_manager.py → header_manager.py} +0 -0
- /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
- {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/WHEEL +0 -0
- {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/licenses/AUTHORS +0 -0
- {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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.
|
|
@@ -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
|
|
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:
|
|
@@ -4,81 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from digitalhub.stores.
|
|
8
|
-
from digitalhub.stores.
|
|
7
|
+
from digitalhub.stores.configurator.configurator import configurator
|
|
8
|
+
from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class SqlStoreConfigurator
|
|
11
|
+
class SqlStoreConfigurator:
|
|
12
12
|
"""
|
|
13
|
-
SQL store configuration
|
|
14
|
-
|
|
15
|
-
Handles credential management and configuration for SQL database
|
|
16
|
-
connections. Loads credentials from environment variables or
|
|
17
|
-
configuration files and provides connection string generation
|
|
18
|
-
for database access.
|
|
19
|
-
|
|
20
|
-
Attributes
|
|
21
|
-
----------
|
|
22
|
-
keys : list[str]
|
|
23
|
-
List of all supported credential keys for SQL connections.
|
|
24
|
-
required_keys : list[str]
|
|
25
|
-
List of mandatory credential keys that must be provided.
|
|
13
|
+
Configurator class for SQL store configuration and credentials management.
|
|
26
14
|
"""
|
|
27
15
|
|
|
28
|
-
keys = [
|
|
29
|
-
CredsEnvVar.DB_USERNAME.value,
|
|
30
|
-
CredsEnvVar.DB_PASSWORD.value,
|
|
31
|
-
CredsEnvVar.DB_HOST.value,
|
|
32
|
-
CredsEnvVar.DB_PORT.value,
|
|
33
|
-
CredsEnvVar.DB_DATABASE.value,
|
|
34
|
-
CredsEnvVar.DB_PLATFORM.value,
|
|
35
|
-
]
|
|
36
|
-
required_keys = [
|
|
37
|
-
CredsEnvVar.DB_USERNAME.value,
|
|
38
|
-
CredsEnvVar.DB_PASSWORD.value,
|
|
39
|
-
CredsEnvVar.DB_HOST.value,
|
|
40
|
-
CredsEnvVar.DB_PORT.value,
|
|
41
|
-
CredsEnvVar.DB_DATABASE.value,
|
|
42
|
-
]
|
|
43
|
-
|
|
44
16
|
def __init__(self):
|
|
45
|
-
|
|
46
|
-
self.load_configs()
|
|
47
|
-
|
|
48
|
-
##############################
|
|
49
|
-
# Configuration methods
|
|
50
|
-
##############################
|
|
51
|
-
|
|
52
|
-
def load_env_vars(self) -> None:
|
|
53
|
-
"""
|
|
54
|
-
Load database credentials from environment variables.
|
|
55
|
-
|
|
56
|
-
Retrieves SQL database connection credentials from the system
|
|
57
|
-
environment variables and stores them in the configurator's
|
|
58
|
-
credential handler for use in database connections.
|
|
59
|
-
"""
|
|
60
|
-
env_creds = self._creds_handler.load_from_env(self.keys)
|
|
61
|
-
self._creds_handler.set_credentials(self._env, env_creds)
|
|
62
|
-
|
|
63
|
-
def load_file_vars(self) -> None:
|
|
64
|
-
"""
|
|
65
|
-
Load database credentials from configuration file.
|
|
66
|
-
|
|
67
|
-
Retrieves SQL database connection credentials from a
|
|
68
|
-
configuration file and stores them in the configurator's
|
|
69
|
-
credential handler for use in database connections.
|
|
70
|
-
"""
|
|
71
|
-
file_creds = self._creds_handler.load_from_file(self.keys)
|
|
72
|
-
self._creds_handler.set_credentials(self._file, file_creds)
|
|
17
|
+
self._validate()
|
|
73
18
|
|
|
74
19
|
def get_sql_conn_string(self) -> str:
|
|
75
20
|
"""
|
|
76
21
|
Generate PostgreSQL connection string from stored credentials.
|
|
77
22
|
|
|
78
|
-
Constructs a PostgreSQL connection string using the configured
|
|
79
|
-
database credentials including username, password, host, port,
|
|
80
|
-
and database name.
|
|
81
|
-
|
|
82
23
|
Returns
|
|
83
24
|
-------
|
|
84
25
|
str
|
|
@@ -86,11 +27,11 @@ class SqlStoreConfigurator(Configurator):
|
|
|
86
27
|
'postgresql://username:password@host:port/database'
|
|
87
28
|
"""
|
|
88
29
|
creds = self.get_sql_credentials()
|
|
89
|
-
user = creds[
|
|
90
|
-
password = creds[
|
|
91
|
-
host = creds[
|
|
92
|
-
port = creds[
|
|
93
|
-
database = creds[
|
|
30
|
+
user = creds[CredentialsVars.DB_USERNAME.value]
|
|
31
|
+
password = creds[CredentialsVars.DB_PASSWORD.value]
|
|
32
|
+
host = creds[ConfigurationVars.DB_HOST.value]
|
|
33
|
+
port = creds[ConfigurationVars.DB_PORT.value]
|
|
34
|
+
database = creds[ConfigurationVars.DB_DATABASE.value]
|
|
94
35
|
return f"postgresql://{user}:{password}@{host}:{port}/{database}"
|
|
95
36
|
|
|
96
37
|
def get_sql_credentials(self) -> dict:
|
|
@@ -108,5 +49,42 @@ class SqlStoreConfigurator(Configurator):
|
|
|
108
49
|
Keys correspond to database connection parameters such as
|
|
109
50
|
username, password, host, port, database, and platform.
|
|
110
51
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
52
|
+
keys = [
|
|
53
|
+
CredentialsVars.DB_USERNAME.value,
|
|
54
|
+
CredentialsVars.DB_PASSWORD.value,
|
|
55
|
+
ConfigurationVars.DB_HOST.value,
|
|
56
|
+
ConfigurationVars.DB_PORT.value,
|
|
57
|
+
ConfigurationVars.DB_DATABASE.value,
|
|
58
|
+
]
|
|
59
|
+
creds = configurator.get_config_creds()
|
|
60
|
+
return {key: creds.get(key) for key in keys}
|
|
61
|
+
|
|
62
|
+
def eval_retry(self) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Evaluate the status of retry lifecycle.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
bool
|
|
69
|
+
True if a retry should be attempted, False otherwise.
|
|
70
|
+
"""
|
|
71
|
+
return configurator.eval_retry()
|
|
72
|
+
|
|
73
|
+
def _validate(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Validate if all required keys are present in the configuration.
|
|
76
|
+
"""
|
|
77
|
+
required_keys = [
|
|
78
|
+
ConfigurationVars.DB_HOST.value,
|
|
79
|
+
ConfigurationVars.DB_PORT.value,
|
|
80
|
+
ConfigurationVars.DB_DATABASE.value,
|
|
81
|
+
CredentialsVars.DB_USERNAME.value,
|
|
82
|
+
CredentialsVars.DB_PASSWORD.value,
|
|
83
|
+
]
|
|
84
|
+
current_keys = configurator.get_config_creds()
|
|
85
|
+
missing_keys = []
|
|
86
|
+
for key in required_keys:
|
|
87
|
+
if key not in current_keys or current_keys[key] is None:
|
|
88
|
+
missing_keys.append(key)
|
|
89
|
+
if missing_keys:
|
|
90
|
+
raise ValueError(f"Missing required variables for SQL store: {', '.join(missing_keys)}")
|
|
@@ -15,6 +15,7 @@ from sqlalchemy.engine import Engine
|
|
|
15
15
|
from sqlalchemy.exc import SQLAlchemyError
|
|
16
16
|
|
|
17
17
|
from digitalhub.stores.data._base.store import Store
|
|
18
|
+
from digitalhub.stores.data.sql.configurator import SqlStoreConfigurator
|
|
18
19
|
from digitalhub.stores.readers.data.api import get_reader_by_object
|
|
19
20
|
from digitalhub.utils.exceptions import ConfigError, StoreError
|
|
20
21
|
from digitalhub.utils.types import SourcesOrListOfSources
|
|
@@ -22,8 +23,8 @@ from digitalhub.utils.types import SourcesOrListOfSources
|
|
|
22
23
|
if typing.TYPE_CHECKING:
|
|
23
24
|
from sqlalchemy.engine.row import Row
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
ENGINE_CONNECTION_TIMEOUT = 30
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class SqlStore(Store):
|
|
@@ -41,9 +42,9 @@ class SqlStore(Store):
|
|
|
41
42
|
and connection parameters.
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
|
-
def __init__(self
|
|
45
|
-
super().__init__(
|
|
46
|
-
self._configurator: SqlStoreConfigurator
|
|
45
|
+
def __init__(self) -> None:
|
|
46
|
+
super().__init__()
|
|
47
|
+
self._configurator: SqlStoreConfigurator = SqlStoreConfigurator()
|
|
47
48
|
|
|
48
49
|
##############################
|
|
49
50
|
# I/O methods
|
|
@@ -70,7 +71,7 @@ class SqlStore(Store):
|
|
|
70
71
|
dst : Path
|
|
71
72
|
The destination path on the local filesystem where the
|
|
72
73
|
Parquet file will be saved.
|
|
73
|
-
overwrite : bool
|
|
74
|
+
overwrite : bool
|
|
74
75
|
Whether to overwrite existing files at the destination path.
|
|
75
76
|
|
|
76
77
|
Returns
|
|
@@ -350,22 +351,7 @@ class SqlStore(Store):
|
|
|
350
351
|
# Helper methods
|
|
351
352
|
##############################
|
|
352
353
|
|
|
353
|
-
def
|
|
354
|
-
"""
|
|
355
|
-
Retrieve the database connection string from the configurator.
|
|
356
|
-
|
|
357
|
-
Gets the PostgreSQL connection string using the configured
|
|
358
|
-
database credentials (username, password, host, port, database).
|
|
359
|
-
|
|
360
|
-
Returns
|
|
361
|
-
-------
|
|
362
|
-
str
|
|
363
|
-
The PostgreSQL connection string in the format
|
|
364
|
-
'postgresql://username:password@host:port/database'.
|
|
365
|
-
"""
|
|
366
|
-
return self._configurator.get_sql_conn_string()
|
|
367
|
-
|
|
368
|
-
def _get_engine(self, schema: str | None = None) -> Engine:
|
|
354
|
+
def _get_engine(self, connection_string: str, schema: str | None = None) -> Engine:
|
|
369
355
|
"""
|
|
370
356
|
Create a SQLAlchemy engine from the connection string.
|
|
371
357
|
|
|
@@ -374,6 +360,8 @@ class SqlStore(Store):
|
|
|
374
360
|
|
|
375
361
|
Parameters
|
|
376
362
|
----------
|
|
363
|
+
connection_string : str
|
|
364
|
+
The database connection string.
|
|
377
365
|
schema : str
|
|
378
366
|
The database schema to set in the search path.
|
|
379
367
|
If provided, sets the PostgreSQL search_path option.
|
|
@@ -382,36 +370,18 @@ class SqlStore(Store):
|
|
|
382
370
|
-------
|
|
383
371
|
Engine
|
|
384
372
|
A configured SQLAlchemy engine instance.
|
|
385
|
-
|
|
386
|
-
Raises
|
|
387
|
-
------
|
|
388
|
-
StoreError
|
|
389
|
-
If the connection string is invalid or engine creation fails.
|
|
390
373
|
"""
|
|
391
|
-
|
|
392
|
-
if not
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
connect_args["options"] = f"-csearch_path={schema}"
|
|
398
|
-
return create_engine(connection_string, connect_args=connect_args)
|
|
399
|
-
except Exception as ex:
|
|
400
|
-
raise StoreError(f"Something wrong with connection string. Arguments: {str(ex.args)}")
|
|
401
|
-
|
|
402
|
-
def _check_factory(self, retry: bool = True, schema: str | None = None) -> Engine:
|
|
374
|
+
connect_args = {"connect_timeout": ENGINE_CONNECTION_TIMEOUT}
|
|
375
|
+
if schema is not None:
|
|
376
|
+
connect_args["options"] = f"-csearch_path={schema}"
|
|
377
|
+
return create_engine(connection_string, connect_args=connect_args)
|
|
378
|
+
|
|
379
|
+
def _check_factory(self, schema: str | None = None) -> Engine:
|
|
403
380
|
"""
|
|
404
381
|
Validate database accessibility and return a working engine.
|
|
405
382
|
|
|
406
|
-
Creates and tests a database engine, with retry capability if
|
|
407
|
-
the initial connection fails. Handles configuration changes
|
|
408
|
-
and ensures the database is accessible before returning.
|
|
409
|
-
|
|
410
383
|
Parameters
|
|
411
384
|
----------
|
|
412
|
-
retry : bool, default True
|
|
413
|
-
Whether to attempt a retry with different configuration
|
|
414
|
-
if the initial connection fails.
|
|
415
385
|
schema : str
|
|
416
386
|
The database schema to configure in the engine.
|
|
417
387
|
|
|
@@ -419,20 +389,15 @@ class SqlStore(Store):
|
|
|
419
389
|
-------
|
|
420
390
|
Engine
|
|
421
391
|
A validated SQLAlchemy engine with confirmed database access.
|
|
422
|
-
|
|
423
|
-
Raises
|
|
424
|
-
------
|
|
425
|
-
ConfigError
|
|
426
|
-
If database access fails and retry is exhausted or disabled.
|
|
427
392
|
"""
|
|
393
|
+
connection_string = self._configurator.get_sql_conn_string()
|
|
394
|
+
engine = self._get_engine(connection_string, schema)
|
|
428
395
|
try:
|
|
429
|
-
engine = self._get_engine(schema)
|
|
430
396
|
self._check_access_to_storage(engine)
|
|
431
397
|
return engine
|
|
432
398
|
except ConfigError as e:
|
|
433
|
-
if
|
|
434
|
-
self.
|
|
435
|
-
return self._check_factory(retry=False, schema=schema)
|
|
399
|
+
if self._configurator.eval_retry():
|
|
400
|
+
return self._check_factory(schema=schema)
|
|
436
401
|
raise e
|
|
437
402
|
|
|
438
403
|
@staticmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digitalhub
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.1b0
|
|
4
4
|
Summary: Python SDK for Digitalhub
|
|
5
5
|
Project-URL: Homepage, https://github.com/scc-digitalhub/digitalhub-sdk
|
|
6
6
|
Author-email: Fondazione Bruno Kessler <digitalhub@fbk.eu>, Matteo Martini <mmartini@fbk.eu>
|