maleo-foundation 0.1.18__py3-none-any.whl → 0.1.20__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.
@@ -0,0 +1,37 @@
1
+ from starlette.authentication import (
2
+ AuthCredentials,
3
+ BaseUser
4
+ )
5
+ from typing import Optional, Sequence
6
+
7
+ class Credentials(AuthCredentials):
8
+ def __init__(
9
+ self,
10
+ token:Optional[str] = None,
11
+ scopes:Optional[Sequence[str]] = None
12
+ ) -> None:
13
+ self.token = token
14
+ super().__init__(scopes)
15
+
16
+ class User(BaseUser):
17
+ def __init__(
18
+ self,
19
+ authenticated:bool = True,
20
+ username:str = "",
21
+ email:str = ""
22
+ ) -> None:
23
+ self._authenticated = authenticated
24
+ self._username = username
25
+ self._email = email
26
+
27
+ @property
28
+ def is_authenticated(self) -> bool:
29
+ return self._authenticated
30
+
31
+ @property
32
+ def display_name(self) -> str:
33
+ return self._username
34
+
35
+ @property
36
+ def identity(self) -> str:
37
+ return self._email
maleo_foundation/enums.py CHANGED
@@ -73,8 +73,11 @@ class BaseEnums:
73
73
  BaseEnums.RESTControllerResponseType.FILE: responses.FileResponse,
74
74
  }.get(self, responses.Response)
75
75
 
76
+ class MiddlewareLoggerType(StrEnum):
77
+ BASE = "base"
78
+ AUTHENTICATION = "authentication"
79
+
76
80
  class ServiceLoggerType(StrEnum):
77
- MIDDLEWARE = "middleware"
78
81
  DATABASE = "database"
79
82
  APPLICATION = "application"
80
83
 
