Skip to content

Commit 161c436

Browse files
committed
Merge branch '1.0-maintenance'
2 parents b2ec6a3 + 1a9caed commit 161c436

File tree

9 files changed

+95
-39
lines changed

9 files changed

+95
-39
lines changed

.appveyor.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
environment:
22
global:
3-
TOXENV: py
3+
TOXENV: py,codecov
44

55
matrix:
6-
- PYTHON: C:\Python36
7-
- PYTHON: C:\Python27
6+
- PYTHON: C:\Python36-x64
7+
- PYTHON: C:\Python27-x64
88

99
init:
1010
- SET PATH=%PYTHON%;%PATH%
1111

1212
install:
13-
- python -m pip install -U pip setuptools wheel tox
13+
- python -m pip install -U tox
1414

1515
build: false
1616

@@ -21,3 +21,6 @@ branches:
2121
only:
2222
- master
2323
- /^.*-maintenance$/
24+
25+
cache:
26+
- '%LOCALAPPDATA%\pip\Cache'

.travis.yml

+13-10
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,30 @@ matrix:
1414
env: TOXENV=py,codecov
1515
- python: 2.7
1616
env: TOXENV=py,simplejson,devel,lowest,codecov
17-
- python: pypy
17+
- python: pypy3
1818
env: TOXENV=py,codecov
1919
- python: nightly
2020
env: TOXENV=py
2121
- os: osx
2222
language: generic
23-
env: TOXENV=py
23+
env: TOXENV=py3,py2,codecov
24+
cache:
25+
pip: false
26+
directories:
27+
- $HOME/Library/Caches/Homebrew
28+
- $HOME/Library/Caches/pip
2429
allow_failures:
30+
- python: pypy3
2531
- python: nightly
26-
env: TOXENV=py
2732
- os: osx
28-
language: generic
29-
env: TOXENV=py
3033
fast_finish: true
3134

3235
before_install:
33-
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
34-
brew update;
35-
brew install python3 redis memcached;
36-
virtualenv -p python3 ~/py-env;
37-
. ~/py-env/bin/activate;
36+
- |
37+
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
38+
brew upgrade python
39+
brew install python@2;
40+
export PATH="/usr/local/opt/python/libexec/bin:${PATH}"
3841
fi
3942
4043
install:

CHANGES.rst

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ Version 1.0.3
1515

1616
Unreleased
1717

18+
- :func:`send_file` encodes filenames as ASCII instead of Latin-1
19+
(ISO-8859-1). This fixes compatibility with Gunicorn, which is
20+
stricter about header encodings than PEP 3333. (`#2766`_)
21+
- Allow custom CLIs using ``FlaskGroup`` to set the debug flag without
22+
it always being overwritten based on environment variables. (`#2765`_)
23+
24+
.. _#2766: https://github.com/pallets/flask/issues/2766
25+
.. _#2765: https://github.com/pallets/flask/pull/2765
26+
1827

1928
Version 1.0.2
2029
-------------

docs/installation.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ Within the activated environment, use the following command to install Flask:
132132
133133
pip install Flask
134134
135+
Flask is now installed. Check out the :doc:`/quickstart` or go to the
136+
:doc:`Documentation Overview </index>`.
137+
135138
Living on the edge
136139
~~~~~~~~~~~~~~~~~~
137140

@@ -177,7 +180,7 @@ On Windows, as an administrator:
177180
\Python27\python.exe Downloads\get-pip.py
178181
\Python27\python.exe -m pip install virtualenv
179182
180-
Now you can continue to :ref:`install-create-env`.
183+
Now you can return above and :ref:`install-create-env`.
181184

182185
.. _virtualenv: https://virtualenv.pypa.io/
183186
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py

flask/cli.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ class ScriptInfo(object):
340340
onwards as click object.
341341
"""
342342

343-
def __init__(self, app_import_path=None, create_app=None):
343+
def __init__(self, app_import_path=None, create_app=None,
344+
set_debug_flag=True):
344345
#: Optionally the import path for the Flask application.
345346
self.app_import_path = app_import_path or os.environ.get('FLASK_APP')
346347
#: Optionally a function that is passed the script info to create
@@ -349,6 +350,7 @@ def __init__(self, app_import_path=None, create_app=None):
349350
#: A dictionary with arbitrary data that can be associated with
350351
#: this script info.
351352
self.data = {}
353+
self.set_debug_flag = set_debug_flag
352354
self._loaded_app = None
353355

354356
def load_app(self):
@@ -386,12 +388,10 @@ def load_app(self):
386388
'"app.py" module was not found in the current directory.'
387389
)
388390

389-
debug = get_debug_flag()
390-
391-
# Update the app's debug flag through the descriptor so that other
392-
# values repopulate as well.
393-
if debug is not None:
394-
app.debug = debug
391+
if self.set_debug_flag:
392+
# Update the app's debug flag through the descriptor so that
393+
# other values repopulate as well.
394+
app.debug = get_debug_flag()
395395

396396
self._loaded_app = app
397397
return app
@@ -459,14 +459,17 @@ class FlaskGroup(AppGroup):
459459
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
460460
files to set environment variables. Will also change the working
461461
directory to the directory containing the first file found.
462+
:param set_debug_flag: Set the app's debug flag based on the active
463+
environment
462464
463465
.. versionchanged:: 1.0
464466
If installed, python-dotenv will be used to load environment variables
465467
from :file:`.env` and :file:`.flaskenv` files.
466468
"""
467469

468470
def __init__(self, add_default_commands=True, create_app=None,
469-
add_version_option=True, load_dotenv=True, **extra):
471+
add_version_option=True, load_dotenv=True,
472+
set_debug_flag=True, **extra):
470473
params = list(extra.pop('params', None) or ())
471474

