Last active
August 16, 2018 14:05
-
-
Save jgonggrijp/e09c3c2c558c16d2c2d5 to your computer and use it in GitHub Desktop.
Demonstration of a curious discrepancy between MySQL and SQLite when using file uploads with Flask-Admin and Flask-SQLAlchemy.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.E | |
====================================================================== | |
ERROR: test_upload_works (test_flask_admin_testcase.PictureTestCase) | |
---------------------------------------------------------------------- | |
Traceback (most recent call last): | |
File "test_flask_admin_testcase.py", line 118, in test_upload_works | |
'path': (test_image_path, test_image_name), | |
File "/path/to/env/lib/python2.7/site-packages/werkzeug/test.py", line 784, in post | |
return self.open(*args, **kw) | |
File "/path/to/env/lib/python2.7/site-packages/flask/testing.py", line 108, in open | |
follow_redirects=follow_redirects) | |
File "/path/to/env/lib/python2.7/site-packages/werkzeug/test.py", line 742, in open | |
response = self.run_wsgi_app(environ, buffered=buffered) | |
File "/path/to/env/lib/python2.7/site-packages/werkzeug/test.py", line 659, in run_wsgi_app | |
rv = run_wsgi_app(self.application, environ, buffered=buffered) | |
File "/path/to/env/lib/python2.7/site-packages/werkzeug/test.py", line 867, in run_wsgi_app | |
app_rv = app(environ, start_response) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__ | |
return self.wsgi_app(environ, start_response) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app | |
response = self.make_response(self.handle_exception(e)) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception | |
reraise(exc_type, exc_value, tb) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app | |
response = self.full_dispatch_request() | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request | |
rv = self.handle_user_exception(e) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception | |
reraise(exc_type, exc_value, tb) | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request | |
rv = self.dispatch_request() | |
File "/path/to/env/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request | |
return self.view_functions[rule.endpoint](**req.view_args) | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/base.py", line 68, in inner | |
return self._run_view(f, *args, **kwargs) | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/base.py", line 359, in _run_view | |
return fn(self, *args, **kwargs) | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/model/base.py", line 1585, in create_view | |
if self.validate_form(form): | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/model/base.py", line 1037, in validate_form | |
return validate_form_on_submit(form) | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/helpers.py", line 65, in validate_form_on_submit | |
return is_form_submitted() and form.validate() | |
File "/path/to/env/lib/python2.7/site-packages/wtforms/form.py", line 310, in validate | |
return super(Form, self).validate(extra) | |
File "/path/to/env/lib/python2.7/site-packages/wtforms/form.py", line 152, in validate | |
if not field.validate(self, extra): | |
File "/path/to/env/lib/python2.7/site-packages/wtforms/fields/core.py", line 200, in validate | |
stop_validation = self._run_validation_chain(form, chain) | |
File "/path/to/env/lib/python2.7/site-packages/wtforms/fields/core.py", line 220, in _run_validation_chain | |
validator(form, self) | |
File "/path/to/env/lib/python2.7/site-packages/flask_admin/contrib/sqla/validators.py", line 37, in __call__ | |
.filter(self.column == field.data) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2473, in one | |
ret = list(self) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2516, in __iter__ | |
return self._execute_and_instances(context) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2531, in _execute_and_instances | |
result = conn.execute(querycontext.statement, self._params) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 914, in execute | |
return meth(self, multiparams, params) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection | |
return connection._execute_clauseelement(self, multiparams, params) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1010, in _execute_clauseelement | |
compiled_sql, distilled_params | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1146, in _execute_context | |
context) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1341, in _handle_dbapi_exception | |
exc_info | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 199, in raise_from_cause | |
reraise(type(exception), exception, tb=exc_tb) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context | |
context) | |
File "/path/to/env/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute | |
cursor.execute(statement, parameters) | |
InterfaceError: (sqlite3.InterfaceError) Error binding parameter 0 - probably unsupported type. [SQL: u'SELECT picture.id AS picture_id, picture.name AS picture_name, picture.path AS picture_path \nFROM picture \nWHERE picture.path = ?'] [parameters: (<FileStorage: u'openclipart_hector_gomez_landscape.png' ('image/png')>,)] | |
---------------------------------------------------------------------- | |
Ran 2 tests in 0.170s | |
FAILED (errors=1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CherryPy==3.8.0 | |
Flask==0.10.1 | |
Flask-Admin==1.2.0 | |
Flask-SQLAlchemy==2.0 | |
Jinja2==2.7.3 | |
MarkupSafe==0.23 | |
MySQL-python==1.2.5 | |
Pillow==2.9.0 | |
PyYAML==3.11 | |
SQLAlchemy==1.0.6 | |
WTForms==2.0.2 | |
Werkzeug==0.10.4 | |
argparse==1.3.0 | |
glob2==0.4.1 | |
itsdangerous==0.24 | |
ordereddict==1.1 | |
pip-tools==0.3.6 | |
six==1.9.0 | |
wsgiref==0.1.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# (c) 2014 Digital Humanities Lab, Faculty of Humanities, Utrecht University | |
# Author: Julian Gonggrijp, j.gonggrijp@uu.nl | |
# Prerequisites: | |
# Create a MySQL database 'example' with user 'example' and no password. | |
# Create a Python 2.7 virtualenv in which you install the requirements. | |
# In order to run the application with the built-in server, run | |
# python test_flask_admin_testcase.py | |
# and visit 127.0.0.1:5000/admin with your browser. | |
# Uploading of images should work perfectly fine in this case. | |
# In order to run the test suite, run | |
# python -m unittest test_flask_admin_testcase | |
# You should get something similar to the contents of output.txt. | |
import os | |
import os.path as op | |
from tempfile import mkdtemp | |
from shutil import rmtree | |
from unittest import TestCase | |
from flask import Flask, current_app | |
from flask.ext.admin import Admin, form | |
from flask.ext.sqlalchemy import SQLAlchemy | |
from flask.ext.admin.contrib.sqla import ModelView | |
db = SQLAlchemy() | |
class Picture(db.Model): | |
id = db.Column(db.Integer, primary_key=True) | |
name = db.Column(db.String(40), nullable=False) | |
path = db.Column(db.String(50), unique=True, nullable=False) | |
class PictureView(ModelView): | |
column_list = ('name',) | |
form_columns = ('name', 'path') | |
form_overrides = { | |
'path': form.FileUploadField, | |
} | |
form_args = { | |
'path': { | |
'label': 'File', | |
'base_path': lambda: current_app.instance_path, | |
'allowed_extensions': ('jpg', 'jpeg', 'png'), | |
} | |
} | |
def __init__(self, session, name='Picture', **kwargs): | |
super(PictureView, self).__init__(Picture, session, name, **kwargs) | |
def create_admin(app): | |
admin = Admin(name='Example') | |
admin.add_view(PictureView(db.session)) | |
admin.init_app(app) | |
def create_app(config_obj, instance=None): | |
app = Flask(__name__, instance_path=instance) | |
app.config.from_object(config_obj) | |
db.init_app(app) | |
db.create_all(app=app) | |
create_admin(app) | |
return app | |
class Configuration(object): | |
SQLALCHEMY_DATABASE_URI = 'mysql://example@localhost/example?charset=utf8&use_unicode=0' | |
SECRET_KEY = 'psiodfnvpsdojfnvpaosihrgt' | |
if __name__ == '__main__': | |
app = create_app(Configuration) | |
app.debug = True | |
app.run() | |
class FixtureConfiguration(Configuration): | |
SQLALCHEMY_DATABASE_URI = 'sqlite://' | |
TESTING = True | |
class PictureTestCase(TestCase): | |
def setUp(self): | |
super(PictureTestCase, self).setUp() | |
tmpdir = mkdtemp('example_instance') | |
self.app = create_app(FixtureConfiguration, tmpdir) | |
self.client = self.app.test_client() | |
def tearDown(self): | |
db.drop_all(app=self.app) | |
rmtree(self.app.instance_path) | |
super(PictureTestCase, self).tearDown() | |
def request_context(self): | |
return self.app.test_request_context() | |
def test_picture_exists(self): | |
pic = Picture(name='Test', path='test/test.jpeg') | |
with self.request_context(): | |
db.session.add(pic) | |
db.session.commit() | |
first = db.session.query(Picture).first() | |
self.assertEqual(pic.id, 1) | |
self.assertEqual(first, pic) | |
def test_upload_works(self): | |
test_image_name = 'openclipart_hector_gomez_landscape.png' | |
tests_dir = op.dirname(__file__) | |
test_image_path = op.join(tests_dir, test_image_name) | |
with self.client as c: | |
c.post('/admin/picture/new/', data={ | |
'name': 'testimage', | |
'path': (test_image_path, test_image_name), | |
}) | |
images = Picture.query.all() | |
self.assertEqual(len(images), 1) | |
self.assertEqual(images[0].path, test_image_name) | |
directory = os.listdir(self.app.instance_path) | |
self.assertEqual(len(directory), 1) | |
self.assertIn(test_image_name, directory) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have the same issue. What was the fix?