Skip to content

Enhance CI workflow and implement global exception handling#4

Merged
WillianSilva51 merged 3 commits intomainfrom
feat/global-exception
Mar 28, 2026
Merged

Enhance CI workflow and implement global exception handling#4
WillianSilva51 merged 3 commits intomainfrom
feat/global-exception

Conversation

@WillianSilva51
Copy link
Copy Markdown
Owner

This pull request introduces a structured and consistent error handling system to the API, along with some minor improvements to dependency management and parameter validation. The changes add custom exception classes, centralized exception handlers, and a standardized error response schema, ensuring that errors are handled gracefully and returned in a predictable format.

Error handling improvements:

  • Added custom exception classes ResourceNotFoundException and DomainValidationException in custom_exceptions.py to represent specific error scenarios.
  • Implemented centralized exception handlers in handlers.py for custom exceptions, request validation errors, HTTP errors, and uncaught exceptions, all returning standardized JSON responses using the new ErrorResponse schema.
  • Registered the new exception handlers in main.py so that all relevant errors are caught and processed consistently across the API. [1] [2]
  • Created a new ErrorResponse schema in error_schema.py to standardize the structure of error responses, including status code, error message, and optional details.
  • Updated module imports and __all__ lists in exceptions and schemas packages to include the new modules and ensure proper exposure. [1] [2]

Other improvements:

  • Made the verify_api_key dependency and its usage in routers accept None as a valid input, improving type safety and clarity. [1] [2]
  • Changed the minimum value for the limit query parameter in get_all_quotes from 1 to 0, allowing clients to explicitly request zero results.
  • Updated the imports in core/__init__.py to reflect recent module changes, removing unused imports and adding new ones.
  • Added a "Check Sync" step to the Python test workflow to ensure requirements.txt is in sync with the lock file.
  • Minor refactor in the lifespan function signature in main.py for consistency.

@WillianSilva51 WillianSilva51 self-assigned this Mar 28, 2026
Copilot AI review requested due to automatic review settings March 28, 2026 20:59
@WillianSilva51 WillianSilva51 added the enhancement New feature or request label Mar 28, 2026
@WillianSilva51 WillianSilva51 requested review from Copilot and removed request for Copilot March 28, 2026 20:59
Removed the Check Sync step from the workflow.
@WillianSilva51 WillianSilva51 merged commit 6cac26b into main Mar 28, 2026
1 check passed
@WillianSilva51 WillianSilva51 deleted the feat/global-exception branch March 28, 2026 21:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces centralized, structured exception handling for the FastAPI app, standardizing error responses via a new ErrorResponse schema, and updates CI/dependencies and a couple of API parameter/type annotations.

Changes:

  • Added ErrorResponse schema plus custom domain exceptions and centralized exception handlers returning standardized JSON.
  • Registered exception handlers globally in api/main.py and adjusted a couple of API typings/validations (verify_api_key optional input; quotes limit allows 0).
  • Updated dependency export/checking (requirements.txt regeneration + CI “Check Sync” step) and core package exports.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
requirements.txt Regenerated exported dependency set with uv export annotations/markers.
api/schemas/error_schema.py Introduces a standard error response model + helper constructor.
api/schemas/init.py Exposes error_schema from the schemas package.
api/exceptions/custom_exceptions.py Adds two custom exception types for domain/not-found cases.
api/exceptions/handlers.py Adds FastAPI exception handlers that map errors to ErrorResponse JSON.
api/exceptions/init.py Exposes the new exceptions modules.
api/main.py Registers global exception handlers and refactors lifespan signature.
api/core/security.py Makes verify_api_key accept None from APIKeyHeader(auto_error=False).
api/core/init.py Updates exported core modules.
api/routers/quotes.py Updates dependency typing and allows limit=0 via query validation.
.github/workflows/python-tests.yml Adds “Check Sync” step to enforce requirements.txt matches lock export.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +63 to +67
app.add_exception_handler(HTTPException, http_handler) # type: ignore
app.add_exception_handler(DomainValidationException, domain_validation_handler) # type: ignore
app.add_exception_handler(ResourceNotFoundException, resource_not_found_handler) # type: ignore
app.add_exception_handler(RequestValidationError, request_validation_handler) # type: ignore
app.add_exception_handler(Exception, global_exception_handler)
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

Global exception handler registration introduces an API-level contract (standardized error shape), but there are no automated tests asserting the JSON schema/status codes for common cases (HTTPException, validation errors, uncaught exceptions). Adding a small FastAPI TestClient suite around these handlers would prevent regressions and ensure clients can rely on the new error response format.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +54
return _json_error_response(
ErrorResponse.from_http_status(
HTTPStatus(exc.status_code),
message=exc.detail,
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

http_handler passes exc.detail into ErrorResponse.message, but HTTPException.detail can be non-string (e.g., dict/list). That will raise a Pydantic validation error inside the exception handler and can prevent the standardized error response from being returned. Consider ensuring message is always a string (e.g., str(exc.detail) when not a string) and optionally put the original detail payload into details.

Suggested change
return _json_error_response(
ErrorResponse.from_http_status(
HTTPStatus(exc.status_code),
message=exc.detail,
detail = exc.detail
message = detail if isinstance(detail, str) else str(detail)
details = None if isinstance(detail, str) else detail
return _json_error_response(
ErrorResponse.from_http_status(
HTTPStatus(exc.status_code),
message=message,
details=details,

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +53
return _json_error_response(
ErrorResponse.from_http_status(
HTTPStatus(exc.status_code),
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

HTTPStatus(exc.status_code) will raise ValueError for non-standard/custom HTTP status codes (FastAPI allows any int). If that happens, the handler itself will error and you’ll lose the intended response formatting. Consider falling back to HTTPStatus.INTERNAL_SERVER_ERROR or using the raw exc.status_code int when it’s not a valid HTTPStatus member.

Suggested change
return _json_error_response(
ErrorResponse.from_http_status(
HTTPStatus(exc.status_code),
status = (
HTTPStatus(exc.status_code)
if exc.status_code in HTTPStatus._value2member_map_
else HTTPStatus.INTERNAL_SERVER_ERROR
)
return _json_error_response(
ErrorResponse.from_http_status(
status,

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +25
@classmethod
def from_http_status(
cls,
status_code: HTTPStatus,
message: str,
details: Sequence[Any] | dict[str, Any] | list[Any] | None = None,
error: str | None = None,
) -> "ErrorResponse":
return ErrorResponse(
status_code=status_code,
error=error or status_code.phrase,
message=message,
details=details,
)
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

from_http_status is a @classmethod but it constructs ErrorResponse(...) directly instead of using cls(...). Using cls avoids hard-coding the concrete type and makes the method safe to reuse if this model is subclassed or customized later.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants