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
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)