【问题标题】:Testing file uploads in Flask在 Flask 中测试文件上传
【发布时间】:2016-06-11 14:23:51
【问题描述】:

我正在使用Flask-Testing 进行我的 Flask 集成测试。我有一个表单,其中包含我正在尝试为其编写测试的徽标的文件上传,但我一直收到错误消息:TypeError: 'str' does not support the buffer interface。

我正在使用 Python 3。我找到的最接近的答案是 this,但它不适合我。

这是我的许多尝试之一:

def test_edit_logo(self):
    """Test can upload logo."""
    data = {'name': 'this is a name', 'age': 12}
    data['file'] = (io.BytesIO(b"abcdef"), 'test.jpg')
    self.login()
    response = self.client.post(
        url_for('items.save'), data=data, follow_redirects=True)
    })
    self.assertIn(b'Your item has been saved.', response.data)
    advert = Advert.query.get(1)
    self.assertIsNotNone(item.logo)

如何在 Flask 中测试文件上传?

【问题讨论】:

  • 如果有一个可复制粘贴的可重现示例和完整的回溯,这个问题会更好。事实上,它并没有告诉我们是哪一行引发了引用的错误,也没有让我们自己重现它变得微不足道,并且对问题的第一次编辑使mmcclannahan's answer 部分荒谬。有点不必要的混乱!

标签: python flask


【解决方案1】:

你需要两件事:

1.) content_type='multipart/form-data' 在您的.post()
2.) 在你的data= 中传入file=(BytesIO(b'my file contents'), "file_name.jpg")

一个完整的例子:

    data = dict(
        file=(BytesIO(b'my file contents'), "work_order.123"),
    )

    response = app.post(url_for('items.save'), content_type='multipart/form-data', data=data)

【讨论】:

  • 谢谢@mam8cc。你能为我澄清第2点吗?在我看来,您是在说将关键字参数传递给字典,我认为这不是您的意思。你能给我一个简短的代码示例吗?
  • @hammygoonan 我用更完整的例子更新了这个问题。
  • 再次感谢@mam8cc,我想我们正在取得进展。如果我使用您在答案中得到的代码,那么它可以解决问题。但是,当我向数据字典添加其他字段时,它会与TypeError 中断。我已经编辑了我的问题以使其更清楚。
  • @hammygoonan 我记得遇到过你的问题。尝试确保您传递的所有值都包含在引号中。我们进行此类测试的代码库中的 cmets 意味着它是必需的(至于为什么我不能告诉你)。 data = dict(miles="1", file=(BytesIO(b'my file contents'), "work_order.pdf"))
  • 如果你的文件系统上有文件,你需要做with open(path_to_file, 'rb') as f: data['file'] = (f, f.name)
【解决方案2】:

问题最终不是当一个人将content_type='multipart/form-data' 添加到post 方法时,它期望data 中的所有值都是文件或字符串。我的数据字典中有整数,感谢this 评论。

所以最终解决方案最终看起来像这样:

def test_edit_logo(self):
    """Test can upload logo."""
    data = {'name': 'this is a name', 'age': 12}
    data = {key: str(value) for key, value in data.items()}
    data['file'] = (io.BytesIO(b"abcdef"), 'test.jpg')
    self.login()
    response = self.client.post(
        url_for('adverts.save'), data=data, follow_redirects=True,
        content_type='multipart/form-data'
    )
    self.assertIn(b'Your item has been saved.', response.data)
    advert = Item.query.get(1)
    self.assertIsNotNone(item.logo)

【讨论】:

  • 我非常爱你,我现在就想吻你。我浪费了整整一个小时试图找出问题所在......好先生,你是我的救星。
  • 是files 是data 参数的特殊键,还是实际传递给API 的键。如果是这样,文件名的意义何在?
  • 为了稍微揭开这个神秘面纱,分配给data['file'] 的“类文件元组”中的值被转换为werkzeug.datastructures.FileMultiDict.add_file 的参数,因此它可以采用可选的第三个值来指定文件的content_type(与整个请求的content_type不同)
【解决方案3】:

您可以使用 Werkzeug 的 FileStorage(由 Flask 底层使用),您不需要安装它,因为它是 Flask 附带的。

