error response due to language models are updated

This commit is contained in:
berkay 2025-01-15 13:39:17 +03:00
parent ad0b9aa218
commit 25539c56cc
17 changed files with 163 additions and 208 deletions

View File

@ -17,21 +17,11 @@ from prometheus_fastapi_instrumentator import Instrumentator
from app_handler import setup_middleware, get_uvicorn_config from app_handler import setup_middleware, get_uvicorn_config
print("Loading app.py module...") app = create_app(routers=routers) # Initialize FastAPI application
Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics
# Initialize FastAPI application setup_middleware(app) # Configure middleware and exception handlers
app = create_app(routers=routers)
# Setup Prometheus metrics
Instrumentator().instrument(app=app).expose(app=app)
# Configure middleware and exception handlers
setup_middleware(app)
if __name__ == "__main__": if __name__ == "__main__":
print("Starting server from __main__...") uvicorn_config = get_uvicorn_config() # Run the application with Uvicorn
# Run the application with Uvicorn
uvicorn_config = get_uvicorn_config()
print(f"Using config: {uvicorn_config}")
uvicorn.Server(uvicorn.Config(**uvicorn_config)).run() uvicorn.Server(uvicorn.Config(**uvicorn_config)).run()

View File

@ -12,14 +12,8 @@ from typing import Dict, Any
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request, HTTPException, status from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from ErrorHandlers.bases import ( from middleware.auth_middleware import RequestTimingMiddleware
BaseErrorModelClass,
StatusesModelClass,
LanguageModelClass,
)
from ErrorHandlers import statuses
from middleware.auth_middleware import MiddlewareModule
def setup_cors_middleware(app: FastAPI) -> None: def setup_cors_middleware(app: FastAPI) -> None:
@ -38,33 +32,6 @@ def setup_cors_middleware(app: FastAPI) -> None:
) )
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
"""
Handle HTTP exceptions and return formatted error responses.
Args:
request: FastAPI request object
exc: HTTP exception instance
Returns:
JSONResponse: Formatted error response
"""
error_code = getattr(exc, "error_code", None)
if error_code:
status_code = StatusesModelClass.retrieve_error_by_code(error_code)
error_message = LanguageModelClass.retrieve_error_by_code(
error_code, request.headers.get("accept-language", "en")
)
else:
status_code = exc.status_code
error_message = str(exc.detail)
return JSONResponse(
status_code=status_code,
content={"detail": error_message, "error_code": error_code},
)
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse: async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
""" """
Handle generic exceptions and return formatted error responses. Handle generic exceptions and return formatted error responses.
@ -89,7 +56,12 @@ def setup_exception_handlers(app: FastAPI) -> None:
Args: Args:
app: FastAPI application instance app: FastAPI application instance
""" """
app.add_exception_handler(HTTPException, http_exception_handler) from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApiHandler
custom_exception_handler = HTTPExceptionApiHandler(response_model=JSONResponse)
app.add_exception_handler(
HTTPExceptionApi, custom_exception_handler.handle_exception
)
app.add_exception_handler(Exception, generic_exception_handler) app.add_exception_handler(Exception, generic_exception_handler)
@ -101,7 +73,7 @@ def setup_middleware(app: FastAPI) -> None:
app: FastAPI application instance app: FastAPI application instance
""" """
setup_cors_middleware(app) setup_cors_middleware(app)
app.add_middleware(MiddlewareModule.RequestTimingMiddleware) app.add_middleware(RequestTimingMiddleware)
setup_exception_handlers(app) setup_exception_handlers(app)

View File

@ -8,6 +8,7 @@ This module provides functionality to create and configure a FastAPI application
- Response class configuration - Response class configuration
- Security requirements for protected endpoints - Security requirements for protected endpoints
""" """
from types import ModuleType from types import ModuleType
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from fastapi import FastAPI, APIRouter from fastapi import FastAPI, APIRouter
@ -36,10 +37,7 @@ def setup_security_schema() -> Dict[str, Any]:
def configure_route_security( def configure_route_security(
path: str, path: str, method: str, schema: Dict[str, Any], protected_paths: List[str]
method: str,
schema: Dict[str, Any],
protected_paths: List[str]
) -> None: ) -> None:
""" """
Configure security requirements for a specific route. Configure security requirements for a specific route.
@ -113,7 +111,7 @@ def create_app(routers: ModuleType) -> FastAPI:
for route in router.routes: for route in router.routes:
if isinstance(route, APIRoute): if isinstance(route, APIRoute):
# Check if the route has auth_required decorator # Check if the route has auth_required decorator
if any(d.__name__ == 'auth_required' for d in route.dependencies): if any(d.__name__ == "auth_required" for d in route.dependencies):
protected_paths.append(route.path) protected_paths.append(route.path)
# Include routers # Include routers
@ -142,7 +140,7 @@ def create_app(routers: ModuleType) -> FastAPI:
route.path, route.path,
route.methods.pop().lower(), route.methods.pop().lower(),
openapi_schema, openapi_schema,
protected_paths protected_paths,
) )
app.openapi_schema = openapi_schema app.openapi_schema = openapi_schema

