Тестирование загрузки файлов в Flask
Я использую Flask-Testing для своих интеграционных тестов Flask. У меня есть форма, в которой есть файл для загрузки логотипа, для которого я пытаюсь написать тесты, но я получаю сообщение об ошибке: TypeError: 'str' does not support the buffer interface
,
Я использую Python 3. Ближайший ответ, который я нашел, - это, но он не работает для меня.
Вот как выглядит одна из моих многочисленных попыток:
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?
3 ответа
Проблема оказалась не в том, что когда кто-то добавляет content_type='multipart/form-data'
к post
Метод это ожидать все значения в data
или быть файлами или строками. В моих данных были целые числа, которые я понял благодаря этому комментарию.
Таким образом, конечное решение в итоге выглядело так:
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)
Вам нужны две вещи:
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)
Вы можете использовать Werkzeug's FileStorage
(используется Flask под капотом).
Обратите внимание, что я использую на своем сервере настоящий файл:tests/assets/my_video.mp4
но вы можете издеваться над этим: io.BytesIO(b"abcdef")
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")
Пытаясь найти ошибку в моем коде, я создал SSCCE для загрузки файлов (на основе документов) с соответствующим тестом на основе других ответов здесь. Кому-нибудь пригодится:
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',
)
Если вам нужно протестировать настоящий файл, потому что вы тестируете интеграцию с облачными сервисами, такими как Cloudinary, вы можете сделать следующее:
asset = "test_asset.jpg"
file = os.path.join(f"./tests/assets/{asset}")
rv = client.put(
"/api/v1/user/1",
data={
"avatar_image": (file, asset)
},
headers=headers
)