你可以像这样模拟一个文件:

from werkzeug.datastructures import FileStorage
import io
import json

# Here we are mocking a JSON file called Input.json
my_dict = {"msg": "hello!"}
input_json = json.dumps(my_dict, indent=4).encode("utf-8")

mock_file = FileStorage(
    stream=io.BytesIO(input_json),
    filename="Input.json",
    content_type="application/json",
)

请注意,我在服务器上使用了真实文件: tests/assets/my_video.mp4

from werkzeug.datastructures import FileStorage


my_video = os.path.join("tests/assets/my_video.mp4")

my_file = FileStorage(
    stream=open(my_video, "rb"),
    filename="my_video.mp4",
    content_type="video/mpeg",
),

rv = client.post(
   "/api/v1/video",
   data={
      "my_video": my_file,
   },
   content_type="multipart/form-data"
)

测试看看它返回响应状态码200:

assert "200" in rv.status

然后我可以测试文件是否到达服务器上的测试目录:

assert "my_video.mp4" in os.listdir("tests/my_test_path")

另请注意,您需要在拆解时将模拟文件设置为 None ,否则您将获得 ValueError: I/O operation on closed file. 。下面是一个 Pytest 示例:

    def setup_method(self):
        self.mock_file = FileStorage(
            stream=io.BytesIO(input_json),
            filename="Input.json",
            content_type="application/json",
        )

    def teardown_method(self):
        self.mock_file = None

【讨论】:

  • 这对我有用,而接受的答案却没有。
【解决方案4】:

在尝试在我的代码中查找错误时,我创建了一个用于文件上传的 SSCCE(基于the docs),并根据此处的其他答案进行了相应的测试。它可能对某人有用:

app.py:

import base64
import os
import pathlib
import tempfile
import textwrap

import flask
import werkzeug.utils

root = flask.Blueprint('root', __name__)

@root.route('/', methods=['GET', 'POST'])
def upload_file():
    if flask.request.method == 'POST':
        try:
            file = flask.request.files['file']
            if not file.filename:
                raise LookupError()
            filename = werkzeug.utils.secure_filename(file.filename)
            file.save(pathlib.Path(flask.current_app.config['UPLOAD_FOLDER'], filename))
            flask.flash('File saved!', 'message')
        except LookupError:
            flask.flash('No file provided!', 'error')
        return flask.redirect(flask.url_for('root.upload_file'))
    else:
        return flask.render_template_string(textwrap.dedent(
            '''\
            <!doctype html>
            <title>Upload new File</title>
            {% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}
            <ul class=flashes>
            {% for category, message in messages %}<li class="{{ category }}">{{ message }}</li>
            {% endfor %}
            </ul>
            {% endif %}{% endwith %}
            <h1>Upload new File</h1>
            <form method=post enctype=multipart/form-data>
            <input type=file name=file>
            <input type=submit value=Upload>
            </form>
            '''
        ))

def create_app():
    app = flask.Flask(__name__)
    app.config['UPLOAD_FOLDER'] = tempfile.gettempdir()
    app.secret_key = 'change-me'
    app.register_blueprint(root)
    return app

if __name__ == '__main__':
    create_app()

test_app.py:

"""upload tests"""

import base64
import io
import unittest

import werkzeug

import app

# https://raw.githubusercontent.com/mathiasbynens/small/master/jpeg.jpg
SMALLEST_JPEG_B64 = """\
/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=
"""

class BaseTestCase(unittest.TestCase):
    def test_save(self):
        with app.create_app().test_client() as client:
            file = werkzeug.datastructures.FileStorage(
                stream=io.BytesIO(base64.b64decode(SMALLEST_JPEG_B64)),
                filename="example image.jpg",
                content_type="image/jpg",
            )
            response = client.post(
                '/',
                data=dict(
                    file=file,
                ),
                follow_redirects=True,
                content_type='multipart/form-data',
            )

【讨论】:

    猜你喜欢
    • 2013-12-03
    • 2016-10-30
    • 2011-10-18
    • 2019-12-28
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 2016-02-25
    相关资源
    最近更新 更多