Flask-Admin+SQLAlchemy 图片上传在 MySQL 生产环境中有效,但在 SQLite 测试用例中无效:InterfaceError
Flask-Admin+SQLAlchemy image upload works in MySQL production env but not in SQLite testcase: InterfaceError
我使用 Flask、Flask-Admin 和 Flask-SQLAlchemy 创建了一个 Web 应用程序,管理员可以在其中上传图像。图片上传功能大部分是模仿 this Flask-Admin example. For the production website, I use MySQL and uploading works perfectly fine. However, in the test suite I use a memory-mapped SQLite database and any attempt to upload an image through the same form fails with an InterfaceError
. See this Gist 的完整细节和简化的测试用例。
它似乎与类型映射有关,其中 MySQLAlchemy 的后端似乎理解上传图像的文件名必须插入 SQL 语句而 SQLite 后端没有。但是,我上面链接的 Flask-Admin 示例工作得非常好,它也基于 SQLite。
谁能告诉我哪里出了问题,需要做什么才能通过测试?
编辑添加:原来这个问题已经被 Flask-Admin 开发人员知道了。参见 ticket on GitHub。
是的,你是对的。这就是你的问题的原因。问题是 SQLite 和 MySQL 后端的差异。
正如您在堆栈跟踪中看到的,它正在尝试绑定 FileStorage
类型的参数,但失败了。
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')>,)]
您要放置断点的位置将在 sqlalchemy.engine.default
模块中的方法 do_execute()
处。
SQLite 后端是二进制扩展,cursor
来自二进制扩展 (_sqlite3.so
)。此二进制扩展获取 FileStorage
类型的参数,并尝试通过调用 FileStorage.__conform__()
方法将其转换为 SQL 表示形式。但是class没有这样的方法。这就是它失败的原因。
另一方面,MySQL 后端来自名为 MySQLdb
的纯 Python 模块。因此它调用 MySQLdb.cursors.BaseCursor.execute()
方法,特别是通过调用 db.literal()
将类型 FileStorage
的参数转换为 SQL 表示,这将通过调用 FileStorage.__repr__()
结束。你最终得到以下查询:
'SELECT picture.id AS picture_id, picture.name AS picture_name, picture.path AS picture_path
FROM picture
WHERE picture.path = \'<FileStorage: u\\'images.jpeg\\' (\\'image/jpeg\\')>\''
没想到吧?现在您不太确定它 正确地 与 MySQL 一起工作。你是?只需尝试使用相同的文件创建两张图片,您将得到 Integrity error. (_mysql_exceptions.IntegrityError) (1062, "Duplicate entry 'images.jpeg' for key 'path'") [SQL: u'INSERT INTO picture (name, path) VALUES (%s, %s)'] [parameters: ('Test', 'images.jpeg')]
而不是有意义的错误消息。
为什么它在 Flask-Admin 的示例中有效?
您在模型的 path
列上设置了 unique
约束。这正是失败的 SQL 查询的来源。在尝试插入新图片之前,它会检查数据库中是否已经存在具有相同路径的图片。
如何修复
问题是 flask_admin.contrib.sqla.validators.Unique
验证器或 flask_admin.form.upload.FileUploadField
中的错误(它们不兼容)。验证器应该使用与通过 flask_admin.form.upload.FileUploadField.populate_obj()
方法输入模型相同的值,而不是直接将 FileStorage
传递给数据库查询。只是 raise an issue GitHub 并参考这个问题。
我不认为它可以在您的测试用例中轻松修复,因为它是您所依赖的库中的一个相当重要的错误。当然,前提是你想在你的 path
字段上保持 unique
约束。
我使用 Flask、Flask-Admin 和 Flask-SQLAlchemy 创建了一个 Web 应用程序,管理员可以在其中上传图像。图片上传功能大部分是模仿 this Flask-Admin example. For the production website, I use MySQL and uploading works perfectly fine. However, in the test suite I use a memory-mapped SQLite database and any attempt to upload an image through the same form fails with an InterfaceError
. See this Gist 的完整细节和简化的测试用例。
它似乎与类型映射有关,其中 MySQLAlchemy 的后端似乎理解上传图像的文件名必须插入 SQL 语句而 SQLite 后端没有。但是,我上面链接的 Flask-Admin 示例工作得非常好,它也基于 SQLite。
谁能告诉我哪里出了问题,需要做什么才能通过测试?
编辑添加:原来这个问题已经被 Flask-Admin 开发人员知道了。参见 ticket on GitHub。
是的,你是对的。这就是你的问题的原因。问题是 SQLite 和 MySQL 后端的差异。
正如您在堆栈跟踪中看到的,它正在尝试绑定 FileStorage
类型的参数,但失败了。
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')>,)]
您要放置断点的位置将在 sqlalchemy.engine.default
模块中的方法 do_execute()
处。
SQLite 后端是二进制扩展,cursor
来自二进制扩展 (_sqlite3.so
)。此二进制扩展获取 FileStorage
类型的参数,并尝试通过调用 FileStorage.__conform__()
方法将其转换为 SQL 表示形式。但是class没有这样的方法。这就是它失败的原因。
另一方面,MySQL 后端来自名为 MySQLdb
的纯 Python 模块。因此它调用 MySQLdb.cursors.BaseCursor.execute()
方法,特别是通过调用 db.literal()
将类型 FileStorage
的参数转换为 SQL 表示,这将通过调用 FileStorage.__repr__()
结束。你最终得到以下查询:
'SELECT picture.id AS picture_id, picture.name AS picture_name, picture.path AS picture_path
FROM picture
WHERE picture.path = \'<FileStorage: u\\'images.jpeg\\' (\\'image/jpeg\\')>\''
没想到吧?现在您不太确定它 正确地 与 MySQL 一起工作。你是?只需尝试使用相同的文件创建两张图片,您将得到 Integrity error. (_mysql_exceptions.IntegrityError) (1062, "Duplicate entry 'images.jpeg' for key 'path'") [SQL: u'INSERT INTO picture (name, path) VALUES (%s, %s)'] [parameters: ('Test', 'images.jpeg')]
而不是有意义的错误消息。
为什么它在 Flask-Admin 的示例中有效?
您在模型的 path
列上设置了 unique
约束。这正是失败的 SQL 查询的来源。在尝试插入新图片之前,它会检查数据库中是否已经存在具有相同路径的图片。
如何修复
问题是 flask_admin.contrib.sqla.validators.Unique
验证器或 flask_admin.form.upload.FileUploadField
中的错误(它们不兼容)。验证器应该使用与通过 flask_admin.form.upload.FileUploadField.populate_obj()
方法输入模型相同的值,而不是直接将 FileStorage
传递给数据库查询。只是 raise an issue GitHub 并参考这个问题。
我不认为它可以在您的测试用例中轻松修复,因为它是您所依赖的库中的一个相当重要的错误。当然,前提是你想在你的 path
字段上保持 unique
约束。