View File

@ -8,7 +8,7 @@ and a middleware for request timing measurements.
from time import perf_counter from time import perf_counter
from typing import Callable, Optional, Dict, Any, Tuple from typing import Callable, Optional, Dict, Any, Tuple
from functools import wraps from functools import wraps
from fastapi import HTTPException, Request, Response, status from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from AllConfigs.Token.config import Auth from AllConfigs.Token.config import Auth
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
@ -21,7 +21,6 @@ class MiddlewareModule:
This class provides: This class provides:
- Token extraction and validation - Token extraction and validation
- Authentication decorator for endpoints - Authentication decorator for endpoints
- Request timing middleware
""" """
@staticmethod @staticmethod
@ -40,23 +39,15 @@ class MiddlewareModule:
""" """
auth_header = request.headers.get(Auth.ACCESS_TOKEN_TAG) auth_header = request.headers.get(Auth.ACCESS_TOKEN_TAG)
if not auth_header: if not auth_header:
raise HTTPExceptionApi( raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No authorization header",
)
try: try:
scheme, token = auth_header.split() scheme, token = auth_header.split()
if scheme.lower() != "bearer": if scheme.lower() != "bearer":
raise HTTPExceptionApi( raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication scheme",
)
return scheme, token return scheme, token
except ValueError: except ValueError:
raise HTTPExceptionApi( raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token format"
)
@staticmethod @staticmethod
async def validate_token(token: str) -> Dict[str, Any]: async def validate_token(token: str) -> Dict[str, Any]:
@ -78,10 +69,7 @@ class MiddlewareModule:
# return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) # return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
return {"user_id": "test", "role": "user"} # Placeholder return {"user_id": "test", "role": "user"} # Placeholder
except Exception as e: except Exception as e:
raise HTTPExceptionApi( raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Token validation failed: {str(e)}",
)
@classmethod @classmethod
def auth_required(cls, func: Callable) -> Callable: def auth_required(cls, func: Callable) -> Callable:
@ -122,15 +110,12 @@ class MiddlewareModule:
return await func(request, *args, **kwargs) return await func(request, *args, **kwargs)
except HTTPExceptionApi: except HTTPExceptionApi:
raise raise HTTPExceptionApi(error_code="NOT_AUTHORIZED", lang="tr")
except Exception as e: except Exception as e:
raise HTTPExceptionApi( raise HTTPExceptionApi(error_code="NOT_AUTHORIZED", lang="tr")
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Authentication failed: {str(e)}",
)
return wrapper return wrapper
class RequestTimingMiddleware(BaseHTTPMiddleware): class RequestTimingMiddleware(BaseHTTPMiddleware):
""" """
Middleware for measuring and logging request timing. Middleware for measuring and logging request timing.
@ -154,19 +139,6 @@ class MiddlewareModule:
response = await call_next(request) response = await call_next(request)
# Add timing information to response headers # Add timing information to response headers
self._add_timing_headers(response, start_time)
return response
@staticmethod
def _add_timing_headers(response: Response, start_time: float) -> None:
"""
Add request timing information to response headers.
Args:
response: FastAPI response object
start_time: Time when request processing started
"""
end_time = perf_counter() end_time = perf_counter()
elapsed = (end_time - start_time) * 1000 # Convert to milliseconds elapsed = (end_time - start_time) * 1000 # Convert to milliseconds
@ -177,3 +149,5 @@ class MiddlewareModule:
"request-duration": f"{elapsed:.2f}ms", "request-duration": f"{elapsed:.2f}ms",
} }
) )
return response

View File

@ -174,7 +174,9 @@ class OpenAPISchemaCreator:
if "components" not in openapi_schema: if "components" not in openapi_schema:
openapi_schema["components"] = {} openapi_schema["components"] = {}
openapi_schema["components"]["securitySchemes"] = self._create_security_schemes() openapi_schema["components"][
"securitySchemes"
] = self._create_security_schemes()
# Configure route security and responses # Configure route security and responses
for route in self.app.routes: for route in self.app.routes:

View File

@ -1,5 +1,3 @@
from .base_router import test_route from .base_router import test_route
__all__ = [ __all__ = ["test_route"]
"test_route"
]

View File

@ -7,16 +7,16 @@ from middleware.auth_middleware import MiddlewareModule
# Create test router # Create test router
test_route = APIRouter(prefix="/test", tags=["Test"]) test_route = APIRouter(prefix="/test", tags=["Test"])
test_route.include_router(test_route, include_in_schema=True)
@test_route.get("/health") @test_route.get("/health")
@MiddlewareModule.auth_required @MiddlewareModule.auth_required
async def health_check(request: Request): async def health_check(request: Request):
return {"status": "healthy", "message": "Service is running"} return {"status": "healthy", "message": "Service is running"}
@test_route.get("/ping") @test_route.get("/ping")
async def ping_test(): async def ping_test():
return {"ping": "pong", "service": "base-router"} return {"ping": "pong", "service": "base-router"}
# Initialize and include test routes
def init_test_routes():
return test_route

View File

@ -19,7 +19,10 @@ RUN poetry config virtualenvs.create false \
&& rm -rf ~/.cache/pypoetry && rm -rf ~/.cache/pypoetry
# Copy application code # Copy application code
COPY DockerApiServices/AllApiNeeds /app/ COPY DockerApiServices/AllApiNeeds /app/
COPY ErrorHandlers /app/ErrorHandlers
COPY LanguageModels /app/LanguageModels
COPY ApiLibrary /app/ApiLibrary COPY ApiLibrary /app/ApiLibrary
COPY ApiValidations /app/ApiValidations COPY ApiValidations /app/ApiValidations
COPY AllConfigs /app/AllConfigs COPY AllConfigs /app/AllConfigs

View File

@ -6,32 +6,32 @@ This repository contains multiple microservices that can be run using Docker Com
For regular development when dependencies haven't changed: For regular development when dependencies haven't changed:
```bash ```bash
# Build and run Auth Service # Build and run Auth Service
docker compose -f ../docker-compose-services.yml up auth-service docker compose -f docker-compose-services.yml up auth-service
# Build and run Event Service # Build and run Event Service
docker compose -f ../docker-compose-services.yml up event-service docker compose -f docker-compose-services.yml up event-service
# Build and run Validation Service # Build and run Validation Service
docker compose -f ../docker-compose-services.yml up validation-service docker compose -f docker-compose-services.yml up validation-service
# Build and run all services # Build and run all services
docker compose -f ../docker-compose-services.yml up docker compose -f docker-compose-services.yml up
``` ```
## Clean Build (No Cache) ## Clean Build (No Cache)
Use these commands when changing Dockerfile or dependencies: Use these commands when changing Dockerfile or dependencies:
```bash ```bash
# Auth Service # Auth Service
docker compose -f ../docker-compose-services.yml build --no-cache auth-service && docker compose -f ../docker-compose-services.yml up auth-service docker compose -f docker-compose-services.yml build --no-cache auth-service && docker compose -f docker-compose-services.yml up auth-service
# Event Service # Event Service
docker compose -f ../docker-compose-services.yml build --no-cache event-service && docker compose -f ../docker-compose-services.yml up event-service docker compose -f docker-compose-services.yml build --no-cache event-service && docker compose -f docker-compose-services.yml up event-service
# Validation Service # Validation Service
docker compose -f ../docker-compose-services.yml build --no-cache validation-service && docker compose -f ../docker-compose-services.yml up validation-service docker compose -f docker-compose-services.yml build --no-cache validation-service && docker compose -f docker-compose-services.yml up validation-service
# All Services # All Services
docker compose -f ../docker-compose-services.yml build --no-cache && docker compose -f ../docker-compose-services.yml up docker compose -f docker-compose-services.yml build --no-cache && docker compose -f docker-compose-services.yml up
``` ```
## Service Ports ## Service Ports
@ -51,9 +51,9 @@ docker compose -f ../docker-compose-services.yml build --no-cache && docker comp
- For faster development iterations - For faster development iterations
- Run in detached mode: - Run in detached mode:
```bash ```bash
docker compose -f ../docker-compose-services.yml up -d auth-service docker compose -f docker-compose-services.yml up -d auth-service
``` ```
- Stop services: - Stop services:
```bash ```bash
docker compose -f ../docker-compose-services.yml down docker compose -f docker-compose-services.yml down
``` ```

