maleo-foundation 0.0.85__py3-none-any.whl → 0.0.87__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.
Files changed (29) hide show
  1. maleo_foundation/clients/google/base.py +32 -0
  2. maleo_foundation/clients/google/cloud/base.py +32 -0
  3. maleo_foundation/clients/google/cloud/logging.py +20 -1
  4. maleo_foundation/clients/google/cloud/secret.py +58 -1
  5. maleo_foundation/clients/google/cloud/storage.py +35 -2
  6. maleo_foundation/clients/google/secret.py +61 -0
  7. maleo_foundation/clients/google/storage.py +57 -0
  8. maleo_foundation/db/engine.py +37 -1
  9. maleo_foundation/db/manager.py +111 -3
  10. maleo_foundation/db/session.py +48 -1
  11. maleo_foundation/enums.py +9 -0
  12. maleo_foundation/managers/__init__.py +0 -0
  13. maleo_foundation/managers/client/__init__.py +0 -0
  14. maleo_foundation/managers/client/base.py +33 -0
  15. maleo_foundation/managers/client/google/__init__.py +0 -0
  16. maleo_foundation/managers/client/google/base.py +26 -0
  17. maleo_foundation/managers/client/google/secret.py +65 -0
  18. maleo_foundation/managers/client/google/storage.py +60 -0
  19. maleo_foundation/managers/client/http.py +40 -0
  20. maleo_foundation/managers/db.py +123 -0
  21. maleo_foundation/managers/service.py +251 -0
  22. maleo_foundation/models/transfers/general/token.py +1 -1
  23. maleo_foundation/utils/keyloader.py +1 -1
  24. maleo_foundation/utils/logger.py +17 -14
  25. maleo_foundation/utils/logging.py +182 -0
  26. {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/METADATA +1 -1
  27. {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/RECORD +29 -14
  28. {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/WHEEL +1 -1
  29. {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,65 @@
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, logs_dir, google_cloud_logging=None, credentials_path=None) -> None:
9
+ self._key = "google-secret-manager"
10
+ self._name = "GoogleSecretManager"
11
+ super().__init__(
12
+ key=self._key,
13
+ name=self._name,
14
+ logs_dir=logs_dir,
15
+ google_cloud_logging=google_cloud_logging,
16
+ credentials_path=credentials_path
17
+ )
18
+ self._client = secretmanager.SecretManagerServiceClient(credentials=self._credentials)
19
+
20
+ @property
21
+ def client(self) -> secretmanager.SecretManagerServiceClient:
22
+ return self._client
23
+
24
+ def dispose(self) -> None:
25
+ if self._client is not None:
26
+ self._client = None
27
+
28
+ @retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
29
+ def get(self, name:str, version:str = "latest") -> Optional[str]:
30
+ try:
31
+ secret_path = f"projects/{self._project_id}/secrets/{name}/versions/{version}"
32
+ request = secretmanager.AccessSecretVersionRequest(name=secret_path)
33
+ response = self._client.access_secret_version(request=request)
34
+ self._logger.info("Successfully retrieved secret '%s' with version '%s'", name, version)
35
+ return response.payload.data.decode()
36
+ except Exception:
37
+ self._logger.error("Exception occured with retrieving secret '%s' with version '%s'", name, version, exc_info=True)
38
+ return None
39
+
40
+ @retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
41
+ def create(self, name:str, data:str) -> Optional[str]:
42
+ parent = f"projects/{self._project_id}"
43
+ secret_path = f"{parent}/secrets/{name}"
44
+ try:
45
+ #* Check if the secret already exists
46
+ request = secretmanager.GetSecretRequest(name=secret_path)
47
+ self._client.get_secret(request=request)
48
+
49
+ except NotFound:
50
+ #* Secret does not exist, create it first
51
+ try:
52
+ secret = secretmanager.Secret(name=name, replication={"automatic": {}})
53
+ request = secretmanager.CreateSecretRequest(parent=parent, secret_id=name, secret=secret)
54
+ self._client.create_secret(request=request)
55
+ except Exception as e:
56
+ return None
57
+
58
+ #* Add a new secret version
59
+ try:
60
+ payload = secretmanager.SecretPayload(data=data.encode())
61
+ request = secretmanager.AddSecretVersionRequest(parent=secret_path, payload=payload)
62
+ response = self._client.add_secret_version(request=request)
63
+ return data
64
+ except Exception as e:
65
+ return None
@@ -0,0 +1,60 @@
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, logs_dir, google_cloud_logging=None, credentials_path=None, bucket_name:BaseTypes.OptionalString=None) -> None:
9
+ self._key = "google-secret-manager"
10
+ self._name = "GoogleSecretManager"
11
+ super().__init__(
12
+ key=self._key,
13
+ name=self._name,
14
+ logs_dir=logs_dir,
15
+ google_cloud_logging=google_cloud_logging,
16
+ credentials_path=credentials_path
17
+ )
18
+ self._client = Client(credentials=self._credentials)
19
+ self._bucket_name = bucket_name or os.getenv("GCS_BUCKET_NAME")
20
+ if self._bucket_name is None:
21
+ self._client.close()
22
+ raise ValueError("GCS_BUCKET_NAME environment variable must be set if 'bucket_name' is set to None")
23
+ self._bucket = self._client.lookup_bucket(bucket_name=self._bucket_name)
24
+ if self._bucket is None:
25
+ self._client.close()
26
+ raise ValueError(f"Bucket '{self._bucket_name}' does not exist.")
27
+
28
+ @property
29
+ def bucket_name(self) -> str:
30
+ return self._bucket_name
31
+
32
+ @property
33
+ def bucket(self) -> Bucket:
34
+ return self._bucket
35
+
36
+ def dispose(self) -> None:
37
+ if self._client is not None:
38
+ self._client.close()
39
+ self._client = None
40
+
41
+ def generate_signed_url(self, location:str) -> str:
42
+ """
43
+ generate signed URL of a file in the bucket based on its location.
44
+
45
+ Args:
46
+ location: str
47
+ Location of the file inside the bucket
48
+
49
+ Returns:
50
+ str: File's pre-signed download url
51
+
52
+ Raises:
53
+ ValueError: If the file does not exist
54
+ """
55
+ blob = self._bucket.blob(blob_name=location)
56
+ if not blob.exists():
57
+ raise ValueError(f"File '{location}' did not exists.")
58
+
59
+ url = blob.generate_signed_url(version="v4", expiration=timedelta(minutes=15), method="GET")
60
+ return url
@@ -0,0 +1,40 @@
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
+
8
+ @classmethod
9
+ def initialize(cls) -> None:
10
+ """Initialize the HTTP client if not already initialized."""
11
+ if cls._client is None:
12
+ cls._client = httpx.AsyncClient()
13
+
14
+ @classmethod
15
+ async def _client_handler(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
16
+ """Reusable generator for client handling."""
17
+ if cls._client is None:
18
+ raise RuntimeError("Client has not been initialized. Call initialize first.")
19
+ yield cls._client
20
+
21
+ @classmethod
22
+ async def inject_client(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
23
+ return cls._client_handler()
24
+
25
+ @classmethod
26
+ @asynccontextmanager
27
+ async def get_client(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
28
+ """
29
+ Async context manager for manual HTTP client handling.
30
+ Supports `async with HTTPClientManager.get() as client:`
31
+ """
32
+ async for client in cls._client_handler():
33
+ yield client
34
+
35
+ @classmethod
36
+ async def dispose(cls) -> None:
37
+ """Dispose of the HTTP client and release any resources."""
38
+ if cls._client is not None:
39
+ await cls._client.aclose()
40
+ cls._client = None
@@ -0,0 +1,123 @@
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 maleo_foundation.db.table import BaseTable
12
+
13
+ class MetadataManager:
14
+ _Base:DeclarativeMeta = declarative_base()
15
+
16
+ @property
17
+ def Base(self) -> DeclarativeMeta:
18
+ return self._Base
19
+
20
+ @property
21
+ def metadata(self) -> MetaData:
22
+ return self._Base.metadata
23
+
24
+ class SessionManager:
25
+ def __init__(self, logger:BaseLogger, engine:Engine):
26
+ self._logger = logger
27
+ self._logger.info("Initializing SessionMaker.")
28
+ self._sessionmaker:sessionmaker[Session] = sessionmaker(bind=engine, expire_on_commit=False)
29
+ self._logger.info("SessionMaker initialized successfully.")
30
+
31
+ def _session_handler(self) -> Generator[Session, None, None]:
32
+ """Reusable function for managing database sessions."""
33
+ session = self._sessionmaker()
34
+ self._logger.debug("New database session created.")
35
+ try:
36
+ yield session #* Provide session
37
+ session.commit() #* Auto-commit on success
38
+ except SQLAlchemyError as e:
39
+ session.rollback() #* Rollback on error
40
+ self._logger.error(f"[SQLAlchemyError] Database transaction failed: {e}", exc_info=True)
41
+ raise
42
+ except Exception as e:
43
+ session.rollback() #* Rollback on error
44
+ self._logger.error(f"[Exception] Database transaction failed: {e}", exc_info=True)
45
+ raise
46
+ finally:
47
+ session.close() #* Ensure session closes
48
+ self._logger.debug("Database session closed.")
49
+
50
+ def inject(self) -> Generator[Session, None, None]:
51
+ """Returns a generator that yields a SQLAlchemy session for dependency injection."""
52
+ return self._session_handler()
53
+
54
+ @contextmanager
55
+ def get(self) -> Generator[Session, None, None]:
56
+ """Context manager for manual session handling. Supports `with SessionManager.get() as session:`"""
57
+ yield from self._session_handler()
58
+
59
+ def dispose(self) -> None:
60
+ """Dispose of the sessionmaker and release any resources."""
61
+ if self._sessionmaker is not None:
62
+ self._sessionmaker.close_all()
63
+ self._sessionmaker = None
64
+
65
+ self._logger.info("SessionManager disposed successfully.")
66
+ self._logger = None
67
+
68
+ class DatabaseManager:
69
+ def __init__(
70
+ self,
71
+ metadata:MetaData,
72
+ logger:BaseLogger,
73
+ url:BaseTypes.OptionalString = None
74
+ ):
75
+ self._metadata = metadata #* Define database metadata
76
+ self._logger = logger #* Define database logger
77
+
78
+ #* Create engine
79
+ url = url or os.getenv("DB_URL")
80
+ if url is None:
81
+ raise ValueError("DB_URL environment variable must be set if url is not provided")
82
+ self._logger.info("Creating SQlAlchemy engine")
83
+ self._engine = create_engine(url=url, echo=False, pool_pre_ping=True, pool_recycle=3600)
84
+ self._logger.info("SQlAlchemy engine created")
85
+
86
+ #* Creating all table from metadata
87
+ self._logger.info("Creating all tables defined in metadata")
88
+ self._metadata.create_all(bind=self._engine) #* Create all tables
89
+ self._logger.info("Created all tables defined in metadata")
90
+
91
+ #* Initializing session manager
92
+ self._logger.info("Initializing session manager")
93
+ self._session = SessionManager(logger=self._logger, engine=self._engine) #* Define session
94
+ self._logger.info("Session manager initialized")
95
+
96
+ @property
97
+ def metadata(self) -> MetaData:
98
+ return self._metadata
99
+
100
+ @property
101
+ def engine(self) -> Engine:
102
+ return self._engine
103
+
104
+ @property
105
+ def session(self) -> SessionManager:
106
+ return self._session
107
+
108
+ def dispose(self) -> None:
109
+ #* Dispose session
110
+ if self._session is not None:
111
+ self._session.dispose()
112
+ self._session = None
113
+ #* Dispose engine
114
+ if self._engine is not None:
115
+ self._engine.dispose()
116
+ self._engine = None
117
+ #* Dispose logger
118
+ if self._logger is not None:
119
+ self._logger.dispose()
120
+ self._logger = None
121
+ #* Dispose metadata
122
+ if self._metadata is not None:
123
+ self._metadata = None
@@ -0,0 +1,251 @@
1
+ import json
2
+ import os
3
+ from pydantic_settings import BaseSettings
4
+ from pydantic import BaseModel, Field
5
+ from sqlalchemy import MetaData
6
+ from typing import Dict, List, Optional, Type
7
+ from maleo_foundation.db.manager import DatabaseManagerV2
8
+ from maleo_foundation.enums import BaseEnums
9
+ from maleo_foundation.models.transfers.general.token import BaseTokenGeneralTransfers
10
+ from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
11
+ from maleo_foundation.managers.client.google.secret import GoogleSecretManager
12
+ from maleo_foundation.managers.client.google.storage import GoogleCloudStorage
13
+ from maleo_foundation.managers.client.http import HTTPClientManager
14
+ from maleo_foundation.managers.db import DatabaseManager
15
+ from maleo_foundation.services.token import BaseTokenService
16
+ from maleo_foundation.types import BaseTypes
17
+ from maleo_foundation.utils.keyloader import load_key
18
+ from maleo_foundation.utils.logging import GoogleCloudLogging, ServiceLogger
19
+
20
+ class Settings(BaseSettings):
21
+ GOOGLE_CREDENTIALS_PATH:str = Field("/creds/maleo-google-service-account.json", description="Internal credential's file path")
22
+ INTERNAL_CREDENTIALS_PATH:str = Field("/creds/maleo-internal-service-account.json", description="Internal credential's file path")
23
+ PRIVATE_KEY_PATH:str = Field("/keys/maleo-private-key.pem", description="Maleo's private key path")
24
+ PUBLIC_KEY_PATH:str = Field("/keys/maleo-public-key.pem", description="Maleo's public key path")
25
+ KEY_PASSWORD:str = Field(..., description="Maleo key's password")
26
+ CONFIGURATIONS_PATH:str = Field("...", description="Service's configuration file path")
27
+
28
+ class Keys(BaseModel):
29
+ password:str = Field(..., description="Key's password")
30
+ private:str = Field(..., description="Private key")
31
+ public:str = Field(..., description="Public key")
32
+
33
+ class GoogleCredentials(BaseModel):
34
+ type:str = Field(..., description="Credentials type")
35
+ project_id:str = Field(..., description="Google project ID")
36
+ private_key_id:str = Field(..., description="Private key ID")
37
+ private_key:str = Field(..., description="Private key")
38
+ client_email:str = Field(..., description="Client email")
39
+ client_id:str = Field(..., description="Client ID")
40
+ auth_uri:str = Field(..., description="Authorization URI")
41
+ token_uri:str = Field(..., description="Token URI")
42
+ auth_provider_x509_cert_url:str = Field(..., description="Authorization provider x509 certificate URL")
43
+ client_x509_cert_url:str = Field(..., description="Client x509 certificate URL")
44
+ universe_domain:str = Field(..., description="Universe domains")
45
+
46
+ class InternalCredentials(BaseModel):
47
+ system_role:str = Field(..., description="System role")
48
+ username:str = Field(..., description="Username")
49
+ email:str = Field(..., description="Email")
50
+ user_type:str = Field(..., description="User type")
51
+
52
+ class Credentials(BaseModel):
53
+ google:GoogleCredentials = Field(..., description="Google's credentials")
54
+ internal:InternalCredentials = Field(..., description="Internal's credentials")
55
+
56
+ class ServiceConfigurations(BaseModel):
57
+ key:str = Field(..., description="Service's key")
58
+ name:str = Field(..., description="Service's name")
59
+
60
+ class MiddlewareConfigurations(BaseModel):
61
+ allowed_origins:List[str] = Field(default_factory=list, description="Allowed origins")
62
+ service_ips:List[str] = Field(default_factory=list, description="Other service's IPs")
63
+
64
+ class DatabaseConfigurations(BaseModel):
65
+ username:str = Field("postgres", description="Database user's username")
66
+ password:str = Field(..., description="Database user's password")
67
+ host:str = Field(..., description="Database's host")
68
+ port:str = Field("5432", description="Database's port")
69
+ database:str = Field(..., description="Database")
70
+
71
+ @property
72
+ def url(self) -> str:
73
+ return f"postgresql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}"
74
+
75
+ class Configurations(BaseModel):
76
+ service:ServiceConfigurations = Field(..., description="Service's configurations")
77
+ middleware:MiddlewareConfigurations = Field(..., description="Middleware's configurations")
78
+ database:DatabaseConfigurations = Field(..., description="Database's configurations")
79
+
80
+ class Loggers(BaseModel):
81
+ application:ServiceLogger = Field(..., description="Application logger")
82
+ database:ServiceLogger = Field(..., description="Database logger")
83
+ middleware:ServiceLogger = Field(..., description="Middleware logger")
84
+
85
+ class GoogleClientManagers(BaseModel):
86
+ secret:GoogleSecretManager = Field(..., description="Google secret manager client manager")
87
+ storage:GoogleCloudStorage = Field(..., description="Google cloud storage client manager")
88
+
89
+ class ClientManagers(BaseModel):
90
+ google:GoogleClientManagers = Field(..., description="Google client's managers")
91
+ http:HTTPClientManager = Field(..., description="HTTP client's manager")
92
+
93
+ class ServiceManager:
94
+ def __init__(
95
+ self,
96
+ db_metadata:MetaData,
97
+ base_dir:BaseTypes.OptionalString = None,
98
+ settings:Optional[Settings] = None,
99
+ google_cloud_logging:Optional[GoogleCloudLogging] = None
100
+ ):
101
+ self._db_metadata = db_metadata
102
+
103
+ if base_dir is None:
104
+ self._base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
105
+ else:
106
+ self._base_dir = base_dir
107
+
108
+ self._logs_dir = os.path.join(self._base_dir, "logs")
109
+
110
+ #* Initialize settings
111
+ if settings is None:
112
+ self._settings = Settings()
113
+ else:
114
+ self._settings = settings
115
+
116
+ #* Load configs
117
+ self._load_configs()
118
+
119
+ #* Initialize google cloud logging
120
+ if google_cloud_logging is None:
121
+ self._google_cloud_logging = GoogleCloudLogging()
122
+ else:
123
+ self._google_cloud_logging = google_cloud_logging
124
+
125
+ self._initialize_loggers()
126
+ self._load_credentials()
127
+ self._parse_keys()
128
+ self._initialize_db()
129
+ self._initialize_clients()
130
+
131
+ @property
132
+ def base_dir(self) -> str:
133
+ return self._base_dir
134
+
135
+ @property
136
+ def logs_dir(self) -> str:
137
+ return self._logs_dir
138
+
139
+ @property
140
+ def settings(self) -> Settings:
141
+ return self._settings
142
+
143
+ def _load_configs(self) -> None:
144
+ with open(self._settings.CONFIGURATIONS_PATH) as f:
145
+ data = json.load(f)
146
+ self._configs = Configurations.model_validate(data)
147
+
148
+ @property
149
+ def configs(self) -> str:
150
+ return self._configs
151
+
152
+ def _initialize_loggers(self) -> None:
153
+ application = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.APPLICATION, google_cloud_logging=self._google_cloud_logging)
154
+ database = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.DATABASE, google_cloud_logging=self._google_cloud_logging)
155
+ middleware = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.MIDDLEWARE, google_cloud_logging=self._google_cloud_logging)
156
+ self._loggers = Loggers(application=application, database=database, middleware=middleware)
157
+
158
+ @property
159
+ def loggers(self) -> Loggers:
160
+ return self._loggers
161
+
162
+ def _load_credentials(self) -> None:
163
+ #* Load google credentials
164
+ with open(self._settings.GOOGLE_CREDENTIALS_PATH) as f:
165
+ data = json.load(f)
166
+ google = GoogleCredentials.model_validate(data)
167
+ #* Load internal credentials
168
+ with open(self._settings.INTERNAL_CREDENTIALS_PATH) as f:
169
+ data = json.load(f)
170
+ internal = InternalCredentials.model_validate(data)
171
+ self._credentials = Credentials(google=google, internal=internal)
172
+
173
+ @property
174
+ def credentials(self) -> Credentials:
175
+ return self._credentials
176
+
177
+ def _parse_keys(self) -> None:
178
+ #* Parse private key
179
+ key_type = BaseEnums.KeyType.PRIVATE
180
+ private = load_key(
181
+ type=key_type,
182
+ path=self._settings.PRIVATE_KEY_PATH,
183
+ password=self._settings.KEY_PASSWORD
184
+ )
185
+ #* Parse public key
186
+ key_type = BaseEnums.KeyType.PUBLIC
187
+ public = load_key(
188
+ type=key_type,
189
+ path=self._settings.PUBLIC_KEY_PATH
190
+ )
191
+ self._keys = Keys(password=self._settings.KEY_PASSWORD, private=private, public=public)
192
+
193
+ @property
194
+ def keys(self) -> Keys:
195
+ return self._keys
196
+
197
+ def _initialize_db(self) -> None:
198
+ self._database = DatabaseManager(metadata=self._db_metadata, logger=self._loggers.database, url=self._configs.database.url)
199
+
200
+ @property
201
+ def database(self) -> DatabaseManager:
202
+ return self._database
203
+
204
+ def _initialize_clients(self) -> None:
205
+ #* Initialize google clients
206
+ secret = GoogleSecretManager(logs_dir=self._logs_dir, google_cloud_logging=self._google_cloud_logging)
207
+ storage = GoogleCloudStorage(logs_dir=self._logs_dir, google_cloud_logging=self._google_cloud_logging)
208
+ self._google_clients = GoogleClientManagers(secret=secret, storage=storage)
209
+ #* Initialize http clients
210
+ http = HTTPClientManager.initialize()
211
+ self._clients = ClientManagers(google=self._google_clients, http=http)
212
+
213
+ @property
214
+ def google_clients(self) -> GoogleClientManagers:
215
+ return self._google_clients
216
+
217
+ @property
218
+ def client_managers(self) -> ClientManagers:
219
+ return self._clients
220
+
221
+ @property
222
+ def token(self) -> str:
223
+ payload = BaseTokenGeneralTransfers.BaseEncodePayload(
224
+ sr=self._credentials.internal.system_role,
225
+ u_u=self._credentials.internal.username,
226
+ u_e=self._credentials.internal.email,
227
+ u_ut=self._credentials.internal.user_type
228
+ )
229
+ parameters = BaseTokenParametersTransfers.Encode(
230
+ key=self.keys.private,
231
+ password=self.keys.password,
232
+ payload=payload
233
+ )
234
+ result = BaseTokenService.encode(parameters=parameters)
235
+ if not result.success:
236
+ raise ValueError("Failed generating token")
237
+ return result.data.token
238
+
239
+ async def dispose(self) -> None:
240
+ if self._database is not None:
241
+ self._database.dispose()
242
+ self._database = None
243
+ if self._clients is not None:
244
+ self._clients.google.storage.dispose()
245
+ self._clients.google.secret.dispose()
246
+ await self._clients.http.dispose()
247
+ if self._loggers is not None:
248
+ self._loggers.application.dispose()
249
+ self._loggers.database.dispose()
250
+ self._loggers.middleware.dispose()
251
+ self._loggers = None
@@ -20,7 +20,7 @@ class BaseTokenGeneralTransfers:
20
20
  exp_dt:datetime = Field(..., description="Expired at (datetime)")
21
21
  exp:int = Field(..., description="Expired at (integet)")
22
22
 
23
- class BaseEncodePayload(BasePayload, BaseTokenSchemas.ExpIn): pass
23
+ class BaseEncodePayload(BaseTokenSchemas.ExpIn, BasePayload): pass
24
24
 
25
25
  class EncodePayload(DecodePayload):
26
26
  iat_dt:datetime = Field(datetime.now(timezone.utc), description="Issued at (datetime)")
@@ -30,7 +30,7 @@ def load_key(
30
30
  if password is not None and not isinstance(password, (str, bytes)):
31
31
  raise TypeError("Invalid passsword type")
32
32
 
33
- if not isinstance(type, BaseEnums.KeyFormatType):
33
+ if not isinstance(format, BaseEnums.KeyFormatType):
34
34
  raise TypeError("Invalid key format type")
35
35
 
36
36
  key_data = file_path.read_bytes()
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
  import os
3
3
  from datetime import datetime
4
- from typing import Callable
5
- from maleo_foundation.clients.google.cloud.logging import GoogleCloudLogging
4
+ from typing import Callable, Optional
5
+ from maleo_foundation.clients.google.cloud.logging import GoogleCloudLoggingV2
6
6
  from maleo_foundation.enums import BaseEnums
7
7
  from maleo_foundation.types import BaseTypes
8
8
 
@@ -13,7 +13,8 @@ class BaseLogger(logging.Logger):
13
13
  type:BaseEnums.LoggerType,
14
14
  service_name:BaseTypes.OptionalString = None,
15
15
  client_name:BaseTypes.OptionalString = None,
16
- level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO
16
+ level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO,
17
+ cloud_logging_manager:Optional[GoogleCloudLoggingV2] = None
17
18
  ):
18
19
  """
19
20
  Custom extended logger with file, console, and Google Cloud Logging.
@@ -23,24 +24,26 @@ class BaseLogger(logging.Logger):
23
24
 
24
25
  Args:
25
26
  base_dir (str): Base directory for logs (e.g., "/path/to/maleo_security")
26
- service_name (str): The service name (e.g., "maleo_security")
27
27
  type (str): Log type (e.g., "application", "middleware")
28
+ service_name (str): The service name (e.g., "maleo_security")
28
29
  """
29
30
  #* Ensure service_name exists
30
31
  service_name = service_name or os.getenv("SERVICE_NAME")
31
32
  if service_name is None:
32
- raise ValueError("SERVICE_NAME environment variable must be set if service_name is set to None")
33
+ raise ValueError("SERVICE_NAME environment variable must be set if 'service_name' is set to None")
33
34
 
34
35
  #* Ensure client_name is valid if logger type is a client
35
36
  if type == BaseEnums.LoggerType.CLIENT and client_name is None:
36
37
  raise ValueError("'client_name' parameter must be provided if 'logger_type' is 'client'")
37
38
 
39
+ self.type = type #* Define logger type
40
+
38
41
  #* Define logger name
39
- if type == BaseEnums.LoggerType.CLIENT:
40
- name = f"{service_name} - {type} - {client_name}"
42
+ if self.type == BaseEnums.LoggerType.CLIENT:
43
+ self.name = f"{service_name} - {self.type} - {client_name}"
41
44
  else:
42
- name = f"{service_name} - {type}"
43
- super().__init__(name, level)
45
+ self.name = f"{service_name} - {self.type}"
46
+ super().__init__(self.name, level)
44
47
 
45
48
  #* Clear existing handlers to prevent duplicates
46
49
  for handler in list(self.handlers):
@@ -56,11 +59,11 @@ class BaseLogger(logging.Logger):
56
59
  self.addHandler(console_handler)
57
60
 
58
61
  #* Google Cloud Logging handler (If enabled)
59
- try:
60
- cloud_handler = GoogleCloudLogging.create_handler(name=name.replace(" ", ""))
61
- self.addHandler(cloud_handler)
62
- except Exception as e:
63
- self.warning(f"Failed to initialize Google Cloud Logging: {str(e)}")
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.")
64
67
 
65
68
  #* Define log directory
66
69
  if type == BaseEnums.LoggerType.CLIENT: