mirror of https://github.com/pallets/flask.git
Add support for PathLike objects in static file helpers
See: https://www.python.org/dev/peps/pep-0519/ This is mostly encountered with pathlib in python 3, but this API suggests any PathLike object can be treated like a filepath with `__fspath__` function.
This commit is contained in:
parent
f7d50d4b67
commit
25de45cbb6
|
@ -15,9 +15,12 @@ Unreleased
|
|||
- Using built-in RequestContext, unprintable Unicode characters in Host
|
||||
header will result in a HTTP 400 response and not HTTP 500 as previously.
|
||||
(`#2994`)
|
||||
- :func:`send_file` supports ``PathLike`` objects as describe in
|
||||
PEP 0519, to support ``pathlib`` in Python 3. (`#3059`_)
|
||||
|
||||
.. _#2935: https://github.com/pallets/flask/issues/2935
|
||||
.. _#2994: https://github.com/pallets/flask/pull/2994
|
||||
.. _#3059: https://github.com/pallets/flask/pull/3059
|
||||
|
||||
|
||||
Version 1.0.3
|
||||
|
|
|
@ -97,3 +97,12 @@ if hasattr(sys, 'pypy_version_info'):
|
|||
BROKEN_PYPY_CTXMGR_EXIT = True
|
||||
except AssertionError:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from os import fspath
|
||||
except ImportError:
|
||||
# Backwards compatibility as proposed in PEP 0519:
|
||||
# https://www.python.org/dev/peps/pep-0519/#backwards-compatibility
|
||||
def fspath(path):
|
||||
return path.__fspath__() if hasattr(path, '__fspath__') else path
|
||||
|
|
|
@ -33,7 +33,7 @@ from jinja2 import FileSystemLoader
|
|||
from .signals import message_flashed
|
||||
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
|
||||
current_app, request
|
||||
from ._compat import string_types, text_type, PY2
|
||||
from ._compat import string_types, text_type, PY2, fspath
|
||||
|
||||
# sentinel
|
||||
_missing = object()
|
||||
|
@ -510,6 +510,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
Filenames are encoded with ASCII instead of Latin-1 for broader
|
||||
compatibility with WSGI servers.
|
||||
|
||||
.. versionchanged:: 1.1
|
||||
Filenames may be a `PathLike` object.
|
||||
|
||||
:param filename_or_fp: the filename of the file to send.
|
||||
This is relative to the :attr:`~Flask.root_path`
|
||||
if a relative path is specified.
|
||||
|
@ -538,6 +541,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
"""
|
||||
mtime = None
|
||||
fsize = None
|
||||
|
||||
if hasattr(filename_or_fp, '__fspath__'):
|
||||
filename_or_fp = fspath(filename_or_fp)
|
||||
|
||||
if isinstance(filename_or_fp, string_types):
|
||||
filename = filename_or_fp
|
||||
if not os.path.isabs(filename):
|
||||
|
@ -705,6 +712,8 @@ def send_from_directory(directory, filename, **options):
|
|||
:param options: optional keyword arguments that are directly
|
||||
forwarded to :func:`send_file`.
|
||||
"""
|
||||
filename = fspath(filename)
|
||||
directory = fspath(directory)
|
||||
filename = safe_join(directory, filename)
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
|
|
|
@ -36,6 +36,19 @@ def has_encoding(name):
|
|||
return False
|
||||
|
||||
|
||||
class FakePath(object):
|
||||
"""Fake object to represent a ``PathLike object``.
|
||||
|
||||
This represents a ``pathlib.Path`` object in python 3.
|
||||
See: https://www.python.org/dev/peps/pep-0519/
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def __fspath__(self):
|
||||
return self.path
|
||||
|
||||
|
||||
class FixedOffset(datetime.tzinfo):
|
||||
"""Fixed offset in hours east from UTC.
|
||||
|
||||
|
@ -527,6 +540,15 @@ class TestSendfile(object):
|
|||
assert 'x-sendfile' not in rv.headers
|
||||
rv.close()
|
||||
|
||||
def test_send_file_pathlike(self, app, req_ctx):
|
||||
rv = flask.send_file(FakePath('static/index.html'))
|
||||
assert rv.direct_passthrough
|
||||
assert rv.mimetype == 'text/html'
|
||||
with app.open_resource('static/index.html') as f:
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data == f.read()
|
||||
rv.close()
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not callable(getattr(Range, 'to_content_range_header', None)),
|
||||
reason="not implemented within werkzeug"
|
||||
|
@ -681,6 +703,12 @@ class TestSendfile(object):
|
|||
assert cc.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
# Test with static file handler.
|
||||
rv = app.send_static_file(FakePath('index.html'))
|
||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||
assert cc.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
class StaticFileApp(flask.Flask):
|
||||
def get_send_file_max_age(self, filename):
|
||||
return 10
|
||||
|
@ -706,6 +734,14 @@ class TestSendfile(object):
|
|||
assert rv.data.strip() == b'Hello Subdomain'
|
||||
rv.close()
|
||||
|
||||
def test_send_from_directory_pathlike(self, app, req_ctx):
|
||||
app.root_path = os.path.join(os.path.dirname(__file__),
|
||||
'test_apps', 'subdomaintestmodule')
|
||||
rv = flask.send_from_directory(FakePath('static'), FakePath('hello.txt'))
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data.strip() == b'Hello Subdomain'
|
||||
rv.close()
|
||||
|
||||
def test_send_from_directory_bad_request(self, app, req_ctx):
|
||||
app.root_path = os.path.join(os.path.dirname(__file__),
|
||||
'test_apps', 'subdomaintestmodule')
|
||||
|
|
Loading…
Reference in New Issue