View File

@ -1,39 +1,57 @@
from typing import Any, Dict from typing import Any, Dict, Union, Awaitable
from fastapi import Request, WebSocket
from fastapi.responses import Response
from LanguageModels.Errors.merge_all_error_languages import MergedErrorLanguageModels
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from ErrorHandlers.bases import BaseErrorModelClass
class HTTPExceptionApiHandler: class HTTPExceptionApiHandler:
def __init__( def __init__(
self, self,
**kwargs, response_model: Any,
): ):
self.EXCEPTIONS = kwargs.get( self.RESPONSE_MODEL: Any = response_model
"exceptions"
) # from fastapi.exceptions import HTTPException
self.STATUSES = kwargs.get("statuses") # from fastapi import status
self.EXCEPTION_DICTS: Dict = kwargs.get("exceptions_dict")
self.ERRORS_DICT: Dict = kwargs.get("errors_dict")
self.ERRORS_LANG: Dict = kwargs.get("error_language_dict")
self.RESPONSE_MODEL: Any = kwargs.get("response_model")
def retrieve_error_status_code(self, exc: HTTPExceptionApi): @staticmethod
grab_status = self.ERRORS_DICT.get(str(exc.error_code).upper(), "") def retrieve_error_status_code(exc: HTTPExceptionApi) -> int:
grab_status_code = self.EXCEPTION_DICTS.get(str(grab_status).upper(), "500") from ErrorHandlers import DEFAULT_ERROR
return getattr( error_by_codes = BaseErrorModelClass.retrieve_error_by_codes()
self.STATUSES, grab_status_code = error_by_codes.get(
str(grab_status_code), str(exc.error_code).upper(), DEFAULT_ERROR
getattr(self.STATUSES, "HTTP_500_INTERNAL_SERVER_ERROR"),
) )
return int(grab_status_code)
def retrieve_error_message(self, exc: HTTPExceptionApi): @staticmethod
message_by_lang = self.ERRORS_LANG.get(str(exc.lang).lower(), {}) def retrieve_error_message(exc: HTTPExceptionApi, error_languages) -> str:
return message_by_lang.get(str(exc.error_code).upper(), "Unknown error") from ErrorHandlers import DEFAULT_ERROR
return error_languages.get(str(exc.error_code).upper(), DEFAULT_ERROR)
def handle_exception(self, request, exc: HTTPExceptionApi): async def handle_exception(
self, request: Union[Request, WebSocket], exc: Exception
) -> Union[Response, Awaitable[None]]:
request_string = str(request.url) if isinstance(request, Request) else request.url.path
if isinstance(exc, HTTPExceptionApi):
error_languages = MergedErrorLanguageModels.get_language_models(
language=exc.lang
)
status_code = self.retrieve_error_status_code(exc) status_code = self.retrieve_error_status_code(exc)
error_message = self.retrieve_error_message(exc) error_message = self.retrieve_error_message(exc, error_languages)
return self.RESPONSE_MODEL( return self.RESPONSE_MODEL(
status_code=int(status_code), status_code=int(status_code),
content={"message": error_message, "lang": exc.lang, "request": request}, content={
"message": error_message,
"lang": exc.lang,
"request": request_string,
},
) )
return self.RESPONSE_MODEL(
status_code=500,
content={
"message": "Internal Server Error",
"lang": "def",
"request": request_string,
},
) # Handle other exceptions with a generic 500 error

View File

