forgeapi 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. fastapi_forge/__init__.py +15 -0
  2. fastapi_forge/cli.py +125 -0
  3. fastapi_forge/commands/__init__.py +5 -0
  4. fastapi_forge/commands/create.py +107 -0
  5. fastapi_forge/generator.py +300 -0
  6. fastapi_forge/models.py +187 -0
  7. fastapi_forge/prompts.py +364 -0
  8. fastapi_forge/templates/base/.dockerignore.jinja +20 -0
  9. fastapi_forge/templates/base/.github/workflows/ci.yml.jinja +155 -0
  10. fastapi_forge/templates/base/.gitignore.jinja +68 -0
  11. fastapi_forge/templates/base/Dockerfile.jinja +69 -0
  12. fastapi_forge/templates/base/README.md.jinja +141 -0
  13. fastapi_forge/templates/base/alembic/README.md.jinja +43 -0
  14. fastapi_forge/templates/base/alembic/env.py.jinja +93 -0
  15. fastapi_forge/templates/base/alembic/script.py.mako.jinja +26 -0
  16. fastapi_forge/templates/base/alembic/versions/.gitkeep.jinja +1 -0
  17. fastapi_forge/templates/base/alembic.ini.jinja +70 -0
  18. fastapi_forge/templates/base/app/__init__.py.jinja +5 -0
  19. fastapi_forge/templates/base/app/api/__init__.py.jinja +3 -0
  20. fastapi_forge/templates/base/app/api/auth_api.py.jinja +72 -0
  21. fastapi_forge/templates/base/app/api/health_api.py.jinja +41 -0
  22. fastapi_forge/templates/base/app/config/__init__.py.jinja +31 -0
  23. fastapi_forge/templates/base/app/config/base.py.jinja +52 -0
  24. fastapi_forge/templates/base/app/config/env.py.jinja +75 -0
  25. fastapi_forge/templates/base/app/core/__init__.py.jinja +3 -0
  26. fastapi_forge/templates/base/app/core/auth.py.jinja +96 -0
  27. fastapi_forge/templates/base/app/core/config.py.jinja +56 -0
  28. fastapi_forge/templates/base/app/core/database.py.jinja +68 -0
  29. fastapi_forge/templates/base/app/core/deps.py.jinja +55 -0
  30. fastapi_forge/templates/base/app/core/redis.py.jinja +41 -0
  31. fastapi_forge/templates/base/app/daos/__init__.py.jinja +3 -0
  32. fastapi_forge/templates/base/app/exceptions/__init__.py.jinja +7 -0
  33. fastapi_forge/templates/base/app/exceptions/exception.py.jinja +34 -0
  34. fastapi_forge/templates/base/app/exceptions/handler.py.jinja +56 -0
  35. fastapi_forge/templates/base/app/main.py.jinja +24 -0
  36. fastapi_forge/templates/base/app/models/__init__.py.jinja +7 -0
  37. fastapi_forge/templates/base/app/models/base.py.jinja +13 -0
  38. fastapi_forge/templates/base/app/schemas/__init__.py.jinja +3 -0
  39. fastapi_forge/templates/base/app/schemas/api_schema.py.jinja +20 -0
  40. fastapi_forge/templates/base/app/schemas/auth_schema.py.jinja +21 -0
  41. fastapi_forge/templates/base/app/server.py.jinja +99 -0
  42. fastapi_forge/templates/base/app/services/__init__.py.jinja +3 -0
  43. fastapi_forge/templates/base/app/utils/__init__.py.jinja +3 -0
  44. fastapi_forge/templates/base/app/utils/log.py.jinja +21 -0
  45. fastapi_forge/templates/base/config.yaml.jinja +71 -0
  46. fastapi_forge/templates/base/docker-compose.yml.jinja +117 -0
  47. fastapi_forge/templates/base/pyproject.toml.jinja +86 -0
  48. fastapi_forge/templates/base/pytest.ini.jinja +10 -0
  49. fastapi_forge/templates/base/ruff.toml.jinja +33 -0
  50. fastapi_forge/templates/base/tests/__init__.py.jinja +3 -0
  51. fastapi_forge/templates/base/tests/conftest.py.jinja +31 -0
  52. fastapi_forge/templates/base/tests/test_health.py.jinja +24 -0
  53. forgeapi-0.1.0.dist-info/METADATA +182 -0
  54. forgeapi-0.1.0.dist-info/RECORD +57 -0
  55. forgeapi-0.1.0.dist-info/WHEEL +4 -0
  56. forgeapi-0.1.0.dist-info/entry_points.txt +2 -0
  57. forgeapi-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,72 @@
