Skip to content

feat: Implement uniform error handling and dynamic versioning#35

Merged
aniebietafia merged 4 commits intomainfrom
global-error-handling
Mar 14, 2026
Merged

feat: Implement uniform error handling and dynamic versioning#35
aniebietafia merged 4 commits intomainfrom
global-error-handling

Conversation

@aniebietafia
Copy link
Contributor

@aniebietafia aniebietafia commented Mar 14, 2026

Summary by CodeRabbit

  • New Features

    • Standardized error response format and global exception handling for consistent API errors.
    • API metadata and health endpoint now use dynamic project versioning.
  • Chores

    • CI test coverage threshold raised to 60% for stricter quality checks.
  • Tests

    • Added comprehensive error-handling tests and updated health-check tests for dynamic versioning.
  • Documentation

    • Added linting & formatting guidance to README; removed older linting guidance document.

- 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 <aniebietafia87@gmail.com>
- 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 <aniebietafia87@gmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

Warning

Rate limit exceeded

@aniebietafia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a691c847-dc30-47f3-a5f8-b02c7422f409

📥 Commits

Reviewing files that changed from the base of the PR and between 0beb3d6 and ae9dc78.

📒 Files selected for processing (2)
  • app/core/config.py
  • app/core/exception_handlers.py
📝 Walkthrough

Walkthrough

Adds a centralized error-handling system (custom exceptions, response models, and global handlers), makes application version dynamic from pyproject.toml, raises CI pytest coverage threshold, updates tests to use dynamic version, and removes an old linting guidance doc.

Changes

Cohort / File(s) Summary
CI Configuration
\.github/workflows/ci.yml
Raised pytest coverage threshold from 5% to 60% for the CI job.
Exception handling core
app/core/exceptions.py, app/core/error_responses.py, app/core/exception_handlers.py
Added FluentMeetException base and specialized exceptions; added ErrorDetail/ErrorResponse Pydantic models and create_error_response; implemented global exception handlers and a register function to wire them into FastAPI.
Config / Versioning
app/core/config.py
Added get_version() to read version from pyproject.toml with fallback; Settings.VERSION now initialized from that; Settings.model_config updated to include extra="ignore".
App integration
app/main.py
Switched API metadata and health endpoint to use settings.PROJECT_NAME and settings.VERSION; registered global exception handlers at startup.
Tests
tests/test_error_handling.py, tests/test_main.py
Added tests covering the new exception handlers and validation error payloads; updated health check test to assert settings.VERSION.
Documentation
linting_issue.md, README.md
Removed legacy linting_issue.md; added linting/formatting instructions and contributing workflow to README.md.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastAPI
    participant Handler as ExceptionHandler
    participant Creator as create_error_response
    participant JSON as JSONResponse

    Client->>FastAPI: Send HTTP request
    FastAPI->>FastAPI: Process request
    alt exception thrown
        FastAPI->>Handler: Route to registered handler
        Handler->>Creator: Build ErrorResponse (code,message,details,status)
        Creator->>JSON: Serialize model_dump()
        JSON->>Client: Return structured error JSON (status code)
    else no exception
        FastAPI->>Client: Return success response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through code with nimble paws,

Errors now boxed with tidy laws,
Versions fetched from toml delight,
CI tighter, tests in sight,
A cheerful rabbit stamps it "right!" 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: implementing uniform error handling (new exception hierarchy, handlers, and structured responses) and dynamic versioning (reading from pyproject.toml).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch global-error-handling
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-advanced-security
Copy link

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
tests/test_error_handling.py (1)

20-62: Consider test route isolation.

These test routes are added directly to the production app instance at module import time. While this works for testing, it means these /test/* routes will exist in the app whenever this module is imported.

This is acceptable for a test-only module, but for better isolation, consider using a fixture that creates a fresh app instance or removes routes after tests. Low priority given the routes are only exposed during test execution.

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

In `@tests/test_error_handling.py` around lines 20 - 62, The test routes
(trigger_bad_request, trigger_unauthorized, trigger_forbidden,
trigger_not_found, trigger_conflict, trigger_http_exception,
trigger_unhandled_exception, trigger_validation_error) are being registered on
the shared production app at import time; refactor tests to avoid polluting the
global app by creating a fresh app instance or removing routes after each test:
implement a test fixture that constructs a new FastAPI app, registers these test
endpoints on that local app (or clones the production app then mounts test
routes), and use that fixture's TestClient in tests, or alternatively capture
and unregister these route handlers at teardown to ensure isolation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/core/config.py`:
- Line 14: Replace the built-in open(...) call with Path.open(...) to follow
pathlib usage: locate the context where pyproject_path is used (the with
open(pyproject_path, "rb") as f block in app/core/config.py) and change it to
use pyproject_path.open("rb") so that the code calls Path.open on the
pyproject_path Path object (ensure mode "rb" is preserved).
- Around line 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.

In `@app/core/exception_handlers.py`:
- Around line 57-66: The function unhandled_exception_handler lacks a return
type and uses an unused parameter; update its signature to include an explicit
Response return annotation (e.g., -> Response) and prefix the unused request
parameter with an underscore (rename request to _request) so linters know it's
intentionally unused; keep exc as-is for logging and ensure imports for Response
are present if needed.
- Around line 46-54: Update the http_exception_handler signature to annotate its
return type and mark the unused request param as intentionally unused: rename
the first parameter from request to _request and add an explicit return type
(e.g., -> Response or -> JSONResponse depending on what create_error_response
returns). Ensure you import the matching Response/JSONResponse type used by
create_error_response and update the function signature for
http_exception_handler(_request: Request, exc: StarletteHTTPException) ->
Response (or -> JSONResponse).
- Around line 25-43: Update the exception handler signature to annotate the
return type and mark the unused request parameter as private: change the
function signature of validation_exception_handler(request: Request, exc:
RequestValidationError) to validation_exception_handler(_request: Request, exc:
RequestValidationError) -> JSONResponse (or the appropriate Response type that
create_error_response returns) and ensure the corresponding
Response/JSONResponse is imported so the type annotation resolves; leave the
body and create_error_response usage unchanged.
- Around line 13-22: Update the FastAPI exception handler signature to include
an explicit return type and mark the unused request parameter as intentionally
unused: rename request to _request in the function definition of
fluentmeet_exception_handler and add the return type annotation (e.g., ->
Response). Also import the Response type from fastapi.responses (or
starlette.responses) so the annotation resolves; no other logic changes required
because only exc is used inside create_error_response.

In `@tests/test_error_handling.py`:
- Around line 1-2: Remove the unused imports causing test pipeline failures:
delete the pytest and FastAPI import names from the top of the file so only
actually used symbols remain (e.g., keep HTTPException if it is referenced
elsewhere); check for any references to pytest or FastAPI in the test file and
remove or replace them as needed so imports match usage.

---

Nitpick comments:
In `@tests/test_error_handling.py`:
- Around line 20-62: The test routes (trigger_bad_request, trigger_unauthorized,
trigger_forbidden, trigger_not_found, trigger_conflict, trigger_http_exception,
trigger_unhandled_exception, trigger_validation_error) are being registered on
the shared production app at import time; refactor tests to avoid polluting the
global app by creating a fresh app instance or removing routes after each test:
implement a test fixture that constructs a new FastAPI app, registers these test
endpoints on that local app (or clones the production app then mounts test
routes), and use that fixture's TestClient in tests, or alternatively capture
and unregister these route handlers at teardown to ensure isolation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b681b5ea-cd4f-4f04-9d69-a077bf3fd703

📥 Commits

Reviewing files that changed from the base of the PR and between 38bd515 and fe67f68.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • app/core/config.py
  • app/core/error_responses.py
  • app/core/exception_handlers.py
  • app/core/exceptions.py
  • app/main.py
  • infra/docker-compose.yml
  • linting_issue.md
  • tests/test_error_handling.py
  • tests/test_main.py
💤 Files with no reviewable changes (1)
  • linting_issue.md

Comment on lines +3 to +6
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.

Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/core/exception_handlers.py`:
- Around line 14-47: Change the broad Any annotations on the exception handler
signatures to the concrete exception types already imported: update
fluentmeet_exception_handler to accept FluentMeetException,
validation_exception_handler to accept RequestValidationError, and
http_exception_handler to accept StarletteHTTPException; this requires editing
the function parameters in the definitions (fluentmeet_exception_handler,
validation_exception_handler, http_exception_handler) to use those specific
types so linting/import checks pass and typing is stronger.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1d309e3-933e-4f84-9b49-a320796f0ae4

📥 Commits

Reviewing files that changed from the base of the PR and between fe67f68 and 0beb3d6.

📒 Files selected for processing (4)
  • README.md
  • app/core/config.py
  • app/core/exception_handlers.py
  • tests/test_error_handling.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_error_handling.py
  • app/core/config.py

Comment on lines +14 to +47
async def fluentmeet_exception_handler(_request: Request, exc: Any) -> JSONResponse:
"""
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: Any) -> JSONResponse:
"""
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: Any) -> JSONResponse:
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unresolved Any usage vs imports in the reviewed file.
rg -n 'from typing import Any|\bAny\b' app/core/exception_handlers.py

Repository: Brints/FluentMeet

Length of output: 318


🏁 Script executed:

head -n 50 app/core/exception_handlers.py | cat -n

Repository: Brints/FluentMeet

Length of output: 1796


Fix undefined Any annotations to avoid module import/lint failures.

Lines 14, 26, and 47 use Any without importing it, which causes F821 pipeline failures and blocks CI.

🔧 Proposed fix
 import logging
 
 from fastapi import FastAPI, Request
-async def fluentmeet_exception_handler(_request: Request, exc: Any) -> JSONResponse:
+async def fluentmeet_exception_handler(
+    _request: Request, exc: FluentMeetException
+) -> JSONResponse:
-async def validation_exception_handler(_request: Request, exc: Any) -> JSONResponse:
+async def validation_exception_handler(
+    _request: Request, exc: RequestValidationError
+) -> JSONResponse:
-async def http_exception_handler(_request: Request, exc: Any) -> JSONResponse:
+async def http_exception_handler(
+    _request: Request, exc: StarletteHTTPException
+) -> JSONResponse:

Replace Any with the concrete exception types that are already imported (FluentMeetException, RequestValidationError, StarletteHTTPException). This provides stronger typing and is already available in the module.

🧰 Tools
🪛 GitHub Actions: Code Quality

[error] 14-14: F821 Undefined name 'Any' in type annotation.


[error] 26-26: F821 Undefined name 'Any' in type annotation.


[error] 47-47: F821 Undefined name 'Any' in type annotation.

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

In `@app/core/exception_handlers.py` around lines 14 - 47, Change the broad Any
annotations on the exception handler signatures to the concrete exception types
already imported: update fluentmeet_exception_handler to accept
FluentMeetException, validation_exception_handler to accept
RequestValidationError, and http_exception_handler to accept
StarletteHTTPException; this requires editing the function parameters in the
definitions (fluentmeet_exception_handler, validation_exception_handler,
http_exception_handler) to use those specific types so linting/import checks
pass and typing is stronger.

Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
@aniebietafia aniebietafia merged commit 098e04b into main Mar 14, 2026
9 checks passed
@aniebietafia aniebietafia linked an issue Mar 14, 2026 that may be closed by this pull request
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Uniform Error Handling & Response Structure

1 participant