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.
@@ -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, Optional, Type
8
+ from typing import Generator
9
9
  from maleo_foundation.types import BaseTypes
10
- from maleo_foundation.utils.logger import BaseLogger
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:BaseLogger, engine:Engine):
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:BaseLogger,
64
+ logger:ServiceLogger,
66
65
  url:BaseTypes.OptionalString = None
67
66
  ):
68
67
  self._metadata = metadata #* Define database metadata
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import os
3
- import uvicorn
4
3
  from fastapi import FastAPI, APIRouter
5
4
  from fastapi.exceptions import RequestValidationError
6
5
  from starlette.exceptions import HTTPException
@@ -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.logger import BaseLogger, LoggerFactory
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.35
3
+ Version: 0.1.36
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -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=Jf0w-9JOAG5X2quDxqqw6d2WUIX7MYGdlPGxWP0rxHs,4699
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=un1rJA9YdFK-8QRDQPurBIsHtRlmaVd2Z0-Lzr2o7_c,20392
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=nk3rD57fDR-D7BQkU1JEKV-Mu7FGMpLSEsqxdDZdKjU,4532
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.35.dist-info/METADATA,sha256=plzE197g_RRnn3THYEHohLmIL9ZZyGOwGt8dKnyiN44,3190
86
- maleo_foundation-0.1.35.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
87
- maleo_foundation-0.1.35.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
88
- maleo_foundation-0.1.35.dist-info/RECORD,,
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,7 +0,0 @@
1
- from __future__ import annotations
2
- from .general import BaseGeneralClients
3
- from .google import GoogleClients
4
-
5
- class BaseClients:
6
- General = BaseGeneralClients
7
- Google = GoogleClients
@@ -1,4 +0,0 @@
1
- from .http import HTTPClientManager
2
-
3
- class BaseGeneralClients:
4
- HTTP = HTTPClientManager
@@ -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,4 +0,0 @@
1
- from .cloud import GoogleCloudClients
2
-
3
- class GoogleClients:
4
- Cloud = GoogleCloudClients
@@ -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,8 +0,0 @@
1
- from .logging import GoogleCloudLogging
2
- from .secret import GoogleSecretManager
3
- from .storage import GoogleCloudStorage
4
-
5
- class GoogleCloudClients:
6
- Logging = GoogleCloudLogging
7
- Secret = GoogleSecretManager
8
- Storage = GoogleCloudStorage
@@ -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,5 +0,0 @@
1
- from __future__ import annotations
2
- from .logger import ClientLoggerManager
3
-
4
- class BaseClientUtils:
5
- Logger = ClientLoggerManager
@@ -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()
@@ -1,4 +0,0 @@
1
- from .table import BaseTable
2
- from .engine import EngineManager
3
- from .session import SessionManager
4
- from .manager import DatabaseManager
@@ -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
@@ -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
@@ -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
@@ -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