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.

Files changed (68) hide show
  1. digitalhub/__init__.py +2 -2
  2. digitalhub/context/api.py +42 -1
  3. digitalhub/context/context.py +3 -6
  4. digitalhub/entities/_base/context/entity.py +0 -3
  5. digitalhub/entities/_base/material/entity.py +2 -2
  6. digitalhub/entities/_processors/base/crud.py +14 -23
  7. digitalhub/entities/_processors/base/import_export.py +0 -5
  8. digitalhub/entities/_processors/base/processor.py +1 -4
  9. digitalhub/entities/_processors/base/special_ops.py +4 -8
  10. digitalhub/entities/_processors/context/crud.py +5 -5
  11. digitalhub/entities/_processors/context/import_export.py +5 -5
  12. digitalhub/entities/_processors/context/material.py +2 -2
  13. digitalhub/entities/_processors/context/special_ops.py +13 -13
  14. digitalhub/entities/_processors/utils.py +2 -111
  15. digitalhub/entities/function/_base/entity.py +0 -3
  16. digitalhub/entities/project/_base/builder.py +0 -6
  17. digitalhub/entities/project/_base/entity.py +4 -12
  18. digitalhub/entities/project/_base/spec.py +4 -4
  19. digitalhub/entities/project/crud.py +9 -44
  20. digitalhub/entities/project/utils.py +7 -3
  21. digitalhub/entities/workflow/_base/entity.py +0 -5
  22. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
  23. digitalhub/stores/client/builder.py +20 -32
  24. digitalhub/stores/client/{dhcore/client.py → client.py} +64 -23
  25. digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +122 -176
  26. digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
  27. digitalhub/stores/client/{dhcore/http_handler.py → http_handler.py} +4 -5
  28. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +13 -13
  29. digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
  30. digitalhub/stores/client/{dhcore/response_processor.py → response_processor.py} +1 -1
  31. digitalhub/stores/client/{dhcore/utils.py → utils.py} +2 -7
  32. digitalhub/stores/{credentials → configurator}/api.py +5 -5
  33. digitalhub/stores/configurator/configurator.py +123 -0
  34. digitalhub/stores/{credentials → configurator}/enums.py +25 -10
  35. digitalhub/stores/configurator/handler.py +213 -0
  36. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -0
  37. digitalhub/stores/data/_base/store.py +0 -4
  38. digitalhub/stores/data/api.py +2 -4
  39. digitalhub/stores/data/builder.py +5 -37
  40. digitalhub/stores/data/s3/configurator.py +30 -114
  41. digitalhub/stores/data/s3/store.py +9 -22
  42. digitalhub/stores/data/sql/configurator.py +49 -71
  43. digitalhub/stores/data/sql/store.py +20 -55
  44. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/METADATA +1 -1
  45. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/RECORD +51 -66
  46. digitalhub/stores/client/_base/api_builder.py +0 -34
  47. digitalhub/stores/client/_base/client.py +0 -243
  48. digitalhub/stores/client/_base/params_builder.py +0 -82
  49. digitalhub/stores/client/api.py +0 -32
  50. digitalhub/stores/client/dhcore/__init__.py +0 -3
  51. digitalhub/stores/client/dhcore/enums.py +0 -18
  52. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  53. digitalhub/stores/client/local/__init__.py +0 -3
  54. digitalhub/stores/client/local/api_builder.py +0 -116
  55. digitalhub/stores/client/local/client.py +0 -605
  56. digitalhub/stores/client/local/enums.py +0 -15
  57. digitalhub/stores/client/local/key_builder.py +0 -62
  58. digitalhub/stores/client/local/params_builder.py +0 -97
  59. digitalhub/stores/credentials/__init__.py +0 -3
  60. digitalhub/stores/credentials/configurator.py +0 -185
  61. digitalhub/stores/credentials/handler.py +0 -164
  62. digitalhub/stores/credentials/store.py +0 -77
  63. /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
  64. /digitalhub/stores/client/{dhcore/header_manager.py → header_manager.py} +0 -0
  65. /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
  66. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/WHEEL +0 -0
  67. {digitalhub-0.14.0b7.dist-info → digitalhub-0.14.1b0.dist-info}/licenses/AUTHORS +0 -0
  68. {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, 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.
@@ -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
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:
@@ -4,81 +4,22 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from digitalhub.stores.credentials.configurator import Configurator
8
- from digitalhub.stores.credentials.enums import CredsEnvVar
7
+ from digitalhub.stores.configurator.configurator import configurator
8
+ from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
9
9
 
10
10
 
11
- class SqlStoreConfigurator(Configurator):
11
+ class SqlStoreConfigurator:
12
12
  """
13
- SQL store configuration manager for database connections.
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
- super().__init__()
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[CredsEnvVar.DB_USERNAME.value]
90
- password = creds[CredsEnvVar.DB_PASSWORD.value]
91
- host = creds[CredsEnvVar.DB_HOST.value]
92
- port = creds[CredsEnvVar.DB_PORT.value]
93
- database = creds[CredsEnvVar.DB_DATABASE.value]
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
- creds = self.get_credentials(self._origin)
112
- return {key: creds.get(key) for key in self.keys}
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
- from digitalhub.stores.credentials.configurator import Configurator
26
- from digitalhub.stores.data.sql.configurator import SqlStoreConfigurator
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, configurator: Configurator | None = None) -> None:
45
- super().__init__(configurator)
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, default False
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 _get_connection_string(self) -> str:
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
- connection_string = self._get_connection_string()
392
- if not isinstance(connection_string, str):
393
- raise StoreError("Connection string must be a string.")
394
- try:
395
- connect_args = {"connect_timeout": 30}
396
- if schema is not None:
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 retry:
434
- self._configurator.eval_change_origin()
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.0b7
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>