Browse Source

Merge branch '1.0-maintenance'

pull/2828/head
ThiefMaster 6 years ago
parent
commit
161c43649d
  1. 11
      .appveyor.yml
  2. 23
      .travis.yml
  3. 9
      CHANGES.rst
  4. 5
      docs/installation.rst
  5. 27
      flask/cli.py
  6. 8
      flask/helpers.py
  7. 26
      tests/test_cli.py
  8. 23
      tests/test_helpers.py
  9. 2
      tox.ini

11
.appveyor.yml

@ -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

@ -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:

9
CHANGES.rst

@ -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
------------- -------------

5
docs/installation.rst

@ -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

@ -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)

8
flask/helpers.py

@ -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:

26
tests/test_cli.py

@ -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)

23
tests/test_helpers.py

@ -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

2
tox.ini

@ -60,7 +60,7 @@ commands =
coverage html coverage html
[testenv:codecov] [testenv:codecov]
passenv = CI TRAVIS TRAVIS_* passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_*
deps = codecov deps = codecov
skip_install = true skip_install = true
commands = commands =

Loading…
Cancel
Save