maleo-foundation 0.1.35__py3-none-any.whl → 0.1.37__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.
- maleo_foundation/managers/db.py +4 -5
- maleo_foundation/managers/service.py +3 -4
- maleo_foundation/middlewares/authentication.py +2 -2
- maleo_foundation/middlewares/base.py +2 -19
- maleo_foundation/utils/__init__.py +4 -2
- maleo_foundation/utils/exceptions.py +1 -13
- maleo_foundation/utils/extractor.py +16 -14
- maleo_foundation/utils/keyloader.py +52 -50
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.37.dist-info}/METADATA +1 -1
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.37.dist-info}/RECORD +13 -32
- maleo_foundation/clients/__init__.py +0 -7
- maleo_foundation/clients/general/__init__.py +0 -4
- maleo_foundation/clients/general/http.py +0 -50
- maleo_foundation/clients/google/__init__.py +0 -4
- maleo_foundation/clients/google/base.py +0 -32
- maleo_foundation/clients/google/cloud/__init__.py +0 -8
- maleo_foundation/clients/google/cloud/base.py +0 -32
- maleo_foundation/clients/google/cloud/logging.py +0 -63
- maleo_foundation/clients/google/cloud/secret.py +0 -149
- maleo_foundation/clients/google/cloud/storage.py +0 -110
- maleo_foundation/clients/google/secret.py +0 -61
- maleo_foundation/clients/google/storage.py +0 -57
- maleo_foundation/clients/utils/__init__.py +0 -5
- maleo_foundation/clients/utils/logger.py +0 -54
- maleo_foundation/db/__init__.py +0 -4
- maleo_foundation/db/engine.py +0 -76
- maleo_foundation/db/manager.py +0 -122
- maleo_foundation/db/session.py +0 -111
- maleo_foundation/utils/logger.py +0 -92
- /maleo_foundation/{db → models}/table.py +0 -0
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.37.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.37.dist-info}/top_level.txt +0 -0
@@ -1,149 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from google.api_core import retry
|
3
|
-
from google.api_core.exceptions import NotFound
|
4
|
-
from google.auth import default
|
5
|
-
from google.cloud import secretmanager
|
6
|
-
from google.oauth2 import service_account
|
7
|
-
from typing import Optional
|
8
|
-
from .base import GoogleCloudClientManager
|
9
|
-
|
10
|
-
class GoogleSecretManager:
|
11
|
-
_project:Optional[str] = None
|
12
|
-
_client:Optional[secretmanager.SecretManagerServiceClient] = None
|
13
|
-
|
14
|
-
@classmethod
|
15
|
-
def initialize(cls, project_id:Optional[str] = None) -> None:
|
16
|
-
"""Initialize the cloud storage if not already initialized."""
|
17
|
-
cls._project = project_id or os.getenv("GCP_PROJECT_ID")
|
18
|
-
if cls._project is None:
|
19
|
-
raise ValueError("GCP_PROJECT_ID environment variable must be set if no project_id is provided")
|
20
|
-
if cls._client is None:
|
21
|
-
#* Setup credentials with fallback chain
|
22
|
-
credentials = None
|
23
|
-
credentials_file = os.getenv("GOOGLE_CREDENTIALS_PATH")
|
24
|
-
try:
|
25
|
-
if credentials_file:
|
26
|
-
credentials = service_account.Credentials.from_service_account_file(credentials_file)
|
27
|
-
else:
|
28
|
-
credentials, _ = default()
|
29
|
-
except Exception as e:
|
30
|
-
raise ValueError(f"Failed to initialize credentials: {str(e)}")
|
31
|
-
|
32
|
-
cls._client = secretmanager.SecretManagerServiceClient(credentials=credentials)
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def dispose(cls):
|
36
|
-
"""Disposes of the Google Secret Manager client"""
|
37
|
-
if cls._client is not None:
|
38
|
-
cls._client = None
|
39
|
-
if cls._project is not None:
|
40
|
-
cls._project = None
|
41
|
-
|
42
|
-
@classmethod
|
43
|
-
@retry.Retry(
|
44
|
-
predicate=retry.if_exception_type(Exception),
|
45
|
-
timeout=5
|
46
|
-
)
|
47
|
-
def get(
|
48
|
-
cls,
|
49
|
-
name:str,
|
50
|
-
version:str = "latest",
|
51
|
-
) -> Optional[str]:
|
52
|
-
try:
|
53
|
-
secret_path = f"projects/{cls._project}/secrets/{name}/versions/{version}"
|
54
|
-
request = secretmanager.AccessSecretVersionRequest(name=secret_path)
|
55
|
-
response = cls._client.access_secret_version(request=request)
|
56
|
-
return response.payload.data.decode()
|
57
|
-
except Exception as e:
|
58
|
-
return None
|
59
|
-
|
60
|
-
@classmethod
|
61
|
-
@retry.Retry(
|
62
|
-
predicate=retry.if_exception_type(Exception),
|
63
|
-
timeout=5
|
64
|
-
)
|
65
|
-
def create(
|
66
|
-
cls,
|
67
|
-
name:str,
|
68
|
-
data:str
|
69
|
-
) -> Optional[str]:
|
70
|
-
parent = f"projects/{cls._project}"
|
71
|
-
secret_path = f"{parent}/secrets/{name}"
|
72
|
-
try:
|
73
|
-
#* Check if the secret already exists
|
74
|
-
request = secretmanager.GetSecretRequest(name=secret_path)
|
75
|
-
cls._client.get_secret(request=request)
|
76
|
-
|
77
|
-
except NotFound:
|
78
|
-
#* Secret does not exist, create it first
|
79
|
-
try:
|
80
|
-
secret = secretmanager.Secret(name=name, replication={"automatic": {}})
|
81
|
-
request = secretmanager.CreateSecretRequest(parent=parent, secret_id=name, secret=secret)
|
82
|
-
cls._client.create_secret(request=request)
|
83
|
-
except Exception as e:
|
84
|
-
return None
|
85
|
-
|
86
|
-
#* Add a new secret version
|
87
|
-
try:
|
88
|
-
payload = secretmanager.SecretPayload(data=data.encode()) # ✅ Fixed attribute name
|
89
|
-
request = secretmanager.AddSecretVersionRequest(parent=secret_path, payload=payload)
|
90
|
-
response = cls._client.add_secret_version(request=request)
|
91
|
-
return data
|
92
|
-
except Exception as e:
|
93
|
-
return None
|
94
|
-
|
95
|
-
class GoogleSecretManagerV2(GoogleCloudClientManager):
|
96
|
-
def __init__(self, google_credentials_path = None) -> None:
|
97
|
-
super().__init__(google_credentials_path)
|
98
|
-
self._project_id = self.credentials.project_id
|
99
|
-
self._client = secretmanager.SecretManagerServiceClient(credentials=self.credentials)
|
100
|
-
|
101
|
-
@property
|
102
|
-
def project_id(self):
|
103
|
-
return self._project_id
|
104
|
-
|
105
|
-
@property
|
106
|
-
def client(self) -> secretmanager.SecretManagerServiceClient:
|
107
|
-
return self._client
|
108
|
-
|
109
|
-
def dispose(self):
|
110
|
-
if self._client is not None:
|
111
|
-
self._client = None
|
112
|
-
return super().dispose()
|
113
|
-
|
114
|
-
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
115
|
-
def get(self, name:str, version:str = "latest") -> Optional[str]:
|
116
|
-
try:
|
117
|
-
secret_path = f"projects/{self._project_id}/secrets/{name}/versions/{version}"
|
118
|
-
request = secretmanager.AccessSecretVersionRequest(name=secret_path)
|
119
|
-
response = self._client.access_secret_version(request=request)
|
120
|
-
return response.payload.data.decode()
|
121
|
-
except Exception as e:
|
122
|
-
return None
|
123
|
-
|
124
|
-
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
125
|
-
def create(self, name:str, data:str) -> Optional[str]:
|
126
|
-
parent = f"projects/{self._project_id}"
|
127
|
-
secret_path = f"{parent}/secrets/{name}"
|
128
|
-
try:
|
129
|
-
#* Check if the secret already exists
|
130
|
-
request = secretmanager.GetSecretRequest(name=secret_path)
|
131
|
-
self._client.get_secret(request=request)
|
132
|
-
|
133
|
-
except NotFound:
|
134
|
-
#* Secret does not exist, create it first
|
135
|
-
try:
|
136
|
-
secret = secretmanager.Secret(name=name, replication={"automatic": {}})
|
137
|
-
request = secretmanager.CreateSecretRequest(parent=parent, secret_id=name, secret=secret)
|
138
|
-
self._client.create_secret(request=request)
|
139
|
-
except Exception as e:
|
140
|
-
return None
|
141
|
-
|
142
|
-
#* Add a new secret version
|
143
|
-
try:
|
144
|
-
payload = secretmanager.SecretPayload(data=data.encode()) # ✅ Fixed attribute name
|
145
|
-
request = secretmanager.AddSecretVersionRequest(parent=secret_path, payload=payload)
|
146
|
-
response = self._client.add_secret_version(request=request)
|
147
|
-
return data
|
148
|
-
except Exception as e:
|
149
|
-
return None
|
@@ -1,110 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from datetime import timedelta
|
3
|
-
from google.auth import default
|
4
|
-
from google.cloud.storage import Bucket, Client
|
5
|
-
from google.oauth2 import service_account
|
6
|
-
from typing import Optional
|
7
|
-
from maleo_foundation.types import BaseTypes
|
8
|
-
from .base import GoogleCloudClientManager
|
9
|
-
|
10
|
-
class GoogleCloudStorage:
|
11
|
-
_client:Optional[Client] = None
|
12
|
-
_bucket:Optional[Bucket] = None
|
13
|
-
|
14
|
-
@classmethod
|
15
|
-
def initialize(cls) -> None:
|
16
|
-
"""Initialize the cloud storage if not already initialized."""
|
17
|
-
if cls._client is None:
|
18
|
-
#* Setup credentials with fallback chain
|
19
|
-
credentials = None
|
20
|
-
credentials_file = os.getenv("GOOGLE_CREDENTIALS_PATH")
|
21
|
-
try:
|
22
|
-
if credentials_file:
|
23
|
-
credentials = service_account.Credentials.from_service_account_file(credentials_file)
|
24
|
-
else:
|
25
|
-
credentials, _ = default()
|
26
|
-
except Exception as e:
|
27
|
-
raise ValueError(f"Failed to initialize credentials: {str(e)}")
|
28
|
-
|
29
|
-
cls._client = Client(credentials=credentials)
|
30
|
-
|
31
|
-
#* Preload bucket
|
32
|
-
bucket_name = os.getenv("GCS_BUCKET_NAME")
|
33
|
-
if not bucket_name:
|
34
|
-
cls._client.close()
|
35
|
-
raise ValueError("GCS_BUCKET_NAME environment variable must be set")
|
36
|
-
|
37
|
-
#* Validate bucket existence
|
38
|
-
bucket = cls._client.lookup_bucket(bucket_name)
|
39
|
-
if bucket is None:
|
40
|
-
raise ValueError(f"Bucket '{bucket_name}' does not exist.")
|
41
|
-
|
42
|
-
cls._bucket = bucket
|
43
|
-
|
44
|
-
@classmethod
|
45
|
-
def dispose(cls) -> None:
|
46
|
-
"""Dispose of the cloud storage and release any resources."""
|
47
|
-
if cls._client is not None:
|
48
|
-
cls._client.close()
|
49
|
-
cls._client = None
|
50
|
-
cls._bucket = None
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def _get_client(cls) -> Client:
|
54
|
-
"""Retrieve the cloud storage client, initializing it if necessary."""
|
55
|
-
cls.initialize()
|
56
|
-
return cls._client
|
57
|
-
|
58
|
-
@classmethod
|
59
|
-
def generate_signed_url(cls, location:str) -> str:
|
60
|
-
"""
|
61
|
-
generate signed URL of a file in the bucket based on its location.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
location: str
|
65
|
-
Location of the file inside the bucket
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
str: File's pre-signed download url
|
69
|
-
|
70
|
-
Raises:
|
71
|
-
ValueError: If the file does not exist
|
72
|
-
"""
|
73
|
-
cls.initialize()
|
74
|
-
blob = cls._bucket.blob(blob_name=location)
|
75
|
-
if not blob.exists():
|
76
|
-
raise ValueError(f"File '{location}' did not exists.")
|
77
|
-
|
78
|
-
url = blob.generate_signed_url(version="v4", expiration=timedelta(minutes=15), method="GET")
|
79
|
-
return url
|
80
|
-
|
81
|
-
class GoogleCloudStorageV2(GoogleCloudClientManager):
|
82
|
-
def __init__(self, google_credentials_path = None, bucket_name:BaseTypes.OptionalString = None) -> None:
|
83
|
-
super().__init__(google_credentials_path)
|
84
|
-
self._client = Client(credentials=self._credentials)
|
85
|
-
self._bucket_name = bucket_name or os.getenv("GCS_BUCKET_NAME")
|
86
|
-
if self._bucket_name is None:
|
87
|
-
self._client.close()
|
88
|
-
raise ValueError("GCS_BUCKET_NAME environment variable must be set if 'bucket_name' is set to None")
|
89
|
-
self._bucket = self._client.lookup_bucket(bucket_name=self._bucket_name)
|
90
|
-
if self._bucket is None:
|
91
|
-
raise ValueError(f"Bucket '{self._bucket_name}' does not exist.")
|
92
|
-
|
93
|
-
@property
|
94
|
-
def client(self) -> Client:
|
95
|
-
return self._client
|
96
|
-
|
97
|
-
@property
|
98
|
-
def bucket_name(self) -> str:
|
99
|
-
return self._bucket_name
|
100
|
-
|
101
|
-
@property
|
102
|
-
def bucket(self) -> Bucket:
|
103
|
-
return self._bucket
|
104
|
-
|
105
|
-
def dispose(self):
|
106
|
-
if self._bucket is not None:
|
107
|
-
self._bucket = None
|
108
|
-
if self._client is not None:
|
109
|
-
self._client = None
|
110
|
-
return super().dispose()
|
@@ -1,61 +0,0 @@
|
|
1
|
-
from google.api_core import retry
|
2
|
-
from google.api_core.exceptions import NotFound
|
3
|
-
from google.cloud import secretmanager
|
4
|
-
from typing import Optional
|
5
|
-
from .base import GoogleClientManager
|
6
|
-
|
7
|
-
class GoogleSecretManager(GoogleClientManager):
|
8
|
-
def __init__(self, google_credentials_path = None) -> None:
|
9
|
-
super().__init__(google_credentials_path)
|
10
|
-
self._project_id = self.credentials.project_id
|
11
|
-
self._client = secretmanager.SecretManagerServiceClient(credentials=self.credentials)
|
12
|
-
|
13
|
-
@property
|
14
|
-
def project_id(self):
|
15
|
-
return self._project_id
|
16
|
-
|
17
|
-
@property
|
18
|
-
def client(self) -> secretmanager.SecretManagerServiceClient:
|
19
|
-
return self._client
|
20
|
-
|
21
|
-
def dispose(self):
|
22
|
-
if self._client is not None:
|
23
|
-
self._client = None
|
24
|
-
return super().dispose()
|
25
|
-
|
26
|
-
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
27
|
-
def get(self, name:str, version:str = "latest") -> Optional[str]:
|
28
|
-
try:
|
29
|
-
secret_path = f"projects/{self._project_id}/secrets/{name}/versions/{version}"
|
30
|
-
request = secretmanager.AccessSecretVersionRequest(name=secret_path)
|
31
|
-
response = self._client.access_secret_version(request=request)
|
32
|
-
return response.payload.data.decode()
|
33
|
-
except Exception as e:
|
34
|
-
return None
|
35
|
-
|
36
|
-
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
37
|
-
def create(self, name:str, data:str) -> Optional[str]:
|
38
|
-
parent = f"projects/{self._project_id}"
|
39
|
-
secret_path = f"{parent}/secrets/{name}"
|
40
|
-
try:
|
41
|
-
#* Check if the secret already exists
|
42
|
-
request = secretmanager.GetSecretRequest(name=secret_path)
|
43
|
-
self._client.get_secret(request=request)
|
44
|
-
|
45
|
-
except NotFound:
|
46
|
-
#* Secret does not exist, create it first
|
47
|
-
try:
|
48
|
-
secret = secretmanager.Secret(name=name, replication={"automatic": {}})
|
49
|
-
request = secretmanager.CreateSecretRequest(parent=parent, secret_id=name, secret=secret)
|
50
|
-
self._client.create_secret(request=request)
|
51
|
-
except Exception as e:
|
52
|
-
return None
|
53
|
-
|
54
|
-
#* Add a new secret version
|
55
|
-
try:
|
56
|
-
payload = secretmanager.SecretPayload(data=data.encode())
|
57
|
-
request = secretmanager.AddSecretVersionRequest(parent=secret_path, payload=payload)
|
58
|
-
response = self._client.add_secret_version(request=request)
|
59
|
-
return data
|
60
|
-
except Exception as e:
|
61
|
-
return None
|
@@ -1,57 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from datetime import timedelta
|
3
|
-
from google.cloud.storage import Bucket, Client
|
4
|
-
from maleo_foundation.types import BaseTypes
|
5
|
-
from .base import GoogleClientManager
|
6
|
-
|
7
|
-
class GoogleCloudStorage(GoogleClientManager):
|
8
|
-
def __init__(self, google_credentials_path = None, bucket_name:BaseTypes.OptionalString = None) -> None:
|
9
|
-
super().__init__(google_credentials_path)
|
10
|
-
self._client = Client(credentials=self._credentials)
|
11
|
-
self._bucket_name = bucket_name or os.getenv("GCS_BUCKET_NAME")
|
12
|
-
if self._bucket_name is None:
|
13
|
-
self._client.close()
|
14
|
-
raise ValueError("GCS_BUCKET_NAME environment variable must be set if 'bucket_name' is set to None")
|
15
|
-
self._bucket = self._client.lookup_bucket(bucket_name=self._bucket_name)
|
16
|
-
if self._bucket is None:
|
17
|
-
raise ValueError(f"Bucket '{self._bucket_name}' does not exist.")
|
18
|
-
|
19
|
-
@property
|
20
|
-
def client(self) -> Client:
|
21
|
-
return self._client
|
22
|
-
|
23
|
-
@property
|
24
|
-
def bucket_name(self) -> str:
|
25
|
-
return self._bucket_name
|
26
|
-
|
27
|
-
@property
|
28
|
-
def bucket(self) -> Bucket:
|
29
|
-
return self._bucket
|
30
|
-
|
31
|
-
def dispose(self):
|
32
|
-
if self._bucket is not None:
|
33
|
-
self._bucket = None
|
34
|
-
if self._client is not None:
|
35
|
-
self._client = None
|
36
|
-
return super().dispose()
|
37
|
-
|
38
|
-
def generate_signed_url(self, location:str) -> str:
|
39
|
-
"""
|
40
|
-
generate signed URL of a file in the bucket based on its location.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
location: str
|
44
|
-
Location of the file inside the bucket
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
str: File's pre-signed download url
|
48
|
-
|
49
|
-
Raises:
|
50
|
-
ValueError: If the file does not exist
|
51
|
-
"""
|
52
|
-
blob = self._bucket.blob(blob_name=location)
|
53
|
-
if not blob.exists():
|
54
|
-
raise ValueError(f"File '{location}' did not exists.")
|
55
|
-
|
56
|
-
url = blob.generate_signed_url(version="v4", expiration=timedelta(minutes=15), method="GET")
|
57
|
-
return url
|
@@ -1,54 +0,0 @@
|
|
1
|
-
from typing import Dict
|
2
|
-
from maleo_foundation.enums import BaseEnums
|
3
|
-
from maleo_foundation.types import BaseTypes
|
4
|
-
from maleo_foundation.utils.logger import BaseLogger
|
5
|
-
|
6
|
-
class ClientLoggerManager:
|
7
|
-
_loggers:Dict[type, BaseLogger] = {}
|
8
|
-
|
9
|
-
@classmethod
|
10
|
-
def initialize(
|
11
|
-
cls,
|
12
|
-
base_dir:str,
|
13
|
-
client_name:str,
|
14
|
-
service_name:BaseTypes.OptionalString = None,
|
15
|
-
level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO
|
16
|
-
) -> BaseLogger:
|
17
|
-
"""Initialize client logger if not already initialized."""
|
18
|
-
if cls not in cls._loggers:
|
19
|
-
cls._loggers[cls] = BaseLogger(
|
20
|
-
base_dir=base_dir,
|
21
|
-
type=BaseEnums.LoggerType.CLIENT,
|
22
|
-
service_name=service_name,
|
23
|
-
client_name=client_name,
|
24
|
-
level=level
|
25
|
-
)
|
26
|
-
return cls._loggers[cls]
|
27
|
-
|
28
|
-
@classmethod
|
29
|
-
def get(cls) -> BaseLogger:
|
30
|
-
"""Return client logger (if exist) or raise Runtime Error"""
|
31
|
-
if cls not in cls._loggers:
|
32
|
-
raise RuntimeError("Logger has not been initialized. Call 'initialize' first.")
|
33
|
-
return cls._loggers[cls]
|
34
|
-
|
35
|
-
class MaleoFoundationLoggerManager(ClientLoggerManager):
|
36
|
-
@classmethod
|
37
|
-
def initialize(
|
38
|
-
cls,
|
39
|
-
base_dir:str,
|
40
|
-
service_name:BaseTypes.OptionalString = None,
|
41
|
-
level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO
|
42
|
-
) -> BaseLogger:
|
43
|
-
"""Initialize MaleoFoundation's client logger if not already initialized."""
|
44
|
-
return super().initialize(
|
45
|
-
base_dir=base_dir,
|
46
|
-
client_name="MaleoFoundation",
|
47
|
-
service_name=service_name,
|
48
|
-
level=level
|
49
|
-
)
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def get(cls) -> BaseLogger:
|
53
|
-
"""Return client logger (if exist) or raise Runtime Error"""
|
54
|
-
return super().get()
|
maleo_foundation/db/__init__.py
DELETED
maleo_foundation/db/engine.py
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from sqlalchemy import create_engine, Engine
|
3
|
-
from typing import Optional
|
4
|
-
from maleo_foundation.types import BaseTypes
|
5
|
-
from maleo_foundation.utils.logger import BaseLogger
|
6
|
-
|
7
|
-
class EngineManager:
|
8
|
-
_logger:Optional[BaseLogger] = None
|
9
|
-
_engine:Optional[Engine] = None
|
10
|
-
|
11
|
-
@classmethod
|
12
|
-
def initialize(cls, logger:BaseLogger, url:Optional[str] = None) -> Engine:
|
13
|
-
"""Initialize the engine if not already initialized."""
|
14
|
-
if cls._engine is None:
|
15
|
-
cls._logger = logger
|
16
|
-
url = url or os.getenv("DB_CONNECTION_STRING")
|
17
|
-
if url is None:
|
18
|
-
raise ValueError("DB_CONNECTION_STRING environment variable must be set if url is not provided")
|
19
|
-
cls._engine = create_engine(url=url, echo=False, pool_pre_ping=True, pool_recycle=3600)
|
20
|
-
cls._logger.info("EngineManager initialized successfully.")
|
21
|
-
return cls._engine
|
22
|
-
|
23
|
-
@classmethod
|
24
|
-
def get(cls) -> Engine:
|
25
|
-
"""Retrieve the engine, initializing it if necessary."""
|
26
|
-
if cls._logger is None:
|
27
|
-
raise RuntimeError("Logger has not been initialized. Call initialize(db_connection_string, logger) first.")
|
28
|
-
if cls._engine is None:
|
29
|
-
raise RuntimeError("Engine has not been initialized. Call initialize(db_connection_string, logger) first.")
|
30
|
-
|
31
|
-
return cls._engine
|
32
|
-
|
33
|
-
@classmethod
|
34
|
-
def dispose(cls) -> None:
|
35
|
-
"""Dispose of the engine and release any resources."""
|
36
|
-
if cls._engine is not None:
|
37
|
-
cls._engine.dispose()
|
38
|
-
cls._engine = None
|
39
|
-
|
40
|
-
cls._logger.info("Engine disposed successfully.")
|
41
|
-
cls._logger = None
|
42
|
-
|
43
|
-
class EngineManagerV2:
|
44
|
-
_logger:Optional[BaseLogger] = None
|
45
|
-
_engine:Optional[Engine] = None
|
46
|
-
|
47
|
-
def __init__(self, logger:BaseLogger, url:BaseTypes.OptionalString = None):
|
48
|
-
"""Initialize the engine manager."""
|
49
|
-
self._logger = logger
|
50
|
-
url = url or os.getenv("DB_CONNECTION_STRING")
|
51
|
-
if url is None:
|
52
|
-
raise ValueError("DB_CONNECTION_STRING environment variable must be set if url is not provided")
|
53
|
-
self._engine = create_engine(url=url, echo=False, pool_pre_ping=True, pool_recycle=3600)
|
54
|
-
|
55
|
-
@property
|
56
|
-
def logger(self) -> BaseLogger:
|
57
|
-
"""Retrieve the logger."""
|
58
|
-
if self._logger is None:
|
59
|
-
raise RuntimeError("Logger has not been initialized. Call initialize(db_connection_string, logger) first.")
|
60
|
-
return self._logger
|
61
|
-
|
62
|
-
@property
|
63
|
-
def engine(self) -> Engine:
|
64
|
-
"""Retrieve the engine."""
|
65
|
-
if self._engine is None:
|
66
|
-
raise RuntimeError("Engine has not been initialized. Call initialize(db_connection_string, logger) first.")
|
67
|
-
return self._engine
|
68
|
-
|
69
|
-
def dispose(self) -> None:
|
70
|
-
"""Dispose of the engine and release any resources."""
|
71
|
-
if self._engine is not None:
|
72
|
-
self._engine.dispose()
|
73
|
-
self._engine = None
|
74
|
-
|
75
|
-
self._logger.info("Engine disposed successfully.")
|
76
|
-
self._logger = None
|
maleo_foundation/db/manager.py
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from contextlib import contextmanager
|
3
|
-
from sqlalchemy import MetaData
|
4
|
-
from sqlalchemy.engine import Engine, create_engine
|
5
|
-
from sqlalchemy.exc import SQLAlchemyError
|
6
|
-
from sqlalchemy.ext.declarative import DeclarativeMeta
|
7
|
-
from sqlalchemy.orm import sessionmaker, Session, declarative_base
|
8
|
-
from typing import Generator, Optional, Type
|
9
|
-
from maleo_foundation.types import BaseTypes
|
10
|
-
from maleo_foundation.utils.logger import BaseLogger
|
11
|
-
from .table import BaseTable
|
12
|
-
|
13
|
-
class DatabaseManager:
|
14
|
-
Base:DeclarativeMeta = declarative_base() #* Correct way to define a declarative base
|
15
|
-
|
16
|
-
#* Explicitly define the type of metadata
|
17
|
-
metadata:MetaData = Base.metadata
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def initialize(cls, engine:Engine):
|
21
|
-
"""Creates the database tables if they do not exist."""
|
22
|
-
cls.metadata.create_all(engine)
|
23
|
-
|
24
|
-
class MetadataManager:
|
25
|
-
_Base:DeclarativeMeta = declarative_base()
|
26
|
-
|
27
|
-
@property
|
28
|
-
def Base(self) -> DeclarativeMeta:
|
29
|
-
return self._Base
|
30
|
-
|
31
|
-
@property
|
32
|
-
def metadata(self) -> MetaData:
|
33
|
-
return self._Base.metadata
|
34
|
-
|
35
|
-
class SessionManager:
|
36
|
-
def __init__(self, logger:BaseLogger, engine:Engine):
|
37
|
-
self._logger = logger
|
38
|
-
self._sessionmaker:sessionmaker[Session] = sessionmaker(bind=engine, expire_on_commit=False)
|
39
|
-
self._logger.info("SessionManager initialized successfully.")
|
40
|
-
|
41
|
-
def _session_handler(self) -> Generator[Session, None, None]:
|
42
|
-
"""Reusable function for managing database sessions."""
|
43
|
-
if self._logger is None:
|
44
|
-
raise RuntimeError("Logger has not been initialized. Call initialize() first.")
|
45
|
-
if self._sessionmaker is None:
|
46
|
-
raise RuntimeError("SessionLocal has not been initialized. Call initialize() first.")
|
47
|
-
|
48
|
-
session = self._sessionmaker()
|
49
|
-
self._logger.debug("New database session created.")
|
50
|
-
try:
|
51
|
-
yield session #* Provide session
|
52
|
-
session.commit() #* Auto-commit on success
|
53
|
-
except SQLAlchemyError as e:
|
54
|
-
session.rollback() #* Rollback on error
|
55
|
-
self._logger.error(f"[SQLAlchemyError] Database transaction failed: {e}", exc_info=True)
|
56
|
-
raise
|
57
|
-
except Exception as e:
|
58
|
-
session.rollback() #* Rollback on error
|
59
|
-
self._logger.error(f"[Exception] Database transaction failed: {e}", exc_info=True)
|
60
|
-
raise
|
61
|
-
finally:
|
62
|
-
session.close() #* Ensure session closes
|
63
|
-
self._logger.debug("Database session closed.")
|
64
|
-
|
65
|
-
def inject(self) -> Generator[Session, None, None]:
|
66
|
-
"""Returns a generator that yields a SQLAlchemy session for dependency injection."""
|
67
|
-
return self._session_handler()
|
68
|
-
|
69
|
-
@contextmanager
|
70
|
-
def get(self) -> Generator[Session, None, None]:
|
71
|
-
"""Context manager for manual session handling. Supports `with SessionManager.get() as session:`"""
|
72
|
-
yield from self._session_handler()
|
73
|
-
|
74
|
-
def dispose(self) -> None:
|
75
|
-
"""Dispose of the sessionmaker and release any resources."""
|
76
|
-
if self._sessionmaker is not None:
|
77
|
-
self._sessionmaker.close_all()
|
78
|
-
self._sessionmaker = None
|
79
|
-
|
80
|
-
self._logger.info("SessionManager disposed successfully.")
|
81
|
-
self._logger = None
|
82
|
-
|
83
|
-
class DatabaseManagerV2:
|
84
|
-
# _metadata:Optional[MetaData] = None
|
85
|
-
# _logger:Optional[BaseLogger] = None
|
86
|
-
# _engine:Optional[Engine] = None
|
87
|
-
# _session:Optional[SessionManager] = None
|
88
|
-
|
89
|
-
def __init__(
|
90
|
-
self,
|
91
|
-
metadata:MetaData,
|
92
|
-
logger:BaseLogger,
|
93
|
-
url:BaseTypes.OptionalString = None
|
94
|
-
):
|
95
|
-
self._metadata = metadata #* Define database metadata
|
96
|
-
self._logger = logger #* Define database logger
|
97
|
-
|
98
|
-
#* Create engine
|
99
|
-
url = url or os.getenv("DB_URL")
|
100
|
-
if url is None:
|
101
|
-
raise ValueError("DB_URL environment variable must be set if url is not provided")
|
102
|
-
self._engine = create_engine(url=url, echo=False, pool_pre_ping=True, pool_recycle=3600)
|
103
|
-
|
104
|
-
self._metadata.create_all(bind=self._engine) #* Create all tables
|
105
|
-
self._session = SessionManager(logger=self._logger, engine=self._engine) #* Define session
|
106
|
-
|
107
|
-
def dispose(self) -> None:
|
108
|
-
#* Dispose session
|
109
|
-
if self._session is not None:
|
110
|
-
self._session.dispose()
|
111
|
-
self._session = None
|
112
|
-
#* Dispose engine
|
113
|
-
if self._engine is not None:
|
114
|
-
self._engine.dispose()
|
115
|
-
self._engine = None
|
116
|
-
#* Dispose logger
|
117
|
-
if self._logger is not None:
|
118
|
-
self._logger.dispose()
|
119
|
-
self._logger = None
|
120
|
-
#* Dispose metadata
|
121
|
-
if self._metadata is not None:
|
122
|
-
self._metadata = None
|