Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization of sqlalchemy proxied dictionary based collections fails #180

Open
ricardog opened this issue Nov 12, 2019 · 0 comments
Open

Comments

@ricardog
Copy link

Hi --

My model uses a sqlalchemy dictionary based collection proxy. When serializing a model instance I get the following exception

  File "/usr/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/lib/python3.7/site-packages/werkzeug/middleware/dispatcher.py", line 66, in __call__
    return app(environ, start_response)
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/lib/python3.7/site-packages/flask_rest_jsonapi/decorators.py", line 47, in wrapper
    return func(*args, **kwargs)
  File "/usr/lib/python3.7/site-packages/flask/views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "/usr/lib/python3.7/site-packages/flask_rest_jsonapi/decorators.py", line 83, in wrapper
    raise e
  File "/usr/lib/python3.7/site-packages/flask_rest_jsonapi/decorators.py", line 76, in wrapper
    return func(*args, **kwargs)
  File "/usr/lib/python3.7/site-packages/flask_rest_jsonapi/resource.py", line 81, in dispatch_request
    return make_response(json.dumps(response, cls=JSONEncoder), 200, headers)
  File "/usr/lib/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.7/site-packages/flask_rest_jsonapi/utils.py", line 26, in default
    return json.JSONEncoder.default(self, obj)
  File "/usr/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type _AssociationDict is not JSON serializable

The sqlalchemy proxy object is implemented using the _AssociationDict class which the JSON encoder does not know how to serialize. The marshmallow schema for the object is defined as

class ResourceSchema(Schema):
    id = fields.Integer(as_string=True, dump_only=True)
    title = fields.Str()
    url = fields.Str()
    language = fields.Str()
    tags = fields.Dict(keys=fields.Str(), values=fields.Str())

I didn't see a way to pass in a specialized JSON encoder to solve this. So I changed utils.py as follows (for reference see the flask-restless implementation)

import json
from uuid import UUID
from datetime import datetime

from sqlalchemy.ext.associationproxy import _AssociationDict
from sqlalchemy.ext.associationproxy import _AssociationList
from sqlalchemy.ext.associationproxy import _AssociationSet

class JSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        # Attributes values that come from association proxy
        # collections need to be cast to plain old Python data types
        # so that the JSON serializer can handle them.
        if isinstance(obj, _AssociationList):
            return list(obj)
        if isinstance(obj, _AssociationSet):
            return set(obj)
        if isinstance(obj, _AssociationDict):
            return dict(obj)
        if isinstance(obj, UUID):
            return str(obj)
        return json.JSONEncoder.default(self, obj)

Alternatively, allowing the user to pass a class for the JSON encoder would also work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant