Search code examples
pythonflaskflask-sqlalchemyflask-restlessmarshmallow

Flask-Restless & Marshmallow: 'dict' object has no attribute '_sa_instance_state'


I'm working on a basic CRUD JSON REST API using Flask-Restless, Flask-SQLAlchemy and Marshmallow.

I'm experiencing a problem using this combination of libraries, but only when I enable deserialization using Marshmallow.

I've extensively googled the error, however I only find similar issues when using database relations, which I'm not.

I've been removing as much code as possible from my application while still getting the same error. The following code blocks are 1:1 what's running locally.

models.py:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

class Case(db.Model):
    __tablename__ = 'case'
    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Integer, nullable=False)

serializers.py:

from marshmallow import Schema


class CaseSchema(Schema):
    pass  # for simplicity - nothing works

case_schema = CaseSchema()


def case_serializer(instance):
    return case_schema.dump(instance).data


def case_deserializer(data):
    return case_schema.load(data).data

main.py:

from flask import Flask
import flask_restless

import models
import serializers

app = Flask(__name__)
app.config['DEBUG'] = True    
models.db.init_app(app)


def init_db():
    with app.app_context():
        models.db.create_all()


def init_api():
    init_db()

    with app.app_context():
        api_manager = flask_restless.APIManager(app, flask_sqlalchemy_db=models.db)
        api_manager.create_api(models.Case, methods=['GET', 'POST', 'PUT'],
                               serializer=serializers.case_serializer,
                               deserializer=serializers.case_deserializer)  # <- stuff breaks when enabling this line


if __name__ == "__main__":
    init_db()
    init_api()
    app.run()

tests.py:

import json
import os
import myapp
import unittest
import tempfile


with myapp.app.app_context():
    myapp.init_api()


class MyappTestCase(unittest.TestCase):
    def setUp(self):
        self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp()
        myapp.app.config['TESTING'] = True
        self.app = myapp.app.test_client()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(myapp.app.config['DATABASE'])

    def test_create_case(self):
        msg = {
            "amount": 1038,
        }

        post_response = self.app.post("/api/case", data=json.dumps(msg),
                                      content_type="application/json")
        assert post_response.status_code == 201

if __name__ == '__main__':
    unittest.main()

Versions in use:

Flask==0.11
Flask-SQLAlchemy==2.1
Flask-Restless==0.17.0
mysqlclient==1.3.7
SQLAlchemy-Utils==0.32.8
marshmallow==2.9.0

Finally, the full stacktrace when running tests:

======================================================================
ERROR: test_create_case (__main__.myappTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 1584, in add
    state = attributes.instance_state(instance)
AttributeError: 'dict' object has no attribute '_sa_instance_state'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "myapp_tests.py", line 37, in test_create_case
    content_type="application/json")
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/werkzeug/test.py", line 788, in post
    return self.open(*args, **kw)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/testing.py", line 113, in open
    follow_redirects=follow_redirects)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/werkzeug/test.py", line 751, in open
    response = self.run_wsgi_app(environ, buffered=buffered)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/werkzeug/test.py", line 668, in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/werkzeug/test.py", line 871, in run_wsgi_app
    app_rv = app(environ, start_response)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 2000, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1991, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1567, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1641, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1544, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask_restless/views.py", line 157, in decorator
    return func(*args, **kw)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/mimerender.py", line 244, in wrapper
    result = target(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask/views.py", line 149, in dispatch_request
    return meth(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask_restless/views.py", line 189, in wrapped
    return func(*args, **kw)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/flask_restless/views.py", line 1449, in post
    self.session.add(instance)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/sqlalchemy/orm/scoping.py", line 157, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myenv/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 1586, in add
    raise exc.UnmappedInstanceError(instance)
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'builtins.dict' is not mapped

Solution

  • Turns out I had to add a __tablename__ to the model definition and extend the Schema:

    class CaseSchema(Schema):
        [...]
    
        @post_load
        def make_case(self, data):
            return Case(**data)