I have declared router in coupe with middleware and added the generic handler for any exception that may happen inside addressing calls to some abstract endpoints:
app = fastapi.FastAPI(openapi_url='/api/v1/openapi.json')
app.include_router(v1.api_router, prefix='/api/v1')
app.add_middleware(middlewares.ClientIPMiddleware)
#app.exception_handler(starlette.exceptions.HTTPException)
async def on_request_exception_handler(
request: fastapi.Request,
exc: starlette.exceptions.HTTPException,
):
return fastapi.responses.JSONResponse(
status_code=exc.status_code,
content={
'detail': exc.detail,
'status': exc.status,
},
)
class ClientIPMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
async def dispatch(self, request: fastapi.Request, call_next: typing.Callable):
request.state.client_ip = get_client_ip(request.headers)
return await call_next(request)
In ClientIPMiddleware class I have to refactor and add something like:
try:
response = await call_next(request)
if request.method != 'GET':
if response.status_code == 200:
return fastapi.responses.JSONResponse(
status_code=200,
content={'status': 'ok'},
)
return response
except Exception as e:
pass
I just need both mechanisms here: the one is for catching all the errors possible at the endpoints' level, the two is for getting either serialized response, or JSONResponse with status_code and status message. In the last snippet of the code try/except block is odd as it can't catch any error there. Is there any better solution to reach the goal? To add a custom or origin decorator to the app instance?
Related
I want to know which way is better to handle page not found error 404. So I have found two ways to redirect pages when someone tries to go to my website, bu they type in a url that I do not have a route built for. The first way is to build an error handler so I could do one like this:
#app.errorhandler(404)
def internal_error(error):
return redirect(url_for('index'))
There is a second method that I found via the flask website, http://flask.pocoo.org/snippets/57/, and is like this:
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return redirect(url_for('index'))
The difference is that one would be handling the error and the other is a dynamic routing. But what would be better to use? I don't really know what the pros of cons would be and before deploying one I would like to better understand it.
To help this is my base code:
#app.route('/', methods=["GET", "POST"])
def index():
return render_template("HomePage.html")
if __name__ == "__main__":
app.debug = True
app.run()
I'll say to handle your scenario, the second approach is good as you need the handle the route that not exist
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return redirect(url_for('index'))
But in general, both have different use-cases, let say, you are rendering multiple templates in different functions and you got an exception while rendering it. So, to cater this situation you need to do implement exception handling in all the method.
Henceforth, instead of doing this and to make our readable and scalable, we can register the exception with this function and create a custom response for the user, refer below:
# It will catch the exception when the Template is not found and
# raise a custom response as per the rasied exception
#app.errorhandler(TemplateNotFound)
def handle_error(error):
message = [str(x) for x in error.args]
status_code = 500
success = False
response = {
'success': success,
'error': {
'type': error.__class__.__name__,
'message': message
}
}
return jsonify(response), status_code
# For any other exception, it will send the reponse with a custom message
#app.errorhandler(Exception)
def handle_unexpected_error(error):
status_code = 500
success = False
response = {
'success': success,
'error': {
'type': 'UnexpectedException',
'message': 'An unexpected error has occurred.'
}
}
return jsonify(response), status_code
How can I return a different error code with django rest framework's serializers?
I have in my serializer.py file:
def create(self, validated_data):
if 'Message' not in validated_data:
# If message is blank, don't create the message
return PermissionDenied()
But when I do that, it just returns 201 with the body {"deleted":null} instead of returning a 403 error.
How can I make it return the 403 error?
You can override the validate_message method as follow:
from rest_framework.exceptions import ValidationError
def validate_message(self, message):
if not message:
raise ValidationError('error message here')
return message
Note that the ValidationError will return a 400 Bad Request status code which is better for when a required field is missing from the POST data
#Marcell Erasmus's answer is nice, but it's not covered the status code part (how to return a HTTP 403 status code)
First, you need to add a Custom exception class as below,
from rest_framework import exceptions
from rest_framework import status
class CustomAPIException(exceptions.APIException):
status_code = status.HTTP_403_FORBIDDEN
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and use the class wherever you want by ,
if some_condition:
raise CustomAPIException({"some": "data"})
One of the most advantage of this particular class is that you could raise API exception with custom status codes by specifying the status_code parameter
Ex.
if some_condition:
raise CustomAPIException({"some": "data"},status_code=status.HTTP_409_CONFLICT)
I have written
class CustomApiException(APIException):
#public fields
detail = None
status_code = None
# create constructor
def __init__(self, status_code, message):
#override public fields
CustomApiException.status_code = status_code
CustomApiException.detail = message
CustomApiException.message = message
which is working for APIView but for TemplateView it gives error. What is the proper way to write custom API Exception which will work for both views.
In your views define the template view as following
from django.http import HttpResponseForbidden
#rest of the views
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException:
return HttpResponseForbidden()
This will raise a Error 403
If you want to render your own template instead of raising HttpResponseForbidden, you could pass the exception variables to the view in following way and write the response of the view appropriately
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException as e:
status_code = e.status_code
detail = e.message
message = e.message
#Handle the response here.
I am using django class based view and rest framework
object = self.get_object()
In Detail view if object does not exist and i do get request like
/user/10
then i get this response
{"detail": "not found"}
Now i want to customize that response
like
try:
obj = self.get_object()
except:
raise Exception("This object does not exist")
But thats not working
We can implement a custom exception handler function that returns the custom response in case the object does not exist.
In case a object does not exist, Http404 exception is raised. So, we will check if the exception raised is Http404 and if that is the case, we will return our custom exception message in the response.
from rest_framework.views import exception_handler
from django.http import Http404
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, Http404):
custom_response_data = {
'detail': 'This object does not exist.' # custom exception message
}
response.data = custom_response_data # set the custom response data on response object
return response
After defining our custom exception handler, we need to add this custom exception handler to our DRF settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
You could create a custom exception class as below, and it would raise APIException with a custom message and custom status_code
from rest_framework.serializers import ValidationError
from rest_framework import status
class CustomAPIException(ValidationError):
"""
raises API exceptions with custom messages and custom status codes
"""
status_code = status.HTTP_400_BAD_REQUEST
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and in your views,
from rest_framework import status
try:
obj = self.get_object()
except:
raise CustomAPIException("This object does not exist", status_code=status.HTTP_404_NOT_FOUND)
The response will be like this
{"detail": "This object does not exist"}
NOTE
the detail parameter of CustomAPIException class takes str,list and dict objects. If you provide a dict object, then it will return that dict as exception response
UPDATE
As #pratibha mentioned, it's not possible to produce desired output if we use this exception class in Serializer's validate() or validate_xxfieldName() methods.
Why this behaviour ?
I wrote a similar answer in SO, here Django REST Framework ValidationError always returns 400
How to obtain desired output within the serializer's validate() method?
Inherit CustomAPIException from rest_framework.exceptions.APIException instead of from rest_framework.serializers.ValidationError
ie,
from rest_framework.exceptions import APIException
class CustomAPIException(APIException):
# .... code
I am not sure how can I catch an Exception in my django app.
I have a view, where I use my own class object which uses Requests framework to make remote API requests.
I need to catch Timeout exception inside my class code. When Timeout exception appears I need to return custom error page. My class code:
try:
response = requests.request(
method=method, url=self.url, auth=self.auth, timeout=3, verify=False, **kwargs
)
except requests.Timeout:
return HttpResponse('API connection timeout')
return response
However, in my django view code, I use something like:
user_details = auth_client['auth/users/%s/details' % (request.POST.get('username'))].get().json()['result']
so, when I try to return HttpResponse from class code, I get error:
'HttpResponse' object has no attribute 'json'
I don't know how can I "omit" code from view and show HttpResponse directly. Any suggestions?
You shouldn't be trying to return an HttpResponse in your requests class. Instead of catching the exception and replacing it with an unwanted HttpResponse, let it propagate and catch it in the view:
try:
user_details = auth_client['auth/users/%s/details' % (request.POST.get('username'))].get().json()['result']
except requests.Timeout:
return HttpResponse('whatever')
Edit OK, so here's another solution. You say that this is a class: so you can define a method, say get_user_details, which either returns the actual JSON, or none.
def get_user_details(username):
try:
user_details = self['auth/users/%s/details' % 'username'].get()
except requests.Timeout:
return []
return user_details.json()['result']
so now in your view you just call auth_client.get_user_details(request.POST['username']).