maleo-foundation 0.1.35__py3-none-any.whl → 0.1.36__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 +0 -1
- maleo_foundation/utils/exceptions.py +1 -13
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.36.dist-info}/METADATA +1 -1
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.36.dist-info}/RECORD +8 -27
- 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.36.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.1.35.dist-info → maleo_foundation-0.1.36.dist-info}/top_level.txt +0 -0
maleo_foundation/managers/db.py
CHANGED
@@ -5,17 +5,16 @@ from sqlalchemy.engine import Engine, create_engine
|
|
5
5
|
from sqlalchemy.exc import SQLAlchemyError
|
6
6
|
from sqlalchemy.ext.declarative import DeclarativeMeta
|
7
7
|
from sqlalchemy.orm import sessionmaker, Session, declarative_base
|
8
|
-
from typing import Generator
|
8
|
+
from typing import Generator
|
9
9
|
from maleo_foundation.types import BaseTypes
|
10
|
-
from maleo_foundation.utils.
|
11
|
-
from maleo_foundation.db.table import BaseTable
|
10
|
+
from maleo_foundation.utils.logging import ServiceLogger
|
12
11
|
|
13
12
|
class MetadataManager:
|
14
13
|
Base:DeclarativeMeta = declarative_base()
|
15
14
|
metadata:MetaData = Base.metadata
|
16
15
|
|
17
16
|
class SessionManager:
|
18
|
-
def __init__(self, logger:
|
17
|
+
def __init__(self, logger:ServiceLogger, engine:Engine):
|
19
18
|
self._logger = logger
|
20
19
|
self._logger.info("Initializing SessionMaker")
|
21
20
|
self._sessionmaker:sessionmaker[Session] = sessionmaker(bind=engine, expire_on_commit=False)
|
@@ -62,7 +61,7 @@ class DatabaseManager:
|
|
62
61
|
def __init__(
|
63
62
|
self,
|
64
63
|
metadata:MetaData,
|
65
|
-
logger:
|
64
|
+
logger:ServiceLogger,
|
66
65
|
url:BaseTypes.OptionalString = None
|
67
66
|
):
|
68
67
|
self._metadata = metadata #* Define database metadata
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
8
8
|
from maleo_foundation.models.responses import BaseResponses
|
9
9
|
from maleo_foundation.models.transfers.results.service.general import BaseServiceGeneralResultsTransfers
|
10
10
|
from maleo_foundation.models.transfers.results.service.query import BaseServiceQueryResultsTransfers
|
11
|
-
from maleo_foundation.utils.
|
11
|
+
from maleo_foundation.utils.logging import BaseLogger
|
12
12
|
|
13
13
|
class BaseExceptions:
|
14
14
|
@staticmethod
|
@@ -27,15 +27,9 @@ class BaseExceptions:
|
|
27
27
|
def database_exception_handler(
|
28
28
|
operation:str,
|
29
29
|
logger:Optional[BaseLogger] = None,
|
30
|
-
logger_factory:Optional[LoggerFactory] = None,
|
31
30
|
fail_result_class:type[BaseServiceQueryResultsTransfers.Fail] = BaseServiceQueryResultsTransfers.Fail
|
32
31
|
):
|
33
32
|
"""Decorator to handle database-related exceptions consistently."""
|
34
|
-
if logger is not None and logger_factory is not None:
|
35
|
-
raise RuntimeError("Only one of 'logger' or 'logger_factory' should be provided, not both.")
|
36
|
-
if logger is None and logger_factory is None:
|
37
|
-
raise RuntimeError("One of 'logger' or 'logger_factory' must be provided.")
|
38
|
-
logger = logger or logger_factory()
|
39
33
|
def decorator(func):
|
40
34
|
@wraps(func)
|
41
35
|
def wrapper(*args, **kwargs):
|
@@ -62,15 +56,9 @@ class BaseExceptions:
|
|
62
56
|
def service_exception_handler(
|
63
57
|
operation:str,
|
64
58
|
logger:Optional[BaseLogger] = None,
|
65
|
-
logger_factory:Optional[LoggerFactory] = None,
|
66
59
|
fail_result_class:type[BaseServiceGeneralResultsTransfers.Fail] = BaseServiceGeneralResultsTransfers.Fail
|
67
60
|
):
|
68
61
|
"""Decorator to handle service-related exceptions consistently."""
|
69
|
-
if logger is not None and logger_factory is not None:
|
70
|
-
raise RuntimeError("Only one of 'logger' or 'logger_factory' should be provided, not both.")
|
71
|
-
if logger is None and logger_factory is None:
|
72
|
-
raise RuntimeError("One of 'logger' or 'logger_factory' must be provided.")
|
73
|
-
logger = logger or logger_factory()
|
74
62
|
def decorator(func):
|
75
63
|
@wraps(func)
|
76
64
|
def wrapper(*args, **kwargs):
|
@@ -4,25 +4,6 @@ maleo_foundation/constants.py,sha256=aBmEfWlBqZxi0k-n6h2NM1YRLOjMnheEiLyQcjP-zCQ
|
|
4
4
|
maleo_foundation/enums.py,sha256=uvwl3dl2r6BoJMEbtSETiLoyJubHup9Lc7VOg7w7zQo,2943
|
5
5
|
maleo_foundation/extended_types.py,sha256=pIKt-_9tby4rmune3fmWcCW_mohaNRh_1lywBmdc-L4,301
|
6
6
|
maleo_foundation/types.py,sha256=aKXnIgEhYGSfFqNMGLc4qIKGkINBRpkOo9R9cb2CbwI,2414
|
7
|
-
maleo_foundation/clients/__init__.py,sha256=W8vydJYeDEi6gdmOZSBFSSDsfZJtb8C05CHErZgsZ30,188
|
8
|
-
maleo_foundation/clients/general/__init__.py,sha256=l9eQrBeLW4aXtGU5aK3i6fD-msVR4526W7D9V8WCXIg,91
|
9
|
-
maleo_foundation/clients/general/http.py,sha256=pObW6EaMeLDNsRZ9QJPzfhZDzcZ33kCsb-9Yfoh2MA4,1670
|
10
|
-
maleo_foundation/clients/google/__init__.py,sha256=1uv6nF9QbATsSAcMimQOT7Y-eBljjDunBojNX6oAtS8,90
|
11
|
-
maleo_foundation/clients/google/base.py,sha256=tdiqNNtvy-Ev_L7R4Dg9y7V14QdlbfCOQ2Mulo238aE,1141
|
12
|
-
maleo_foundation/clients/google/secret.py,sha256=4k3pDs4HAibeCEgv8nUBADfhk2aftG9L1sfR3UxAyfE,2516
|
13
|
-
maleo_foundation/clients/google/storage.py,sha256=smsmrjKAyxtfdqz3EzuUVx_rQeaTaYZ5VKNWCvwQ4U8,1971
|
14
|
-
maleo_foundation/clients/google/cloud/__init__.py,sha256=WGMPxEKKdkz3XGY5dZn9E-nYhD1kv1MgRHbmVnky4zk,245
|
15
|
-
maleo_foundation/clients/google/cloud/base.py,sha256=V_5Zdu90FQLgAfUZvlB1KojFzXNKZNIR516gjDXgaPU,1146
|
16
|
-
maleo_foundation/clients/google/cloud/logging.py,sha256=s9T9bex0GeCPwIHrBRvilT23iyNKqJ5z50KcT76Jt5Y,2202
|
17
|
-
maleo_foundation/clients/google/cloud/secret.py,sha256=1dua0V2FHesjltLdc1N4PF8xTXPzmcSA3sgwBzYNUtM,5853
|
18
|
-
maleo_foundation/clients/google/cloud/storage.py,sha256=t8hAZiQj_RFhJJXE8a20WP7spNKTEFw1RK1AqurL3T8,3848
|
19
|
-
maleo_foundation/clients/utils/__init__.py,sha256=hChEGABHH4tOFxPRcpxmlhkM9PgF18M7wXapH88hpu4,131
|
20
|
-
maleo_foundation/clients/utils/logger.py,sha256=FMnHKV4i6xR6e8XN7kCNwTf1jhSLdJUIO7teSm5g0D4,1829
|
21
|
-
maleo_foundation/db/__init__.py,sha256=fmvhCz4_siHfyKJujcUakKDKmuLxMhxn2w5tmfQwfcM,135
|
22
|
-
maleo_foundation/db/engine.py,sha256=hhYjCt5IEb864H2RNlUVS7GfMzuThHKRV260Bgkhn_o,3003
|
23
|
-
maleo_foundation/db/manager.py,sha256=nSstMJ9JBoEKTSLlz6MNf4Wuet8DLp2Pipfveg4kM1c,4663
|
24
|
-
maleo_foundation/db/session.py,sha256=6flx_HVh4Fe3EohK2xDcyXmVAPOci1KFvhVK4wFcKtA,4736
|
25
|
-
maleo_foundation/db/table.py,sha256=Dk5GXeO0gbBBPN2PJtZhlUx2x3vMbT4dxTBc3YLBbuc,1199
|
26
7
|
maleo_foundation/expanded_types/__init__.py,sha256=lm_r7rxlLA0sgSTGQBMR9ZbZbVDpD7Te-UYb3oRVi1g,364
|
27
8
|
maleo_foundation/expanded_types/client.py,sha256=To0kRXp3QTmuSu5rWKaCiTsMK9qkYiyYKYbHfw-y1fY,2396
|
28
9
|
maleo_foundation/expanded_types/general.py,sha256=bjIBREYTS73tvS-Key7P7db82a2HHlSJ1XBAvKuYmT0,826
|
@@ -30,9 +11,9 @@ maleo_foundation/expanded_types/query.py,sha256=0yUG-JIVsanzB7KAkrRz_OsrhP6J0bRq
|
|
30
11
|
maleo_foundation/expanded_types/service.py,sha256=q8jpKdbCbLWwH1UPQavKpVE14rC5rveduk2cFWzuhGw,2416
|
31
12
|
maleo_foundation/expanded_types/token.py,sha256=4fRTJw6W5MYq71NksNrWNi7qYHQ4_lQwfu9WxwrMipc,355
|
32
13
|
maleo_foundation/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
-
maleo_foundation/managers/db.py,sha256=
|
14
|
+
maleo_foundation/managers/db.py,sha256=ZN0b43OgqQtk2WHKMJQ0E2TeaSEyVJ0-l4FEkrSG0Qo,4645
|
34
15
|
maleo_foundation/managers/middleware.py,sha256=7CDXPMb28AR7J72TWOeKFxOlMypKezEtO9mr53a88B0,4032
|
35
|
-
maleo_foundation/managers/service.py,sha256=
|
16
|
+
maleo_foundation/managers/service.py,sha256=c6syGWL5DdGWix075_WVaAIfSV7SSHbfrPlCQBnYOK8,20377
|
36
17
|
maleo_foundation/managers/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
18
|
maleo_foundation/managers/client/base.py,sha256=lYREmEoTLShlPPXOKKiAopjefJ8nIWHCi7IvWkXXKeY,1465
|
38
19
|
maleo_foundation/managers/client/maleo.py,sha256=NibYGdiN3EXUw5nx-tL48QAZym14GcA4BDZihGJNQ-g,4254
|
@@ -46,6 +27,7 @@ maleo_foundation/middlewares/base.py,sha256=3OaB5F57F3epKNieoAgarYM6PimoUdUl3Dgp
|
|
46
27
|
maleo_foundation/middlewares/cors.py,sha256=9uvBvY2N6Vxa9RP_YtESxcWo6Doi6uS0lzAG9iLY7Uc,2288
|
47
28
|
maleo_foundation/models/__init__.py,sha256=AaKehO7c1HyKhoTGRmNHDddSeBXkW-_YNrpOGBu8Ms8,246
|
48
29
|
maleo_foundation/models/responses.py,sha256=Ka9Peb1fVuQKaxyy11FVkUPtGtzyEm_9Xfe8Vla3ml0,4764
|
30
|
+
maleo_foundation/models/table.py,sha256=Dk5GXeO0gbBBPN2PJtZhlUx2x3vMbT4dxTBc3YLBbuc,1199
|
49
31
|
maleo_foundation/models/schemas/__init__.py,sha256=Xj8Ahsqyra-fmEaVcGPok5GOOsPQlKcknHYMvbjvENA,277
|
50
32
|
maleo_foundation/models/schemas/general.py,sha256=hEQQPcddxMsJXMb36nqItIAfp3pqUyJOlaD7nXkNM2I,3609
|
51
33
|
maleo_foundation/models/schemas/parameter.py,sha256=K47z2NzmTEhUiOfRiRLyRPXoQurbWsKBL7ObXAxIWRY,2100
|
@@ -74,15 +56,14 @@ maleo_foundation/services/__init__.py,sha256=Ho5zJSA89xdGFKIwOdzjmd8sm23cIuwrqYA
|
|
74
56
|
maleo_foundation/services/token.py,sha256=ZqRqOdGUnaSIam6-JHVdAW1UST-2EDtcVN0fpbPmXY4,1638
|
75
57
|
maleo_foundation/utils/__init__.py,sha256=FavmL5XYGCm955EAKiWWcXYeU15p5rSzfcglpV2yI6c,387
|
76
58
|
maleo_foundation/utils/controller.py,sha256=ECzPzpw36zBAjKcWcDbUAhIJGbc6UpeypdUUX6ipXBg,6396
|
77
|
-
maleo_foundation/utils/exceptions.py,sha256=
|
59
|
+
maleo_foundation/utils/exceptions.py,sha256=LPPcU-6_3NbRIBZg2Nr2Ac5HF1qZJbHbMVnwfIfZg6g,3702
|
78
60
|
maleo_foundation/utils/extractor.py,sha256=BOYkEiEJZTixScd_mEiLt2svKZ4gXBgD2WDlU6giI3w,718
|
79
61
|
maleo_foundation/utils/keyloader.py,sha256=g7LYypj7UolmSgHRGXMFgVaWr55UayMdtKXR5NmB7VM,2341
|
80
|
-
maleo_foundation/utils/logger.py,sha256=uTvzzKnGjbxRVLHbiMDw2zKKWNaCwV35sxgjDStEwNQ,3569
|
81
62
|
maleo_foundation/utils/logging.py,sha256=MwvZmZSA8SIdfq-knEvpYIgqnSpHcyHrZY9TVHWVHJA,9023
|
82
63
|
maleo_foundation/utils/query.py,sha256=ODQ3adOYQNj5E2cRW9ytbjBz56nEDcnfq8mQ6YZbCCM,4375
|
83
64
|
maleo_foundation/utils/formatter/__init__.py,sha256=iKf5YCbEdg1qKnFHyKqqcQbqAqEeRUf8mhI3v3dQoj8,78
|
84
65
|
maleo_foundation/utils/formatter/case.py,sha256=TmvvlfzGdC_omMTB5vAa40TZBxQ3hnr-SYeo0M52Rlg,1352
|
85
|
-
maleo_foundation-0.1.
|
86
|
-
maleo_foundation-0.1.
|
87
|
-
maleo_foundation-0.1.
|
88
|
-
maleo_foundation-0.1.
|
66
|
+
maleo_foundation-0.1.36.dist-info/METADATA,sha256=XkamA11MYU7rbddrU-0vy_Lzhaq1SsdiJ9G7xsT4JbA,3190
|
67
|
+
maleo_foundation-0.1.36.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
68
|
+
maleo_foundation-0.1.36.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
|
69
|
+
maleo_foundation-0.1.36.dist-info/RECORD,,
|
@@ -1,50 +0,0 @@
|
|
1
|
-
import httpx
|
2
|
-
from contextlib import asynccontextmanager
|
3
|
-
from typing import AsyncGenerator, Optional
|
4
|
-
|
5
|
-
class HTTPClientManager:
|
6
|
-
client:Optional[httpx.AsyncClient] = None
|
7
|
-
base_url:Optional[str] = None
|
8
|
-
|
9
|
-
@classmethod
|
10
|
-
def initialize(cls) -> None:
|
11
|
-
"""Initialize the HTTP client if not already initialized."""
|
12
|
-
if cls.client is None:
|
13
|
-
cls.client = httpx.AsyncClient()
|
14
|
-
|
15
|
-
@classmethod
|
16
|
-
async def _client_handler(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
|
17
|
-
"""Reusable generator for client handling."""
|
18
|
-
if cls.client is None:
|
19
|
-
raise RuntimeError("Client has not been initialized. Call initialize first.")
|
20
|
-
|
21
|
-
yield cls.client
|
22
|
-
|
23
|
-
@classmethod
|
24
|
-
async def inject_client(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
|
25
|
-
return cls._client_handler()
|
26
|
-
|
27
|
-
@classmethod
|
28
|
-
@asynccontextmanager
|
29
|
-
async def get_client(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
|
30
|
-
"""
|
31
|
-
Async context manager for manual HTTP client handling.
|
32
|
-
Supports `async with HTTPClientManager.get() as client:`
|
33
|
-
"""
|
34
|
-
async for client in cls._client_handler():
|
35
|
-
yield client
|
36
|
-
|
37
|
-
@classmethod
|
38
|
-
def get_url(cls) -> str:
|
39
|
-
if cls.base_url is None:
|
40
|
-
raise RuntimeError("Base URL has not been initialized. Call initialize first.")
|
41
|
-
return cls.base_url
|
42
|
-
|
43
|
-
@classmethod
|
44
|
-
async def dispose(cls) -> None:
|
45
|
-
"""Dispose of the HTTP client and release any resources."""
|
46
|
-
if cls.client is not None:
|
47
|
-
await cls.client.aclose()
|
48
|
-
cls.client = None
|
49
|
-
if cls.base_url is not None:
|
50
|
-
cls.base_url = None
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from google.auth import default
|
3
|
-
from google.oauth2 import service_account
|
4
|
-
from maleo_foundation.types import BaseTypes
|
5
|
-
|
6
|
-
class GoogleClientManager:
|
7
|
-
def __init__(self, google_credentials_path:BaseTypes.OptionalString = None) -> None:
|
8
|
-
google_credentials_path = google_credentials_path or os.getenv("GOOGLE_CREDENTIALS_PATH")
|
9
|
-
try:
|
10
|
-
if google_credentials_path is not None:
|
11
|
-
self._credentials = service_account.Credentials.from_service_account_file(filename=google_credentials_path)
|
12
|
-
else:
|
13
|
-
self._credentials, _ = default()
|
14
|
-
except Exception as e:
|
15
|
-
raise ValueError(f"Failed to initialize credentials: {str(e)}")
|
16
|
-
|
17
|
-
@property
|
18
|
-
def credentials(self) -> service_account.Credentials:
|
19
|
-
return self._credentials
|
20
|
-
|
21
|
-
@property
|
22
|
-
def name(self) -> str:
|
23
|
-
raise NotImplementedError()
|
24
|
-
|
25
|
-
@property
|
26
|
-
def client(self):
|
27
|
-
raise NotImplementedError()
|
28
|
-
|
29
|
-
def dispose(self) -> None:
|
30
|
-
"""Dispose of the client and release any resources."""
|
31
|
-
if self._credentials is not None:
|
32
|
-
self._credentials = None
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from google.auth import default
|
3
|
-
from google.oauth2 import service_account
|
4
|
-
from maleo_foundation.types import BaseTypes
|
5
|
-
|
6
|
-
class GoogleCloudClientManager:
|
7
|
-
def __init__(self, google_credentials_path:BaseTypes.OptionalString = None) -> None:
|
8
|
-
google_credentials_path = google_credentials_path or os.getenv("GOOGLE_CREDENTIALS_PATH")
|
9
|
-
try:
|
10
|
-
if google_credentials_path is not None:
|
11
|
-
self._credentials = service_account.Credentials.from_service_account_file(filename=google_credentials_path)
|
12
|
-
else:
|
13
|
-
self._credentials, _ = default()
|
14
|
-
except Exception as e:
|
15
|
-
raise ValueError(f"Failed to initialize credentials: {str(e)}")
|
16
|
-
|
17
|
-
@property
|
18
|
-
def credentials(self) -> service_account.Credentials:
|
19
|
-
return self._credentials
|
20
|
-
|
21
|
-
@property
|
22
|
-
def name(self) -> str:
|
23
|
-
raise NotImplementedError()
|
24
|
-
|
25
|
-
@property
|
26
|
-
def client(self):
|
27
|
-
raise NotImplementedError()
|
28
|
-
|
29
|
-
def dispose(self) -> None:
|
30
|
-
"""Dispose of the client and release any resources."""
|
31
|
-
if self._credentials is not None:
|
32
|
-
self._credentials = None
|
@@ -1,63 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from google.auth import default
|
3
|
-
from google.cloud.logging import Client
|
4
|
-
from google.cloud.logging.handlers import CloudLoggingHandler
|
5
|
-
from google.oauth2 import service_account
|
6
|
-
from typing import Optional
|
7
|
-
from .base import GoogleCloudClientManager
|
8
|
-
|
9
|
-
class GoogleCloudLogging:
|
10
|
-
_client:Optional[Client] = None
|
11
|
-
|
12
|
-
@classmethod
|
13
|
-
def initialize(cls) -> Client:
|
14
|
-
"""Initialize the cloud logging if not already initialized."""
|
15
|
-
if cls._client is None:
|
16
|
-
#* Setup credentials with fallback chain
|
17
|
-
credentials = None
|
18
|
-
credentials_file = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
19
|
-
try:
|
20
|
-
if credentials_file:
|
21
|
-
credentials = service_account.Credentials.from_service_account_file(credentials_file)
|
22
|
-
else:
|
23
|
-
credentials, _ = default()
|
24
|
-
except Exception as e:
|
25
|
-
raise ValueError(f"Failed to initialize credentials: {str(e)}")
|
26
|
-
|
27
|
-
cls._client = Client(credentials=credentials)
|
28
|
-
cls._client.setup_logging()
|
29
|
-
|
30
|
-
@classmethod
|
31
|
-
def dispose(cls) -> None:
|
32
|
-
"""Dispose of the cloud logging and release any resources."""
|
33
|
-
if cls._client is not None:
|
34
|
-
cls._client = None
|
35
|
-
|
36
|
-
@classmethod
|
37
|
-
def _get_client(cls) -> Client:
|
38
|
-
"""Retrieve the cloud logging client, initializing it if necessary."""
|
39
|
-
cls.initialize()
|
40
|
-
return cls._client
|
41
|
-
|
42
|
-
@classmethod
|
43
|
-
def create_handler(cls, name:str):
|
44
|
-
cls.initialize()
|
45
|
-
return CloudLoggingHandler(client=cls._client, name=name)
|
46
|
-
|
47
|
-
class GoogleCloudLoggingV2(GoogleCloudClientManager):
|
48
|
-
def __init__(self, google_credentials_path = None) -> None:
|
49
|
-
super().__init__(google_credentials_path)
|
50
|
-
self._client = Client(credentials=self._credentials)
|
51
|
-
self._client.setup_logging()
|
52
|
-
|
53
|
-
@property
|
54
|
-
def client(self) -> Client:
|
55
|
-
return self._client
|
56
|
-
|
57
|
-
def dispose(self) -> None:
|
58
|
-
if self._client is not None:
|
59
|
-
self._client = None
|
60
|
-
return super().dispose()
|
61
|
-
|
62
|
-
def create_handler(self, name:str) -> CloudLoggingHandler:
|
63
|
-
return CloudLoggingHandler(client=self._client, name=name)
|
@@ -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
|
maleo_foundation/db/session.py
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
from contextlib import contextmanager
|
2
|
-
from sqlalchemy import Engine
|
3
|
-
from sqlalchemy.orm import sessionmaker, Session
|
4
|
-
from sqlalchemy.exc import SQLAlchemyError
|
5
|
-
from typing import Generator, Optional
|
6
|
-
from maleo_foundation.utils.logger import BaseLogger
|
7
|
-
|
8
|
-
class SessionManager:
|
9
|
-
_logger:Optional[BaseLogger] = None
|
10
|
-
_sessionmaker:Optional[sessionmaker[Session]] = None
|
11
|
-
|
12
|
-
@classmethod
|
13
|
-
def initialize(cls, logger:BaseLogger, engine:Engine) -> None:
|
14
|
-
"""Initialize the sessionmaker if not already initialized."""
|
15
|
-
if cls._sessionmaker is None:
|
16
|
-
cls._logger = logger
|
17
|
-
cls._sessionmaker = sessionmaker(bind=engine, expire_on_commit=False)
|
18
|
-
cls._logger.info("SessionManager initialized successfully.")
|
19
|
-
|
20
|
-
@classmethod
|
21
|
-
def _session_handler(cls) -> Generator[Session, None, None]:
|
22
|
-
"""Reusable function for managing database sessions."""
|
23
|
-
if cls._logger is None:
|
24
|
-
raise RuntimeError("Logger has not been initialized. Call initialize() first.")
|
25
|
-
if cls._sessionmaker is None:
|
26
|
-
raise RuntimeError("SessionLocal has not been initialized. Call initialize() first.")
|
27
|
-
|
28
|
-
session = cls._sessionmaker()
|
29
|
-
cls._logger.debug("New database session created.")
|
30
|
-
try:
|
31
|
-
yield session #* Provide session
|
32
|
-
session.commit() #* Auto-commit on success
|
33
|
-
except SQLAlchemyError as e:
|
34
|
-
session.rollback() #* Rollback on error
|
35
|
-
cls._logger.error(f"[SQLAlchemyError] Database transaction failed: {e}", exc_info=True)
|
36
|
-
raise
|
37
|
-
except Exception as e:
|
38
|
-
session.rollback() #* Rollback on error
|
39
|
-
cls._logger.error(f"[Exception] Database transaction failed: {e}", exc_info=True)
|
40
|
-
raise
|
41
|
-
finally:
|
42
|
-
session.close() #* Ensure session closes
|
43
|
-
cls._logger.debug("Database session closed.")
|
44
|
-
|
45
|
-
@classmethod
|
46
|
-
def inject(cls) -> Generator[Session, None, None]:
|
47
|
-
"""Returns a generator that yields a SQLAlchemy session for dependency injection."""
|
48
|
-
return cls._session_handler()
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
@contextmanager
|
52
|
-
def get(cls) -> Generator[Session, None, None]:
|
53
|
-
"""Context manager for manual session handling. Supports `with SessionManager.get() as session:`"""
|
54
|
-
yield from cls._session_handler()
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def dispose(cls) -> None:
|
58
|
-
"""Dispose of the sessionmaker and release any resources."""
|
59
|
-
if cls._sessionmaker is not None:
|
60
|
-
cls._sessionmaker.close_all()
|
61
|
-
cls._sessionmaker = None
|
62
|
-
|
63
|
-
cls._logger.info("SessionManager disposed successfully.")
|
64
|
-
cls._logger = None
|
65
|
-
|
66
|
-
class SessionManagerV2:
|
67
|
-
def __init__(self, logger:BaseLogger, sessionmaker:sessionmaker[Session]):
|
68
|
-
self._logger = logger
|
69
|
-
self._sessionmaker = sessionmaker
|
70
|
-
|
71
|
-
def _session_handler(self) -> Generator[Session, None, None]:
|
72
|
-
"""Reusable function for managing database sessions."""
|
73
|
-
if self._logger is None:
|
74
|
-
raise RuntimeError("Logger has not been initialized. Call initialize() first.")
|
75
|
-
if self._sessionmaker is None:
|
76
|
-
raise RuntimeError("SessionLocal has not been initialized. Call initialize() first.")
|
77
|
-
|
78
|
-
session = self._sessionmaker()
|
79
|
-
self._logger.debug("New database session created.")
|
80
|
-
try:
|
81
|
-
yield session #* Provide session
|
82
|
-
session.commit() #* Auto-commit on success
|
83
|
-
except SQLAlchemyError as e:
|
84
|
-
session.rollback() #* Rollback on error
|
85
|
-
self._logger.error(f"[SQLAlchemyError] Database transaction failed: {e}", exc_info=True)
|
86
|
-
raise
|
87
|
-
except Exception as e:
|
88
|
-
session.rollback() #* Rollback on error
|
89
|
-
self._logger.error(f"[Exception] Database transaction failed: {e}", exc_info=True)
|
90
|
-
raise
|
91
|
-
finally:
|
92
|
-
session.close() #* Ensure session closes
|
93
|
-
self._logger.debug("Database session closed.")
|
94
|
-
|
95
|
-
def inject(self) -> Generator[Session, None, None]:
|
96
|
-
"""Returns a generator that yields a SQLAlchemy session for dependency injection."""
|
97
|
-
return self._session_handler()
|
98
|
-
|
99
|
-
@contextmanager
|
100
|
-
def get(self) -> Generator[Session, None, None]:
|
101
|
-
"""Context manager for manual session handling. Supports `with SessionManager.get() as session:`"""
|
102
|
-
yield from self._session_handler()
|
103
|
-
|
104
|
-
def dispose(self) -> None:
|
105
|
-
"""Dispose of the sessionmaker and release any resources."""
|
106
|
-
if self._sessionmaker is not None:
|
107
|
-
self._sessionmaker.close_all()
|
108
|
-
self._sessionmaker = None
|
109
|
-
|
110
|
-
self._logger.info("SessionManager disposed successfully.")
|
111
|
-
self._logger = None
|
maleo_foundation/utils/logger.py
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import os
|
3
|
-
from datetime import datetime
|
4
|
-
from typing import Callable, Optional
|
5
|
-
from maleo_foundation.clients.google.cloud.logging import GoogleCloudLoggingV2
|
6
|
-
from maleo_foundation.enums import BaseEnums
|
7
|
-
from maleo_foundation.types import BaseTypes
|
8
|
-
|
9
|
-
class BaseLogger(logging.Logger):
|
10
|
-
def __init__(
|
11
|
-
self,
|
12
|
-
base_dir:str,
|
13
|
-
type:BaseEnums.LoggerType,
|
14
|
-
service_name:BaseTypes.OptionalString = None,
|
15
|
-
client_name:BaseTypes.OptionalString = None,
|
16
|
-
level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO,
|
17
|
-
cloud_logging_manager:Optional[GoogleCloudLoggingV2] = None
|
18
|
-
):
|
19
|
-
"""
|
20
|
-
Custom extended logger with file, console, and Google Cloud Logging.
|
21
|
-
|
22
|
-
- Logs are stored in `base_dir/logs/{type}`
|
23
|
-
- Uses Google Cloud Logging if configured
|
24
|
-
|
25
|
-
Args:
|
26
|
-
base_dir (str): Base directory for logs (e.g., "/path/to/maleo_security")
|
27
|
-
type (str): Log type (e.g., "application", "middleware")
|
28
|
-
service_name (str): The service name (e.g., "maleo_security")
|
29
|
-
"""
|
30
|
-
#* Ensure service_name exists
|
31
|
-
service_name = service_name or os.getenv("SERVICE_NAME")
|
32
|
-
if service_name is None:
|
33
|
-
raise ValueError("SERVICE_NAME environment variable must be set if 'service_name' is set to None")
|
34
|
-
|
35
|
-
#* Ensure client_name is valid if logger type is a client
|
36
|
-
if type == BaseEnums.LoggerType.CLIENT and client_name is None:
|
37
|
-
raise ValueError("'client_name' parameter must be provided if 'logger_type' is 'client'")
|
38
|
-
|
39
|
-
self.type = type #* Define logger type
|
40
|
-
|
41
|
-
#* Define logger name
|
42
|
-
if self.type == BaseEnums.LoggerType.CLIENT:
|
43
|
-
self.name = f"{service_name} - {self.type} - {client_name}"
|
44
|
-
else:
|
45
|
-
self.name = f"{service_name} - {self.type}"
|
46
|
-
super().__init__(self.name, level)
|
47
|
-
|
48
|
-
#* Clear existing handlers to prevent duplicates
|
49
|
-
for handler in list(self.handlers):
|
50
|
-
self.removeHandler(handler)
|
51
|
-
handler.close()
|
52
|
-
|
53
|
-
#* Formatter for logs
|
54
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
55
|
-
|
56
|
-
#* Console handler
|
57
|
-
console_handler = logging.StreamHandler()
|
58
|
-
console_handler.setFormatter(formatter)
|
59
|
-
self.addHandler(console_handler)
|
60
|
-
|
61
|
-
#* Google Cloud Logging handler (If enabled)
|
62
|
-
if cloud_logging_manager is not None:
|
63
|
-
cloud_logging_handler = cloud_logging_manager.create_handler(name=self.name)
|
64
|
-
self.addHandler(cloud_logging_handler)
|
65
|
-
else:
|
66
|
-
self.info("Cloud logging is not configured.")
|
67
|
-
|
68
|
-
#* Define log directory
|
69
|
-
if type == BaseEnums.LoggerType.CLIENT:
|
70
|
-
log_dir = f"logs/{type}/{client_name}"
|
71
|
-
else:
|
72
|
-
log_dir = f"logs/{type}"
|
73
|
-
full_log_dir = os.path.join(base_dir, log_dir)
|
74
|
-
os.makedirs(full_log_dir, exist_ok=True)
|
75
|
-
|
76
|
-
#* Generate timestamped filename
|
77
|
-
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
78
|
-
log_filename = os.path.join(full_log_dir, f"{timestamp}.log")
|
79
|
-
|
80
|
-
#* File handler
|
81
|
-
file_handler = logging.FileHandler(log_filename, mode="a")
|
82
|
-
file_handler.setFormatter(formatter)
|
83
|
-
self.addHandler(file_handler)
|
84
|
-
|
85
|
-
def dispose(self):
|
86
|
-
"""Dispose of the logger by removing all handlers."""
|
87
|
-
for handler in list(self.handlers):
|
88
|
-
self.removeHandler(handler)
|
89
|
-
handler.close()
|
90
|
-
self.handlers.clear()
|
91
|
-
|
92
|
-
LoggerFactory = Callable[[], BaseLogger]
|
File without changes
|
File without changes
|
File without changes
|