@@ -0,0 +1,107 @@
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel, Field
3
+ from typing import List, Optional, Sequence
4
+ from maleo_foundation.middlewares.authentication import Backend, add_authentication_middleware
5
+ from maleo_foundation.middlewares.base import add_base_middleware, RequestProcessor
6
+ from maleo_foundation.middlewares.cors import add_cors_middleware
7
+ from maleo_foundation.utils.logging import MiddlewareLogger
8
+
9
+ _ALLOW_METHODS:List[str] = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
10
+ _ALLOW_HEADERS:List[str] = ["X-Organization", "X-User", "X-Signature"]
11
+ _EXPOSE_HEADERS:List[str] = ["X-Request-Timestamp", "X-Response-Timestamp", "X-Process-Time", "X-Signature"]
12
+
13
+ class GeneralMiddlewareConfigurations(BaseModel):
14
+ allow_origins:List[str] = Field(default_factory=list, description="Allowed origins")
15
+ allow_methods:List[str] = Field(_ALLOW_METHODS, description="Allowed methods")
16
+ allow_headers:list[str] = Field(_ALLOW_HEADERS, description="Allowed headers")
17
+ allow_credentials:bool = Field(False, description="Allowed credentials")
18
+
19
+ class CORSMiddlewareConfigurations(BaseModel):
20
+ expose_headers:List[str] = Field(_EXPOSE_HEADERS, description="Exposed headers")
21
+
22
+ class BaseMiddlewareConfigurations(BaseModel):
23
+ limit:int = Field(10, description="Request limit (per 'window' seconds)")
24
+ window:int = Field(1, description="Request limit window (seconds)")
25
+ cleanup_interval:int = Field(60, description="Interval for middleware cleanup (seconds)")
26
+ ip_timeout:int = Field(300, description="Idle IP's timeout (seconds)")
27
+
28
+ class MiddlewareConfigurations(BaseModel):
29
+ general:GeneralMiddlewareConfigurations = Field(..., description="Middleware's general configurations")
30
+ cors:CORSMiddlewareConfigurations = Field(..., description="CORS middleware's configurations")
31
+ base:BaseMiddlewareConfigurations = Field(..., description="Base middleware's configurations")
32
+
33
+ class MiddlewareLoggers(BaseModel):
34
+ base:MiddlewareLogger = Field(..., description="Base middleware's logger")
35
+ authentication:MiddlewareLogger = Field(..., description="Authentication middleware's logger")
36
+
37
+ class Config:
38
+ arbitrary_types_allowed=True
39
+
40
+ class MiddlewareManager:
41
+ def __init__(self, app:FastAPI, configurations:MiddlewareConfigurations):
42
+ self._app = app
43
+ self._configurations = configurations
44
+
45
+ def add_all(
46
+ self,
47
+ loggers:MiddlewareLoggers,
48
+ key:str,
49
+ authentication_backend:Optional[Backend] = None,
50
+ request_processor:Optional[RequestProcessor] = None
51
+ ):
52
+ self.add_cors(
53
+ allow_origins=self._configurations.general.allow_origins,
54
+ allow_methods=self._configurations.general.allow_methods,
55
+ allow_headers=self._configurations.general.allow_headers,
56
+ allow_credentials=self._configurations.general.allow_credentials,
57
+ expose_headers=self._configurations.cors.expose_headers
58
+ )
59
+ self.add_base(
60
+ logger=loggers.base,
61
+ allow_origins=self._configurations.general.allow_origins,
62
+ allow_methods=self._configurations.general.allow_methods,
63
+ allow_headers=self._configurations.general.allow_headers,
64
+ allow_credentials=self._configurations.general.allow_credentials,
65
+ limit=self._configurations.base.limit,
66
+ window=self._configurations.base.window,
67
+ cleanup_interval=self._configurations.base.cleanup_interval,
68
+ ip_timeout=self._configurations.base.ip_timeout,
69
+ request_processor=request_processor
70
+ )
71
+ if authentication_backend is None:
72
+ self._authentication_backend = Backend(logger=loggers.authentication, key=key)
73
+ else:
74
+ self._authentication_backend = authentication_backend
75
+ self.add_authentication(backend=self._authentication_backend)
76
+
77
+ def add_cors(self) -> None:
78
+ add_cors_middleware(
79
+ app=self._app,
80
+ allow_origins=self._configurations.general.allow_origins,
81
+ allow_methods=self._configurations.general.allow_methods,
82
+ allow_headers=self._configurations.general.allow_headers,
83
+ allow_credentials=self._configurations.general.allow_credentials,
84
+ expose_headers=self._configurations.cors.expose_headers
85
+ )
86
+
87
+ def add_base(self, logger:MiddlewareLogger, request_processor:Optional[RequestProcessor] = None):
88
+ add_base_middleware(
89
+ app=self._app,
90
+ logger=logger,
91
+ allow_origins=self._configurations.general.allow_origins,
92
+ allow_methods=self._configurations.general.allow_methods,
93
+ allow_headers=self._configurations.general.allow_headers,
94
+ allow_credentials=self._configurations.general.allow_credentials,
95
+ limit=self._configurations.base.limit,
96
+ window=self._configurations.base.window,
97
+ cleanup_interval=self._configurations.base.cleanup_interval,
98
+ ip_timeout=self._configurations.base.ip_timeout,
99
+ request_processor=request_processor
100
+ )
101
+
102
+ def add_authentication(self, logger:MiddlewareLogger, key:str, backend:Optional[Backend] = None):
103
+ if backend is None:
104
+ self._authentication_backend = Backend(logger=logger, key=key)
105
+ else:
106
+ self._authentication_backend = backend
107
+ add_authentication_middleware(app=self._app, backend=self._authentication_backend)
@@ -1,10 +1,14 @@
1
1
  import json
2
2
  import os
3
+ import uvicorn
4
+ from fastapi import FastAPI, APIRouter
5
+ from fastapi.exceptions import RequestValidationError
6
+ from starlette.exceptions import HTTPException
7
+ from starlette.types import Lifespan, AppType
3
8
  from pydantic_settings import BaseSettings
4
9
  from pydantic import BaseModel, Field
5
10
  from sqlalchemy import MetaData
6
- from typing import Dict, List, Optional, Type
7
- from maleo_foundation.db.manager import DatabaseManagerV2
11
+ from typing import Optional, Type
8
12
  from maleo_foundation.enums import BaseEnums
9
13
  from maleo_foundation.models.transfers.general.token import BaseTokenGeneralTransfers
10
14
  from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
@@ -13,10 +17,13 @@ from maleo_foundation.managers.client.google.storage import GoogleCloudStorage
13
17
  from maleo_foundation.managers.client.http import HTTPClientManager
14
18
  from maleo_foundation.managers.client.maleo import MaleoClientManager
15
19
  from maleo_foundation.managers.db import DatabaseManager
20
+ from maleo_foundation.managers.middleware import MiddlewareConfigurations, MiddlewareLoggers, MiddlewareManager
21
+ from maleo_foundation.middlewares.base import RequestProcessor
16
22
  from maleo_foundation.services.token import BaseTokenService