@ -4,9 +4,10 @@ from ErrorHandlers.ErrorHandlers.api_exc_handler import (
from ErrorHandlers.Exceptions.api_exc import ( from ErrorHandlers.Exceptions.api_exc import (
HTTPExceptionApi, HTTPExceptionApi,
) )
DEFAULT_ERROR = "UNKNOWN_ERROR"
__all__ = [ __all__ = [
"HTTPExceptionApiHandler", "HTTPExceptionApiHandler",
"HTTPExceptionApi", "HTTPExceptionApi",
"DEFAULT_ERROR"
] ]

View File

@ -1,7 +1,4 @@
from ErrorHandlers.bases import BaseErrorModelClass class BaseError:
class BaseError(BaseErrorModelClass):
NOT_CREATED: int = 405 NOT_CREATED: int = 405
NOT_DELETED: int = 405 NOT_DELETED: int = 405
NOT_UPDATED: int = 405 NOT_UPDATED: int = 405
@ -14,3 +11,4 @@ class BaseError(BaseErrorModelClass):
NOT_ACCEPTABLE: int = 406 NOT_ACCEPTABLE: int = 406
INVALID_DATA: int = 422 INVALID_DATA: int = 422
UNKNOWN_ERROR: int = 502 UNKNOWN_ERROR: int = 502

View File

@ -1,30 +1,19 @@
from typing import Optional from ErrorHandlers.base import BaseError
from ErrorHandlers.statuses import Statuses
class BaseErrorModelClass: class BaseErrorModelClass:
list_of_statuses = [Statuses, BaseError]
@classmethod @classmethod
def retrieve_error_by_code(cls, error_code: str): def retrieve_error_by_codes(cls):
return getattr(cls, error_code, 502) language_model_status = {}
for list_of_language in cls.list_of_statuses:
clean_dict = {
key: value
for key, value in list_of_language.__dict__.items()
if "__" not in str(key)[0:3]
}
language_model_status.update(clean_dict)
return language_model_status
class StatusesModelClass:
@classmethod
def retrieve_error_by_code(cls, error_code: str):
return getattr(cls, error_code, 502)
class ErrorLanguageModelClass:
@classmethod
def retrieve_error_header(cls, error_code: str):
return getattr(cls, error_code, "Unknown Error occured.")
class LanguageModelClass:
@classmethod
def retrieve_error_by_code(cls, error_code: str, language: Optional[str] = "tr"):
language_model: ErrorLanguageModelClass = getattr(cls, language, "tr")
return language_model.retrieve_error_header(error_code)

View File

@ -1,7 +1,4 @@
from ErrorHandlers.bases import StatusesModelClass class Statuses:
class Statuses(StatusesModelClass):
HTTP_100_CONTINUE = 100 HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101 HTTP_101_SWITCHING_PROTOCOLS = 101
HTTP_102_PROCESSING = 102 HTTP_102_PROCESSING = 102

View File

@ -1,3 +1,3 @@
from LanguageModels.Errors.base_languages import BaseErrorLanguageModels from .merge_all_error_languages import MergedErrorLanguageModels
__all__ = ["BaseErrorLanguageModels"] __all__ = ["MergedErrorLanguageModels"]

View File

@ -1,7 +1,4 @@
from ErrorHandlers.bases import ErrorLanguageModelClass, LanguageModelClass class BaseErrorLanguageModelTurkish:
class BaseErrorLanguageModelTurkish(ErrorLanguageModelClass):
NOT_CREATED: str = "Kayıt oluşturulamadı." NOT_CREATED: str = "Kayıt oluşturulamadı."
NOT_DELETED: str = "Kayıt silinemedi." NOT_DELETED: str = "Kayıt silinemedi."
@ -17,7 +14,7 @@ class BaseErrorLanguageModelTurkish(ErrorLanguageModelClass):
UNKNOWN_ERROR: str = "Bilinmeyen bir hata oluştu." UNKNOWN_ERROR: str = "Bilinmeyen bir hata oluştu."
class BaseErrorLanguageModelEnglish(ErrorLanguageModelClass): class BaseErrorLanguageModelEnglish:
NOT_CREATED: str = "Not Created." NOT_CREATED: str = "Not Created."
NOT_DELETED: str = "Not Deleted." NOT_DELETED: str = "Not Deleted."
@ -33,6 +30,6 @@ class BaseErrorLanguageModelEnglish(ErrorLanguageModelClass):
UNKNOWN_ERROR: str = "Unknown Error occured." UNKNOWN_ERROR: str = "Unknown Error occured."
class BaseErrorLanguageModels(LanguageModelClass): class BaseErrorLanguageModels:
tr: BaseErrorLanguageModelTurkish = BaseErrorLanguageModelTurkish tr: BaseErrorLanguageModelTurkish = BaseErrorLanguageModelTurkish
en: BaseErrorLanguageModelEnglish = BaseErrorLanguageModelEnglish en: BaseErrorLanguageModelEnglish = BaseErrorLanguageModelEnglish

View File

@ -0,0 +1,18 @@
from LanguageModels.Errors.base_languages import BaseErrorLanguageModels
class MergedErrorLanguageModels:
list_of_languages = [BaseErrorLanguageModels]
@classmethod
def get_language_models(cls, language: str):
language_model_keys = {}
for list_of_language in cls.list_of_languages:
language_model_class = getattr(list_of_language, language, None)
clean_dict = {
key: value
for key, value in language_model_class.__dict__.items()
if "__" not in str(key)[0:3]
}
language_model_keys.update(clean_dict)
return language_model_keys