mirror of https://github.com/pallets/flask.git
Merge branch '1.0-maintenance'
This commit is contained in:
commit
1fa9185c7e
39
CHANGES.rst
39
CHANGES.rst
|
|
@ -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
|
||||
-----------
|
||||
|
|
@ -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
|
||||
--------------
|
||||
|
||||
|
|
|
|||
13
docs/api.rst
13
docs/api.rst
|
|
@ -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
|
||||
|
|
|
|||
24
docs/cli.rst
24
docs/cli.rst
|
|
@ -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
|
||||
-------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
@ -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':
|
||||
|
|
@ -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
|
||||
|
|
|
|||
22
flask/app.py
22
flask/app.py
|
|
@ -27,9 +27,11 @@ from ._compat import integer_types, reraise, string_types, text_type
|
|||
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, \
|
||||
|
|
@ -904,7 +906,7 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
|
|
@ -1663,8 +1665,14 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
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
|
||||
|
|
@ -1923,7 +1931,7 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
|
|
|
|||
|
|
@ -49,12 +49,10 @@ class BlueprintSetupState(object):
|
|||
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
|
||||
|
|
@ -67,8 +65,8 @@ class BlueprintSetupState(object):
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from werkzeug.utils import import_string
|
|||
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
|
||||
|
|
@ -544,7 +544,7 @@ class FlaskGroup(AppGroup):
|
|||
# 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')
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1027,21 +1027,34 @@ def test_errorhandler_precedence(app, client):
|
|||
|
||||
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -115,17 +115,22 @@ def test_blueprint_app_error_handling(app, client):
|
|||
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/')
|
||||
@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('/foo')
|
||||
def foo():
|
||||
@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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue