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
print("Loading app.py module...")
# Initialize FastAPI application
app = create_app(routers=routers)
# Setup Prometheus metrics
Instrumentator().instrument(app=app).expose(app=app)
# Configure middleware and exception handlers
setup_middleware(app)
app = create_app(routers=routers) # Initialize FastAPI application
Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics
setup_middleware(app) # Configure middleware and exception handlers
if __name__ == "__main__":
print("Starting server from __main__...")
# Run the application with Uvicorn
uvicorn_config = get_uvicorn_config()
print(f"Using config: {uvicorn_config}")
uvicorn_config = get_uvicorn_config() # Run the application with Uvicorn
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 import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from ErrorHandlers.bases import (
BaseErrorModelClass,
StatusesModelClass,
LanguageModelClass,
)
from ErrorHandlers import statuses
from middleware.auth_middleware import MiddlewareModule
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from middleware.auth_middleware import RequestTimingMiddleware
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:
"""
Handle generic exceptions and return formatted error responses.
@ -89,7 +56,12 @@ def setup_exception_handlers(app: FastAPI) -> None:
Args:
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)
@ -101,7 +73,7 @@ def setup_middleware(app: FastAPI) -> None:
app: FastAPI application instance
"""
setup_cors_middleware(app)
app.add_middleware(MiddlewareModule.RequestTimingMiddleware)
app.add_middleware(RequestTimingMiddleware)
setup_exception_handlers(app)

View File

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

View File

