having trouble serializer a string during a try/except statement.
here i have a endpoint that calls another function refund the response i get back from that function i'm trying to serialize.
class RefundOrder(APIView):
def post(self, request, **kwargs):
print('test')
body_unicode = request.body.decode('utf-8')
body_data = json.loads(body_unicode)
amount = body_data['amount']
tenant = get_object_or_404(Tenant, pk=kwargs['tenant_id'])
refund = SquareGateway(tenant).refund(amount)
serializer = RefundSerializer(refund)
return Response(serializer.data)
this is the function that gets called in the post endpoint. i added it in a try statement to handle the errors from square api. if the api call fails, i want to return an error if their is one, else serialize that data.
def refund(self, order, amount, reason):
try:
response = self.client.transaction().create_refund(stuff)
refund = Refund(
order=order,
amount=response.refund.amount_money.amount,
)
refund.save()
return refund
except ApiException as e:
return json.loads(e.body)['errors'][0]['detail']
this is the the Refundserialize
class RefundSerializer(serializers.ModelSerializer):
class Meta:
model = Refund
fields = ('id', 'amount')
serializing the string doesn't throw an error it just doesn't return the error message that im returning.Currently it returns a empty serialized object.
As far as I understood, You need a custom API exception which returns a custom message.
So, Initially create a custom exception class as below,
from rest_framework.exceptions import APIException
from rest_framework import status
class GenericAPIException(APIException):
"""
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
Then raise the exception in refund() function.
def refund(self, order, amount, reason):
try:
# your code
except ApiException as e:
raise GenericAPIException({"message":"my custom msg"})
Related
As per the flask-restful doc, it calls handle_error() function on any 400 or 500 error that happens on a Flask-RESTful route. So I tried to handle exceptions through out my application using this call back, thinking it would leverage multiple try catch block in my application. But while raising custom exception(ResourceNotFound) from a model raises another exception TypeError: Object of type ResourceNotFound is not JSON serializable
from werkzeug.exceptions import HTTPException
from flask_restful import Api
class ExtendedAPI(Api):
def handle_error(self, err):
"""
This class overrides 'handle_error' method of 'Api' class ,
to extend global exception handing functionality of 'flask-restful'
and helps preventing writing unnecessary
try/except block though out the application
"""
# import pdb; pdb.set_trace()
logger.error(err)
# Handle HTTPExceptions
if isinstance(err, HTTPException):
return jsonify({
'message': getattr(
err, 'description', HTTP_STATUS_CODES.get(err.code, '')
)
}), err.code
if isinstance(err, ValidationError):
resp = {
'success': False,
'errors': err.messages
}
return jsonify(resp), 400
# If msg attribute is not set,
# consider it as Python core exception and
# hide sensitive error info from end user
# if not getattr(err, 'message', None):
# return jsonify({
# 'message': 'Server has encountered some error'
# }), 500
# Handle application specific custom exceptions
return jsonify(**err.kwargs), err.http_status_code
Custom exception:
class Error(Exception):
"""Base class for other exceptions"""
def __init__(self, http_status_code: int, *args, **kwargs):
# If the key `msg` is provided, provide the msg string
# to Exception class in order to display
# the msg while raising the exception
self.http_status_code = http_status_code
self.kwargs = kwargs
msg = kwargs.get('msg', kwargs.get('message'))
if msg:
args = (msg,)
super().__init__(args)
self.args = list(args)
for key in kwargs.keys():
setattr(self, key, kwargs[key])
class ResourceNotFound(Error):
"""Should be raised in case any resource is not found in the DB"""
The handle_error function handles HTTPException, marshmallow validation errors and the last statement to handle my custom exceptions.
but using pdb, I saw the err object received by handle_error() differs from the custom exception I raised from the model. Not able to figure out any solution for this. Any thoughts on solving this problem or any different approach I can follow??
I have the following custom exception handler in Django REST framework.
class ErrorMessage:
def __init__(self, message):
self.message = message
def insta_exception_handler(exc, context):
response = {}
if isinstance(exc, ValidationError):
response['success'] = False
response['data'] = ErrorMessage("Validation error")
return Response(response)
I want a JSON output as shown below
"success":false,
"data":{ "message" : "Validation error" }
But I get the error TypeError: Object of type 'ErrorMessage' is not JSON serializable. Why is a class as simple as ErrorMessage above not JSON serializable? How can I solve this problem?
It is not serializable because it is an object, it should be dict, list or plain value. But you can easily fix your problem by using magic property __dict__
def insta_exception_handler(exc, context):
response = {}
if isinstance(exc, ValidationError):
response['success'] = False
# like this
response['data'] = ErrorMessage("Validation error").__dict__
return Response(response)
I think more generic way would be to create a serializer for serializing the error message object:
from rest_framework import serializers
class ErrorMessageSerializer(serializers.Serializer):
message = serializers.CharField(max_length=256)
Then you can do:
def insta_exception_handler(exc, context):
...
serializer = ErrorMessageSerializer(ErrorMessage("Validation error"))
response["data"] = serializer.data
...
I have a try and except which is used super often, so I am thinking of taking it out and make it into a function in other file but I cannot seem to make it work.
Can someone please give me a hand?
this is the code which I use really really often
try:
user = get_object_or_404(User, email=data['email'])
except Exception as e:
print(e)
return JSONresponse(False, e.message)
I tried making it into another file and did it this way
def auth_token(data):
try:
return get_object_or_404(User, email=data['email'])
except Exception as e:
return JSONresponse({'status': False})
then when I call auth_token I did it this way in another way and of course I did import it
user = auth_token(data)
I can understand this would then return me JSONresponse({'status': False}) and I tried using raise and it would tell me that I can't use JSONresponse
Can someone give me an idea how this can be done?
Best practice is to use as simple parameters as possible (this makes your function easier to test).
What you're looking for in the exception case is something that can be sent from auth_token through your view:
def auth_token(email): # this returns a User not a token..?
try:
return User.objects.get(email=email)
except User.DoesNotExist:
raise ImmediateHttpResponse(JSONresponse({'status': False}))
Usage would then be:
def myview(request, ...):
token = auth_token(data['email'])
...
Any ImmediateHttpResponse exception will escape the view and must be picked up by a middleware class.
Unfortunately, Django doesn't come with an ImmediateHttpResponse class, but TastyPie has one that we can steal. First the exception class:
class ImmediateHttpResponse(Exception):
"""This exception is used to interrupt the flow of processing to immediately
return a custom HttpResponse.
Common uses include::
* for authentication (like digest/OAuth)
* for throttling
(from TastyPie).
"""
_response = http.HttpResponse("Nothing provided.")
def __init__(self, response):
self._response = response
#property
def response(self):
return self._response
then the middleware:
from myexceptions import ImmediateHttpResponse
class ImmediateHttpResponseMiddleware(object):
"""Middleware that handles ImmediateHttpResponse exceptions, allowing
us to return a response from deep in form handling code.
"""
def process_exception(self, request, exception):
if isinstance(exception, ImmediateHttpResponse):
return exception.response
return None
This class must be added to your MIDDLEWARE_CLASSES in your settings.py file as 'mymiddleware.ImmediateHttpResponseMiddleware'.
I want to define custom error handling for a Flask-restful API.
The suggested approach in the documentation here is to do the following:
errors = {
'UserAlreadyExistsError': {
'message': "A user with that username already exists.",
'status': 409,
},
'ResourceDoesNotExist': {
'message': "A resource with that ID no longer exists.",
'status': 410,
'extra': "Any extra information you want.",
},
}
app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)
Now I find this format pretty attractive but I need to specify more parameters when some exception happens. For example, when encountering ResourceDoesNotExist, I want to specify what id does not exist.
Currently, I'm doing the following:
app = Flask(__name__)
api = flask_restful.Api(app)
class APIException(Exception):
def __init__(self, code, message):
self._code = code
self._message = message
#property
def code(self):
return self._code
#property
def message(self):
return self._message
def __str__(self):
return self.__class__.__name__ + ': ' + self.message
class ResourceDoesNotExist(APIException):
"""Custom exception when resource is not found."""
def __init__(self, model_name, id):
message = 'Resource {} {} not found'.format(model_name.title(), id)
super(ResourceNotFound, self).__init__(404, message)
class MyResource(Resource):
def get(self, id):
try:
model = MyModel.get(id)
if not model:
raise ResourceNotFound(MyModel.__name__, id)
except APIException as e:
abort(e.code, str(e))
When called with an id that doesn't exist MyResource will return the following JSON:
{'message': 'ResourceDoesNotExist: Resource MyModel 5 not found'}
This works fine but I'd like to use to Flask-restful error handling instead.
According to the docs
Flask-RESTful will call the handle_error() function on any 400 or 500 error that happens on a Flask-RESTful route, and leave other routes alone.
You can leverage this to implement the required functionality. The only downside is having to create a custom Api.
class CustomApi(flask_restful.Api):
def handle_error(self, e):
flask_restful.abort(e.code, str(e))
If you keep your defined exceptions, when an exception occurs, you'll get the same behaviour as
class MyResource(Resource):
def get(self, id):
try:
model = MyModel.get(id)
if not model:
raise ResourceNotFound(MyModel.__name__, id)
except APIException as e:
abort(e.code, str(e))
Instead of attaching errors dict to Api, I am overriding handle_error method of Api class to handle exceptions of my application.
# File: app.py
# ------------
from flask import Blueprint, jsonify
from flask_restful import Api
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.exceptions import HTTPException
from view import SomeView
class ExtendedAPI(Api):
"""This class overrides 'handle_error' method of 'Api' class ,
to extend global exception handing functionality of 'flask-restful'.
"""
def handle_error(self, err):
"""It helps preventing writing unnecessary
try/except block though out the application
"""
print(err) # log every exception raised in the application
# Handle HTTPExceptions
if isinstance(err, HTTPException):
return jsonify({
'message': getattr(
err, 'description', HTTP_STATUS_CODES.get(err.code, '')
)
}), err.code
# If msg attribute is not set,
# consider it as Python core exception and
# hide sensitive error info from end user
if not getattr(err, 'message', None):
return jsonify({
'message': 'Server has encountered some error'
}), 500
# Handle application specific custom exceptions
return jsonify(**err.kwargs), err.http_status_code
api_bp = Blueprint('api', __name__)
api = ExtendedAPI(api_bp)
# Routes
api.add_resource(SomeView, '/some_list')
Custom exceptions can be kept in separate file, like:
# File: errors.py
# ---------------
class Error(Exception):
"""Base class for other exceptions"""
def __init__(self, http_status_code:int, *args, **kwargs):
# If the key `msg` is provided, provide the msg string
# to Exception class in order to display
# the msg while raising the exception
self.http_status_code = http_status_code
self.kwargs = kwargs
msg = kwargs.get('msg', kwargs.get('message'))
if msg:
args = (msg,)
super().__init__(args)
self.args = list(args)
for key in kwargs.keys():
setattr(self, key, kwargs[key])
class ValidationError(Error):
"""Should be raised in case of custom validations"""
And in the views exceptions can be raised like:
# File: view.py
# -------------
from flask_restful import Resource
from errors import ValidationError as VE
class SomeView(Resource):
def get(self):
raise VE(
400, # Http Status code
msg='some error message', code=SomeCode
)
Like in view, exceptions can actually be raised from any file in the app which will be handled by the ExtendedAPI handle_error method.
I've used the Blueprint to work with the flask-restful, and I've found that the solution #billmccord and #cedmt provided on the issue not worked for this case, because the Blueprint don't have the handle_exception and handle_user_exception functions.
My workaround is that enhance the function handle_error of the Api, if the "error handler" of the "Exception" have been registered, just raise it, the "error handler" registered on app will deal with that Exception, or the Exception will be passed to the "flask-restful" controlled "custom error handler".
class ImprovedApi(Api):
def handle_error(self, e):
for val in current_app.error_handler_spec.values():
for handler in val.values():
registered_error_handlers = list(filter(lambda x: isinstance(e, x), handler.keys()))
if len(registered_error_handlers) > 0:
raise e
return super().handle_error(e)
api_entry = ImprovedApi(api_entry_bp)
BTW, seems the flask-restful had been deprecated...
I faced the same issue too and after extending flask-restful.Api I realized that
you really don't need to extend the flask-restful.Api
you can easily do this by inheriting from werkzeug.exceptions.HTTPException and solve this issue
app = Flask(__name__)
api = flask_restful.Api(app)
from werkzeug.exceptions import HTTPException
class APIException(HTTPException):
def __init__(self, code, message):
super().__init__()
self.code = code
self.description = message
class ResourceDoesNotExist(APIException):
"""Custom exception when resource is not found."""
def __init__(self, model_name, id):
message = 'Resource {} {} not found'.format(model_name.title(), id)
super().__init__(404, message)
class MyResource(Resource):
def get(self, id):
try:
model = MyModel.get(id)
if not model:
raise ResourceNotFound(MyModel.__name__, id)
except APIException as e:
abort(e.code, str(e))
I'm trying to make a PATCH request using to the Django Rest Framework but get the following error:
{"image_data": [{"non_field_errors": ["Invalid data"]}]
I understand that JSONField() could give some issues so I have taken care of that by adding to_native and from_native, But, I'm still running into this issue. I don't think JSONField() is the problem here at all, but still worth mentioning.
I believe I'm doing something fundamentally wrong in how I am trying to update the related field.
Code below...
Models:
class Photo(models.Model):
user = models.ForeignKey(AppUser, help_text="Item belongs to.")
image_data = models.ForeignKey("PhotoData", null=True, blank=True)
class PhotoData(models.Model):
thisdata = JSONField()
Serializers:
class ExternalJSONField(serializers.WritableField):
def to_native(self, obj):
return json.dumps(obj)
def from_native(self, value):
try:
val = json.loads(value)
except TypeError:
raise serializers.ValidationError(
"Could not load json <{}>".format(value)
)
return val
class PhotoDataSerializer(serializers.ModelSerializer):
thisdata = ExternalJSONField()
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer()
class Meta:
model = Photo
fields = ("id","user", "image_data")
PATCH:
> payload = {"image_data": {"thisdata": "{}"}}
> requests.patch("/photo/123/",payload )
I have also tried:
> payload = {"image_data": [{"thisdata": "{}"}]}
> requests.patch("/photo/123/",payload )
But again giving the same error:
[{"non_field_errors": ["Invalid data"]}]
The original idea of Django Rest Framework's serialization of relations is to not change values of related fields.
It means that your payload should contain a pk of PhotoData object, not a dataset for it.
It's like in models you can't assign a dict to a foreign key field.
Good (works only with serializers.PrimaryKeyRelatedField which contains problems itself):
payload = {"image_data": 2}
Bad (not works in DRF by default):
payload = {"image_data": {'thisdata': '{}'}}
Actually the data model that you provided doesn't need PhotoData at all (you can move thisdata field to Photo), but let's assume you have a special case even when Zen of Python says Special cases aren't special enough to break the rules..
So, here is some possible ways:
Using fields serializers (your original way)
What you want to do now is possible but is very ugly solution.
You may create a PhotoDataField (works for me, but not ready to use code, only for demonstration)
class PhotoDataField(serializers.PrimaryKeyRelatedField):
def field_to_native(self, *args):
"""
Use field_to_native from RelatedField for correct `to_native` result
"""
return super(serializers.RelatedField, self).field_to_native(*args)
# Prepare value to output
def to_native(self, obj):
if isinstance(obj, PhotoData):
return obj.thisdata
return super(PhotoDataField, self).to_native(obj)
# Handle input value
def field_from_native(self, data, files, field_name, into):
try:
int(data['image_data'])
except ValueError:
# Looks like we have a data for `thisdata` field here.
# So let's do write this to PhotoData model right now.
# Why? Because you can't do anything with `image_data` in further.
if not self.root.object.image_data:
# Create a new `PhotoData` instance and use it.
self.root.object.image_data = PhotoData.objects.create()
self.root.object.image_data.thisdata = data['image_data']
self.root.object.image_data.save()
return data['image_data']
except KeyError:
pass
# So native behaviour works (e.g. via web GUI)
return super(PhotoDataField, self).field_from_native(data, files, field_name, into)
and use it in PhotoSerializer
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataField(read_only=False, source='image_data')
class Meta:
model = Photo
fields = ("id", "user", "image_data")
so the request will works well
payload = {"image_data": '{}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
and the "good" request also
photodata = PhotoData.objects.get(pk=1)
payload = {"image_data": photodata.pk}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
and in result you will see in GET request "image_data": <photodata's thisdata value>,.
But, even if you will fix the validation problems with this approach it still be a huge pain in ass as you can see from my code (this is only thing DRF can offers you when you want to "break a normal workflow", Tastypie offers more).
Normalize your code and use #action (recommended)
class PhotoDataSerializer(serializers.ModelSerializer):
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer() # or serializers.RelatedField
class Meta:
model = Photo
fields = ("id", "user", "image_data", "test")
and now define a specific method in your api's view that you will be able to use to set the data for any photo
from rest_framework import viewsets, routers, generics
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
# ViewSets define the view behavior.
class PhotoViewSet(viewsets.ModelViewSet):
model = Photo
serializer_class = PhotoSerializer
#action(methods=['PATCH'])
def set_photodata(self, request, pk=None):
photo = self.get_object()
serializer = PhotoDataSerializer(data=request.DATA)
if serializer.is_valid():
if not photo.image_data:
photo.image_data = PhotoData.objects.create()
photo.save()
photo.image_data.thisdata = serializer.data
photo.image_data.save()
return Response({'status': 'ok'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Now you can do almost the same request as you doing now, but you have much more extensibility and division of responsibilities in code.
See the URL, it's appended when you have #action's wrapped method.
payload = {"thisdata": '{"test": "ok"}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/set_photodata/"), payload)
Hope this helps.