Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/fluentmeet_test
REDIS_URL: redis://localhost:6379/1
run: |
pytest --cov=app --cov-fail-under=5 tests/
pytest --cov=app --cov-fail-under=60 tests/
22 changes: 20 additions & 2 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import pathlib

try:
import tomllib
except ImportError:
import tomli as tomllib
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if tomli is already in requirements
rg -i "tomli" requirements*.txt pyproject.toml 2>/dev/null || echo "tomli not found in dependencies"

Repository: Brints/FluentMeet

Length of output: 92


Add tomli to project dependencies and suppress type checker warnings.

The tomli module is not listed in your project dependencies, which will cause import failures on Python < 3.11. Add it as a dependency with Python version constraint, and suppress the mypy redefinition warning:

Suggested changes

In app/core/config.py:

 try:
     import tomllib
 except ImportError:
-    import tomli as tomllib
+    import tomli as tomllib  # type: ignore[import-not-found,no-redef]

In requirements.txt (or pyproject.toml):

+tomli>=2.0.0;python_version<"3.11"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
import tomllib
except ImportError:
import tomli as tomllib
try:
import tomllib
except ImportError:
import tomli as tomllib # type: ignore[import-not-found,no-redef]
🧰 Tools
🪛 GitHub Actions: CI

[error] 6-6: Cannot find implementation or library stub for module named 'tomli' [import-not-found]


[error] 6-6: Name 'tomllib' already defined (by an import) [no-redef]

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/core/config.py` around lines 3 - 6, The fallback import block in
app/core/config.py (the try/except that imports tomllib or falls back to tomli)
requires adding tomli to project dependencies and silencing mypy's redefinition
warning: add tomli to requirements.txt or pyproject.toml with a Python
constraint (e.g., only for python_version < "3.11") and append a mypy
suppression comment to the fallback import line (the `import tomli as tomllib`
branch) to suppress the redefinition/import warning so the type checker does not
complain.


from pydantic_settings import BaseSettings, SettingsConfigDict


def get_version() -> str:
pyproject_path = pathlib.Path(__file__).parent.parent.parent / "pyproject.toml"
if pyproject_path.exists():
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
return str(data.get("project", {}).get("version", "1.0.0"))
return "1.0.0"


class Settings(BaseSettings):
PROJECT_NAME: str = "FluentMeet"
VERSION: str = "1.0.0"
VERSION: str = get_version()
API_V1_STR: str = "/api/v1"

# Security
Expand Down Expand Up @@ -31,7 +47,9 @@ class Settings(BaseSettings):
VOICE_AI_API_KEY: str | None = None
OPENAI_API_KEY: str | None = None

model_config = SettingsConfigDict(env_file=".env", case_sensitive=True)
model_config = SettingsConfigDict(
env_file=".env", case_sensitive=True, extra="ignore"
)


settings = Settings()
50 changes: 50 additions & 0 deletions app/core/error_responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import Any

from fastapi.responses import JSONResponse
from pydantic import BaseModel


class ErrorDetail(BaseModel):
field: str | None = None
message: str


class ErrorResponse(BaseModel):
status: str = "error"
code: str
message: str
details: list[ErrorDetail] = []


def create_error_response(
status_code: int,
code: str,
message: str,
details: list[dict[str, Any]] | None = None,
) -> JSONResponse:
"""
Helper to create a standardized JSON error response.
"""
error_details = []
if details:
for detail in details:
error_details.append(
ErrorDetail(
field=detail.get("field"),
message=detail.get("msg")
or detail.get("message")
or "Unknown error",
)
)

response_content = ErrorResponse(
status="error",
code=code,
message=message,
details=error_details,
)

return JSONResponse(
status_code=status_code,
content=response_content.model_dump(),
)
76 changes: 76 additions & 0 deletions app/core/exception_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import logging

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

from app.core.error_responses import create_error_response
from app.core.exceptions import FluentMeetException

logger = logging.getLogger(__name__)


async def fluentmeet_exception_handler(request: Request, exc: FluentMeetException):
"""
Handler for all custom FluentMeetException exceptions.
"""
return create_error_response(
status_code=exc.status_code,
code=exc.code,
message=exc.message,
details=exc.details,
)


async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""
Handler for Pydantic validation errors (422 -> 400).
"""
details = []
for error in exc.errors():
details.append(
{
"field": ".".join(str(loc) for loc in error["loc"]),
"msg": error["msg"],
}
)

return create_error_response(
status_code=400,
code="VALIDATION_ERROR",
message="Request validation failed",
details=details,
)


async def http_exception_handler(request: Request, exc: StarletteHTTPException):
"""
Handler for Starlette/FastAPI HTTP exceptions.
"""
return create_error_response(
status_code=exc.status_code,
code=getattr(exc, "code", "HTTP_ERROR"),
message=exc.detail,
)


async def unhandled_exception_handler(request: Request, exc: Exception):
"""
Handler for all other unhandled exceptions (500).
"""
logger.exception("Unhandled exception occurred: %s", str(exc))
return create_error_response(
status_code=500,
code="INTERNAL_SERVER_ERROR",
message="An unexpected server error occurred",
)


def register_exception_handlers(app: FastAPI) -> None:
"""
Register all custom exception handlers to the FastAPI app.
"""
app.add_exception_handler(FluentMeetException, fluentmeet_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
app.add_exception_handler(Exception, unhandled_exception_handler)
80 changes: 80 additions & 0 deletions app/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import Any


class FluentMeetException(Exception):
"""
Base exception for all FluentMeet API errors.
"""

def __init__(
self,
status_code: int = 500,
code: str = "INTERNAL_SERVER_ERROR",
message: str = "An unexpected error occurred",
details: list[dict[str, Any]] | None = None,
) -> None:
self.status_code = status_code
self.code = code
self.message = message
self.details = details or []
super().__init__(self.message)


class BadRequestException(FluentMeetException):
def __init__(
self,
message: str = "Bad Request",
code: str = "BAD_REQUEST",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(400, code, message, details)


class UnauthorizedException(FluentMeetException):
def __init__(
self,
message: str = "Unauthorized",
code: str = "UNAUTHORIZED",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(401, code, message, details)


class ForbiddenException(FluentMeetException):
def __init__(
self,
message: str = "Forbidden",
code: str = "FORBIDDEN",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(403, code, message, details)


class NotFoundException(FluentMeetException):
def __init__(
self,
message: str = "Not Found",
code: str = "NOT_FOUND",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(404, code, message, details)


class ConflictException(FluentMeetException):
def __init__(
self,
message: str = "Conflict",
code: str = "CONFLICT",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(409, code, message, details)


class InternalServerException(FluentMeetException):
def __init__(
self,
message: str = "Internal Server Error",
code: str = "INTERNAL_SERVER_ERROR",
details: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(500, code, message, details)
11 changes: 8 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.core.config import settings
from app.core.exception_handlers import register_exception_handlers

app = FastAPI(
title="FluentMeet API",
title=settings.PROJECT_NAME,
description="Real-time voice translation video conferencing platform API",
version="1.1.0",
version=settings.VERSION,
)

# Set all CORS enabled origins
Expand All @@ -16,10 +19,12 @@
allow_headers=["*"],
)

register_exception_handlers(app)


@app.get("/health", tags=["health"])
async def health_check() -> dict[str, str]:
return {"status": "ok", "version": "1.1.0"}
return {"status": "ok", "version": settings.VERSION}


if __name__ == "__main__":
Expand Down
File renamed without changes.
33 changes: 0 additions & 33 deletions linting_issue.md

This file was deleted.

Loading
Loading