17
23
  from maleo_foundation.types import BaseTypes
24
+ from maleo_foundation.utils.exceptions import BaseExceptions
18
25
  from maleo_foundation.utils.keyloader import load_key
19
- from maleo_foundation.utils.logging import GoogleCloudLogging, ServiceLogger, ClientLoggerManager
26
+ from maleo_foundation.utils.logging import GoogleCloudLogging, ServiceLogger, MiddlewareLogger
20
27
 
21
28
  class LogConfig(BaseModel):
22
29
  logs_dir:str = Field(..., description="Logs directory")
@@ -68,10 +75,8 @@ class Credentials(BaseModel):
68
75
  class ServiceConfigurations(BaseModel):
69
76
  key:str = Field(..., description="Service's key")
70
77
  name:str = Field(..., description="Service's name")
71
-
72
- class MiddlewareConfigurations(BaseModel):
73
- allowed_origins:List[str] = Field(default_factory=list, description="Allowed origins")
74
- service_ips:List[str] = Field(default_factory=list, description="Other service's IPs")
78
+ host:str = Field(..., description="Service's host")
79
+ port:int = Field(..., description="Service's port")
75
80
 
76
81
  class DatabaseConfigurations(BaseModel):
77
82
  username:str = Field("postgres", description="Database user's username")
@@ -113,18 +118,15 @@ class Configurations(BaseModel):
113
118
  service:ServiceConfigurations = Field(..., description="Service's configurations")
114
119
  middleware:MiddlewareConfigurations = Field(..., description="Middleware's configurations")
115
120
  database:DatabaseConfigurations = Field(..., description="Database's configurations")
116
- client:ClientConfigurations = Field(...)
121
+ client:ClientConfigurations = Field(..., description="Service's configurations")
117
122
 
118
123
  class Config:
119
124
  arbitrary_types_allowed=True
120
125
 
121
- ClientLoggerManagers = Dict[str, ClientLoggerManager]
122
-
123
126
  class Loggers(BaseModel):
124
127
  application:ServiceLogger = Field(..., description="Application logger")
125
128
  database:ServiceLogger = Field(..., description="Database logger")
126
- middleware:ServiceLogger = Field(..., description="Middleware logger")
127
- client:ClientLoggerManagers = Field(default_factory=dict, description="Client logger manager")
129
+ middleware:MiddlewareLoggers = Field(..., description="Middleware logger")
128
130
 
129
131
  class Config:
130
132
  arbitrary_types_allowed=True
@@ -185,7 +187,6 @@ class ServiceManager:
185
187
  base_dir:BaseTypes.OptionalString = None,
186
188
  settings:Optional[Settings] = None,
187
189
  google_cloud_logging:Optional[GoogleCloudLogging] = None,
188
- client_logger_managers:ClientLoggerManagers = {},
189
190
  maleo_client_manager_classes:Optional[MaleoClientManagerClasses] = None
190
191
  ):
191
192
  self._db_metadata = db_metadata
@@ -217,7 +218,6 @@ class ServiceManager:
217
218
  google_cloud_logging=self._google_cloud_logging
218
219
  )
219
220
 
220
- self._client_logger_managers = client_logger_managers
221
221
  self._initialize_loggers()
222
222
  self._load_credentials()
223
223
  self._parse_keys()
@@ -256,14 +256,14 @@ class ServiceManager:
256
256
  return self._configs
257
257
 
258
258
  def _initialize_loggers(self) -> None:
259
- application = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.APPLICATION, google_cloud_logging=self._google_cloud_logging)
260
- database = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.DATABASE, google_cloud_logging=self._google_cloud_logging)
261
- middleware = ServiceLogger(logs_dir=self._logs_dir, type=BaseEnums.LoggerType.MIDDLEWARE, google_cloud_logging=self._google_cloud_logging)
262
- client = self._client_logger_managers
263
- for key, logger_manager in client.items():
264
- application.info(f"Initializing logger manager for client '{key}'")
265
- logger_manager.initialize(logs_dir=self._logs_dir ,client_key=key)
266
- self._loggers = Loggers(application=application, database=database, middleware=middleware, client=client)
259
+ #* Service's loggers
260
+ application = ServiceLogger(type=BaseEnums.LoggerType.APPLICATION, service_key=self._configs.service.key, **self._log_config.model_dump())
261
+ database = ServiceLogger(type=BaseEnums.LoggerType.DATABASE, service_key=self._configs.service.key, **self._log_config.model_dump())
262
+ #* Middleware's loggers
263
+ base = MiddlewareLogger(middleware_type=BaseEnums.MiddlewareLoggerType.BASE, service_key=self._configs.service.key, **self._log_config.model_dump())
264
+ authentication = MiddlewareLogger(middleware_type=BaseEnums.MiddlewareLoggerType.AUTHENTICATION, service_key=self._configs.service.key, **self._log_config.model_dump())
265
+ middleware = MiddlewareLoggers(base=base, authentication=authentication)
266
+ self._loggers = Loggers(application=application, database=database, middleware=middleware)
267
267
 
268
268
  @property
269
269
  def loggers(self) -> Loggers:
@@ -321,14 +321,12 @@ class ServiceManager:
321
321
  self._http_client.initialize()
322
322
  #* Initialize maleo clients
323
323
  self._maleo_clients = MaleoClientManagers()
324
- # if self._maleo_client_manager_classes.metadata is not None and issubclass(self._maleo_client_manager_classes.metadata, MaleoClientManager):
325
- # self._maleo_clients.metadata = self._maleo_client_manager_classes.metadata(**self._configs.client.maleo.metadata.model_dump(), **self._log_config.model_dump())
326
- for field_name in self._maleo_client_manager_classes.__class__.__annotations__:
327
- cls = getattr(self._maleo_client_manager_classes, field_name)
328
- if cls is not None and issubclass(cls, MaleoClientManager):
329
- cfg:MaleoClientConfiguration = getattr(self._configs.client.maleo, field_name)
330
- client_instance = cls(**cfg.model_dump(), **self._log_config.model_dump())
331
- setattr(self._maleo_clients, field_name, client_instance)
324
+ for client in self._maleo_client_manager_classes.__class__.__annotations__:
325
+ client_cls = getattr(self._maleo_client_manager_classes, client)
326
+ if client_cls is not None and issubclass(client_cls, MaleoClientManager):
327
+ cfg:MaleoClientConfiguration = getattr(self._configs.client.maleo, client)
328
+ client_instance = client_cls(**cfg.model_dump(), **self._log_config.model_dump())
329
+ setattr(self._maleo_clients, client, client_instance)
332
330
  self._clients = ClientManagers(google=self._google_clients, http=self._http_client, maleo=self._maleo_clients)
333
331
 
334
332
  @property
@@ -365,6 +363,28 @@ class ServiceManager:
365
363
  raise ValueError("Failed generating token")
366
364
  return result.data.token
367
365
 
366
+ def create_app(self, router:APIRouter, lifespan:Optional[Lifespan[AppType]] = None, request_processor:Optional[RequestProcessor] = None) -> FastAPI:
367
+ self._app = FastAPI(title=self._configs.service.name, lifespan=lifespan)
368
+
369
+ self._middleware = MiddlewareManager(app=self._app, configurations=self._configs.middleware)
370
+ self._middleware.add_all(loggers=self.loggers.middleware, key=self._keys.public, request_processor=request_processor)
371
+
372
+ #* Add exception handler(s)
373
+ self._app.add_exception_handler(RequestValidationError, BaseExceptions.validation_exception_handler)
374
+ self._app.add_exception_handler(HTTPException, BaseExceptions.http_exception_handler)
375
+
376
+ #* Include router
377
+ self._app.include_router(router)
378
+
379
+ return self._app
380
+
381
+ @property
382
+ def app(self) -> FastAPI:
383
+ return self._app
384
+
385
+ def run_app(self) -> None:
386
+ uvicorn.run(self._app, host=self._configs.service.host, port=self._configs.service.port, reload=True)
387
+
368
388
  async def dispose(self) -> None:
369
389
  self._loggers.application.info("Disposing service manager")
370
390
  if self._database is not None:
@@ -374,11 +394,14 @@ class ServiceManager:
374
394
  self._clients.google.storage.dispose()
375
395
  self._clients.google.secret.dispose()
376
396
  await self._clients.http.dispose()
377
- if self._clients.maleo.metadata is not None:
378
- await self._clients.maleo.metadata.dispose()
397
+ for client in self._maleo_clients.__class__.__annotations__:
398
+ client_instance = getattr(self._maleo_clients, client)
399
+ if client_instance is not None and isinstance(client_instance, MaleoClientManager):
400
+ await client_instance.dispose()
379
401
  self._loggers.application.info("Service manager disposed successfully")
380
402
  if self._loggers is not None:
381
403
  self._loggers.application.dispose()
382
404
  self._loggers.database.dispose()