1
+ """
2
+ Authentication API
3
+
4
+ This module provides authentication endpoints for user login/logout.
5
+ """
6
+
7
+ from typing import Annotated
8
+
9
+ from fastapi import APIRouter, Depends, status
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.security import OAuth2PasswordRequestForm
12
+ {% if use_database %}
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+
15
+ from app.core.deps import get_db_session
16
+ {% endif %}
17
+ from app.core.config import get_config
18
+ from app.core.auth import AuthHandler
19
+ from app.schemas.auth_schema import TokenResponse
20
+ from app.utils.log import log
21
+
22
+ config = get_config()
23
+ authAPI = APIRouter(prefix=f"{config.app.api}/auth")
24
+
25
+
26
+ @authAPI.post("/login", response_model=TokenResponse)
27
+ async def login(
28
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
29
+ {% if use_database %}
30
+ db_session: AsyncSession = Depends(get_db_session),
31
+ {% endif %}
32
+ ):
33
+ """
34
+ User login endpoint.
35
+
36
+ Args:
37
+ form_data: OAuth2 password request form with username and password
38
+ {% if use_database %}
39
+ db_session: Database session
40
+ {% endif %}
41
+
42
+ Returns:
43
+ JWT access token on successful authentication
44
+ """
45
+ # TODO: Implement actual authentication logic
46
+ # This is a placeholder - replace with real user validation
47
+
48
+ # For demo purposes, accept any credentials
49
+ log.info(f"Login attempt for user: {form_data.username}")
50
+
51
+ # Generate access token
52
+ access_token = AuthHandler.create_access_token({"sub": form_data.username})
53
+
54
+ return TokenResponse(
55
+ access_token=access_token,
56
+ token_type="bearer",
57
+ )
58
+
59
+
60
+ @authAPI.post("/logout")
61
+ async def logout():
62
+ """
63
+ User logout endpoint.
64
+
65
+ Returns:
66
+ Success message
67
+ """
68
+ # TODO: Implement token invalidation if needed
69
+ return JSONResponse(
70
+ status_code=status.HTTP_200_OK,
71
+ content={"message": "Logged out successfully"},
72
+ )
@@ -0,0 +1,41 @@
1
+ """
2
+ Health Check API
3
+
4
+ This module provides health check endpoints for monitoring.
5
+ """
6
+
7
+ from fastapi import APIRouter
8
+
9
+ from app.core.config import get_config
10
+ from app.schemas.api_schema import HealthResponse
11
+
12
+ config = get_config()
13
+ healthAPI = APIRouter(prefix=f"{config.app.api}/health")
14
+
15
+
16
+ @healthAPI.get("", response_model=HealthResponse)
17
+ async def health_check():
18
+ """
19
+ Health check endpoint.
20
+
21
+ Returns:
22
+ Health status response
23
+ """
24
+ return HealthResponse(
25
+ status="healthy",
26
+ version=config.app.version,
27
+ )
28
+
29
+
30
+ @healthAPI.get("/ready", response_model=HealthResponse)
31
+ async def readiness_check():
32
+ """
33
+ Readiness check endpoint.
34
+
35
+ Returns:
36
+ Readiness status response
37
+ """
38
+ return HealthResponse(
39
+ status="ready",
40
+ version=config.app.version,
41
+ )
@@ -0,0 +1,31 @@
1
+ """
2
+ Configuration Module
3
+ """
4
+
5
+ from app.config.base import BaseConfig, DevConfig, ProdConfig
6
+ from app.config.env import AppConfig
7
+ {% if use_database %}
8
+ from app.config.env import DatabaseConfig
9
+ {% endif %}
10
+ {% if use_redis %}
11
+ from app.config.env import RedisConfig
12
+ {% endif %}
13
+ {% if use_auth %}
14
+ from app.config.env import JwtConfig
15
+ {% endif %}
16
+
17
+ __all__ = [
18
+ "BaseConfig",
19
+ "DevConfig",
20
+ "ProdConfig",
21
+ "AppConfig",
22
+ {% if use_database %}
23
+ "DatabaseConfig",
24
+ {% endif %}
25
+ {% if use_redis %}
26
+ "RedisConfig",
27
+ {% endif %}
28
+ {% if use_auth %}
29
+ "JwtConfig",
30
+ {% endif %}
31
+ ]
@@ -0,0 +1,52 @@
1
+ """
2
+ Base Configuration Classes
3
+
4
+ This module defines the base configuration classes for different environments.
5
+ """
6
+
7
+ from pydantic_settings import BaseSettings
8
+
9
+ from app.config.env import AppConfig
10
+ {% if use_database %}
11
+ from app.config.env import DatabaseConfig
12
+ {% endif %}
13
+ {% if use_redis %}
14
+ from app.config.env import RedisConfig
15
+ {% endif %}
16
+ {% if use_auth %}
17
+ from app.config.env import JwtConfig
18
+ {% endif %}
19
+
20
+
21
+ class BaseConfig(BaseSettings):
22
+ """Base configuration for all environments."""
23
+
24
+ env: str
25
+ app: AppConfig
26
+ {% if use_database %}
27
+ db: DatabaseConfig
28
+ {% endif %}
29
+ {% if use_redis %}
30
+ redis: RedisConfig
31
+ {% endif %}
32
+ {% if use_auth %}
33
+ jwt: JwtConfig
34
+ {% endif %}
35
+
36
+
37
+ class DevConfig(BaseConfig):
38
+ """Development environment configuration."""
39
+
40
+ pass
41
+
42
+
43
+ class StagingConfig(BaseConfig):
44
+ """Staging environment configuration."""
45
+
46
+ pass
47
+
48
+
49
+ class ProdConfig(BaseConfig):
50
+ """Production environment configuration."""
51
+
52
+ pass
@@ -0,0 +1,75 @@
1
+ """
2
+ Environment Configuration Classes
3
+
4
+ This module defines Pydantic models for environment-specific configuration.
5
+ """
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class AppConfig(BaseModel):
11
+ """Application configuration."""
12
+
13
+ name: str
14
+ description: str = ""
15
+ api: str = "/api"
16
+ host: str = "0.0.0.0"
17
+ port: int = 8000
18
+ uvicorn: str = "app.server:app"
19
+ version: str = "0.1.0"
20
+ reload: bool = True
21
+
22
+ {% if use_database %}
23
+
24
+ class DatabaseConfig(BaseModel):
25
+ """Database configuration."""
26
+
27
+ host: str
28
+ port: int
29
+ user: str
30
+ password: str
31
+ database: str
32
+ driver_name: str = "postgresql+asyncpg"
33
+ echo: bool = False
34
+ pool_size: int = 5
35
+ max_overflow: int = 10
36
+ pool_recycle: int = 3600
37
+ pool_timeout: int = 30
38
+
39
+ @property
40
+ def database_url(self) -> str:
41
+ """Get the database URL."""
42
+ {% if use_sqlite %}
43
+ if "sqlite" in self.driver_name:
44
+ return f"{self.driver_name}:///{self.database}"
45
+ {% endif %}
46
+ return f"{self.driver_name}://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
47
+ {% endif %}
48
+
49
+ {% if use_redis %}
50
+
51
+ class RedisConfig(BaseModel):
52
+ """Redis configuration."""
53
+
54
+ host: str = "localhost"
55
+ port: int = 6379
56
+ db: int = 0
57
+ password: str = ""
58
+
59
+ @property
60
+ def redis_url(self) -> str:
61
+ """Get the Redis URL."""
62
+ if self.password:
63
+ return f"redis://:{self.password}@{self.host}:{self.port}/{self.db}"
64
+ return f"redis://{self.host}:{self.port}/{self.db}"
65
+ {% endif %}
66
+
67
+ {% if use_auth %}
68
+
69
+ class JwtConfig(BaseModel):
70
+ """JWT configuration."""
71
+
72
+ secret_key: str
73
+ algorithm: str = "HS256"
74
+ expire_minutes: int = 1440
75
+ {% endif %}
@@ -0,0 +1,3 @@
1
+ """
2
+ Core Module
3
+ """
@@ -0,0 +1,96 @@
1
+ """
2
+ Authentication Handler
3
+
4
+ This module provides JWT token creation and validation utilities.
5
+ """
6
+
7
+ from datetime import datetime, timedelta, timezone
8
+
9
+ import jwt
10
+ from passlib.context import CryptContext
11
+
12
+ from app.core.config import get_config
13
+
14
+ config = get_config()
15
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
16
+
17
+
18
+ class AuthHandler:
19
+ """Authentication handler for JWT operations."""
20
+
21
+ @staticmethod
22
+ def hash_password(password: str) -> str:
23
+ """
24
+ Hash a password using bcrypt.
25
+
26
+ Args:
27
+ password: Plain text password
28
+
29
+ Returns:
30
+ Hashed password
31
+ """
32
+ return pwd_context.hash(password)
33
+
34
+ @staticmethod
35
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
36
+ """
37
+ Verify a password against its hash.
38
+
39
+ Args:
40
+ plain_password: Plain text password
41
+ hashed_password: Hashed password
42
+
43
+ Returns:
44
+ True if password matches, False otherwise
45
+ """
46
+ return pwd_context.verify(plain_password, hashed_password)
47
+
48
+ @staticmethod
49
+ def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
50
+ """
51
+ Create a JWT access token.
52
+
53
+ Args:
54
+ data: Data to encode in the token
55
+ expires_delta: Optional expiration time delta
56
+
57
+ Returns:
58
+ Encoded JWT token
59
+ """
60
+ to_encode = data.copy()
61
+
62
+ if expires_delta:
63
+ expire = datetime.now(timezone.utc) + expires_delta
64
+ else:
65
+ expire = datetime.now(timezone.utc) + timedelta(
66
+ minutes=config.jwt.expire_minutes
67
+ )
68
+
69
+ to_encode.update({"exp": expire})
70
+ encoded_jwt = jwt.encode(
71
+ to_encode,
72
+ config.jwt.secret_key,
73
+ algorithm=config.jwt.algorithm,
74
+ )
75
+ return encoded_jwt
76
+
77
+ @staticmethod
78
+ def decode_token(token: str) -> dict | None:
79
+ """
80
+ Decode a JWT token.
81
+
82
+ Args:
83
+ token: JWT token to decode
84
+
85
+ Returns:
86
+ Decoded token data or None if invalid
87
+ """
88
+ try:
89
+ payload = jwt.decode(
90
+ token,
91
+ config.jwt.secret_key,
92
+ algorithms=[config.jwt.algorithm],
93
+ )
94
+ return payload
95
+ except jwt.PyJWTError:
96
+ return None
@@ -0,0 +1,56 @@
1
+ """
2
+ Configuration Loader
3
+
4
+ This module handles loading configuration from YAML files.
5
+ """
6
+
7
+ from functools import lru_cache
8
+ from pathlib import Path
9
+
10
+ import yaml
11
+
12
+ from app.config.base import BaseConfig, DevConfig, StagingConfig, ProdConfig
13
+
14
+
15
+ def get_project_path() -> Path:
16
+ """Get the project root path."""
17
+ return Path(__file__).parent.parent.parent
18
+
19
+
20
+ @lru_cache()
21
+ def get_config(config_file: str = "config.yaml", env: str | None = None) -> BaseConfig:
22
+ """
23
+ Get configuration for the specified environment.
24
+
25
+ Args:
26
+ config_file: Path to the configuration file
27
+ env: Environment name (dev, staging, prod)
28
+
29
+ Returns:
30
+ Configuration object for the specified environment
31
+ """
32
+ project_path = get_project_path()
33
+ config_path = project_path / config_file
34
+
35
+ with open(config_path, "r", encoding="utf-8") as f:
36
+ yaml_config = yaml.safe_load(f)
37
+
38
+ # Get environment from config or parameter
39
+ if env:
40
+ yaml_config["env"] = env
41
+ else:
42
+ env = yaml_config.get("env", "dev")
43
+
44
+ # Get environment-specific config
45
+ env_config = yaml_config.get(env, {})
46
+ env_config["env"] = env
47
+
48
+ # Select configuration class based on environment
49
+ config_classes = {
50
+ "dev": DevConfig,
51
+ "staging": StagingConfig,
52
+ "prod": ProdConfig,
53
+ }
54
+
55
+ config_class = config_classes.get(env, DevConfig)
56
+ return config_class(**env_config)
@@ -0,0 +1,68 @@
1
+ """
2
+ Database Utilities
3
+
4
+ This module provides database connection and session management.
5
+ """
6
+
7
+ from sqlalchemy.ext.asyncio import (
8
+ AsyncEngine,
9
+ AsyncSession,
10
+ async_sessionmaker,
11
+ create_async_engine,
12
+ )
13
+
14
+ from app.config.env import DatabaseConfig
15
+
16
+
17
+ class DatabaseUtil:
18
+ """Database utility class."""
19
+
20
+ @classmethod
21
+ async def init_engine(cls, config: DatabaseConfig) -> AsyncEngine:
22
+ """
23
+ Initialize async database engine.
24
+
25
+ Args:
26
+ config: Database configuration
27
+
28
+ Returns:
29
+ Async database engine
30
+ """
31
+ engine = create_async_engine(
32
+ config.database_url,
33
+ echo=config.echo,
34
+ pool_size=config.pool_size,
35
+ max_overflow=config.max_overflow,
36
+ pool_recycle=config.pool_recycle,
37
+ pool_timeout=config.pool_timeout,
38
+ )
39
+ return engine
40
+
41
+ @classmethod
42
+ async def close_engine(cls, engine: AsyncEngine) -> None:
43
+ """
44
+ Close database engine.
45
+
46
+ Args:
47
+ engine: Async database engine to close
48
+ """
49
+ await engine.dispose()
50
+
51
+ @classmethod
52
+ async def get_session(cls, engine: AsyncEngine):
53
+ """
54
+ Get async database session.
55
+
56
+ Args:
57
+ engine: Async database engine
58
+
59
+ Yields:
60
+ Async database session
61
+ """
62
+ async_session = async_sessionmaker(
63
+ engine,
64
+ class_=AsyncSession,
65
+ expire_on_commit=False,
66
+ )
67
+ async with async_session() as session:
68
+ yield session
@@ -0,0 +1,55 @@
1
+ """
2
+ FastAPI Dependencies
3
+
4
+ This module provides dependency injection utilities for FastAPI.
5
+ """
6
+
7
+ from fastapi import Request
8
+ {% if use_database %}
9
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
10
+
11
+ from app.core.database import DatabaseUtil
12
+ {% endif %}
13
+
14
+ {% if use_database %}
15
+
16
+ def get_db_engine(request: Request) -> AsyncEngine:
17
+ """
18
+ Get database engine from request state.
19
+
20
+ Args:
21
+ request: FastAPI request object
22
+
23
+ Returns:
24
+ Async database engine
25
+ """
26
+ return request.app.state.db_engine
27
+
28
+
29
+ async def get_db_session(request: Request) -> AsyncSession:
30
+ """
31
+ Get database session.
32
+
33
+ Args:
34
+ request: FastAPI request object
35
+
36
+ Yields:
37
+ Async database session
38
+ """
39
+ engine = get_db_engine(request)
40
+ async for session in DatabaseUtil.get_session(engine):
41
+ yield session
42
+ {% endif %}
43
+
44
+
45
+ def get_config(request: Request):
46
+ """
47
+ Get application configuration from request state.
48
+
49
+ Args:
50
+ request: FastAPI request object
51
+
52
+ Returns:
53
+ Application configuration
54
+ """
55
+ return request.app.state.config
@@ -0,0 +1,41 @@
1
+ """
2
+ Redis Utilities
3
+
4
+ This module provides Redis connection management.
5
+ """
6
+
7
+ import redis.asyncio as redis
8
+
9
+ from app.config.env import RedisConfig
10
+
11
+
12
+ class RedisUtil:
13
+ """Redis utility class."""
14
+
15
+ @classmethod
16
+ async def init_redis(cls, config: RedisConfig) -> redis.Redis:
17
+ """
18
+ Initialize Redis connection.
19
+
20
+ Args:
21
+ config: Redis configuration
22
+
23
+ Returns:
24
+ Redis client instance
25
+ """
26
+ client = redis.from_url(
27
+ config.redis_url,
28
+ encoding="utf-8",
29
+ decode_responses=True,
30
+ )
31
+ return client
32
+
33
+ @classmethod
34
+ async def close_redis(cls, client: redis.Redis) -> None:
35
+ """
36
+ Close Redis connection.
37
+
38
+ Args:
39
+ client: Redis client to close
40
+ """
41
+ await client.aclose()
@@ -0,0 +1,3 @@
1
+ """
2
+ Data Access Objects Module
3
+ """
@@ -0,0 +1,7 @@
1
+ """
2
+ Exceptions Module
3
+ """
4
+
5
+ from app.exceptions.exception import AppException, AuthException
6
+
7
+ __all__ = ["AppException", "AuthException"]
@@ -0,0 +1,34 @@
1
+ """
2
+ Custom Exceptions
3
+
4
+ This module defines custom exception classes for the application.
5
+ """
6
+
7
+
8
+ class AppException(Exception):
9
+ """Base application exception."""
10
+
11
+ def __init__(self, message: str = "An error occurred"):
12
+ self.message = message
13
+ super().__init__(self.message)
14
+
15
+
16
+ class AuthException(AppException):
17
+ """Authentication exception."""
18
+
19
+ def __init__(self, message: str = "Authentication failed"):
20
+ super().__init__(message)
21
+
22
+
23
+ class NotFoundException(AppException):
24
+ """Resource not found exception."""
25
+
26
+ def __init__(self, message: str = "Resource not found"):
27
+ super().__init__(message)
28
+
29
+
30
+ class ValidationException(AppException):
31
+ """Validation exception."""
32
+
33
+ def __init__(self, message: str = "Validation error"):
34
+ super().__init__(message)
@@ -0,0 +1,56 @@
1
+ """
2
+ Exception Handlers
3
+
4
+ This module registers global exception handlers for the FastAPI application.
5
+ """
6
+
7
+ from fastapi import FastAPI, Request, status
8
+ from fastapi.responses import JSONResponse
9
+ from starlette.exceptions import HTTPException
10
+
11
+ from app.exceptions.exception import AppException, AuthException
12
+ from app.utils.log import log
13
+
14
+
15
+ def register_exception_handlers(app: FastAPI) -> None:
16
+ """
17
+ Register global exception handlers.
18
+
19
+ Args:
20
+ app: FastAPI application instance
21
+ """
22
+
23
+ @app.exception_handler(AuthException)
24
+ async def auth_exception_handler(request: Request, exc: AuthException):
25
+ """Handle authentication exceptions."""
26
+ return JSONResponse(
27
+ status_code=status.HTTP_401_UNAUTHORIZED,
28
+ content={"message": exc.message},
29
+ )
30
+
31
+ @app.exception_handler(AppException)
32
+ async def app_exception_handler(request: Request, exc: AppException):
33
+ """Handle application exceptions."""
34
+ log.error(f"Application error: {exc.message}")
35
+ return JSONResponse(
36
+ status_code=status.HTTP_400_BAD_REQUEST,
37
+ content={"message": exc.message},
38
+ )
39
+
40
+ @app.exception_handler(HTTPException)
41
+ async def http_exception_handler(request: Request, exc: HTTPException):
42
+ """Handle HTTP exceptions."""
43
+ log.error(f"HTTP error: {exc.detail}")
44
+ return JSONResponse(
45
+ status_code=exc.status_code,
46
+ content={"message": exc.detail},
47
+ )
48
+
49
+ @app.exception_handler(Exception)
50
+ async def general_exception_handler(request: Request, exc: Exception):
51
+ """Handle all other exceptions."""
52
+ log.error(f"Unexpected error: {exc}")
53
+ return JSONResponse(
54
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
55
+ content={"message": "Internal server error"},
56
+ )