mirror of https://github.com/pallets/flask.git
Merge branch '1.0-maintenance'
This commit is contained in:
commit
161c43649d
|
@ -1,16 +1,16 @@
|
||||||
environment:
|
environment:
|
||||||
global:
|
global:
|
||||||
TOXENV: py
|
TOXENV: py,codecov
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:\Python36
|
- PYTHON: C:\Python36-x64
|
||||||
- PYTHON: C:\Python27
|
- PYTHON: C:\Python27-x64
|
||||||
|
|
||||||
init:
|
init:
|
||||||
- SET PATH=%PYTHON%;%PATH%
|
- SET PATH=%PYTHON%;%PATH%
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python -m pip install -U pip setuptools wheel tox
|
- python -m pip install -U tox
|
||||||
|
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
|
@ -21,3 +21,6 @@ branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- /^.*-maintenance$/
|
- /^.*-maintenance$/
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- '%LOCALAPPDATA%\pip\Cache'
|
||||||
|
|
23
.travis.yml
23
.travis.yml
|
@ -14,27 +14,30 @@ matrix:
|
||||||
env: TOXENV=py,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: TOXENV=py,simplejson,devel,lowest,codecov
|
env: TOXENV=py,simplejson,devel,lowest,codecov
|
||||||
- python: pypy
|
- python: pypy3
|
||||||
env: TOXENV=py,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: nightly
|
- python: nightly
|
||||||
env: TOXENV=py
|
env: TOXENV=py
|
||||||
- os: osx
|
- os: osx
|
||||||
language: generic
|
language: generic
|
||||||
env: TOXENV=py
|
env: TOXENV=py3,py2,codecov
|
||||||
|
cache:
|
||||||
|
pip: false
|
||||||
|
directories:
|
||||||
|
- $HOME/Library/Caches/Homebrew
|
||||||
|
- $HOME/Library/Caches/pip
|
||||||
allow_failures:
|
allow_failures:
|
||||||
|
- python: pypy3
|
||||||
- python: nightly
|
- python: nightly
|
||||||
env: TOXENV=py
|
|
||||||
- os: osx
|
- os: osx
|
||||||
language: generic
|
|
||||||
env: TOXENV=py
|
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
- |
|
||||||
brew update;
|
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
||||||
brew install python3 redis memcached;
|
brew upgrade python
|
||||||
virtualenv -p python3 ~/py-env;
|
brew install python@2;
|
||||||
. ~/py-env/bin/activate;
|
export PATH="/usr/local/opt/python/libexec/bin:${PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|
|
@ -15,6 +15,15 @@ Version 1.0.3
|
||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
|
|
||||||
|
- :func:`send_file` encodes filenames as ASCII instead of Latin-1
|
||||||
|
(ISO-8859-1). This fixes compatibility with Gunicorn, which is
|
||||||
|
stricter about header encodings than PEP 3333. (`#2766`_)
|
||||||
|
- Allow custom CLIs using ``FlaskGroup`` to set the debug flag without
|
||||||
|
it always being overwritten based on environment variables. (`#2765`_)
|
||||||
|
|
||||||
|
.. _#2766: https://github.com/pallets/flask/issues/2766
|
||||||
|
.. _#2765: https://github.com/pallets/flask/pull/2765
|
||||||
|
|
||||||
|
|
||||||
Version 1.0.2
|
Version 1.0.2
|
||||||
-------------
|
-------------
|
||||||
|
|
|
@ -132,6 +132,9 @@ Within the activated environment, use the following command to install Flask:
|
||||||
|
|
||||||
pip install Flask
|
pip install Flask
|
||||||
|
|
||||||
|
Flask is now installed. Check out the :doc:`/quickstart` or go to the
|
||||||
|
:doc:`Documentation Overview </index>`.
|
||||||
|
|
||||||
Living on the edge
|
Living on the edge
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -177,7 +180,7 @@ On Windows, as an administrator:
|
||||||
\Python27\python.exe Downloads\get-pip.py
|
\Python27\python.exe Downloads\get-pip.py
|
||||||
\Python27\python.exe -m pip install virtualenv
|
\Python27\python.exe -m pip install virtualenv
|
||||||
|
|
||||||
Now you can continue to :ref:`install-create-env`.
|
Now you can return above and :ref:`install-create-env`.
|
||||||
|
|
||||||
.. _virtualenv: https://virtualenv.pypa.io/
|
.. _virtualenv: https://virtualenv.pypa.io/
|
||||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||||
|
|
27
flask/cli.py
27
flask/cli.py
|
@ -340,7 +340,8 @@ class ScriptInfo(object):
|
||||||
onwards as click object.
|
onwards as click object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app_import_path=None, create_app=None):
|
def __init__(self, app_import_path=None, create_app=None,
|
||||||
|
set_debug_flag=True):
|
||||||
#: Optionally the import path for the Flask application.
|
#: Optionally the import path for the Flask application.
|
||||||
self.app_import_path = app_import_path or os.environ.get('FLASK_APP')
|
self.app_import_path = app_import_path or os.environ.get('FLASK_APP')
|
||||||
#: Optionally a function that is passed the script info to create
|
#: Optionally a function that is passed the script info to create
|
||||||
|
@ -349,6 +350,7 @@ class ScriptInfo(object):
|
||||||
#: A dictionary with arbitrary data that can be associated with
|
#: A dictionary with arbitrary data that can be associated with
|
||||||
#: this script info.
|
#: this script info.
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
self.set_debug_flag = set_debug_flag
|
||||||
self._loaded_app = None
|
self._loaded_app = None
|
||||||
|
|
||||||
def load_app(self):
|
def load_app(self):
|
||||||
|
@ -386,12 +388,10 @@ class ScriptInfo(object):
|
||||||
'"app.py" module was not found in the current directory.'
|
'"app.py" module was not found in the current directory.'
|
||||||
)
|
)
|
||||||
|
|
||||||
debug = get_debug_flag()
|
if self.set_debug_flag:
|
||||||
|
# Update the app's debug flag through the descriptor so that
|
||||||
# Update the app's debug flag through the descriptor so that other
|
# other values repopulate as well.
|
||||||
# values repopulate as well.
|
app.debug = get_debug_flag()
|
||||||
if debug is not None:
|
|
||||||
app.debug = debug
|
|
||||||
|
|
||||||
self._loaded_app = app
|
self._loaded_app = app
|
||||||
return app
|
return app
|
||||||
|
@ -459,6 +459,8 @@ class FlaskGroup(AppGroup):
|
||||||
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
|
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
|
||||||
files to set environment variables. Will also change the working
|
files to set environment variables. Will also change the working
|
||||||
directory to the directory containing the first file found.
|
directory to the directory containing the first file found.
|
||||||
|
:param set_debug_flag: Set the app's debug flag based on the active
|
||||||
|
environment
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
.. versionchanged:: 1.0
|
||||||
If installed, python-dotenv will be used to load environment variables
|
If installed, python-dotenv will be used to load environment variables
|
||||||
|
@ -466,7 +468,8 @@ class FlaskGroup(AppGroup):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, add_default_commands=True, create_app=None,
|
def __init__(self, add_default_commands=True, create_app=None,
|
||||||
add_version_option=True, load_dotenv=True, **extra):
|
add_version_option=True, load_dotenv=True,
|
||||||
|
set_debug_flag=True, **extra):
|
||||||
params = list(extra.pop('params', None) or ())
|
params = list(extra.pop('params', None) or ())
|
||||||
|
|
||||||
if add_version_option:
|
if add_version_option:
|
||||||
|
@ -475,6 +478,7 @@ class FlaskGroup(AppGroup):
|
||||||
AppGroup.__init__(self, params=params, **extra)
|
AppGroup.__init__(self, params=params, **extra)
|
||||||
self.create_app = create_app
|
self.create_app = create_app
|
||||||
self.load_dotenv = load_dotenv
|
self.load_dotenv = load_dotenv
|
||||||
|
self.set_debug_flag = set_debug_flag
|
||||||
|
|
||||||
if add_default_commands:
|
if add_default_commands:
|
||||||
self.add_command(run_command)
|
self.add_command(run_command)
|
||||||
|
@ -550,7 +554,8 @@ class FlaskGroup(AppGroup):
|
||||||
obj = kwargs.get('obj')
|
obj = kwargs.get('obj')
|
||||||
|
|
||||||
if obj is None:
|
if obj is None:
|
||||||
obj = ScriptInfo(create_app=self.create_app)
|
obj = ScriptInfo(create_app=self.create_app,
|
||||||
|
set_debug_flag=self.set_debug_flag)
|
||||||
|
|
||||||
kwargs['obj'] = obj
|
kwargs['obj'] = obj
|
||||||
kwargs.setdefault('auto_envvar_prefix', 'FLASK')
|
kwargs.setdefault('auto_envvar_prefix', 'FLASK')
|
||||||
|
@ -670,7 +675,7 @@ class CertParamType(click.ParamType):
|
||||||
|
|
||||||
obj = import_string(value, silent=True)
|
obj = import_string(value, silent=True)
|
||||||
|
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7, 9):
|
||||||
if obj:
|
if obj:
|
||||||
return obj
|
return obj
|
||||||
else:
|
else:
|
||||||
|
@ -687,7 +692,7 @@ def _validate_key(ctx, param, value):
|
||||||
cert = ctx.params.get('cert')
|
cert = ctx.params.get('cert')
|
||||||
is_adhoc = cert == 'adhoc'
|
is_adhoc = cert == 'adhoc'
|
||||||
|
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7, 9):
|
||||||
is_context = cert and not isinstance(cert, (text_type, bytes))
|
is_context = cert and not isinstance(cert, (text_type, bytes))
|
||||||
else:
|
else:
|
||||||
is_context = isinstance(cert, ssl.SSLContext)
|
is_context = isinstance(cert, ssl.SSLContext)
|
||||||
|
|
|
@ -506,6 +506,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
|
|
||||||
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
|
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0.3
|
||||||
|
Filenames are encoded with ASCII instead of Latin-1 for broader
|
||||||
|
compatibility with WSGI servers.
|
||||||
|
|
||||||
:param filename_or_fp: the filename of the file to send.
|
:param filename_or_fp: the filename of the file to send.
|
||||||
This is relative to the :attr:`~Flask.root_path`
|
This is relative to the :attr:`~Flask.root_path`
|
||||||
if a relative path is specified.
|
if a relative path is specified.
|
||||||
|
@ -564,11 +568,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
'sending as attachment')
|
'sending as attachment')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attachment_filename = attachment_filename.encode('latin-1')
|
attachment_filename = attachment_filename.encode('ascii')
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
filenames = {
|
filenames = {
|
||||||
'filename': unicodedata.normalize(
|
'filename': unicodedata.normalize(
|
||||||
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
|
'NFKD', attachment_filename).encode('ascii', 'ignore'),
|
||||||
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
|
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -356,6 +356,28 @@ def test_flaskgroup(runner):
|
||||||
assert result.output == 'flaskgroup\n'
|
assert result.output == 'flaskgroup\n'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('set_debug_flag', (True, False))
|
||||||
|
def test_flaskgroup_debug(runner, set_debug_flag):
|
||||||
|
"""Test FlaskGroup debug flag behavior."""
|
||||||
|
|
||||||
|
def create_app(info):
|
||||||
|
app = Flask("flaskgroup")
|
||||||
|
app.debug = True
|
||||||
|
return app
|
||||||
|
|
||||||
|
@click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
|
||||||
|
def cli(**params):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def test():
|
||||||
|
click.echo(str(current_app.debug))
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ['test'])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output == '%s\n' % str(not set_debug_flag)
|
||||||
|
|
||||||
|
|
||||||
def test_print_exceptions(runner):
|
def test_print_exceptions(runner):
|
||||||
"""Print the stacktrace if the CLI."""
|
"""Print the stacktrace if the CLI."""
|
||||||
|
|
||||||
|
@ -537,12 +559,12 @@ def test_run_cert_import(monkeypatch):
|
||||||
run_command.make_context('run', ['--cert', 'not_here'])
|
run_command.make_context('run', ['--cert', 'not_here'])
|
||||||
|
|
||||||
# not an SSLContext
|
# not an SSLContext
|
||||||
if sys.version_info >= (2, 7):
|
if sys.version_info >= (2, 7, 9):
|
||||||
with pytest.raises(click.BadParameter):
|
with pytest.raises(click.BadParameter):
|
||||||
run_command.make_context('run', ['--cert', 'flask'])
|
run_command.make_context('run', ['--cert', 'flask'])
|
||||||
|
|
||||||
# SSLContext
|
# SSLContext
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7, 9):
|
||||||
ssl_context = object()
|
ssl_context = object()
|
||||||
else:
|
else:
|
||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
|
|
@ -638,15 +638,22 @@ class TestSendfile(object):
|
||||||
assert options['filename'] == 'index.txt'
|
assert options['filename'] == 'index.txt'
|
||||||
rv.close()
|
rv.close()
|
||||||
|
|
||||||
def test_attachment_with_utf8_filename(self, app, req_ctx):
|
@pytest.mark.usefixtures('req_ctx')
|
||||||
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt')
|
@pytest.mark.parametrize(('filename', 'ascii', 'utf8'), (
|
||||||
content_disposition = set(rv.headers['Content-Disposition'].split('; '))
|
('index.html', 'index.html', False),
|
||||||
assert content_disposition == set((
|
(u'Ñandú/pingüino.txt', '"Nandu/pinguino.txt"',
|
||||||
'attachment',
|
'%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt'),
|
||||||
'filename="Nandu/pinguino.txt"',
|
(u'Vögel.txt', 'Vogel.txt', 'V%C3%B6gel.txt'),
|
||||||
"filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"
|
))
|
||||||
))
|
def test_attachment_filename_encoding(self, filename, ascii, utf8):
|
||||||
|
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=filename)
|
||||||
rv.close()
|
rv.close()
|
||||||
|
content_disposition = rv.headers['Content-Disposition']
|
||||||
|
assert 'filename=%s' % ascii in content_disposition
|
||||||
|
if utf8:
|
||||||
|
assert "filename*=UTF-8''" + utf8 in content_disposition
|
||||||
|
else:
|
||||||
|
assert "filename*=UTF-8''" not in content_disposition
|
||||||
|
|
||||||
def test_static_file(self, app, req_ctx):
|
def test_static_file(self, app, req_ctx):
|
||||||
# default cache timeout is 12 hours
|
# default cache timeout is 12 hours
|
||||||
|
|
Loading…
Reference in New Issue