383
- self._loggers.middleware.dispose()
405
+ self._loggers.middleware.base.dispose()
406
+ self._loggers.middleware.authentication.dispose()
384
407
  self._loggers = None
@@ -0,0 +1,62 @@
1
+ from fastapi import FastAPI
2
+ from starlette.authentication import AuthenticationBackend
3
+ from starlette.middleware.authentication import AuthenticationMiddleware
4
+ from starlette.requests import HTTPConnection
5
+ from typing import Tuple
6
+ from maleo_foundation.authentication import Credentials, User
7
+ from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
8
+ from maleo_foundation.services.token import BaseTokenService
9
+ from maleo_foundation.utils.extractor import extract_client_ip
10
+ from maleo_foundation.utils.logging import MiddlewareLogger
11
+
12
+ class Backend(AuthenticationBackend):
13
+ def __init__(self, logger:MiddlewareLogger, key:str):
14
+ super().__init__()
15
+ self._logger = logger
16
+ self._key = key
17
+
18
+ async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
19
+ client_ip = extract_client_ip(conn)
20
+ if "Authorization" not in conn.headers:
21
+ self._logger.info(f"Authentication - Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Header did not contain authorization")
22
+ return Credentials(), User(authenticated=False)
23
+
24
+ auth = conn.headers["Authorization"]
25
+ scheme, token = auth.split()
26
+ if scheme != 'Bearer':
27
+ self._logger.info(f"Authentication - Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Authorization scheme is not Bearer")
28
+ return Credentials(), User(authenticated=False)
29
+
30
+ decode_token_parameters = BaseTokenParametersTransfers.Decode(key=self._key, token=token)
31
+ decode_token_result = BaseTokenService.decode(parameters=decode_token_parameters)
32
+ if not decode_token_result.success:
33
+ self._logger.error(f"Authentication - Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Failed decoding authorization token")
34
+ return Credentials(token=token), User(authenticated=False)
35
+
36
+ self._logger.info(f"Authentication - Request | IP: {client_ip} | URL: {conn.url.path} - Result | Username: {decode_token_result.data.u_u} | Email: {decode_token_result.data.u_e}")
37
+ return Credentials(token=token), User(authenticated=True, username=decode_token_result.data.u_u, email=decode_token_result.data.u_e)
38
+
39
+ def add_authentication_middleware(app:FastAPI, backend:Backend) -> None:
40
+ """
41
+ Adds Authentication middleware to the FastAPI application.
42
+
43
+ Args:
44
+ app: FastAPI
45
+ The FastAPI application instance to which the middleware will be added.
46
+
47
+ backend: Backend
48
+ The authentication middleware backend to be used.
49
+
50
+ Returns:
51
+ None: The function modifies the FastAPI app by adding Base middleware.
52
+
53
+ Note:
54
+ FastAPI applies middleware in reverse order of registration, so this middleware
55
+ will execute after any middleware added subsequently.
56
+
57
+ Example:
58
+ ```python
59
+ add_authentication_middleware(app=app, limit=10, window=1, cleanup_interval=60, ip_timeout=300)
60
+ ```
61
+ """
62
+ app.add_middleware(AuthenticationMiddleware, backend=backend)
@@ -6,10 +6,11 @@ from collections import defaultdict
6
6
  from datetime import datetime, timedelta, timezone
7
7
  from fastapi import FastAPI, Request, Response, status
8
8
  from fastapi.responses import JSONResponse
9
- from logging import Logger
10
9
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
11
10
  from typing import Awaitable, Callable, Optional, Sequence
12
11
  from maleo_foundation.models.responses import BaseResponses
12
+ from maleo_foundation.utils.extractor import extract_client_ip
13
+ from maleo_foundation.utils.logging import MiddlewareLogger
13
14
 
14
15
  RequestProcessor = Callable[[Request], Awaitable[Optional[Response]]]
15
16
  ResponseProcessor = Callable[[Response], Awaitable[Response]]
