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.
- maleo_foundation/clients/google/base.py +32 -0
- maleo_foundation/clients/google/cloud/base.py +32 -0
- maleo_foundation/clients/google/cloud/logging.py +20 -1
- maleo_foundation/clients/google/cloud/secret.py +58 -1
- maleo_foundation/clients/google/cloud/storage.py +35 -2
- maleo_foundation/clients/google/secret.py +61 -0
- maleo_foundation/clients/google/storage.py +57 -0
- maleo_foundation/db/engine.py +37 -1
- maleo_foundation/db/manager.py +111 -3
- maleo_foundation/db/session.py +48 -1
- maleo_foundation/enums.py +9 -0
- maleo_foundation/managers/__init__.py +0 -0
- maleo_foundation/managers/client/__init__.py +0 -0
- maleo_foundation/managers/client/base.py +33 -0
- maleo_foundation/managers/client/google/__init__.py +0 -0
- maleo_foundation/managers/client/google/base.py +26 -0
- maleo_foundation/managers/client/google/secret.py +65 -0
- maleo_foundation/managers/client/google/storage.py +60 -0
- maleo_foundation/managers/client/http.py +40 -0
- maleo_foundation/managers/db.py +123 -0
- maleo_foundation/managers/service.py +251 -0
- maleo_foundation/models/transfers/general/token.py +1 -1
- maleo_foundation/utils/keyloader.py +1 -1
- maleo_foundation/utils/logger.py +17 -14
- maleo_foundation/utils/logging.py +182 -0
- {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/METADATA +1 -1
- {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/RECORD +29 -14
- {maleo_foundation-0.0.85.dist-info → maleo_foundation-0.0.87.dist-info}/WHEEL +1 -1
- {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(
|
|
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(
|
|
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()
|
maleo_foundation/utils/logger.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
self.addHandler(
|
|
62
|
-
|
|
63
|
-
self.
|
|
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:
|