That exception looks like Flask-Uploads is trying to from werkzeug import secure_filename which should be from werkzeug.utils import secure_filename, as per your own code.
Going by the Flask-Uploads github repo this appears to have been fixed 12 months ago.
I'd try pip install -U flask-uploads in your virtual environment, to ensure the latest version.
EDIT:
As @mattficke points out, the PyPi version is dated, and there's not a more recent release on the repo. Turns out you can install directly based on a commit hash, so for the latest (at the time of writing this):
pip install git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc
Or in a requirements.txt:
git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc
Then pip install -r requirements.txt.
Which works wonders:
>>> from flask_uploads import UploadSet,configure_uploads,IMAGES,DATA,ALL
>>> # No Exception
Answer from v25 on Stack Overflow'Secure' Filenames
ImportError: cannot import name 'secure_filename' from 'werkzeug'
Import Error when importing secure_filename from Werkzeug.utils : Forums : PythonAnywhere
secure_filename in werkzeug.util is problematic when the file name isnon-ASCII characters
That exception looks like Flask-Uploads is trying to from werkzeug import secure_filename which should be from werkzeug.utils import secure_filename, as per your own code.
Going by the Flask-Uploads github repo this appears to have been fixed 12 months ago.
I'd try pip install -U flask-uploads in your virtual environment, to ensure the latest version.
EDIT:
As @mattficke points out, the PyPi version is dated, and there's not a more recent release on the repo. Turns out you can install directly based on a commit hash, so for the latest (at the time of writing this):
pip install git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc
Or in a requirements.txt:
git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc
Then pip install -r requirements.txt.
Which works wonders:
>>> from flask_uploads import UploadSet,configure_uploads,IMAGES,DATA,ALL
>>> # No Exception
Alternatively to above suggested solution, you can use the well maintained fork called Flask-Reuploaded.
You do not even have to change import statements, as it tries to stay compatible with the no longer well maintained Flask-Uploads.
See https://github.com/jugmac00/flask-reuploaded
Also, Flask-Reuploaded certainly provides uptodate packages on PyPI:
https://pypi.org/project/Flask-Reuploaded/
Flask's WSGI library werkzeug has a utility function called secure_filename - you're intended to pass the filename of a user uploaded file to it but I'm having trouble understanding what exactly the concern is, and what securing a filename could possibly entail.
The Pooco site notes
what does that secure_filename() function actually do? Now the problem is that there is that principle called “never trust user input”. This is also true for the filename of an uploaded file. All submitted form data can be forged, and filenames can be dangerous. For the moment just remember: always use that function to secure a filename before storing it directly on the filesystem.
What's returned is the exact same filename except I guess sanitized in some way. What my concern is is that I don't really understand what this function accomplishes. I plan on changing the filename of any user submitted file anyways (random uuid string with a suffix of '-small', '-large', etc.). Does it do me any good to first run the filename through secure_filename() and then change the actual filename? Can I just initially change the filename?
In flask_uploads.py
Change
from werkzeug import secure_filename,FileStorage
to
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage
According to this issue, it is a bug related to the current version 1.0.0 of workzeug. It's merged but not yet published in pypi.
The workaround know until now is to downgrade from werkzeug=1.0.0 to werkzeug==0.16.0
So for do that you just need run the command:
pip install -U Werkzeug==0.16.0
Looking in the release notes from werkzeug there is a version 0.16.1, but in bug report there is no evidence that using that version could be of any help.
Flask-Uploads is actually using the patterns found in Flask's documentation for file upload handling. It uses werkzeug.secure_filename, it provides a way to set MAX_CONTENT_LENGTH if, for some reason, you are using Flask 0.5 or older, and it provides a way to validate files based on their extension.
In fact, Flask's documentation actually explicitly suggests using Flask-Uploads:
Because the common pattern for file uploads exists almost unchanged in all applications dealing with uploads, there is a Flask extension called Flask-Uploads that implements a full fledged upload mechanism with white and blacklisting of extensions and more.
I ended up with using my own secure_filename in my case that supports Arabic:
import os
import re
import unicodedata
# edit this to add more characters
_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9\u0600-\u06FF_.-]")
_windows_device_files = {
"CON",
"PRN",
"AUX",
"NUL",
*(f"COM{i}" for i in range(10)),
*(f"LPT{i}" for i in range(10)),
}
def secure_filename(filename: str) -> str:
# Normalize the filename to handle UTF-8 characters
filename = unicodedata.normalize("NFKC", filename)
# Replace path separators with spaces
for sep in os.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, " ")
# Remove unsafe characters
filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip("._")
# Ensure the filename is not a reserved device name on Windows
if os.name == "nt" and filename and filename.split(".")[0].upper() in _windows_device_files:
filename = f"_{filename}"
return filename