@@ -18,7 +19,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
18
19
  def __init__(
19
20
  self,
20
21
  app,
21
- logger:Logger,
22
+ logger:MiddlewareLogger,
22
23
  allow_origins:Sequence[str] = (),
23
24
  allow_methods:Sequence[str] = ("GET",),
24
25
  allow_headers:Sequence[str] = (),
@@ -178,7 +179,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
178
179
  self._cleanup_old_data() #* Run periodic cleanup
179
180
  request_timestamp = datetime.now(tz=timezone.utc) #* Record the request timestamp
180
181
  start_time = time.perf_counter() #* Record the start time
181
- client_ip = self._extract_client_ip(request) #* Get request IP with improved extraction
182
+ client_ip = extract_client_ip(request) #* Get request IP with improved extraction
182
183
 
183
184
  try:
184
185
  #* 1. Rate limit check
@@ -225,7 +226,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
225
226
 
226
227
  def add_base_middleware(
227
228
  app:FastAPI,
228
- logger:Logger,
229
+ logger:MiddlewareLogger,
229
230
  allow_origins:Sequence[str] = (),
230
231
  allow_methods:Sequence[str] = ("GET",),
231
232
  allow_headers:Sequence[str] = (),
@@ -0,0 +1,18 @@
1
+ from starlette.requests import HTTPConnection
2
+
3
+ def extract_client_ip(conn:HTTPConnection) -> str:
4
+ """Extract client IP with more robust handling of proxies"""
5
+ #* Check for X-Forwarded-For header (common when behind proxy/load balancer)
6
+ x_forwarded_for = conn.headers.get("X-Forwarded-For")
7
+ if x_forwarded_for:
8
+ #* The client's IP is the first one in the list
9
+ ips = [ip.strip() for ip in x_forwarded_for.split(",")]
10
+ return ips[0]
11
+
12
+ #* Check for X-Real-IP header (used by some proxies)
13
+ x_real_ip = conn.headers.get("X-Real-IP")
14
+ if x_real_ip:
15
+ return x_real_ip
16
+
17
+ #* Fall back to direct client connection
18
+ return conn.client.host if conn.client else "unknown"
@@ -45,6 +45,7 @@ class BaseLogger(logging.Logger):
45
45
  logs_dir:str,
46
46
  type:BaseEnums.LoggerType,
47
47
  service_key:BaseTypes.OptionalString = None,
48
+ middleware_type:Optional[BaseEnums.MiddlewareLoggerType] = None,
48
49
  client_key:BaseTypes.OptionalString = None,
49
50
  level:BaseEnums.LoggerLevel = BaseEnums.LoggerLevel.INFO,
50
51
  google_cloud_logging:Optional[GoogleCloudLogging] = None
@@ -67,6 +68,11 @@ class BaseLogger(logging.Logger):
67
68
  if self._service_key is None:
68
69
  raise ValueError("SERVICE_KEY environment variable must be set if 'service_key' is set to None")
69
70
 
71
+ self._middleware_type = middleware_type #* Declare middleware type
72
+
73
+ if self._type == BaseEnums.LoggerType.MIDDLEWARE and self._middleware_type is None:
74
+ raise ValueError("'middleware_type' parameter must be provided if 'logger_type' is 'middleware'")
75
+
70
76
  self._client_key = client_key #* Declare client key
71
77
 
72
78
  #* Ensure client_key is valid if logger type is a client
@@ -75,7 +81,15 @@ class BaseLogger(logging.Logger):
75
81
 
76
82
  #* Define logger name
77
83
  if self._type == BaseEnums.LoggerType.CLIENT:
84
+ #* Ensure client_key is valid if logger type is client
85
+ if self._client_key is None:
86
+ raise ValueError("'client_key' parameter must be provided if 'logger_type' is 'client'")
78
87
  self._name = f"{self._service_key} - {self._type} - {self._client_key}"
88
+ elif self._type == BaseEnums.LoggerType.MIDDLEWARE:
89
+ #* Ensure middleware_type is valid if logger type is middleware
90
+ if self._middleware_type is None:
91
+ raise ValueError("'middleware_type' parameter must be provided if 'logger_type' is 'middleware'")
92
+ self._name = f"{self._service_key} - {self._type} - {self._middleware_type}"
79
93
  else:
80
94
  self._name = f"{self._service_key} - {self._type}"
81
95
 
@@ -102,7 +116,9 @@ class BaseLogger(logging.Logger):
102
116
  self.info("Cloud logging is not configured.")
103
117
 
104
118
  #* Define log directory
105
- if self._type == BaseEnums.LoggerType.CLIENT:
119
+ if self._type == BaseEnums.LoggerType.MIDDLEWARE:
120
+ log_dir = f"{self._type}/{self._middleware_type}"
121
+ elif self._type == BaseEnums.LoggerType.CLIENT:
106
122
  log_dir = f"{self._type}/{self._client_key}"
107
123
  else:
108
124
  log_dir = f"{self._type}"
@@ -145,6 +161,25 @@ class BaseLogger(logging.Logger):
145
161
  handler.close()
146
162
  self.handlers.clear()
147
163
 
164
+ class MiddlewareLogger(BaseLogger):
165
+ def __init__(
166
+ self,
167
+ logs_dir:str,
168
+ service_key:BaseTypes.OptionalString = None,
169
+ middleware_type = None,
170
+ level = BaseEnums.LoggerLevel.INFO,
171
+ google_cloud_logging = None
172
+ ):
173
+ super().__init__(
174
+ logs_dir=logs_dir,
175
+ type=BaseEnums.LoggerType.MIDDLEWARE,
176
+ service_key=service_key,
177
+ middleware_type=middleware_type,
178
+ client_key=None,
179
+ level=level,
180
+ google_cloud_logging=google_cloud_logging
181
+ )
182
+
148
183
  class ServiceLogger(BaseLogger):
149
184
  def __init__(
150
185
  self,
@@ -158,6 +193,7 @@ class ServiceLogger(BaseLogger):
158
193
  logs_dir=logs_dir,
159
194
  type=type,
160
195
  service_key=service_key,
196
+ middleware_type=None,
161
197
  client_key=None,
162
198
  level=level,
163
199
  google_cloud_logging=google_cloud_logging
@@ -176,6 +212,7 @@ class ClientLogger(BaseLogger):
176
212
  logs_dir=logs_dir,
177
213
  type=BaseEnums.LoggerType.CLIENT,
178
214
  service_key=service_key,
215
+ middleware_type=None,
179
216
  client_key=client_key,
180
217
  level=level,
181
218
  google_cloud_logging=google_cloud_logging
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -1,6 +1,7 @@
1
1
  maleo_foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ maleo_foundation/authentication.py,sha256=iwc9cGGi5srW3goPRyuZjoPvdyA3XPgYzIEbVl-IW78,842
2
3
  maleo_foundation/constants.py,sha256=aBmEfWlBqZxi0k-n6h2NM1YRLOjMnheEiLyQcjP-zCQ,1164
3
- maleo_foundation/enums.py,sha256=mhLidFNxo89ZlmfJptB7dNqX7Q_7H9jVS8tvUyp_jQg,2748
4
+ maleo_foundation/enums.py,sha256=93fqLJvg0oZAXuJSFCf0-gsW8JmB6AkJvZzXLd47dIo,2820
4
5
  maleo_foundation/extended_types.py,sha256=pIKt-_9tby4rmune3fmWcCW_mohaNRh_1lywBmdc-L4,301
5
6
  maleo_foundation/types.py,sha256=aKXnIgEhYGSfFqNMGLc4qIKGkINBRpkOo9R9cb2CbwI,2414
6
7
  maleo_foundation/clients/__init__.py,sha256=W8vydJYeDEi6gdmOZSBFSSDsfZJtb8C05CHErZgsZ30,188
@@ -30,7 +31,8 @@ maleo_foundation/expanded_types/service.py,sha256=q8jpKdbCbLWwH1UPQavKpVE14rC5rv
30
31
  maleo_foundation/expanded_types/token.py,sha256=4fRTJw6W5MYq71NksNrWNi7qYHQ4_lQwfu9WxwrMipc,355
31
32
  maleo_foundation/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
33
  maleo_foundation/managers/db.py,sha256=Jf0w-9JOAG5X2quDxqqw6d2WUIX7MYGdlPGxWP0rxHs,4699
33
- maleo_foundation/managers/service.py,sha256=U-KCSl-101if1KOk7Mk1TlPQG2b7j0-gALJZYGNCADQ,18199
34
+ maleo_foundation/managers/middleware.py,sha256=nEhNgvGpvzQ2CouU8dgNe4eBKtYHkihHSHA6yhxR_2k,5461
35
+ maleo_foundation/managers/service.py,sha256=RSjaJDjrvzfTE2z0IYpzJMKYnl4BFaF7UEPl3uupH5k,19215
34
36
  maleo_foundation/managers/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
37
  maleo_foundation/managers/client/base.py,sha256=K8AFV2MTZrC1jbTqxkx7eedmxodJirNIjoRXGGcppy4,1022
36
38
  maleo_foundation/managers/client/http.py,sha256=dWFZlG1z4TERYBITReR5oSrlzvdhh2EtztVnsU8gCeA,2712
@@ -40,7 +42,8 @@ maleo_foundation/managers/client/google/base.py,sha256=1lrGoyGnYW5Xq05bVXbKqnsqq
40
42
  maleo_foundation/managers/client/google/secret.py,sha256=bWR_3Xl_wUFqj4M48w8ZE692U7PQKX9ap0ndDJviV80,3074
41
43
  maleo_foundation/managers/client/google/storage.py,sha256=041iSSaFubnZrP-pS8MiLVtOgeLQ591Iz64JekZ5K7o,2350
42
44
  maleo_foundation/middlewares/__init__.py,sha256=bqE2EIFC3rWcR2AwFPR0fk2kSFfeTRzgA24GbnuT5RA,3697
43
- maleo_foundation/middlewares/base.py,sha256=KcpODNs0DQXX8Cwc6T9dC6aiZIqxTLtuJjVAGWXPSKk,11633
45
+ maleo_foundation/middlewares/authentication.py,sha256=acMYZES6OPan6U15s8Nr5C1DC43U2Yz0MY-cla1Zkk4,3072
46
+ maleo_foundation/middlewares/base.py,sha256=3OaB5F57F3epKNieoAgarYM6PimoUdUl3DgpadtVuqs,11743
44
47
  maleo_foundation/middlewares/cors.py,sha256=9uvBvY2N6Vxa9RP_YtESxcWo6Doi6uS0lzAG9iLY7Uc,2288
45
48
  maleo_foundation/models/__init__.py,sha256=AaKehO7c1HyKhoTGRmNHDddSeBXkW-_YNrpOGBu8Ms8,246
46
49
  maleo_foundation/models/responses.py,sha256=Ka9Peb1fVuQKaxyy11FVkUPtGtzyEm_9Xfe8Vla3ml0,4764
@@ -73,13 +76,14 @@ maleo_foundation/services/token.py,sha256=ZqRqOdGUnaSIam6-JHVdAW1UST-2EDtcVN0fpb
73
76
  maleo_foundation/utils/__init__.py,sha256=FavmL5XYGCm955EAKiWWcXYeU15p5rSzfcglpV2yI6c,387
74
77
  maleo_foundation/utils/controller.py,sha256=ECzPzpw36zBAjKcWcDbUAhIJGbc6UpeypdUUX6ipXBg,6396
75
78
  maleo_foundation/utils/exceptions.py,sha256=nk3rD57fDR-D7BQkU1JEKV-Mu7FGMpLSEsqxdDZdKjU,4532
79
+ maleo_foundation/utils/extractor.py,sha256=BOYkEiEJZTixScd_mEiLt2svKZ4gXBgD2WDlU6giI3w,718
76
80
  maleo_foundation/utils/keyloader.py,sha256=g7LYypj7UolmSgHRGXMFgVaWr55UayMdtKXR5NmB7VM,2341
77
81
  maleo_foundation/utils/logger.py,sha256=uTvzzKnGjbxRVLHbiMDw2zKKWNaCwV35sxgjDStEwNQ,3569
78
- maleo_foundation/utils/logging.py,sha256=DuAaKQ1X7lB0y6udR-GF95BRKeluh0JoYN0K_jcNlZ0,7313
82
+ maleo_foundation/utils/logging.py,sha256=MwvZmZSA8SIdfq-knEvpYIgqnSpHcyHrZY9TVHWVHJA,9023
79
83
  maleo_foundation/utils/query.py,sha256=ODQ3adOYQNj5E2cRW9ytbjBz56nEDcnfq8mQ6YZbCCM,4375
80
84
  maleo_foundation/utils/formatter/__init__.py,sha256=iKf5YCbEdg1qKnFHyKqqcQbqAqEeRUf8mhI3v3dQoj8,78
81
85
  maleo_foundation/utils/formatter/case.py,sha256=TmvvlfzGdC_omMTB5vAa40TZBxQ3hnr-SYeo0M52Rlg,1352
82
- maleo_foundation-0.1.18.dist-info/METADATA,sha256=hJ1VIOIKgdXHglxFOwiwS7lbQSuFtOfbwQxnJNjEusM,3190
83
- maleo_foundation-0.1.18.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
84
- maleo_foundation-0.1.18.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
85
- maleo_foundation-0.1.18.dist-info/RECORD,,
86
+ maleo_foundation-0.1.20.dist-info/METADATA,sha256=mdOFrN9SO_ckBh80_c1scxXWrxxv6_d7YCT05VUY0QQ,3190
87
+ maleo_foundation-0.1.20.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
88
+ maleo_foundation-0.1.20.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
89
+ maleo_foundation-0.1.20.dist-info/RECORD,,