Refty / mongo-thingy

:leaves: Powerful schema-less ODM for MongoDB and Python (sync + async)
https://mongo-thingy.readthedocs.io
MIT License
68 stars 12 forks source link

JSON serialization is failing for Thingy classes. #31

Closed candlecare closed 4 years ago

candlecare commented 4 years ago

I have a model and API controller:

class User(Thingy):
    pass

from flask import request
from flask_json import as_json

@app.route("/users/")
@as_json
def index():
  query = request.args
  user = User.find_one(query)
  return user

I am using Flask-JSON to decode Mongo-Thingy models. I tried both json.dumps(user) and both gave me the below error. I am not really sure it is mongo-thingy issue. It looks like it is related to Python 3.7 dataclasses to json conversion failure.

Thanking you in advance.

Sithu

Traceback (most recent call last):
  File "/Users/xxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/xxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/xxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/xxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/xxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/xxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/xxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask_json.py", line 233, in wrapper
    return _build_response(rv)
  File "/Users/xxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask_json.py", line 181, in _build_response
    return json_response(data_=data)
  File "/Users/xxxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask_json.py", line 139, in json_response
    response = jsonify(data_) if data_ is not None else jsonify(**kwargs)
  File "/Users/xxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/json/__init__.py", line 370, in jsonify
    dumps(data, indent=indent, separators=separators) + "\n",
  File "/Users/xxxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/Users/xxxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask_json.py", line 456, in default
    return super(JSONEncoderEx, self).default(o)
  File "/Users/xxxxxxx/.local/share/virtualenvs/app-7ln-8Bmp/lib/python3.7/site-packages/flask/json/__init__.py", line 97, in default
    return dataclasses.asdict(o)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py", line 1013, in asdict
    return _asdict_inner(obj, dict_factory)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py", line 1019, in _asdict_inner
    for f in fields(obj):
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py", line 978, in fields
    return tuple(f for f in fields.values() if f._field_type is _FIELD)
AttributeError: 'NoneType' object has no attribute 'values'
[2019-10-16 23:05:12]: 41336 INFO 127.0.0.1 - - [16/Oct/2019 23:05:12] "GET /api/users/ HTTP/1.1" 500 -
ramnes commented 4 years ago

Hello Sithu!

Yes, Thingy instances are not JSON serializable as they are not dictionaries. But you can simply use the user.view() method, which will output a dictionary corresponding to your object, using the default view.

Another option is to modify your JSON serializer to support Thingy objects and call .view() on them automatically. For example, as I see that you are using Flask-JSON, you could do something like this:

json = FlaskJson(app)
...

@json.encoder
def encoder(o):
    if isinstance(o, Thingy):
        return o.view()

Does that help?

candlecare commented 4 years ago

After adding this, it worked :)

@json.encoder
    def encoder(o):
        from bson import ObjectId
        if isinstance(o, ObjectId):
            return str(o)

        if isinstance(o, Thingy):
            return o.view()

Thank you, @ramnes