472475
if add_version_option:
@@ -475,6 +478,7 @@ def __init__(self, add_default_commands=True, create_app=None,
475478
AppGroup.__init__(self, params=params, **extra)
476479
self.create_app = create_app
477480
self.load_dotenv = load_dotenv
481+
self.set_debug_flag = set_debug_flag
478482

479483
if add_default_commands:
480484
self.add_command(run_command)
@@ -550,7 +554,8 @@ def main(self, *args, **kwargs):
550554
obj = kwargs.get('obj')
551555

552556
if obj is None:
553-
obj = ScriptInfo(create_app=self.create_app)
557+
obj = ScriptInfo(create_app=self.create_app,
558+
set_debug_flag=self.set_debug_flag)
554559

555560
kwargs['obj'] = obj
556561
kwargs.setdefault('auto_envvar_prefix', 'FLASK')
@@ -670,7 +675,7 @@ def convert(self, value, param, ctx):
670675

671676
obj = import_string(value, silent=True)
672677

673-
if sys.version_info < (2, 7):
678+
if sys.version_info < (2, 7, 9):
674679
if obj:
675680
return obj
676681
else:
@@ -687,7 +692,7 @@ def _validate_key(ctx, param, value):
687692
cert = ctx.params.get('cert')
688693
is_adhoc = cert == 'adhoc'
689694

690-
if sys.version_info < (2, 7):
695+
if sys.version_info < (2, 7, 9):
691696
is_context = cert and not isinstance(cert, (text_type, bytes))
692697
else:
693698
is_context = isinstance(cert, ssl.SSLContext)

flask/helpers.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
506506
507507
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
508508
509+
.. versionchanged:: 1.0.3
510+
Filenames are encoded with ASCII instead of Latin-1 for broader
511+
compatibility with WSGI servers.
512+
509513
:param filename_or_fp: the filename of the file to send.
510514
This is relative to the :attr:`~Flask.root_path`
511515
if a relative path is specified.
@@ -564,11 +568,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
564568
'sending as attachment')
565569

566570
try:
567-
attachment_filename = attachment_filename.encode('latin-1')
571+
attachment_filename = attachment_filename.encode('ascii')
568572
except UnicodeEncodeError:
569573
filenames = {
570574
'filename': unicodedata.normalize(
571-
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
575+
'NFKD', attachment_filename).encode('ascii', 'ignore'),
572576
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
573577
}
574578
else:

tests/test_cli.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,28 @@ def test():
356356
assert result.output == 'flaskgroup\n'
357357

358358

359+
@pytest.mark.parametrize('set_debug_flag', (True, False))
360+
def test_flaskgroup_debug(runner, set_debug_flag):
361+
"""Test FlaskGroup debug flag behavior."""
362+
363+
def create_app(info):
364+
app = Flask("flaskgroup")
365+
app.debug = True
366+
return app
367+
368+
@click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
369+
def cli(**params):
370+
pass
371+
372+
@cli.command()
373+
def test():
374+
click.echo(str(current_app.debug))
375+
376+
result = runner.invoke(cli, ['test'])
377+
assert result.exit_code == 0
378+
assert result.output == '%s\n' % str(not set_debug_flag)
379+
380+
359381
def test_print_exceptions(runner):
360382
"""Print the stacktrace if the CLI."""
361383

@@ -537,12 +559,12 @@ def test_run_cert_import(monkeypatch):
537559
run_command.make_context('run', ['--cert', 'not_here'])
538560

539561
# not an SSLContext
540-
if sys.version_info >= (2, 7):
562+
if sys.version_info >= (2, 7, 9):
541563
with pytest.raises(click.BadParameter):
542564
run_command.make_context('run', ['--cert', 'flask'])
543565

544566
# SSLContext
545-
if sys.version_info < (2, 7):
567+
if sys.version_info < (2, 7, 9):
546568
ssl_context = object()
547569
else:
548570
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)

tests/test_helpers.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -638,15 +638,22 @@ def test_attachment(self, app, req_ctx):
638638
assert options['filename'] == 'index.txt'
639639
rv.close()
640640

641-
def test_attachment_with_utf8_filename(self, app, req_ctx):
642-
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt')
643-
content_disposition = set(rv.headers['Content-Disposition'].split('; '))
644-
assert content_disposition == set((
645-
'attachment',
646-
'filename="Nandu/pinguino.txt"',
647-
"filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"
648-
))
641+
@pytest.mark.usefixtures('req_ctx')
642+
@pytest.mark.parametrize(('filename', 'ascii', 'utf8'), (
643+
('index.html', 'index.html', False),
644+
(u'Ñandú/pingüino.txt', '"Nandu/pinguino.txt"',
645+
'%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt'),
646+
(u'Vögel.txt', 'Vogel.txt', 'V%C3%B6gel.txt'),
647+
))
648+
def test_attachment_filename_encoding(self, filename, ascii, utf8):
649+
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=filename)
649650
rv.close()
651+
content_disposition = rv.headers['Content-Disposition']
652+
assert 'filename=%s' % ascii in content_disposition
653+
if utf8:
654+
assert "filename*=UTF-8''" + utf8 in content_disposition
655+
else:
656+
assert "filename*=UTF-8''" not in content_disposition
650657

651658
def test_static_file(self, app, req_ctx):
652659
# default cache timeout is 12 hours

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ commands =
6060
coverage html
6161

6262
[testenv:codecov]
63-
passenv = CI TRAVIS TRAVIS_*
63+
passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_*
6464
deps = codecov
6565
skip_install = true
6666
commands =

0 commit comments

Comments
 (0)