Skip to content

Commit

Permalink
Merge branch '1.0-maintenance'
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Apr 30, 2018
2 parents ea316d2 + 4f155fb commit 1fa9185
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 38 deletions.
39 changes: 36 additions & 3 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,40 @@ Flask Changelog
Version 1.1
-----------

unreleased
Unreleased


Version 1.0.2
-------------

Unreleased


Version 1.0.1
-------------

unreleased
Released on April 29 2018

- Fix registering partials (with no ``__name__``) as view functions.
(`#2730`_)
- Don't treat lists returned from view functions the same as tuples.
Only tuples are interpreted as response data. (`#2736`_)
- Extra slashes between a blueprint's ``url_prefix`` and a route URL
are merged. This fixes some backwards compatibility issues with the
change in 1.0. (`#2731`_, `#2742`_)
- Only trap ``BadRequestKeyError`` errors in debug mode, not all
``BadRequest`` errors. This allows ``abort(400)`` to continue
working as expected. (`#2735`_)
- The ``FLASK_SKIP_DOTENV`` environment variable can be set to ``1``
to skip automatically loading dotenv files. (`#2722`_)

.. _#2722: https://github.com/pallets/flask/issues/2722
.. _#2730: https://github.com/pallets/flask/pull/2730
.. _#2731: https://github.com/pallets/flask/issues/2731
.. _#2735: https://github.com/pallets/flask/issues/2735
.. _#2736: https://github.com/pallets/flask/issues/2736
.. _#2742: https://github.com/pallets/flask/issues/2742

- Fix registering partials (with no ``__name__``) as view functions

Version 1.0
-----------
Expand Down Expand Up @@ -228,6 +253,14 @@ Released on April 26th 2018
.. _#2709: https://github.com/pallets/flask/pull/2709


Version 0.12.4
--------------

Released on April 29 2018

- Repackage 0.12.3 to fix package layout issue. (`#2728`_)


Version 0.12.3
--------------

Expand Down
13 changes: 12 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,18 @@ definition for a URL that accepts an optional page::
pass

This specifies that ``/users/`` will be the URL for page one and
``/users/page/N`` will be the URL for page `N`.
``/users/page/N`` will be the URL for page ``N``.

If a URL contains a default value, it will be redirected to its simpler
form with a 301 redirect. In the above example, ``/users/page/1`` will
be redirected to ``/users/``. If your route handles ``GET`` and ``POST``
requests, make sure the default route only handles ``GET``, as redirects
can't preserve form data. ::

@app.route('/region/', defaults={'id': 1})
@app.route('/region/<id>', methods=['GET', 'POST'])
def region(id):
pass

Here are the parameters that :meth:`~flask.Flask.route` and
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
Expand Down
24 changes: 24 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,30 @@ These can be added to the ``.flaskenv`` file just like ``FLASK_APP`` to
control default command options.


Disable dotenv
~~~~~~~~~~~~~~

The ``flask`` command will show a message if it detects dotenv files but
python-dotenv is not installed.

.. code-block:: none
flask run
* Tip: There are .env files present. Do "pip install python-dotenv" to use them.
You can tell Flask not to load dotenv files even when python-dotenv is
installed by setting the ``FLASK_SKIP_DOTENV`` environment variable.
This can be useful if you want to load them manually, or if you're using
a project runner that loads them already. Keep in mind that the
environment variables must be set before the app loads or it won't
configure as expected.

.. code-block:: none
export FLASK_SKIP_DOTENV=1
flask run
Environment Variables From virtualenv
-------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/patterns/fileuploads.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ the file and redirects the user to the URL for the uploaded file::
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('upload_file',
return redirect(url_for('uploaded_file',
filename=filename))
return '''
<!doctype html>
Expand Down
6 changes: 4 additions & 2 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ Python shell. See :ref:`context-locals`. ::

@app.route('/user/<username>')
def profile(username):
return '{}'s profile'.format(username)
return '{}\'s profile'.format(username)

with app.test_request_context():
print(url_for('index'))
Expand All @@ -315,6 +315,8 @@ a route only answers to ``GET`` requests. You can use the ``methods`` argument
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
::

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
Expand All @@ -323,7 +325,7 @@ of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
return show_the_login_form()

If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
and handles ``HEAD`` requests according to the `HTTP RFC`_. Likewise,
``OPTIONS`` is automatically implemented for you.

.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
Expand Down
22 changes: 15 additions & 7 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
from .config import Config, ConfigAttribute
from .ctx import AppContext, RequestContext, _AppCtxGlobals
from .globals import _request_ctx_stack, g, request, session
from .helpers import _PackageBoundObject, \
_endpoint_from_view_func, find_package, get_env, get_debug_flag, \
get_flashed_messages, locked_cached_property, url_for
from .helpers import (
_PackageBoundObject,
_endpoint_from_view_func, find_package, get_env, get_debug_flag,
get_flashed_messages, locked_cached_property, url_for, get_load_dotenv
)
from .logging import create_logger
from .sessions import SecureCookieSessionInterface
from .signals import appcontext_tearing_down, got_request_exception, \
Expand Down Expand Up @@ -904,7 +906,7 @@ def run(self, host=None, port=None, debug=None,
explain_ignored_app_run()
return

if load_dotenv:
if get_load_dotenv(load_dotenv):
cli.load_dotenv()

# if set, let env vars override previous values
Expand Down Expand Up @@ -1663,8 +1665,14 @@ def trap_http_exception(self, e):

trap_bad_request = self.config['TRAP_BAD_REQUEST_ERRORS']

# if unset, trap based on debug mode
if (trap_bad_request is None and self.debug) or trap_bad_request:
# if unset, trap key errors in debug mode
if (
trap_bad_request is None and self.debug
and isinstance(e, BadRequestKeyError)
):
return True

if trap_bad_request:
return isinstance(e, BadRequest)

return False
Expand Down Expand Up @@ -1923,7 +1931,7 @@ def make_response(self, rv):
status = headers = None

# unpack tuple returns
if isinstance(rv, (tuple, list)):
if isinstance(rv, tuple):
len_rv = len(rv)

# a 3-tuple is unpacked directly
Expand Down
10 changes: 4 additions & 6 deletions flask/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,10 @@ def __init__(self, blueprint, app, options, first_registration):
url_prefix = self.options.get('url_prefix')
if url_prefix is None:
url_prefix = self.blueprint.url_prefix

if url_prefix:
url_prefix = url_prefix.rstrip('/')
#: The prefix that should be used for all URLs defined on the
#: blueprint.
if url_prefix and url_prefix[-1] == '/':
url_prefix = url_prefix[:-1]

self.url_prefix = url_prefix

#: A dictionary with URL defaults that is added to each and every
Expand All @@ -67,8 +65,8 @@ def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
to the application. The endpoint is automatically prefixed with the
blueprint's name.
"""
if self.url_prefix:
rule = self.url_prefix + rule
if self.url_prefix is not None:
rule = '/'.join((self.url_prefix, rule.lstrip('/')))
options.setdefault('subdomain', self.subdomain)
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
Expand Down
7 changes: 3 additions & 4 deletions flask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from . import __version__
from ._compat import getargspec, iteritems, reraise, text_type
from .globals import current_app
from .helpers import get_debug_flag, get_env
from .helpers import get_debug_flag, get_env, get_load_dotenv

try:
import dotenv
Expand Down Expand Up @@ -544,7 +544,7 @@ def main(self, *args, **kwargs):
# script that is loaded here also attempts to start a server.
os.environ['FLASK_RUN_FROM_CLI'] = 'true'

if self.load_dotenv:
if get_load_dotenv(self.load_dotenv):
load_dotenv()

obj = kwargs.get('obj')
Expand Down Expand Up @@ -583,12 +583,11 @@ def load_dotenv(path=None):
.. versionadded:: 1.0
"""

if dotenv is None:
if path or os.path.exists('.env') or os.path.exists('.flaskenv'):
click.secho(
' * Tip: There are .env files present.'
' Do "pip install python-dotenv" to use them',
' Do "pip install python-dotenv" to use them.',
fg='yellow')
return

Expand Down
15 changes: 15 additions & 0 deletions flask/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ def get_debug_flag():
return val.lower() not in ('0', 'false', 'no')


def get_load_dotenv(default=True):
"""Get whether the user has disabled loading dotenv files by setting
:envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load the
files.
:param default: What to return if the env var isn't set.
"""
val = os.environ.get('FLASK_SKIP_DOTENV')

if not val:
return default

return val.lower() in ('0', 'false', 'no')


def _endpoint_from_view_func(view_func):
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
Expand Down
25 changes: 19 additions & 6 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,21 +1027,34 @@ def raise_e3():


def test_trapping_of_bad_request_key_errors(app, client):
@app.route('/fail')
@app.route('/key')
def fail():
flask.request.form['missing_key']

rv = client.get('/fail')
@app.route('/abort')
def allow_abort():
flask.abort(400)

rv = client.get('/key')
assert rv.status_code == 400
assert b'missing_key' not in rv.data
rv = client.get('/abort')
assert rv.status_code == 400

app.config['TRAP_BAD_REQUEST_ERRORS'] = True

app.debug = True
with pytest.raises(KeyError) as e:
client.get("/fail")

client.get("/key")
assert e.errisinstance(BadRequest)
assert 'missing_key' in e.value.description
rv = client.get('/abort')
assert rv.status_code == 400

app.debug = False
app.config['TRAP_BAD_REQUEST_ERRORS'] = True
with pytest.raises(KeyError):
client.get('/key')
with pytest.raises(BadRequest):
client.get('/abort')


def test_trapping_of_all_http_exceptions(app, client):
Expand Down
21 changes: 13 additions & 8 deletions tests/test_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,22 @@ def bp_forbidden():
assert client.get('/nope').data == b'you shall not pass'


def test_blueprint_prefix_slash(app, client):
bp = flask.Blueprint('test', __name__, url_prefix='/bar/')

@bp.route('/foo')
def foo():
@pytest.mark.parametrize(('prefix', 'rule', 'url'), (
('/foo/', '/bar', '/foo/bar'),
('/foo/', 'bar', '/foo/bar'),
('/foo', '/bar', '/foo/bar'),
('/foo/', '//bar', '/foo/bar'),
('/foo//', '/bar', '/foo/bar'),
))
def test_blueprint_prefix_slash(app, client, prefix, rule, url):
bp = flask.Blueprint('test', __name__, url_prefix=prefix)

@bp.route(rule)
def index():
return '', 204

app.register_blueprint(bp)
app.register_blueprint(bp, url_prefix='/spam/')
assert client.get('/bar/foo').status_code == 204
assert client.get('/spam/foo').status_code == 204
assert client.get(url).status_code == 204


def test_blueprint_url_defaults(app, client):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,14 @@ def test_dotenv_optional(monkeypatch):
assert 'FOO' not in os.environ


@need_dotenv
def test_disable_dotenv_from_env(monkeypatch, runner):
monkeypatch.chdir(test_path)
monkeypatch.setitem(os.environ, 'FLASK_SKIP_DOTENV', '1')
runner.invoke(FlaskGroup())
assert 'FOO' not in os.environ


def test_run_cert_path():
# no key
with pytest.raises(click.BadParameter):
Expand Down

0 comments on commit 1fa9185

Please sign in to comment.