@ -8,7 +8,7 @@ and a middleware for request timing measurements.
from time import perf_counter
from typing import Callable, Optional, Dict, Any, Tuple
from functools import wraps
from fastapi import HTTPException, Request, Response, status
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from AllConfigs.Token.config import Auth
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
@ -21,7 +21,6 @@ class MiddlewareModule:
This class provides:
- Token extraction and validation
- Authentication decorator for endpoints
- Request timing middleware
"""
@staticmethod
@ -40,23 +39,15 @@ class MiddlewareModule:
"""
auth_header = request.headers.get(Auth.ACCESS_TOKEN_TAG)
if not auth_header:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No authorization header",
)
raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
try:
scheme, token = auth_header.split()
if scheme.lower() != "bearer":
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication scheme",
)
raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
return scheme, token
except ValueError:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token format"
)
raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
@staticmethod
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 {"user_id": "test", "role": "user"} # Placeholder
except Exception as e:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Token validation failed: {str(e)}",
)
raise HTTPExceptionApi(error_code="HTTP_401_UNAUTHORIZED", lang="tr")
@classmethod
def auth_required(cls, func: Callable) -> Callable:
@ -122,16 +110,13 @@ class MiddlewareModule:
return await func(request, *args, **kwargs)
except HTTPExceptionApi:
raise
raise HTTPExceptionApi(error_code="NOT_AUTHORIZED", lang="tr")
except Exception as e:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Authentication failed: {str(e)}",
)
raise HTTPExceptionApi(error_code="NOT_AUTHORIZED", lang="tr")
return wrapper
class RequestTimingMiddleware(BaseHTTPMiddleware):
class RequestTimingMiddleware(BaseHTTPMiddleware):
"""
Middleware for measuring and logging request timing.
Only handles timing, no authentication.
@ -154,19 +139,6 @@ class MiddlewareModule:
response = await call_next(request)
# 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()
elapsed = (end_time - start_time) * 1000 # Convert to milliseconds
@ -177,3 +149,5 @@ class MiddlewareModule:
"request-duration": f"{elapsed:.2f}ms",
}
)
return response

View File

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

View File

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

View File

@ -7,16 +7,16 @@ from middleware.auth_middleware import MiddlewareModule
# Create test router
test_route = APIRouter(prefix="/test", tags=["Test"])
test_route.include_router(test_route, include_in_schema=True)
@test_route.get("/health")
@MiddlewareModule.auth_required
async def health_check(request: Request):
return {"status": "healthy", "message": "Service is running"}
@test_route.get("/ping")
async def ping_test():
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
# Copy application code
COPY DockerApiServices/AllApiNeeds /app/
COPY ErrorHandlers /app/ErrorHandlers
COPY LanguageModels /app/LanguageModels
COPY ApiLibrary /app/ApiLibrary
COPY ApiValidations /app/ApiValidations
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:
```bash
# 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
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
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
docker compose -f ../docker-compose-services.yml up
docker compose -f docker-compose-services.yml up
```
## Clean Build (No Cache)
Use these commands when changing Dockerfile or dependencies:
```bash
# 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
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
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
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
@ -51,9 +51,9 @@ docker compose -f ../docker-compose-services.yml build --no-cache && docker comp
- For faster development iterations
- Run in detached mode:
```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:
```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.bases import BaseErrorModelClass
class HTTPExceptionApiHandler:
def __init__(
self,
**kwargs,
response_model: Any,
):
self.EXCEPTIONS = kwargs.get(
"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")
self.RESPONSE_MODEL: Any = response_model
def retrieve_error_status_code(self, exc: HTTPExceptionApi):
grab_status = self.ERRORS_DICT.get(str(exc.error_code).upper(), "")
grab_status_code = self.EXCEPTION_DICTS.get(str(grab_status).upper(), "500")
return getattr(
self.STATUSES,
str(grab_status_code),
getattr(self.STATUSES, "HTTP_500_INTERNAL_SERVER_ERROR"),
@staticmethod
def retrieve_error_status_code(exc: HTTPExceptionApi) -> int:
from ErrorHandlers import DEFAULT_ERROR
error_by_codes = BaseErrorModelClass.retrieve_error_by_codes()
grab_status_code = error_by_codes.get(
str(exc.error_code).upper(), DEFAULT_ERROR
)
return int(grab_status_code)
def retrieve_error_message(self, exc: HTTPExceptionApi):
message_by_lang = self.ERRORS_LANG.get(str(exc.lang).lower(), {})
return message_by_lang.get(str(exc.error_code).upper(), "Unknown error")
@staticmethod
def retrieve_error_message(exc: HTTPExceptionApi, error_languages) -> str:
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)
error_message = self.retrieve_error_message(exc)
error_message = self.retrieve_error_message(exc, error_languages)
return self.RESPONSE_MODEL(
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 (
HTTPExceptionApi,
)
DEFAULT_ERROR = "UNKNOWN_ERROR"
__all__ = [
"HTTPExceptionApiHandler",
"HTTPExceptionApi",
"DEFAULT_ERROR"
]

View File

@ -1,7 +1,4 @@
from ErrorHandlers.bases import BaseErrorModelClass
class BaseError(BaseErrorModelClass):
class BaseError:
NOT_CREATED: int = 405
NOT_DELETED: int = 405
NOT_UPDATED: int = 405
@ -14,3 +11,4 @@ class BaseError(BaseErrorModelClass):
NOT_ACCEPTABLE: int = 406
INVALID_DATA: int = 422
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:
list_of_statuses = [Statuses, BaseError]
@classmethod
def retrieve_error_by_code(cls, error_code: str):
return getattr(cls, error_code, 502)
def retrieve_error_by_codes(cls):
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(StatusesModelClass):
class Statuses:
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
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(ErrorLanguageModelClass):
class BaseErrorLanguageModelTurkish:
NOT_CREATED: str = "Kayıt oluşturulamadı."
NOT_DELETED: str = "Kayıt silinemedi."
@ -17,7 +14,7 @@ class BaseErrorLanguageModelTurkish(ErrorLanguageModelClass):
UNKNOWN_ERROR: str = "Bilinmeyen bir hata oluştu."
class BaseErrorLanguageModelEnglish(ErrorLanguageModelClass):
class BaseErrorLanguageModelEnglish:
NOT_CREATED: str = "Not Created."
NOT_DELETED: str = "Not Deleted."
@ -33,6 +30,6 @@ class BaseErrorLanguageModelEnglish(ErrorLanguageModelClass):
UNKNOWN_ERROR: str = "Unknown Error occured."
class BaseErrorLanguageModels(LanguageModelClass):
class BaseErrorLanguageModels:
tr: BaseErrorLanguageModelTurkish = BaseErrorLanguageModelTurkish
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