Kami mengalami masalah awal tahun ini dengan SpooledTemporaryFile melemparkan pengecualian pada unggahan file setelah melompat dari 0.12.x ke 0.14.x. Tidak punya waktu untuk melihat lebih dalam, tidak dapat menemukan banyak hal secara online, dan tidak ingin merepotkan Anda semua jika itu adalah kesalahan kami atau karena beberapa ketergantungan yang tidak kompatibel. Baru saja disematkan ke 0.12.2, menulis tes kecil yang dapat mendeteksi masalah, dan melanjutkan.
Saya melihat pertanyaan SO untuk flask-admin di kotak masuk saya malam ini yang terdengar mirip dengan apa yang kami lihat, jadi saya pikir itu setidaknya layak untuk diangkat. Pertanyaan SO tidak diutarakan dengan baik, jadi itu mungkin bukan yang paling berguna (https://stackoverflow.com/questions/51858248/attributeerror-spooledtemporaryfile-object-has-no-attribute-translate).
Jika tes membantu:
def test_werkzeug_spooled_temp_file(self):
"""
When we jumped from Werkzeug 0.12.x to 0.14.x, we started running into an error on any attempt to upload a CSV:
File "/home/vagrant/site/videohub/admin.py", line 728, in import_content
for line in csv.DictReader(io.TextIOWrapper(new_file), restval=None):
File "/usr/local/lib/python3.6/dist-packages/werkzeug/datastructures.py", line 2745, in __getattr__
return getattr(self.stream, name)
AttributeError: 'SpooledTemporaryFile' object has no attribute 'readable'
Until/unless we debug this, we need to stay on Werkzeug versions where it doesn't break.
"""
with self.client:
from videohub import admin
admin.auth_admin = lambda: True
try:
what = self.client.post(
"/admin/importcontent/",
data={"csv": (io.BytesIO(b"my file contents"), "test.csv")},
content_type="multipart/form-data",
)
except AttributeError as e:
if (
str(e)
== "'SpooledTemporaryFile' object has no attribute 'readable'"
):
self.fail("Werkzeug/SpooledTemporaryFile still exists.")
Di utas SO saya menandatangani serangkaian masalah/PR github, masalah Python, dan utas SO lain yang semuanya mungkin terkait; menautkan ulang di sini jika itu hilang:
Untuk apa nilainya, tampaknya tidak sesederhana versi Werkzeug. Kami menggunakan 0.14.1 dengan python 3.6.6 di Alpine3.6 dan semuanya berfungsi. Ketika kita menggunakan Werkzeug 0.14.1 pada python 3.7.0 dengan Alpine 3.8.1 maka kita melihat masalahnya.
Sepertinya https://github.com/python/cpython/pull/3249 pada akhirnya dapat menghasilkan perbaikan di sisi Python.
Hai @abathur - Saya akan melihat...
Catatan:
HI @abathur - Saya mencoba membuat repo, seperti di bawah ini, dan sepertinya saya tidak dapat mereproduksi kesalahan =
app.py
import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = './uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
if __name__ == '__main__':
app.run()
Cuplikan untuk menghasilkan file besar
dd if=/dev/zero of=filename bs=1024 count=2M
Ubuntu - 18.10
Python - 3.6.7rc1
> pip freeze
Click==7.0
Flask==1.0.2
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
Werkzeug==0.14.1
/cc @davidisme
Tidak punya banyak waktu untuk melihat saat ini, tetapi saya juga mengalami kesulitan untuk mereproduksinya sekarang. Saya sedang dalam perjalanan saat ini dan bersiap untuk penerbangan di pagi hari, tetapi saya akan mencoba mengingat untuk melihat apakah saya dapat mereproduksi kondisi awal akhir pekan ini.
@jdalegonzalez Apakah Anda memiliki beberapa catatan tentang masalah Anda yang mungkin membantu mengisi gambar untuk sementara?
Permintaan maaf untuk kasus reproduksi yang sulit. Saya telah mengkonfirmasi bahwa kami masih melihat masalah ini dan menghabiskan sedikit waktu sore ini untuk mereproduksinya menjadi reproduksi yang cukup singkat.
Dalam prosesnya saya berhasil menemukan solusi potensial. Saya belum menguji solusinya secara ekstensif, jadi saya tidak tahu apakah itu akan menyebabkan masalah sendiri (terutama mengingat laporan oleh @jdalegonzalez bahwa platform mungkin menjadi faktor di sini).
Dependensi:
pip install flask==1.0.2 pytest pytest-flask
Uji:
import pytest, io, csv
from flask import Flask, request
def fields(ob):
return csv.DictReader(ob).fieldnames[0]
@pytest.fixture(scope="session")
def app():
x = Flask(__name__)
x.testing = True
@x.route("/textio", methods=["POST"])
def upload_file_textio():
"""succeeds with 0.12.2; fails with werkzeug==0.14.1;"""
return fields(io.TextIOWrapper(request.files["csv"]))
@x.route("/stringio", methods=["POST"])
def upload_file_stringio():
"""potential workaround; succeeds for both versions"""
return fields(io.StringIO(request.files["csv"].read().decode()))
with x.app_context():
yield x
@pytest.mark.parametrize("uri", ["/textio", "/stringio"])
def test_werkzeug_spooled_temp_file(client, uri):
what = client.post(
uri,
data={"csv": (io.BytesIO(b"my file contents"), "test.csv")},
content_type="multipart/form-data",
)
assert what.data == b"my file contents"
HI @abathur ,
Saya telah melihat sekilas dalam menjalankan repo pytest Anda dan sekilas output di bawah ini terlihat dapat direproduksi dari masalah tersebut. Saya akan mencoba melihat lebih jauh dalam beberapa hari ke depan, tetapi saya seorang pemula dalam hal flask, pytests, dan perlengkapan
Testing started at 20:35 ...
/Users/jayCee/PycharmProjects/test_werkzeug_spooled_temp_file/venv/bin/python "/Users/jayCee/Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-1/182.4505.26/PyCharm.app/Contents/helpers/pycharm/_jb_pytest_runner.py" --target test_werkzeug_spooled_temp.py::test_werkzeug_spooled_temp_file
Launching pytest with arguments test_werkzeug_spooled_temp.py::test_werkzeug_spooled_temp_file in /Users/jayCee/PycharmProjects/test_werkzeug_spooled_temp_file/test
============================= test session starts ==============================
platform darwin -- Python 3.6.6, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
rootdir: /Users/jayCee/PycharmProjects/test_werkzeug_spooled_temp_file/test, inifile:
plugins: flask-0.14.0collected 2 items
test_werkzeug_spooled_temp.py F
test_werkzeug_spooled_temp.py:27 (test_werkzeug_spooled_temp_file[/textio])
client = <FlaskClient <Flask 'test_werkzeug_spooled_temp'>>, uri = '/textio'
@pytest.mark.parametrize("uri", ["/textio", "/stringio"])
def test_werkzeug_spooled_temp_file(client, uri):
what = client.post(
uri,
data={"csv": (io.BytesIO(b"my file contents"), "test.csv")},
> content_type="multipart/form-data",
)
test_werkzeug_spooled_temp.py:34:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venv/lib/python3.6/site-packages/werkzeug/test.py:840: in post
return self.open(*args, **kw)
../venv/lib/python3.6/site-packages/flask/testing.py:200: in open
follow_redirects=follow_redirects
../venv/lib/python3.6/site-packages/werkzeug/test.py:803: in open
response = self.run_wsgi_app(environ, buffered=buffered)
../venv/lib/python3.6/site-packages/werkzeug/test.py:716: in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
../venv/lib/python3.6/site-packages/werkzeug/test.py:923: in run_wsgi_app
app_rv = app(environ, start_response)
../venv/lib/python3.6/site-packages/flask/app.py:2309: in __call__
return self.wsgi_app(environ, start_response)
../venv/lib/python3.6/site-packages/flask/app.py:2295: in wsgi_app
response = self.handle_exception(e)
../venv/lib/python3.6/site-packages/flask/app.py:1741: in handle_exception
reraise(exc_type, exc_value, tb)
../venv/lib/python3.6/site-packages/flask/_compat.py:35: in reraise
raise value
../venv/lib/python3.6/site-packages/flask/app.py:2292: in wsgi_app
response = self.full_dispatch_request()
../venv/lib/python3.6/site-packages/flask/app.py:1815: in full_dispatch_request
rv = self.handle_user_exception(e)
../venv/lib/python3.6/site-packages/flask/app.py:1718: in handle_user_exception
reraise(exc_type, exc_value, tb)
../venv/lib/python3.6/site-packages/flask/_compat.py:35: in reraise
raise value
../venv/lib/python3.6/site-packages/flask/app.py:1813: in full_dispatch_request
rv = self.dispatch_request()
../venv/lib/python3.6/site-packages/flask/app.py:1799: in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
test_werkzeug_spooled_temp.py:17: in upload_file_textio
return fields(io.TextIOWrapper(request.files["csv"]))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <FileStorage: 'test.csv' ('text/csv')>, name = 'readable'
def __getattr__(self, name):
> return getattr(self.stream, name)
E AttributeError: 'SpooledTemporaryFile' object has no attribute 'readable'
../venv/lib/python3.6/site-packages/werkzeug/datastructures.py:2745: AttributeError
. [100%]
=================================== FAILURES ===================================
___________________ test_werkzeug_spooled_temp_file[/textio] ___________________
...
# truncated duplicate above output ...
Mengepung
macOS Mojave 10.14
Python 3.6.6
pip freeze
atomicwrites==1.2.1
attrs==18.2.0
Click==7.0
Flask==1.0.2
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
more-itertools==4.3.0
pluggy==0.8.0
py==1.7.0
pytest==4.0.0
pytest-flask==0.14.0
six==1.11.0
Werkzeug==0.14.1
Hai,
Sepertinya pengenalan SpooledTemporaryFile
dari PR di bawah ini adalah sumber masalah ini...
PR: #1198: Mendukung penyandian transfer chunked
PR: #1222 - Dukungan tambahan untuk platform yang tidak memiliki file temp yang digulung
SpooledTemporaryFile
dan total_content_length > max_size
Mengomentari implementasi SpooledTemporaryFile
- tampaknya lulus kode uji https://github.com/pallets/werkzeug/issues/1344#issuecomment -438836862
def default_stream_factory(total_content_length, filename, content_type,
content_length=None):
"""The stream factory that is used per default."""
max_size = 1024 * 500
# if SpooledTemporaryFile is not None:
# return SpooledTemporaryFile(max_size=max_size, mode='wb+')
if total_content_length is None or total_content_length > max_size:
return TemporaryFile('wb+')
return BytesIO()
Sepertinya dari tautan @abathur - ada masalah dengan kelas SpooledTemporaryFile
karena kehilangan implementasi atribut abstrak "asumsi" IOBase
. Juga, ini akan berdampak pada kasus file yang kurang dari 512 kilobyte(?)
Menerapkan patch monyet di bawah ini ke FormDataParser.default_stream_factory tampaknya lulus kode uji https://github.com/pallets/werkzeug/issues/1344#issuecomment -438836862:
seekable
, readable
, dan writable
tidak ada) - https://bugs.python.org/issue26175def default_stream_factory(total_content_length, filename, content_type,
content_length=None):
"""The stream factory that is used per default."""
max_size = 1024 * 500
if SpooledTemporaryFile is not None:
monkeypatch_SpooledTemporaryFile = SpooledTemporaryFile(max_size=max_size, mode='wb+')
monkeypatch_SpooledTemporaryFile.readable = monkeypatch_SpooledTemporaryFile._file.readable
monkeypatch_SpooledTemporaryFile.writable = monkeypatch_SpooledTemporaryFile._file.writable
monkeypatch_SpooledTemporaryFile.seekable = monkeypatch_SpooledTemporaryFile._file.seekable
return monkeypatch_SpooledTemporaryFile
if total_content_length is None or total_content_length > max_size:
return TemporaryFile('wb+')
return BytesIO()
atau mungkin...
class SpooledTemporaryFile_Patched(SpooledTemporaryFile):
"""Patch for `SpooledTemporaryFile exceptions on file upload #1344`
- SpooledTemporaryFile does not fully satisfy the abstract for IOBase - https://bugs.python.org/issue26175
- bpo-26175: Fix SpooledTemporaryFile IOBase abstract - https://github.com/python/cpython/pull/3249
TODO: Remove patch once `bpo-26175: Fix SpooledTemporaryFile IOBase abstract` is resolved..."""
def readable(self):
return self._file.readable
def writable(self):
return self._file.writable
def seekable(self):
return self._file.seekable
...
def default_stream_factory(total_content_length, filename, content_type, content_length=None):
"""The stream factory that is used per default.
Patch: `SpooledTemporaryFile exceptions on file upload #1344`, Remove once `bpo-26175: Fix SpooledTemporaryFile IOBase abstract` is resolved...
- SpooledTemporaryFile does not fully satisfy the abstract for IOBase - https://bugs.python.org/issue26175
- bpo-26175: Fix SpooledTemporaryFile IOBase abstract - https://github.com/python/cpython/pull/3249"""
max_size = 1024 * 500
if SpooledTemporaryFile is not None:
# TODO: Remove patch once `bpo-26175: Fix SpooledTemporaryFile IOBase abstract` is resolved...
SpooledTemporaryFile = SpooledTemporaryFile_Patched
return SpooledTemporaryFile_Patched(max_size=max_size, mode='wb+')
if total_content_length is None or total_content_length > max_size:
return TemporaryFile('wb+')
return BytesIO()
Apa yang akan menjadi tindakan terbaik?
SpooledTemporaryFile
? (Mungkin ada cara yang lebih baik untuk melakukan ini...)SpooledTemporaryFile
, hingga perbaikan dirilis di versi python selanjutnya dan tambahkan pengalihan tergantung pada python ver?Kami dulu tidak menggunakan SpooledTemporaryFile
, tetapi sepertinya cara bawaan untuk melakukan apa yang dilakukan kode asli. Itu menyebabkan banyak masalah sejak itu. Kita mungkin harus kembali untuk tidak menggunakan SpooledTemporaryFile
.
Saya perhatikan bahwa bpo-26175 memiliki permintaan tarik yang sedang berlangsung sekarang, setidaknya, lihat python/cpython#3249
Ngobrol dengan @mjpieters memunculkan ide berbeda. Kita dapat mengubah FileStorage.__getattr__
untuk mencoba stream._file
jika attr tidak ada pada stream
tetapi memiliki atribut _file
.
class FileStorage:
def __getattr__(self, name):
try:
return getattr(self.stream, name)
except AttributeError:
if hasattr(self.stream, "_file"):
return getattr(self.stream._file, name)
raise
Hai, hari ini tingkatkan labu ke 1.0.2 dengan kebutuhan werkzeug >= 0.14
Saya memiliki kesalahan yang sama dengan versi terakhir werkzeug (0.14.1) dengan python 3.6.7 (yang datang dengan ubuntu 18.04):
AttributeError: objek 'SpooledTemporaryFile' tidak memiliki atribut 'dapat dibaca'
Apakah Anda berencana untuk mem-porting perbaikan ke versi 0.14?
Terima kasih.
Versi berikutnya adalah 0.15. Silakan ikuti repo ini, blog kami , atau https://twitter.com/PalletsTeam untuk pengumuman rilis.
Komentar yang paling membantu
Permintaan maaf untuk kasus reproduksi yang sulit. Saya telah mengkonfirmasi bahwa kami masih melihat masalah ini dan menghabiskan sedikit waktu sore ini untuk mereproduksinya menjadi reproduksi yang cukup singkat.
Dalam prosesnya saya berhasil menemukan solusi potensial. Saya belum menguji solusinya secara ekstensif, jadi saya tidak tahu apakah itu akan menyebabkan masalah sendiri (terutama mengingat laporan oleh @jdalegonzalez bahwa platform mungkin menjadi faktor di sini).
Dependensi:
Uji: