From ca892a6659ac7c3aaa21e940bf576884e4bcfca9 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 14 Mar 2026 12:50:35 +0100 Subject: [PATCH 1/4] feat: implement uniform error handling and dynamic versioning - Create centralized exception hierarchy in app/core/exceptions.py - Standardize API error response schema in app/core/error_responses.py - Implement global FastAPI exception handlers for custom/validation/HTTP errors - Dynamically read project version from pyproject.toml in Settings - Add comprehensive test suite for error handling scenarios - Configure Pydantic to ignore extra environment variables Signed-off-by: aniebietafia --- .github/workflows/ci.yml | 2 +- app/core/config.py | 22 ++- app/core/error_responses.py | 50 +++++++ app/core/exception_handlers.py | 76 ++++++++++ app/core/exceptions.py | 80 +++++++++++ app/main.py | 11 +- github-issues/sample-issue.md | 45 ++++++ .../docker-compose.yml | 0 linting_issue.md | 33 ----- tests/test_error_handling.py | 136 ++++++++++++++++++ tests/test_main.py | 3 +- 11 files changed, 418 insertions(+), 40 deletions(-) create mode 100644 app/core/error_responses.py create mode 100644 app/core/exception_handlers.py create mode 100644 app/core/exceptions.py create mode 100644 github-issues/sample-issue.md rename docker-compose.yml => infra/docker-compose.yml (100%) delete mode 100644 linting_issue.md create mode 100644 tests/test_error_handling.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70a47ee..670480f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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/ diff --git a/app/core/config.py b/app/core/config.py index a222a35..f11df33 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,9 +1,25 @@ +import pathlib + +try: + import tomllib +except ImportError: + import tomli as tomllib + 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 @@ -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() diff --git a/app/core/error_responses.py b/app/core/error_responses.py new file mode 100644 index 0000000..1e56cc5 --- /dev/null +++ b/app/core/error_responses.py @@ -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(), + ) diff --git a/app/core/exception_handlers.py b/app/core/exception_handlers.py new file mode 100644 index 0000000..0af72ac --- /dev/null +++ b/app/core/exception_handlers.py @@ -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) diff --git a/app/core/exceptions.py b/app/core/exceptions.py new file mode 100644 index 0000000..52aec8b --- /dev/null +++ b/app/core/exceptions.py @@ -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) diff --git a/app/main.py b/app/main.py index d1a121c..d7e5ca4 100644 --- a/app/main.py +++ b/app/main.py @@ -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 @@ -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__": diff --git a/github-issues/sample-issue.md b/github-issues/sample-issue.md new file mode 100644 index 0000000..cc76026 --- /dev/null +++ b/github-issues/sample-issue.md @@ -0,0 +1,45 @@ +### Feature: Implement Email Service for User Account Verification + +**Problem** +The user registration process requires email verification to ensure users provide a valid and accessible email address. However, the application currently lacks the functionality to send emails, making it impossible to send verification tokens or links to new users. + +**Proposed Solution** +Integrate a robust email sending service into the application using the Spring Boot Mail starter. This will involve creating a generic `EmailService` that can be configured to use an SMTP server or a third-party email provider (e.g., SendGrid, Mailgun). This service will be used to send a welcome email containing a unique, time-sensitive verification link to users upon registration. + +**User Stories** +* **As a new user,** I want to receive an email with a verification link after I sign up, so I can confirm ownership of my email account and activate my user account. +* **As a developer,** I want a centralized and reusable `EmailService` so that I can easily send various types of transactional emails (e.g., password resets, notifications) in the future. + +**Acceptance Criteria** +1. An `EmailService` is implemented and integrated into the application. +2. Email provider credentials (e.g., SMTP host, port, username, password) are externalized in the application's configuration files (`application.properties` or `application.yml`) and are not hardcoded. +3. Upon successful user registration, the system generates a unique verification token, saves it, and associates it with the new user. +4. The `EmailService` is called to send a verification email to the user's provided email address. The email body must contain the verification link. +5. The user's account is marked as `UNVERIFIED` until they click the verification link. +6. A new public endpoint (e.g., `GET /api/v1/auth/verify?token=...`) is created to handle the verification process. +7. When a user clicks the link, the endpoint validates the token, marks the user's account as `VERIFIED`, and invalidates the token to prevent reuse. +8. Email sending should be performed asynchronously to avoid blocking the user registration HTTP request. + +**Proposed Technical Details** +* **Dependency:** Add the `spring-boot-starter-mail` dependency to the `pom.xml`. +* **Configuration:** Configure Spring Mail properties in `application.properties`. For development, a tool like Mailtrap or a local SMTP server can be used. +* **Service:** Create an `EmailService` with a method like `sendHtmlEmail(String to, String subject, String htmlBody)`. Annotate the sending method with `@Async` for non-blocking execution. +* **Entity:** Create a `VerificationToken` entity to store the token, its expiry date, and a `@OneToOne` relationship to the `User` entity. +* **Templating:** Use a templating engine like Thymeleaf to create a professional HTML email template for the verification message. +* **Controller:** Add a new method in an `AuthController` or a dedicated `VerificationController` to handle the `GET /api/v1/auth/verify` request. + +**Tasks** +- [ ] Add `spring-boot-starter-mail` and `spring-boot-starter-thymeleaf` to `pom.xml`. +- [ ] Configure mail server settings in `application.properties`. +- [ ] Implement the `EmailService` and enable asynchronous processing with `@EnableAsync`. +- [ ] Create the `VerificationToken` entity, repository, and service. +- [ ] Design and create the HTML email template (`verification-email.html`). +- [ ] Update the user registration logic to generate a token and trigger the email sending process. +- [ ] Implement the verification endpoint (`GET /api/v1/auth/verify`) to validate the token and activate the user. +- [ ] Write unit and integration tests for token generation, email sending, and the verification flow. + +**Open Questions/Considerations** +* Which email service provider (SendGrid, AWS SES, Mailgun) will be used in production? +* What should be the expiration time for verification tokens (e.g., 24 hours)? +* How should the application handle cases where the email fails to send? Should there be a retry mechanism? +* Should we provide an endpoint for users to request a new verification email if the original one expires or is lost? \ No newline at end of file diff --git a/docker-compose.yml b/infra/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to infra/docker-compose.yml diff --git a/linting_issue.md b/linting_issue.md deleted file mode 100644 index 4ae748e..0000000 --- a/linting_issue.md +++ /dev/null @@ -1,33 +0,0 @@ -# Issue: Enforce Linting, Type-Checking, and Code Style - -## Problem -The project currently uses `black` and `isort` for formatting, but lacks a comprehensive linter and consistent type-checking. This can lead to subtle bugs, inconsistent coding patterns, and poor maintainability as the codebase grows. There is no automated enforcement of these standards outside of the newly planned CI workflow. - -## Proposed Solution -Standardize the project's code style by adopting a modern linter (e.g., `ruff`) and fully configuring `mypy` for static type analysis. Update `pyproject.toml` to serve as the single source of truth for all linting and formatting configurations. - -## User Stories -- As a developer, I want clear feedback on code quality and style violations as I write code. -- As a reviewer, I want to spend less time on stylistic comments and more time on logic and architecture. - -## Acceptance Criteria -- [ ] `ruff` is added as a development dependency and configured in `pyproject.toml`. -- [ ] `mypy` is added as a development dependency and configured in `pyproject.toml`. -- [ ] Existing code is updated to pass all linting and type-checking rules. -- [ ] A `make lint` or similar command is available for local verification. -- [ ] Documentation is updated to include the coding standards and how to run the tools. - -## Proposed Technical Details -- Use `ruff` to replace multiple tools (flake8, autoflake, etc.) for performance and simplicity. -- Configure `ruff` rules to be strict but pragmatic (e.g., following the `B`, `E`, `F`, and `I` rule sets). -- Set up `mypy` with `strict = true` or a similar high-standard configuration to ensure type safety. -- Update `pyproject.toml` sections for `[tool.ruff]` and `[tool.mypy]`. - -## Tasks -- [ ] Add `ruff` and `mypy` to `requirements.txt` (or a new `requirements-dev.txt`). -- [ ] Configure `ruff` in `pyproject.toml`. -- [ ] Configure `mypy` in `pyproject.toml`. -- [ ] Run `ruff check . --fix` to address auto-fixable issues. -- [ ] Manually fix remaining linting violations. -- [ ] Fix type-checking errors reported by `mypy`. -- [ ] Update `README.md` with instructions for running linting tools. diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py new file mode 100644 index 0000000..be1e100 --- /dev/null +++ b/tests/test_error_handling.py @@ -0,0 +1,136 @@ +import pytest +from fastapi import FastAPI, HTTPException +from fastapi.testclient import TestClient +from pydantic import BaseModel + +from app.core.config import settings +from app.core.exceptions import ( + BadRequestException, + ConflictException, + ForbiddenException, + NotFoundException, + UnauthorizedException, +) +from app.main import app + +client = TestClient(app, raise_server_exceptions=False) + + +# Mock endpoints to trigger exceptions +@app.get("/test/bad-request") +async def trigger_bad_request(): + raise BadRequestException(message="Custom bad request message") + + +@app.get("/test/unauthorized") +async def trigger_unauthorized(): + raise UnauthorizedException() + + +@app.get("/test/forbidden") +async def trigger_forbidden(): + raise ForbiddenException() + + +@app.get("/test/not-found") +async def trigger_not_found(): + raise NotFoundException() + + +@app.get("/test/conflict") +async def trigger_conflict(): + raise ConflictException() + + +@app.get("/test/http-exception") +async def trigger_http_exception(): + raise HTTPException(status_code=418, detail="I'm a teapot") + + +@app.get("/test/unhandled-exception") +async def trigger_unhandled_exception(): + raise ValueError("Something went wrong internally") + + +class ValidationModel(BaseModel): + name: str + age: int + + +@app.post("/test/validation") +async def trigger_validation_error(data: ValidationModel): + return data + + +def test_bad_request_handler(): + response = client.get("/test/bad-request") + assert response.status_code == 400 + data = response.json() + assert data["status"] == "error" + assert data["code"] == "BAD_REQUEST" + assert data["message"] == "Custom bad request message" + + +def test_unauthorized_handler(): + response = client.get("/test/unauthorized") + assert response.status_code == 401 + data = response.json() + assert data["code"] == "UNAUTHORIZED" + + +def test_forbidden_handler(): + response = client.get("/test/forbidden") + assert response.status_code == 403 + data = response.json() + assert data["code"] == "FORBIDDEN" + + +def test_not_found_handler(): + response = client.get("/test/not-found") + assert response.status_code == 404 + data = response.json() + assert data["code"] == "NOT_FOUND" + + +def test_conflict_handler(): + response = client.get("/test/conflict") + assert response.status_code == 409 + data = response.json() + assert data["code"] == "CONFLICT" + + +def test_http_exception_handler(): + response = client.get("/test/http-exception") + assert response.status_code == 418 + data = response.json() + assert data["status"] == "error" + assert data["message"] == "I'm a teapot" + + +def test_validation_error_handler(): + # Missing required fields + response = client.post("/test/validation", json={"age": "not-an-int"}) + assert response.status_code == 400 + data = response.json() + assert data["status"] == "error" + assert data["code"] == "VALIDATION_ERROR" + assert len(data["details"]) > 0 + # Check if field name is present in details + fields = [d["field"] for d in data["details"]] + assert "body.name" in fields + assert "body.age" in fields + + +def test_unhandled_exception_handler(): + response = client.get("/test/unhandled-exception") + assert response.status_code == 500 + data = response.json() + assert data["status"] == "error" + assert data["code"] == "INTERNAL_SERVER_ERROR" + assert data["message"] == "An unexpected server error occurred" + + +def test_health_check_remains_ok(): + response = client.get("/health") + assert response.status_code == 200 + assert response.json() == {"status": "ok", "version": settings.VERSION} diff --git a/tests/test_main.py b/tests/test_main.py index 563bbae..21e5916 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,6 @@ from fastapi.testclient import TestClient +from app.core.config import settings from app.main import app client = TestClient(app) @@ -8,4 +9,4 @@ def test_health_check(): response = client.get("/health") assert response.status_code == 200 - assert response.json() == {"status": "ok", "version": "1.0.0"} + assert response.json() == {"status": "ok", "version": settings.VERSION} From fe67f6852e07319c54af9a8b5414c322b2699128 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 14 Mar 2026 13:54:33 +0100 Subject: [PATCH 2/4] feat: implement uniform error handling and dynamic versioning - Create centralized exception hierarchy in app/core/exceptions.py - Standardize API error response schema in app/core/error_responses.py - Implement global FastAPI exception handlers for custom/validation/HTTP errors - Dynamically read project version from pyproject.toml in Settings - Add comprehensive test suite for error handling scenarios - Configure Pydantic to ignore extra environment variables Signed-off-by: aniebietafia --- github-issues/sample-issue.md | 45 ----------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 github-issues/sample-issue.md diff --git a/github-issues/sample-issue.md b/github-issues/sample-issue.md deleted file mode 100644 index cc76026..0000000 --- a/github-issues/sample-issue.md +++ /dev/null @@ -1,45 +0,0 @@ -### Feature: Implement Email Service for User Account Verification - -**Problem** -The user registration process requires email verification to ensure users provide a valid and accessible email address. However, the application currently lacks the functionality to send emails, making it impossible to send verification tokens or links to new users. - -**Proposed Solution** -Integrate a robust email sending service into the application using the Spring Boot Mail starter. This will involve creating a generic `EmailService` that can be configured to use an SMTP server or a third-party email provider (e.g., SendGrid, Mailgun). This service will be used to send a welcome email containing a unique, time-sensitive verification link to users upon registration. - -**User Stories** -* **As a new user,** I want to receive an email with a verification link after I sign up, so I can confirm ownership of my email account and activate my user account. -* **As a developer,** I want a centralized and reusable `EmailService` so that I can easily send various types of transactional emails (e.g., password resets, notifications) in the future. - -**Acceptance Criteria** -1. An `EmailService` is implemented and integrated into the application. -2. Email provider credentials (e.g., SMTP host, port, username, password) are externalized in the application's configuration files (`application.properties` or `application.yml`) and are not hardcoded. -3. Upon successful user registration, the system generates a unique verification token, saves it, and associates it with the new user. -4. The `EmailService` is called to send a verification email to the user's provided email address. The email body must contain the verification link. -5. The user's account is marked as `UNVERIFIED` until they click the verification link. -6. A new public endpoint (e.g., `GET /api/v1/auth/verify?token=...`) is created to handle the verification process. -7. When a user clicks the link, the endpoint validates the token, marks the user's account as `VERIFIED`, and invalidates the token to prevent reuse. -8. Email sending should be performed asynchronously to avoid blocking the user registration HTTP request. - -**Proposed Technical Details** -* **Dependency:** Add the `spring-boot-starter-mail` dependency to the `pom.xml`. -* **Configuration:** Configure Spring Mail properties in `application.properties`. For development, a tool like Mailtrap or a local SMTP server can be used. -* **Service:** Create an `EmailService` with a method like `sendHtmlEmail(String to, String subject, String htmlBody)`. Annotate the sending method with `@Async` for non-blocking execution. -* **Entity:** Create a `VerificationToken` entity to store the token, its expiry date, and a `@OneToOne` relationship to the `User` entity. -* **Templating:** Use a templating engine like Thymeleaf to create a professional HTML email template for the verification message. -* **Controller:** Add a new method in an `AuthController` or a dedicated `VerificationController` to handle the `GET /api/v1/auth/verify` request. - -**Tasks** -- [ ] Add `spring-boot-starter-mail` and `spring-boot-starter-thymeleaf` to `pom.xml`. -- [ ] Configure mail server settings in `application.properties`. -- [ ] Implement the `EmailService` and enable asynchronous processing with `@EnableAsync`. -- [ ] Create the `VerificationToken` entity, repository, and service. -- [ ] Design and create the HTML email template (`verification-email.html`). -- [ ] Update the user registration logic to generate a token and trigger the email sending process. -- [ ] Implement the verification endpoint (`GET /api/v1/auth/verify`) to validate the token and activate the user. -- [ ] Write unit and integration tests for token generation, email sending, and the verification flow. - -**Open Questions/Considerations** -* Which email service provider (SendGrid, AWS SES, Mailgun) will be used in production? -* What should be the expiration time for verification tokens (e.g., 24 hours)? -* How should the application handle cases where the email fails to send? Should there be a retry mechanism? -* Should we provide an endpoint for users to request a new verification email if the original one expires or is lost? \ No newline at end of file From 0beb3d6c88c0e8155c5af3edf08f816f9a6e714b Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 14 Mar 2026 14:06:22 +0100 Subject: [PATCH 3/4] fix: resolve Mypy type errors and Ruff linting issues Signed-off-by: aniebietafia --- README.md | 17 +++++++++++++++++ app/core/config.py | 8 +++++--- app/core/exception_handlers.py | 11 +++++++---- tests/test_error_handling.py | 3 +-- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e2ba2bf..540d114 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,23 @@ pytest tests/ -v --cov=app --cov-report=html --cov-report=term --- +## Linting & Formatting +- **Black**: Enforce consistent code formatting. +```bash +black . +``` +- **isort**: Sort imports for readability. +```bash +isort . +``` +- **ruff**: Linting for code quality and style. +```bash +ruff . +``` +```bash +python -m ruff check . +``` + ## 🤝 Contributing We welcome contributions! Please follow these steps: diff --git a/app/core/config.py b/app/core/config.py index f11df33..8055bb8 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,8 +1,10 @@ import pathlib -try: +import sys + +if sys.version_info >= (3, 11): import tomllib -except ImportError: +else: import tomli as tomllib from pydantic_settings import BaseSettings, SettingsConfigDict @@ -11,7 +13,7 @@ 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: + with pyproject_path.open("rb") as f: data = tomllib.load(f) return str(data.get("project", {}).get("version", "1.0.0")) return "1.0.0" diff --git a/app/core/exception_handlers.py b/app/core/exception_handlers.py index 0af72ac..f1af4f8 100644 --- a/app/core/exception_handlers.py +++ b/app/core/exception_handlers.py @@ -2,6 +2,7 @@ from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException from app.core.error_responses import create_error_response @@ -10,7 +11,7 @@ logger = logging.getLogger(__name__) -async def fluentmeet_exception_handler(request: Request, exc: FluentMeetException): +async def fluentmeet_exception_handler(_request: Request, exc: Any) -> JSONResponse: """ Handler for all custom FluentMeetException exceptions. """ @@ -22,7 +23,7 @@ async def fluentmeet_exception_handler(request: Request, exc: FluentMeetExceptio ) -async def validation_exception_handler(request: Request, exc: RequestValidationError): +async def validation_exception_handler(_request: Request, exc: Any) -> JSONResponse: """ Handler for Pydantic validation errors (422 -> 400). """ @@ -43,7 +44,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE ) -async def http_exception_handler(request: Request, exc: StarletteHTTPException): +async def http_exception_handler(_request: Request, exc: Any) -> JSONResponse: """ Handler for Starlette/FastAPI HTTP exceptions. """ @@ -54,7 +55,9 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException): ) -async def unhandled_exception_handler(request: Request, exc: Exception): +async def unhandled_exception_handler( + _request: Request, exc: Exception +) -> JSONResponse: """ Handler for all other unhandled exceptions (500). """ diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py index be1e100..63e3323 100644 --- a/tests/test_error_handling.py +++ b/tests/test_error_handling.py @@ -1,5 +1,4 @@ -import pytest -from fastapi import FastAPI, HTTPException +from fastapi import HTTPException from fastapi.testclient import TestClient from pydantic import BaseModel From ae9dc782e060d9584ca324145d42d80566d07f35 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 14 Mar 2026 14:19:47 +0100 Subject: [PATCH 4/4] fix: resolve Mypy type errors and Ruff linting issues Signed-off-by: aniebietafia --- app/core/config.py | 8 +------- app/core/exception_handlers.py | 1 + 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 8055bb8..1b4f5a4 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,11 +1,5 @@ import pathlib - -import sys - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib +import tomllib from pydantic_settings import BaseSettings, SettingsConfigDict diff --git a/app/core/exception_handlers.py b/app/core/exception_handlers.py index f1af4f8..fd2b614 100644 --- a/app/core/exception_handlers.py +++ b/app/core/exception_handlers.py @@ -1,4 +1,5 @@ import logging +from typing import Any from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError