diff --git a/README.md b/README.md index 0bb15a2..d4ba771 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ The goal is to build a better API framework for Frappe. - [x] ResponseValidationError - [x] Register custom exception handlers. See [Add custom headers](https://fastapi.tiangolo.com/tutorial/handling-errors/#add-custom-headers) - [x] Override default exception handlers. +- [ ] Maintain Frappe Transaction Management. ### Response Models @@ -96,6 +97,14 @@ The goal is to build a better API framework for Frappe. - [x] Metadata, `title`, `description`, `deprecated`. - [x] others, `include_in_schema`. +### Rate Limiting + +- [ ] Support for rate limiting. + +### Miscellaneous + +- [ ] Debugging capabilities. + ## Related Frappe PRs and Issues - [PR #23135](https://github.com/frappe/frappe/pull/23135): Introducing type hints for API functions. diff --git a/frappeapi/exceptions.py b/frappeapi/exceptions.py index cd84468..756b068 100644 --- a/frappeapi/exceptions.py +++ b/frappeapi/exceptions.py @@ -1,6 +1,8 @@ import http from typing import Any, Sequence +from werkzeug.exceptions import HTTPException as WerkzeugHTTPException + class FrappeAPIError(Exception): pass @@ -10,7 +12,7 @@ class ErrorWrapper(Exception): pass -class HTTPException(Exception): +class HTTPException(WerkzeugHTTPException): def __init__( self, status_code: int, @@ -19,6 +21,8 @@ def __init__( ) -> None: if detail is None: detail = http.HTTPStatus(status_code).phrase + super().__init__(description=detail, response=None) + self.status_code = status_code self.detail = detail self.headers = headers diff --git a/frappeapi/routing.py b/frappeapi/routing.py index 13f0a71..689c15f 100644 --- a/frappeapi/routing.py +++ b/frappeapi/routing.py @@ -675,36 +675,55 @@ def handle_request(self, *args, **kwargs) -> WerkzeugResponse: if errors: validation_error = RequestValidationError(errors, body=body) raise validation_error - - except HTTPException as exc: - if self.exception_handlers.get(HTTPException): - return self.exception_handlers[HTTPException](request, exc) - else: - return http_exception_handler(request, exc) except RequestValidationError as exc: if self.exception_handlers.get(RequestValidationError): - return self.exception_handlers[RequestValidationError](request, exc) + response = self.exception_handlers[RequestValidationError](request, exc) else: - return request_validation_exception_handler(request, exc) + response = request_validation_exception_handler(request, exc) + except ResponseValidationError as exc: if self.exception_handlers.get(ResponseValidationError): - return self.exception_handlers[ResponseValidationError](request, exc) + response = self.exception_handlers[ResponseValidationError](request, exc) else: - return response_validation_exception_handler(request, exc) + response = response_validation_exception_handler(request, exc) + except HTTPException as exc: + if self.exception_handlers.get(HTTPException): + response = self.exception_handlers[HTTPException](request, exc) + else: + response = http_exception_handler(request, exc) except Exception as exc: # If any other exception is raised, return a 500 response. # First check if there is a custom exception handler for this exception. # If not, return a 500 response with the exception details. # Subress the exception details to avoid exposing sensitive information. - for exc_type, handler in self.exception_handlers.items(): - if isinstance(exc, exc_type): - return handler(request, exc) - - # TODO: To be determined: use repr or str? repr for type and message, str for message only - return JSONResponse(content={"detail": str(exc)}, status_code=500) + if self.exception_handlers.get(type(exc)): + response = self.exception_handlers[type(exc)](request, exc) + else: + response = JSONResponse(content={"detail": repr(exc)}, status_code=500) + else: + # The else block will run only if no exception is raised in the try block + # So no need to handle anything here. Let Frappe handle DB sync. + pass + finally: + # https://docs.python.org/3/tutorial/errors.html#defining-clean-up-actions + + # > - If an exception occurs during execution of the try clause, + # the exception may be handled by an except clause. + # > - If the exception is not handled by an except clause, + # the exception is re-raised after the finally clause has been executed. + # > - An exception could occur during execution of an except or else clause. + # Again, the exception is re-raised after the finally clause has been executed. + # > If the finally clause executes a break, continue or return statement, + # exceptions are not re-raised. + # > If the try statement reaches a break, continue or return statement, + # the finally clause will execute just prior to the break, continue or return statement’s execution. + # > If a finally clause includes a return statement, + # the returned value will be the one from the finally clause’s return statement, + # not the value from the try clause’s return statement. + pass # TODO: - # Again, it's a workaround to avoid the error from bubbling up to the user. + # Avoid the error from bubbling up to the user. # If there's a FrappeAPIError, return a 500 response with the 'Internal server error' message. # And print the traceback to the console for debugging. try: