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