From 9be01564f28ed49cbf55049265bd578c7a7257e1 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Wed, 28 Apr 2021 14:02:19 +0800 Subject: [PATCH 01/99] Update changelog for #3932 --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e9eb70fa..06e46425 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -76,6 +76,8 @@ Unreleased - Support async views, error handlers, before and after request, and teardown functions. :pr:`3412` - Support nesting blueprints. :issue:`593, 1548`, :pr:`3923` +- Set the default encoding to "UTF-8" when loading ``.env`` and + ``.flaskenv`` files to allow to use non-ASCII characters. :issue:`3931` - ``flask shell`` sets up tab and history completion like the default ``python`` shell if ``readline`` is installed. :issue:`3941` - ``helpers.total_seconds()`` is deprecated. Use From d124214129fd76cd620736f9dadc5e726eb8f34f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:46:45 +0000 Subject: [PATCH 02/99] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..86e010df --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: monthly + time: "08:00" + open-pull-requests-limit: 99 From 36e6ec45a9885fb1a6a22b0eb15b8810a8712418 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:54:53 +0000 Subject: [PATCH 03/99] Bump pre-commit from 2.12.0 to 2.12.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.12.0 to 2.12.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.12.0...v2.12.1) Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 934e7f31..1356aab7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -64,7 +64,7 @@ pluggy==0.13.1 # via # pytest # tox -pre-commit==2.12.0 +pre-commit==2.12.1 # via -r requirements/dev.in py==1.10.0 # via From ec044a24e10d2bd3c730f69909cd44639b469fd0 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Thu, 29 Apr 2021 07:40:11 +0800 Subject: [PATCH 04/99] Add tips for using async on Windows on Python 3.8 --- docs/async-await.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/async-await.rst b/docs/async-await.rst index 3fc24a06..5363ca13 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -17,6 +17,12 @@ defined with ``async def`` and use ``await``. data = await async_db_query(...) return jsonify(data) +.. admonition:: Using ``async`` on Windows on Python 3.8 + + Python 3.8 has a bug related to asyncio on Windows. If you encounter + something like ``ValueError: set_wakeup_fd only works in main thread``, + please upgrade to Python 3.9. + Performance ----------- From 467d2e2a437c73fc8eabdd887efe0453663939c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 May 2021 08:02:41 +0000 Subject: [PATCH 05/99] Bump python-dotenv from 0.17.0 to 0.17.1 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.17.0 to 0.17.1. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.17.0...v0.17.1) Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- requirements/tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 1356aab7..87466f4b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -78,7 +78,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.3 # via -r requirements/tests.in -python-dotenv==0.17.0 +python-dotenv==0.17.1 # via -r requirements/tests.in pytz==2021.1 # via babel diff --git a/requirements/tests.txt b/requirements/tests.txt index 04ee36ba..23f5cf49 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -24,7 +24,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.3 # via -r requirements/tests.in -python-dotenv==0.17.0 +python-dotenv==0.17.1 # via -r requirements/tests.in toml==0.10.2 # via pytest From 8bfce88e39dd427dc1258a04c6b0cafa9667ff3e Mon Sep 17 00:00:00 2001 From: Joshua Bronson Date: Sun, 2 May 2021 16:05:08 -0400 Subject: [PATCH 06/99] Minor docs fixes. (#3988) --- docs/async-await.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/async-await.rst b/docs/async-await.rst index 5363ca13..23b418de 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -57,7 +57,7 @@ example via ``asyncio.create_task``. If you wish to use background tasks it is best to use a task queue to trigger background work, rather than spawn tasks in a view function. With that in mind you can spawn asyncio tasks by serving -Flask with a ASGI server and utilising the asgiref WsgiToAsgi adapter +Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter as described in :ref:`asgi`. This works as the adapter creates an event loop that runs continually. @@ -70,7 +70,7 @@ to the way it is implemented. If you have a mainly async codebase it would make sense to consider `Quart`_. Quart is a reimplementation of Flask based on the `ASGI`_ standard instead of WSGI. This allows it to handle many concurrent requests, long running requests, and websockets -without requiring individual worker processes or threads. +without requiring multiple worker processes or threads. It has also already been possible to run Flask with Gevent or Eventlet to get many of the benefits of async request handling. These libraries @@ -86,8 +86,8 @@ to understanding the specific needs of your project. Extensions ---------- -Existing Flask extensions only expect views to be synchronous. If they -provide decorators to add functionality to views, those will probably +Flask extensions predating Flask's async support do not expect async views. +If they provide decorators to add functionality to views, those will probably not work with async views because they will not await the function or be awaitable. Other functions they provide will not be awaitable either and will probably be blocking if called within an async view. From cb13128cf03b2f1b3c5b48eab518e57e7b5f6516 Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 2 May 2021 20:23:08 +0100 Subject: [PATCH 07/99] Remove context copying from run_async function This was required with the previous implementation of Werkzeug's locals which didn't persist across threads. However as the current implementation uses ContextVars which do persist the context copying is no longer required. --- src/flask/helpers.py | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/src/flask/helpers.py b/src/flask/helpers.py index f7d37ed7..9e84960e 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -819,33 +819,8 @@ def run_async(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]: ) @wraps(func) - def outer(*args: t.Any, **kwargs: t.Any) -> t.Any: - """This function grabs the current context for the inner function. + def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any: + return async_to_sync(func)(*args, **kwargs) - This is similar to the copy_current_xxx_context functions in the - ctx module, except it has an async inner. - """ - ctx = None - - if _request_ctx_stack.top is not None: - ctx = _request_ctx_stack.top.copy() - - @wraps(func) - async def inner(*a: t.Any, **k: t.Any) -> t.Any: - """This restores the context before awaiting the func. - - This is required as the function must be awaited within the - context. Only calling ``func`` (as per the - ``copy_current_xxx_context`` functions) doesn't work as the - with block will close before the coroutine is awaited. - """ - if ctx is not None: - with ctx: - return await func(*a, **k) - else: - return await func(*a, **k) - - return async_to_sync(inner)(*args, **kwargs) - - outer._flask_sync_wrapper = True # type: ignore - return outer + wrapper._flask_sync_wrapper = True # type: ignore + return wrapper From 7f87f3dd93baf45d5f69f02a2dcf4493dfc4a1be Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 2 May 2021 20:49:46 +0100 Subject: [PATCH 08/99] Simplify the async handling code Firstly `run_sync` was a misleading name as it didn't run anything, instead I think `async_to_sync` is much clearer as it converts a coroutine function to a function. (Name stolen from asgiref). Secondly trying to run the ensure_sync during registration made the code more complex and brittle, e.g. the _flask_async_wrapper usage. This was done to pay any setup costs during registration rather than runtime, however this only saved a iscoroutne check. It allows the weirdness of the Blueprint and Scaffold ensure_sync methods to be removed. Switching to runtime ensure_sync usage provides a method for extensions to also support async, as now documented. --- docs/async-await.rst | 15 +++++++++++++++ src/flask/app.py | 30 ++++++++++++++---------------- src/flask/blueprints.py | 40 ++++++++-------------------------------- src/flask/helpers.py | 19 +++++++++---------- src/flask/scaffold.py | 13 +++++-------- tests/test_async.py | 4 ++-- 6 files changed, 53 insertions(+), 68 deletions(-) diff --git a/docs/async-await.rst b/docs/async-await.rst index 23b418de..34751d47 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -92,6 +92,21 @@ not work with async views because they will not await the function or be awaitable. Other functions they provide will not be awaitable either and will probably be blocking if called within an async view. +Extension authors can support async functions by utilising the +:meth:`flask.Flask.ensure_sync` method. For example, if the extension +provides a view function decorator add ``ensure_sync`` before calling +the decorated function, + +.. code-block:: python + + def extension(func): + @wraps(func) + def wrapper(*args, **kwargs): + ... # Extension logic + return current_app.ensure_sync(func)(*args, **kwargs) + + return wrapper + Check the changelog of the extension you want to use to see if they've implemented async support, or make a feature request or PR to them. diff --git a/src/flask/app.py b/src/flask/app.py index 7afb0a1e..7bc952b1 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -35,12 +35,12 @@ from .globals import _request_ctx_stack from .globals import g from .globals import request from .globals import session +from .helpers import async_to_sync from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_flashed_messages from .helpers import get_load_dotenv from .helpers import locked_cached_property -from .helpers import run_async from .helpers import url_for from .json import jsonify from .logging import create_logger @@ -1080,14 +1080,12 @@ class Flask(Scaffold): self.url_map.add(rule) if view_func is not None: old_func = self.view_functions.get(endpoint) - if getattr(old_func, "_flask_sync_wrapper", False): - old_func = old_func.__wrapped__ # type: ignore if old_func is not None and old_func != view_func: raise AssertionError( "View function mapping is overwriting an existing" f" endpoint function: {endpoint}" ) - self.view_functions[endpoint] = self.ensure_sync(view_func) + self.view_functions[endpoint] = view_func @setupmethod def template_filter(self, name: t.Optional[str] = None) -> t.Callable: @@ -1208,7 +1206,7 @@ class Flask(Scaffold): .. versionadded:: 0.8 """ - self.before_first_request_funcs.append(self.ensure_sync(f)) + self.before_first_request_funcs.append(f) return f @setupmethod @@ -1241,7 +1239,7 @@ class Flask(Scaffold): .. versionadded:: 0.9 """ - self.teardown_appcontext_funcs.append(self.ensure_sync(f)) + self.teardown_appcontext_funcs.append(f) return f @setupmethod @@ -1308,7 +1306,7 @@ class Flask(Scaffold): handler = self._find_error_handler(e) if handler is None: return e - return handler(e) + return self.ensure_sync(handler)(e) def trap_http_exception(self, e: Exception) -> bool: """Checks if an HTTP exception should be trapped or not. By default @@ -1375,7 +1373,7 @@ class Flask(Scaffold): if handler is None: raise - return handler(e) + return self.ensure_sync(handler)(e) def handle_exception(self, e: Exception) -> Response: """Handle an exception that did not have an error handler @@ -1422,7 +1420,7 @@ class Flask(Scaffold): handler = self._find_error_handler(server_error) if handler is not None: - server_error = handler(server_error) + server_error = self.ensure_sync(handler)(server_error) return self.finalize_request(server_error, from_error_handler=True) @@ -1484,7 +1482,7 @@ class Flask(Scaffold): ): return self.make_default_options_response() # otherwise dispatch to the handler for that endpoint - return self.view_functions[rule.endpoint](**req.view_args) + return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) def full_dispatch_request(self) -> Response: """Dispatches the request and on top of that performs request @@ -1545,7 +1543,7 @@ class Flask(Scaffold): if self._got_first_request: return for func in self.before_first_request_funcs: - func() + self.ensure_sync(func)() self._got_first_request = True def make_default_options_response(self) -> Response: @@ -1581,7 +1579,7 @@ class Flask(Scaffold): .. versionadded:: 2.0 """ if iscoroutinefunction(func): - return run_async(func) + return async_to_sync(func) return func @@ -1807,7 +1805,7 @@ class Flask(Scaffold): if bp in self.before_request_funcs: funcs = chain(funcs, self.before_request_funcs[bp]) for func in funcs: - rv = func() + rv = self.ensure_sync(func)() if rv is not None: return rv @@ -1834,7 +1832,7 @@ class Flask(Scaffold): if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: - response = handler(response) + response = self.ensure_sync(handler)(response) if not self.session_interface.is_null_session(ctx.session): self.session_interface.save_session(self, ctx.session, response) return response @@ -1871,7 +1869,7 @@ class Flask(Scaffold): if bp in self.teardown_request_funcs: funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) for func in funcs: - func(exc) + self.ensure_sync(func)(exc) request_tearing_down.send(self, exc=exc) def do_teardown_appcontext( @@ -1894,7 +1892,7 @@ class Flask(Scaffold): if exc is _sentinel: exc = sys.exc_info()[1] for func in reversed(self.teardown_appcontext_funcs): - func(exc) + self.ensure_sync(func)(exc) appcontext_tearing_down.send(self, exc=exc) def app_context(self) -> AppContext: diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index a2b6c0f5..5fb84d86 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -292,13 +292,10 @@ class Blueprint(Scaffold): # Merge blueprint data into parent. if first_registration: - def extend(bp_dict, parent_dict, ensure_sync=False): + def extend(bp_dict, parent_dict): for key, values in bp_dict.items(): key = self.name if key is None else f"{self.name}.{key}" - if ensure_sync: - values = [app.ensure_sync(func) for func in values] - parent_dict[key].extend(values) for key, value in self.error_handler_spec.items(): @@ -307,8 +304,7 @@ class Blueprint(Scaffold): dict, { code: { - exc_class: app.ensure_sync(func) - for exc_class, func in code_values.items() + exc_class: func for exc_class, func in code_values.items() } for code, code_values in value.items() }, @@ -316,16 +312,13 @@ class Blueprint(Scaffold): app.error_handler_spec[key] = value for endpoint, func in self.view_functions.items(): - app.view_functions[endpoint] = app.ensure_sync(func) + app.view_functions[endpoint] = func - extend( - self.before_request_funcs, app.before_request_funcs, ensure_sync=True - ) - extend(self.after_request_funcs, app.after_request_funcs, ensure_sync=True) + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) extend( self.teardown_request_funcs, app.teardown_request_funcs, - ensure_sync=True, ) extend(self.url_default_functions, app.url_default_functions) extend(self.url_value_preprocessors, app.url_value_preprocessors) @@ -478,9 +471,7 @@ class Blueprint(Scaffold): before each request, even if outside of a blueprint. """ self.record_once( - lambda s: s.app.before_request_funcs.setdefault(None, []).append( - s.app.ensure_sync(f) - ) + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) ) return f @@ -490,9 +481,7 @@ class Blueprint(Scaffold): """Like :meth:`Flask.before_first_request`. Such a function is executed before the first request to the application. """ - self.record_once( - lambda s: s.app.before_first_request_funcs.append(s.app.ensure_sync(f)) - ) + self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) return f def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable: @@ -500,9 +489,7 @@ class Blueprint(Scaffold): is executed after each request, even if outside of the blueprint. """ self.record_once( - lambda s: s.app.after_request_funcs.setdefault(None, []).append( - s.app.ensure_sync(f) - ) + lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) ) return f @@ -553,14 +540,3 @@ class Blueprint(Scaffold): lambda s: s.app.url_default_functions.setdefault(None, []).append(f) ) return f - - def ensure_sync(self, f: t.Callable) -> t.Callable: - """Ensure the function is synchronous. - - Override if you would like custom async to sync behaviour in - this blueprint. Otherwise the app's - :meth:`~flask.Flask.ensure_sync` is used. - - .. versionadded:: 2.0 - """ - return f diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 9e84960e..621d51e6 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -6,7 +6,6 @@ import typing as t import warnings from datetime import timedelta from functools import update_wrapper -from functools import wraps from threading import RLock import werkzeug.utils @@ -803,10 +802,15 @@ def is_ip(value: str) -> bool: return False -def run_async(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]: - """Return a sync function that will run the coroutine function *func*.""" +def async_to_sync(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]: + """Return a sync function that will run the coroutine function *func*. + + This can be used as so + + result = async_to_async(func)(*args, **kwargs) + """ try: - from asgiref.sync import async_to_sync + from asgiref.sync import async_to_sync as asgiref_async_to_sync except ImportError: raise RuntimeError( "Install Flask with the 'async' extra in order to use async views." @@ -818,9 +822,4 @@ def run_async(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]: "Async cannot be used with this combination of Python & Greenlet versions." ) - @wraps(func) - def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any: - return async_to_sync(func)(*args, **kwargs) - - wrapper._flask_sync_wrapper = True # type: ignore - return wrapper + return asgiref_async_to_sync(func) diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index d40dfdd8..56d37ddd 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -521,7 +521,7 @@ class Scaffold: """ def decorator(f): - self.view_functions[endpoint] = self.ensure_sync(f) + self.view_functions[endpoint] = f return f return decorator @@ -545,7 +545,7 @@ class Scaffold: return value from the view, and further request handling is stopped. """ - self.before_request_funcs.setdefault(None, []).append(self.ensure_sync(f)) + self.before_request_funcs.setdefault(None, []).append(f) return f @setupmethod @@ -561,7 +561,7 @@ class Scaffold: should not be used for actions that must execute, such as to close resources. Use :meth:`teardown_request` for that. """ - self.after_request_funcs.setdefault(None, []).append(self.ensure_sync(f)) + self.after_request_funcs.setdefault(None, []).append(f) return f @setupmethod @@ -600,7 +600,7 @@ class Scaffold: debugger can still access it. This behavior can be controlled by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. """ - self.teardown_request_funcs.setdefault(None, []).append(self.ensure_sync(f)) + self.teardown_request_funcs.setdefault(None, []).append(f) return f @setupmethod @@ -706,7 +706,7 @@ class Scaffold: " instead." ) - self.error_handler_spec[None][code][exc_class] = self.ensure_sync(f) + self.error_handler_spec[None][code][exc_class] = f @staticmethod def _get_exc_class_and_code( @@ -734,9 +734,6 @@ class Scaffold: else: return exc_class, None - def ensure_sync(self, func: t.Callable) -> t.Callable: - raise NotImplementedError() - def _endpoint_from_view_func(view_func: t.Callable) -> str: """Internal helper that returns the default endpoint for a given diff --git a/tests/test_async.py b/tests/test_async.py index 8c096f69..798eed85 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -6,7 +6,7 @@ import pytest from flask import Blueprint from flask import Flask from flask import request -from flask.helpers import run_async +from flask.helpers import async_to_sync pytest.importorskip("asgiref") @@ -137,4 +137,4 @@ def test_async_before_after_request(): @pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7") def test_async_runtime_error(): with pytest.raises(RuntimeError): - run_async(None) + async_to_sync(None) From 2889da67cb15ac6d5d882781d54014286d9ae010 Mon Sep 17 00:00:00 2001 From: pgjones Date: Mon, 3 May 2021 10:59:28 +0100 Subject: [PATCH 09/99] Remove the async helper method It is better to encourage users to utilise the app ensure_sync method (or the newely added async_to_sync method) so that any extensions that alter these methods take affect throughout the users code. With the helper method users code fix parts of their code to the asgiref async_to_sync ignoring any extension changes. --- src/flask/app.py | 34 ++++++++++++++++++++++++++++++++-- src/flask/helpers.py | 24 ------------------------ tests/test_async.py | 4 ++-- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/flask/app.py b/src/flask/app.py index 7bc952b1..85306d7c 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -16,6 +16,7 @@ from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequestKeyError from werkzeug.exceptions import HTTPException from werkzeug.exceptions import InternalServerError +from werkzeug.local import ContextVar from werkzeug.routing import BuildError from werkzeug.routing import Map from werkzeug.routing import MapAdapter @@ -35,7 +36,6 @@ from .globals import _request_ctx_stack from .globals import g from .globals import request from .globals import session -from .helpers import async_to_sync from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_flashed_messages @@ -1579,10 +1579,40 @@ class Flask(Scaffold): .. versionadded:: 2.0 """ if iscoroutinefunction(func): - return async_to_sync(func) + return self.async_to_sync(func) return func + def async_to_sync( + self, func: t.Callable[..., t.Coroutine] + ) -> t.Callable[..., t.Any]: + """Return a sync function that will run the coroutine function. + + .. code-block:: python + + result = app.async_to_sync(func)(*args, **kwargs) + + Override this method to change how the app converts async code + to be synchronously callable. + + .. versionadded:: 2.0 + """ + try: + from asgiref.sync import async_to_sync as asgiref_async_to_sync + except ImportError: + raise RuntimeError( + "Install Flask with the 'async' extra in order to use async views." + ) + + # Check that Werkzeug isn't using its fallback ContextVar class. + if ContextVar.__module__ == "werkzeug.local": + raise RuntimeError( + "Async cannot be used with this combination of Python " + "and Greenlet versions." + ) + + return asgiref_async_to_sync(func) + def make_response(self, rv: ResponseReturnValue) -> Response: """Convert the return value from a view function to an instance of :attr:`response_class`. diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 621d51e6..109f544f 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -10,7 +10,6 @@ from threading import RLock import werkzeug.utils from werkzeug.exceptions import NotFound -from werkzeug.local import ContextVar from werkzeug.routing import BuildError from werkzeug.urls import url_quote @@ -800,26 +799,3 @@ def is_ip(value: str) -> bool: return True return False - - -def async_to_sync(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]: - """Return a sync function that will run the coroutine function *func*. - - This can be used as so - - result = async_to_async(func)(*args, **kwargs) - """ - try: - from asgiref.sync import async_to_sync as asgiref_async_to_sync - except ImportError: - raise RuntimeError( - "Install Flask with the 'async' extra in order to use async views." - ) - - # Check that Werkzeug isn't using its fallback ContextVar class. - if ContextVar.__module__ == "werkzeug.local": - raise RuntimeError( - "Async cannot be used with this combination of Python & Greenlet versions." - ) - - return asgiref_async_to_sync(func) diff --git a/tests/test_async.py b/tests/test_async.py index 798eed85..26a91118 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -6,7 +6,6 @@ import pytest from flask import Blueprint from flask import Flask from flask import request -from flask.helpers import async_to_sync pytest.importorskip("asgiref") @@ -136,5 +135,6 @@ def test_async_before_after_request(): @pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7") def test_async_runtime_error(): + app = Flask(__name__) with pytest.raises(RuntimeError): - async_to_sync(None) + app.async_to_sync(None) From e2b7d1056c6c8f6f26a4b9dfcc5dc4b2ef87f573 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 3 May 2021 07:02:42 -0700 Subject: [PATCH 10/99] release version 2.0.0rc2 --- src/flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flask/__init__.py b/src/flask/__init__.py index c15821ba..a2d92cd4 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "2.0.0rc1" +__version__ = "2.0.0rc2" From c93a2d76d5874f2cb175b7e91407dc6c877e0139 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 May 2021 17:21:04 +0000 Subject: [PATCH 11/99] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.13.0 → v2.14.0](https://github.com/asottile/pyupgrade/compare/v2.13.0...v2.14.0) - [github.com/psf/black: 21.4b0 → 21.4b2](https://github.com/psf/black/compare/21.4b0...21.4b2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af21506b..6c69d4a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.13.0 + rev: v2.14.0 hooks: - id: pyupgrade args: ["--py36-plus"] @@ -12,7 +12,7 @@ repos: files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 21.4b0 + rev: 21.4b2 hooks: - id: black - repo: https://github.com/PyCQA/flake8 From 95e5843ad51b025098ee33acbf273bafef26a1d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 20:25:42 +0000 Subject: [PATCH 12/99] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.14.0 → v2.15.0](https://github.com/asottile/pyupgrade/compare/v2.14.0...v2.15.0) - [github.com/psf/black: 21.4b2 → 21.5b1](https://github.com/psf/black/compare/21.4b2...21.5b1) - [github.com/PyCQA/flake8: 3.9.1 → 3.9.2](https://github.com/PyCQA/flake8/compare/3.9.1...3.9.2) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c69d4a2..130c0046 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.14.0 + rev: v2.15.0 hooks: - id: pyupgrade args: ["--py36-plus"] @@ -12,11 +12,11 @@ repos: files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 21.4b2 + rev: 21.5b1 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 3.9.1 + rev: 3.9.2 hooks: - id: flake8 additional_dependencies: From 79ac96f1239601eb12f6f69fcb3ce6cb269ac5fb Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 10 May 2021 22:45:42 -0700 Subject: [PATCH 13/99] show type hints in description --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 4b7ea13e..b26ba8cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,6 +20,7 @@ extensions = [ "sphinx_issues", "sphinx_tabs.tabs", ] +autodoc_typehints = "description" intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "werkzeug": ("https://werkzeug.palletsprojects.com/", None), From 531671c9c5177f24e79a3062e2dc5d0aa60c29af Mon Sep 17 00:00:00 2001 From: Grey Li Date: Thu, 6 May 2021 09:27:35 +0800 Subject: [PATCH 14/99] Improve the wording of using FLASK_APP --- docs/cli.rst | 10 +++++----- docs/quickstart.rst | 12 +++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index b390c96e..6036cb2d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -45,13 +45,13 @@ While ``FLASK_APP`` supports a variety of options for specifying your application, most use cases should be simple. Here are the typical values: (nothing) - The file :file:`wsgi.py` is imported, automatically detecting an app - (``app``). This provides an easy way to create an app from a factory with - extra arguments. + The name "app" or "wsgi" is imported (as a ".py" file, or package), + automatically detecting an app (``app`` or ``application``) or + factory (``create_app`` or ``make_app``). ``FLASK_APP=hello`` - The name is imported, automatically detecting an app (``app``) or factory - (``create_app``). + The given name is imported, automatically detecting an app (``app`` + or ``application``) or factory (``create_app`` or ``make_app``). ---- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 0aca7ffd..9418835a 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -50,7 +50,7 @@ to tell your terminal the application to work with by exporting the .. code-block:: text - $ export FLASK_APP=hello.py + $ export FLASK_APP=hello $ flask run * Running on http://127.0.0.1:5000/ @@ -58,7 +58,7 @@ to tell your terminal the application to work with by exporting the .. code-block:: text - > set FLASK_APP=hello.py + > set FLASK_APP=hello > flask run * Running on http://127.0.0.1:5000/ @@ -66,10 +66,16 @@ to tell your terminal the application to work with by exporting the .. code-block:: text - > $env:FLASK_APP = "hello.py" + > $env:FLASK_APP = "hello" > flask run * Running on http://127.0.0.1:5000/ +.. admonition:: Application Discovery Behavior + + As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you + don't have to set the ``FLASK_APP`` environment variable. See + :doc:`/cli` for more details. + This launches a very simple builtin server, which is good enough for testing but probably not what you want to use in production. For deployment options see :doc:`deploying/index`. From 8b72f6abd72b32225299c57c981180df35a89547 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 06:57:24 -0700 Subject: [PATCH 15/99] update pre-commit monthly --- .pre-commit-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 130c0046..d75f3c31 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,5 @@ +ci: + autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade rev: v2.15.0 From f8f0caf5c6e560b8e7fd322cbfd140c2bf7aa61b Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:28:21 -0700 Subject: [PATCH 16/99] update requirements --- requirements/dev.in | 1 + requirements/dev.txt | 42 ++++++++++++++++++++++++++--------------- requirements/docs.in | 2 +- requirements/docs.txt | 18 +++++++++++------- requirements/tests.txt | 6 +++--- requirements/typing.txt | 2 +- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/requirements/dev.in b/requirements/dev.in index c854000e..2588467c 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -1,5 +1,6 @@ -r docs.in -r tests.in +-r typing.in pip-tools pre-commit tox diff --git a/requirements/dev.txt b/requirements/dev.txt index 87466f4b..cc5355bf 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -10,9 +10,9 @@ appdirs==1.4.4 # via virtualenv asgiref==3.3.4 # via -r requirements/tests.in -attrs==20.3.0 +attrs==21.2.0 # via pytest -babel==2.9.0 +babel==2.9.1 # via sphinx blinker==1.4 # via -r requirements/tests.in @@ -22,19 +22,21 @@ cfgv==3.2.0 # via pre-commit chardet==4.0.0 # via requests -click==7.1.2 +click==8.0.0 # via pip-tools distlib==0.3.1 # via virtualenv docutils==0.16 - # via sphinx + # via + # sphinx + # sphinx-tabs filelock==3.0.12 # via # tox # virtualenv -greenlet==1.0.0 +greenlet==1.1.0 # via -r requirements/tests.in -identify==2.2.3 +identify==2.2.4 # via pre-commit idna==2.10 # via requests @@ -45,7 +47,13 @@ iniconfig==1.1.1 jinja2==2.11.3 # via sphinx markupsafe==1.1.1 - # via jinja2 + # via + # jinja2 + # sphinx +mypy-extensions==0.4.3 + # via mypy +mypy==0.812 + # via -r requirements/typing.in nodeenv==1.6.0 # via pre-commit packaging==20.9 @@ -54,7 +62,7 @@ packaging==20.9 # pytest # sphinx # tox -pallets-sphinx-themes==2.0.0rc1 +pallets-sphinx-themes==2.0.0 # via -r requirements/docs.in pep517==0.10.0 # via pip-tools @@ -70,13 +78,13 @@ py==1.10.0 # via # pytest # tox -pygments==2.8.1 +pygments==2.9.0 # via # sphinx # sphinx-tabs pyparsing==2.4.7 # via packaging -pytest==6.2.3 +pytest==6.2.4 # via -r requirements/tests.in python-dotenv==0.17.1 # via -r requirements/tests.in @@ -86,7 +94,7 @@ pyyaml==5.4.1 # via pre-commit requests==2.25.1 # via sphinx -six==1.15.0 +six==1.16.0 # via # tox # virtualenv @@ -94,9 +102,9 @@ snowballstemmer==2.1.0 # via sphinx sphinx-issues==1.2.0 # via -r requirements/docs.in -sphinx-tabs==2.1.0 +sphinx-tabs==3.0.0 # via -r requirements/docs.in -sphinx==3.5.4 +sphinx==4.0.1 # via # -r requirements/docs.in # pallets-sphinx-themes @@ -123,11 +131,15 @@ toml==0.10.2 # pre-commit # pytest # tox -tox==3.23.0 +tox==3.23.1 # via -r requirements/dev.in +typed-ast==1.4.3 + # via mypy +typing-extensions==3.10.0.0 + # via mypy urllib3==1.26.4 # via requests -virtualenv==20.4.3 +virtualenv==20.4.6 # via # pre-commit # tox diff --git a/requirements/docs.in b/requirements/docs.in index c1898bc7..3ee050af 100644 --- a/requirements/docs.in +++ b/requirements/docs.in @@ -1,4 +1,4 @@ -Pallets-Sphinx-Themes >= 2.0.0rc1 +Pallets-Sphinx-Themes Sphinx sphinx-issues sphinxcontrib-log-cabinet diff --git a/requirements/docs.txt b/requirements/docs.txt index 55682252..1c0acaf6 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -6,14 +6,16 @@ # alabaster==0.7.12 # via sphinx -babel==2.9.0 +babel==2.9.1 # via sphinx certifi==2020.12.5 # via requests chardet==4.0.0 # via requests docutils==0.16 - # via sphinx + # via + # sphinx + # sphinx-tabs idna==2.10 # via requests imagesize==1.2.0 @@ -21,14 +23,16 @@ imagesize==1.2.0 jinja2==2.11.3 # via sphinx markupsafe==1.1.1 - # via jinja2 + # via + # jinja2 + # sphinx packaging==20.9 # via # pallets-sphinx-themes # sphinx -pallets-sphinx-themes==2.0.0rc1 +pallets-sphinx-themes==2.0.0 # via -r requirements/docs.in -pygments==2.8.1 +pygments==2.9.0 # via # sphinx # sphinx-tabs @@ -42,9 +46,9 @@ snowballstemmer==2.1.0 # via sphinx sphinx-issues==1.2.0 # via -r requirements/docs.in -sphinx-tabs==2.1.0 +sphinx-tabs==3.0.0 # via -r requirements/docs.in -sphinx==3.5.4 +sphinx==4.0.1 # via # -r requirements/docs.in # pallets-sphinx-themes diff --git a/requirements/tests.txt b/requirements/tests.txt index 23f5cf49..deb93d62 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -6,11 +6,11 @@ # asgiref==3.3.4 # via -r requirements/tests.in -attrs==20.3.0 +attrs==21.2.0 # via pytest blinker==1.4 # via -r requirements/tests.in -greenlet==1.0.0 +greenlet==1.1.0 # via -r requirements/tests.in iniconfig==1.1.1 # via pytest @@ -22,7 +22,7 @@ py==1.10.0 # via pytest pyparsing==2.4.7 # via packaging -pytest==6.2.3 +pytest==6.2.4 # via -r requirements/tests.in python-dotenv==0.17.1 # via -r requirements/tests.in diff --git a/requirements/typing.txt b/requirements/typing.txt index 29e12e5e..0e342aaa 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -10,5 +10,5 @@ mypy==0.812 # via -r requirements/typing.in typed-ast==1.4.3 # via mypy -typing-extensions==3.7.4.3 +typing-extensions==3.10.0.0 # via mypy From 3a5532b4ed0db5cfd826e7fd2006ede6e873b028 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:28:49 -0700 Subject: [PATCH 17/99] update pallets projects minimum versions --- setup.py | 6 +++--- tox.ini | 11 +---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index bfd58e94..1b7013ae 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,9 @@ from setuptools import setup setup( name="Flask", install_requires=[ - "Werkzeug>=2.0.0rc4", - "Jinja2>=3.0.0rc1", - "itsdangerous>=2.0.0rc2", + "Werkzeug>=2.0", + "Jinja2>=3.0", + "itsdangerous>=2.0", "click>=7.1.2", ], extras_require={ diff --git a/tox.ini b/tox.ini index 9c772d38..c24ec0ff 100644 --- a/tox.ini +++ b/tox.ini @@ -11,12 +11,6 @@ skip_missing_interpreters = true deps = -r requirements/tests.txt - https://github.com/pallets/werkzeug/archive/master.tar.gz - https://github.com/pallets/markupsafe/archive/master.tar.gz - https://github.com/pallets/jinja/archive/master.tar.gz - https://github.com/pallets/itsdangerous/archive/master.tar.gz - - !click7: https://github.com/pallets/click/archive/master.tar.gz click7: click<8 examples/tutorial[test] @@ -33,8 +27,5 @@ deps = -r requirements/typing.txt commands = mypy [testenv:docs] -deps = - -r requirements/docs.txt - - https://github.com/pallets/werkzeug/archive/master.tar.gz +deps = -r requirements/docs.txt commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html From 1403d35e2a107e0fc693a09898cf605bb18989ad Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:41:45 -0700 Subject: [PATCH 18/99] update type annotations --- src/flask/app.py | 2 +- src/flask/json/__init__.py | 2 +- src/flask/templating.py | 13 ++++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/flask/app.py b/src/flask/app.py index 85306d7c..f8856a52 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -704,7 +704,7 @@ class Flask(Scaffold): session=session, g=g, ) - rv.policies["json.dumps_function"] = json.dumps # type: ignore + rv.policies["json.dumps_function"] = json.dumps return rv def create_global_jinja_loader(self) -> DispatchingJinjaLoader: diff --git a/src/flask/json/__init__.py b/src/flask/json/__init__.py index 5a6e4942..5780e204 100644 --- a/src/flask/json/__init__.py +++ b/src/flask/json/__init__.py @@ -5,7 +5,7 @@ import uuid import warnings from datetime import date -from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps # type: ignore +from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps from werkzeug.http import http_date from ..globals import current_app diff --git a/src/flask/templating.py b/src/flask/templating.py index 1987d9e9..bb3e7fd5 100644 --- a/src/flask/templating.py +++ b/src/flask/templating.py @@ -51,18 +51,21 @@ class DispatchingJinjaLoader(BaseLoader): def __init__(self, app: "Flask") -> None: self.app = app - def get_source( + def get_source( # type: ignore self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Callable]: + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: return self._get_source_explained(environment, template) return self._get_source_fast(environment, template) def _get_source_explained( self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Callable]: + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: attempts = [] - trv = None + rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]] + trv: t.Optional[ + t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] + ] = None for srcobj, loader in self._iter_loaders(template): try: @@ -83,7 +86,7 @@ class DispatchingJinjaLoader(BaseLoader): def _get_source_fast( self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Callable]: + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: for _srcobj, loader in self._iter_loaders(template): try: return loader.get_source(environment, template) From f8e63d39913f9a7bb887066025724569aa3423a6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:30:47 -0700 Subject: [PATCH 19/99] release version 2.0.0 --- CHANGES.rst | 2 +- src/flask/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 06e46425..0001bfe3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,7 @@ Version 2.0.0 ------------- -Unreleased +Released 2021-05-11 - Drop support for Python 2 and 3.5. - Bump minimum versions of other Pallets projects: Werkzeug >= 2, diff --git a/src/flask/__init__.py b/src/flask/__init__.py index a2d92cd4..2c7bfbe7 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "2.0.0rc2" +__version__ = "2.0.0" From 6fb1101f7075b4ff8197f3ef3b9fecfa41dc34ee Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:48:08 -0700 Subject: [PATCH 20/99] start version 2.0.1.dev0 --- CHANGES.rst | 6 ++++++ src/flask/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0001bfe3..241433ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,11 @@ .. currentmodule:: flask +Version 2.0.1 +------------- + +Unreleased + + Version 2.0.0 ------------- diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 2c7bfbe7..b53cf10f 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "2.0.0" +__version__ = "2.0.1.dev0" From 2846abaefed01f1da0d58ca3a83a2a09bea71e9c Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:48:48 -0700 Subject: [PATCH 21/99] start version 2.1.0.dev0 --- CHANGES.rst | 6 ++++++ src/flask/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 241433ef..da0a6098 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,11 @@ .. currentmodule:: flask +Version 2.1.0 +------------- + +Unreleased + + Version 2.0.1 ------------- diff --git a/src/flask/__init__.py b/src/flask/__init__.py index b53cf10f..5c54b9d3 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "2.0.1.dev0" +__version__ = "2.1.0.dev0" From 80a4e62096f4010235823180462b4d6e058d025a Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 14:50:16 -0700 Subject: [PATCH 22/99] update click minimum version --- CHANGES.rst | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index da0a6098..446d2963 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ Version 2.1.0 Unreleased +- Update Click dependency to >= 8.0. + Version 2.0.1 ------------- diff --git a/setup.py b/setup.py index 1b7013ae..e9629d34 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( "Werkzeug>=2.0", "Jinja2>=3.0", "itsdangerous>=2.0", - "click>=7.1.2", + "click>=8.0", ], extras_require={ "async": ["asgiref>=3.2"], From 52adf2ec219a0cb9abcfd116ec104c9a362c602a Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 11 May 2021 15:18:41 -0700 Subject: [PATCH 23/99] rename default branch in files --- .github/workflows/tests.yaml | 4 ++-- CONTRIBUTING.rst | 6 +++--- README.rst | 2 +- docs/conf.py | 2 +- examples/tutorial/README.rst | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e656dcf8..06338f6b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -2,7 +2,7 @@ name: Tests on: push: branches: - - master + - main - '*.x' paths-ignore: - 'docs/**' @@ -10,7 +10,7 @@ on: - '*.rst' pull_request: branches: - - master + - main - '*.x' paths-ignore: - 'docs/**' diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 01a01177..1ae1b72a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -143,15 +143,15 @@ Start coding .. code-block:: text $ git fetch origin - $ git checkout -b your-branch-name origin/1.1.x + $ git checkout -b your-branch-name origin/2.0.x If you're submitting a feature addition or change, branch off of the - "master" branch. + "main" branch. .. code-block:: text $ git fetch origin - $ git checkout -b your-branch-name origin/master + $ git checkout -b your-branch-name origin/main - Using your favorite editor, make your changes, `committing as you go`_. diff --git a/README.rst b/README.rst index 95f9bbae..3b1f2fbd 100644 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ Contributing For guidance on setting up a development environment and how to make a contribution to Flask, see the `contributing guidelines`_. -.. _contributing guidelines: https://github.com/pallets/flask/blob/master/CONTRIBUTING.rst +.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst Donate diff --git a/docs/conf.py b/docs/conf.py index b26ba8cb..bcea8858 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,7 +78,7 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None words = None if packaging.version.parse(release).is_devrelease: - url = f"{base_url}master/{text}" + url = f"{base_url}main/{text}" else: url = f"{base_url}{release}/{text}" diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index 7c7255f9..41f3f6ba 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -11,7 +11,7 @@ Install **Be sure to use the same version of the code as the version of the docs you're reading.** You probably want the latest tagged version, but the -default Git version is the master branch. :: +default Git version is the main branch. :: # clone the repository $ git clone https://github.com/pallets/flask @@ -35,7 +35,7 @@ Install Flaskr:: $ pip install -e . -Or if you are using the master branch, install Flask from source before +Or if you are using the main branch, install Flask from source before installing Flaskr:: $ pip install -e ../.. From 2ddbceeaa91b77cd6b837035c5697ac5b94fafab Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Wed, 12 May 2021 00:28:56 +0200 Subject: [PATCH 24/99] Install sphinx 4.1-dev from git Otherwise docs builds are broken due to sphinx-doc/sphinx#9216 --- requirements/dev.txt | 10 ++++------ requirements/docs.in | 2 +- requirements/docs.txt | 10 ++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index cc5355bf..4a2acbb7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -44,12 +44,10 @@ imagesize==1.2.0 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==2.11.3 +jinja2==3.0.0 # via sphinx -markupsafe==1.1.1 - # via - # jinja2 - # sphinx +markupsafe==2.0.0 + # via jinja2 mypy-extensions==0.4.3 # via mypy mypy==0.812 @@ -104,7 +102,7 @@ sphinx-issues==1.2.0 # via -r requirements/docs.in sphinx-tabs==3.0.0 # via -r requirements/docs.in -sphinx==4.0.1 +git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 # via # -r requirements/docs.in # pallets-sphinx-themes diff --git a/requirements/docs.in b/requirements/docs.in index 3ee050af..d3519043 100644 --- a/requirements/docs.in +++ b/requirements/docs.in @@ -1,5 +1,5 @@ Pallets-Sphinx-Themes -Sphinx +git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 # https://github.com/sphinx-doc/sphinx/issues/921 sphinx-issues sphinxcontrib-log-cabinet sphinx-tabs diff --git a/requirements/docs.txt b/requirements/docs.txt index 1c0acaf6..e99d842e 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -20,12 +20,10 @@ idna==2.10 # via requests imagesize==1.2.0 # via sphinx -jinja2==2.11.3 +jinja2==3.0.0 # via sphinx -markupsafe==1.1.1 - # via - # jinja2 - # sphinx +markupsafe==2.0.0 + # via jinja2 packaging==20.9 # via # pallets-sphinx-themes @@ -48,7 +46,7 @@ sphinx-issues==1.2.0 # via -r requirements/docs.in sphinx-tabs==3.0.0 # via -r requirements/docs.in -sphinx==4.0.1 +git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 # via # -r requirements/docs.in # pallets-sphinx-themes From 57b19fda540b596cd211ac864362485e09be203b Mon Sep 17 00:00:00 2001 From: Grey Li Date: Wed, 12 May 2021 12:58:23 +0800 Subject: [PATCH 25/99] Fix typo in the example of nesting bp docs --- docs/blueprints.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 6e8217ab..af368bac 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -127,8 +127,8 @@ It is possible to register a blueprint on another blueprint. .. code-block:: python - parent = Blueprint("parent", __name__, url_prefix="/parent") - child = Blueprint("child", __name__, url_prefix="/child) + parent = Blueprint('parent', __name__, url_prefix='/parent') + child = Blueprint('child', __name__, url_prefix='/child') parent.register_blueprint(child) app.register_blueprint(parent) From d575de5159a6e40944275763c9ada2801214058b Mon Sep 17 00:00:00 2001 From: Grey Li Date: Wed, 12 May 2021 12:58:23 +0800 Subject: [PATCH 26/99] Fix typo in the example of nesting bp docs --- docs/blueprints.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 6e8217ab..af368bac 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -127,8 +127,8 @@ It is possible to register a blueprint on another blueprint. .. code-block:: python - parent = Blueprint("parent", __name__, url_prefix="/parent") - child = Blueprint("child", __name__, url_prefix="/child) + parent = Blueprint('parent', __name__, url_prefix='/parent') + child = Blueprint('child', __name__, url_prefix='/child') parent.register_blueprint(child) app.register_blueprint(parent) From 2889ea4dd9bfee0d4b63a435c5a8d20337770f9c Mon Sep 17 00:00:00 2001 From: Andrew J Roth Date: Wed, 12 May 2021 09:14:34 -0400 Subject: [PATCH 27/99] Added Google Cloud Run as a Hosted option --- docs/deploying/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 4511bf45..e1ed9269 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -16,6 +16,7 @@ Hosted options - `Deploying Flask on Heroku `_ - `Deploying Flask on Google App Engine `_ +- `Deploying Flask on Google Cloud Run `_ - `Deploying Flask on AWS Elastic Beanstalk `_ - `Deploying on Azure (IIS) `_ - `Deploying on PythonAnywhere `_ From fc82dd50e39700b14799df17578e2497b8f0248c Mon Sep 17 00:00:00 2001 From: Grey Li Date: Wed, 12 May 2021 19:04:20 +0800 Subject: [PATCH 28/99] Re-add filename param for send_from_directory Add a deprecation warning for the old name --- CHANGES.rst | 4 ++++ src/flask/helpers.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 241433ef..030f742a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,10 @@ Version 2.0.1 Unreleased +- Re-add the ``filename`` parameter in ``send_from_directory``. The + ``filename`` parameter has been renamed to ``path``, the old name + is deprecated. :pr:`4019` + Version 2.0.0 ------------- diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 109f544f..262729aa 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -642,7 +642,9 @@ def safe_join(directory: str, *pathnames: str) -> str: return path -def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response": +def send_from_directory( + directory: str, path: str, filename: t.Optional[str] = None, **kwargs: t.Any +) -> "Response": """Send a file from within a directory using :func:`send_file`. .. code-block:: python @@ -666,12 +668,24 @@ def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response ``directory``. :param kwargs: Arguments to pass to :func:`send_file`. + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + .. versionadded:: 2.0 Moved the implementation to Werkzeug. This is now a wrapper to pass some Flask-specific arguments. .. versionadded:: 0.5 """ + if filename is not None: + warnings.warn( + "The 'filename' parameter has been renamed to 'path'. The" + " old name will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=2, + ) + path = filename + return werkzeug.utils.send_from_directory( # type: ignore directory, path, **_prepare_send_file_kwargs(**kwargs) ) From 14921608871f15f8c34d48026473293e225ad608 Mon Sep 17 00:00:00 2001 From: oleksis Date: Thu, 13 May 2021 03:54:41 -0400 Subject: [PATCH 29/99] Fix Fork link --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1ae1b72a..64c8e197 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -129,7 +129,7 @@ First time setup .. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git .. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address .. _GitHub account: https://github.com/join -.. _Fork: https://github.com/pallets/jinja/fork +.. _Fork: https://github.com/pallets/flask/fork .. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork From 89475e5d1e3e25ce56c9d9411496528f4a1ba82b Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 May 2021 12:47:49 -0700 Subject: [PATCH 30/99] mark top-level names as exported --- CHANGES.rst | 2 ++ src/flask/__init__.py | 82 +++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 030f742a..dbcc0667 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,8 @@ Unreleased - Re-add the ``filename`` parameter in ``send_from_directory``. The ``filename`` parameter has been renamed to ``path``, the old name is deprecated. :pr:`4019` +- Mark top-level names as exported so type checking understands + imports in user projects. :issue:`4024` Version 2.0.0 diff --git a/src/flask/__init__.py b/src/flask/__init__.py index b53cf10f..008e1a81 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -1,46 +1,46 @@ from markupsafe import escape from markupsafe import Markup -from werkzeug.exceptions import abort -from werkzeug.utils import redirect +from werkzeug.exceptions import abort as abort +from werkzeug.utils import redirect as redirect -from . import json -from .app import Flask -from .app import Request -from .app import Response -from .blueprints import Blueprint -from .config import Config -from .ctx import after_this_request -from .ctx import copy_current_request_context -from .ctx import has_app_context -from .ctx import has_request_context -from .globals import _app_ctx_stack -from .globals import _request_ctx_stack -from .globals import current_app -from .globals import g -from .globals import request -from .globals import session -from .helpers import flash -from .helpers import get_flashed_messages -from .helpers import get_template_attribute -from .helpers import make_response -from .helpers import safe_join -from .helpers import send_file -from .helpers import send_from_directory -from .helpers import stream_with_context -from .helpers import url_for -from .json import jsonify -from .signals import appcontext_popped -from .signals import appcontext_pushed -from .signals import appcontext_tearing_down -from .signals import before_render_template -from .signals import got_request_exception -from .signals import message_flashed -from .signals import request_finished -from .signals import request_started -from .signals import request_tearing_down -from .signals import signals_available -from .signals import template_rendered -from .templating import render_template -from .templating import render_template_string +from . import json as json +from .app import Flask as Flask +from .app import Request as Request +from .app import Response as Response +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import _app_ctx_stack as _app_ctx_stack +from .globals import _request_ctx_stack as _request_ctx_stack +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import flash as flash +from .helpers import get_flashed_messages as get_flashed_messages +from .helpers import get_template_attribute as get_template_attribute +from .helpers import make_response as make_response +from .helpers import safe_join as safe_join +from .helpers import send_file as send_file +from .helpers import send_from_directory as send_from_directory +from .helpers import stream_with_context as stream_with_context +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import signals_available as signals_available +from .signals import template_rendered as template_rendered +from .templating import render_template as render_template +from .templating import render_template_string as render_template_string __version__ = "2.0.1.dev0" From 2baaa8fd8ebdd0f7bf7a24bff4855ef53476c92d Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 11:32:19 -0700 Subject: [PATCH 31/99] fix annotation for g object --- src/flask/globals.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/flask/globals.py b/src/flask/globals.py index 5e6e8c75..6d91c75e 100644 --- a/src/flask/globals.py +++ b/src/flask/globals.py @@ -6,7 +6,7 @@ from werkzeug.local import LocalStack if t.TYPE_CHECKING: from .app import Flask - from .ctx import AppContext + from .ctx import _AppCtxGlobals from .sessions import SessionMixin from .wrappers import Request @@ -53,5 +53,7 @@ _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app: "Flask" = LocalProxy(_find_app) # type: ignore request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore -session: "SessionMixin" = LocalProxy(partial(_lookup_req_object, "session")) # type: ignore # noqa: B950 -g: "AppContext" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore +session: "SessionMixin" = LocalProxy( # type: ignore + partial(_lookup_req_object, "session") +) +g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore From 6fe7f45725dd0fc94fb3ce7ab3b3ff477b10401a Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 11:33:01 -0700 Subject: [PATCH 32/99] inform mypy that g has arbitrary attributes --- CHANGES.rst | 2 ++ src/flask/ctx.py | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index dbcc0667..2214dc49 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,8 @@ Unreleased is deprecated. :pr:`4019` - Mark top-level names as exported so type checking understands imports in user projects. :issue:`4024` +- Fix type annotation for ``g`` and inform mypy that it is a namespace + object that has arbitrary attributes. :issue:`4020` Version 2.0.0 diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 70de8cad..065edd5f 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -41,6 +41,24 @@ class _AppCtxGlobals: .. versionadded:: 0.10 """ + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any: """Get an attribute by name, or a default value. Like :meth:`dict.get`. @@ -78,10 +96,10 @@ class _AppCtxGlobals: """ return self.__dict__.setdefault(name, default) - def __contains__(self, item: t.Any) -> bool: + def __contains__(self, item: str) -> bool: return item in self.__dict__ - def __iter__(self) -> t.Iterator: + def __iter__(self) -> t.Iterator[str]: return iter(self.__dict__) def __repr__(self) -> str: From 25884c433f1eddf4537694d4c5f9f78cd9a14955 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 12:53:32 -0700 Subject: [PATCH 33/99] fix typing that wasn't available in Python 3.6.0 --- CHANGES.rst | 1 + src/flask/app.py | 3 ++- src/flask/sessions.py | 3 ++- src/flask/wrappers.py | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2214dc49..1c4a7558 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Unreleased imports in user projects. :issue:`4024` - Fix type annotation for ``g`` and inform mypy that it is a namespace object that has arbitrary attributes. :issue:`4020` +- Fix some types that weren't available in Python 3.6.0. :issue:`4040` Version 2.0.0 diff --git a/src/flask/app.py b/src/flask/app.py index f8856a52..f0f31486 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -72,6 +72,7 @@ from .wrappers import Request from .wrappers import Response if t.TYPE_CHECKING: + import typing_extensions as te from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner @@ -1441,7 +1442,7 @@ class Flask(Scaffold): f"Exception on {request.path} [{request.method}]", exc_info=exc_info ) - def raise_routing_exception(self, request: Request) -> t.NoReturn: + def raise_routing_exception(self, request: Request) -> "te.NoReturn": """Exceptions that are recording during routing are reraised with this method. During debug we are not reraising redirect requests for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising diff --git a/src/flask/sessions.py b/src/flask/sessions.py index 0e68e884..34b1d0ce 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -12,6 +12,7 @@ from .helpers import is_ip from .json.tag import TaggedJSONSerializer if t.TYPE_CHECKING: + import typing_extensions as te from .app import Flask from .wrappers import Request, Response @@ -92,7 +93,7 @@ class NullSession(SecureCookieSession): but fail on setting. """ - def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + def _fail(self, *args: t.Any, **kwargs: t.Any) -> "te.NoReturn": raise RuntimeError( "The session is unavailable because no secret " "key was set. Set the secret_key on the " diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index 48fcc34b..bfa9d7ce 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -8,6 +8,7 @@ from . import json from .globals import current_app if t.TYPE_CHECKING: + import typing_extensions as te from werkzeug.routing import Rule @@ -91,7 +92,7 @@ class Request(RequestBase): attach_enctype_error_multidict(self) - def on_json_loading_failed(self, e: Exception) -> t.NoReturn: + def on_json_loading_failed(self, e: Exception) -> "te.NoReturn": if current_app and current_app.debug: raise BadRequest(f"Failed to decode JSON object: {e}") From d5aadba4d34f6070377e88429b178ba08eea5349 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Wed, 12 May 2021 22:52:25 +0800 Subject: [PATCH 34/99] improve type hints for send_file and send_from_directory --- CHANGES.rst | 2 ++ src/flask/helpers.py | 50 +++++++++++++++++++++++++------------------ src/flask/scaffold.py | 2 +- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1c4a7558..d546dcab 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,8 @@ Unreleased - Fix type annotation for ``g`` and inform mypy that it is a namespace object that has arbitrary attributes. :issue:`4020` - Fix some types that weren't available in Python 3.6.0. :issue:`4040` +- Improve typing for ``send_file``, ``send_from_directory``, and + ``get_send_file_max_age``. :issue:`4044`, :pr:`4026` Version 2.0.0 diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 262729aa..585b4dea 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -4,6 +4,7 @@ import socket import sys import typing as t import warnings +from datetime import datetime from datetime import timedelta from functools import update_wrapper from threading import RLock @@ -436,14 +437,16 @@ def get_flashed_messages( def _prepare_send_file_kwargs( - download_name=None, - attachment_filename=None, - etag=None, - add_etags=None, - max_age=None, - cache_timeout=None, - **kwargs, -): + download_name: t.Optional[str] = None, + attachment_filename: t.Optional[str] = None, + etag: t.Optional[t.Union[bool, str]] = None, + add_etags: t.Optional[t.Union[bool]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = None, + cache_timeout: t.Optional[int] = None, + **kwargs: t.Any, +) -> t.Dict[str, t.Any]: if attachment_filename is not None: warnings.warn( "The 'attachment_filename' parameter has been renamed to" @@ -482,23 +485,25 @@ def _prepare_send_file_kwargs( max_age=max_age, use_x_sendfile=current_app.use_x_sendfile, response_class=current_app.response_class, - _root_path=current_app.root_path, + _root_path=current_app.root_path, # type: ignore ) return kwargs def send_file( - path_or_file, - mimetype=None, - as_attachment=False, - download_name=None, - attachment_filename=None, - conditional=True, - etag=True, - add_etags=None, - last_modified=None, - max_age=None, - cache_timeout=None, + path_or_file: t.Union[os.PathLike, str, t.BinaryIO], + mimetype: t.Optional[str] = None, + as_attachment: bool = False, + download_name: t.Optional[str] = None, + attachment_filename: t.Optional[str] = None, + conditional: bool = True, + etag: t.Union[bool, str] = True, + add_etags: t.Optional[bool] = None, + last_modified: t.Optional[t.Union[datetime, int, float]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = None, + cache_timeout: t.Optional[int] = None, ): """Send the contents of a file to the client. @@ -643,7 +648,10 @@ def safe_join(directory: str, *pathnames: str) -> str: def send_from_directory( - directory: str, path: str, filename: t.Optional[str] = None, **kwargs: t.Any + directory: t.Union[os.PathLike, str], + path: t.Union[os.PathLike, str], + filename: t.Optional[str] = None, + **kwargs: t.Any, ) -> "Response": """Send a file from within a directory using :func:`send_file`. diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 56d37ddd..f50c9b1b 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -288,7 +288,7 @@ class Scaffold: self._static_url_path = value - def get_send_file_max_age(self, filename: str) -> t.Optional[int]: + def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]: """Used by :func:`send_file` to determine the ``max_age`` cache value for a given file path if it wasn't passed. From 7c5261407dae61679c268b44c63d73909c3fe652 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 14:31:50 -0700 Subject: [PATCH 35/99] blueprint name may not contain a dot --- CHANGES.rst | 3 ++ src/flask/blueprints.py | 16 +++++--- tests/test_basic.py | 6 +-- tests/test_blueprints.py | 88 ++++++---------------------------------- 4 files changed, 28 insertions(+), 85 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d546dcab..c2a42443 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,9 @@ Unreleased - Fix some types that weren't available in Python 3.6.0. :issue:`4040` - Improve typing for ``send_file``, ``send_from_directory``, and ``get_send_file_max_age``. :issue:`4044`, :pr:`4026` +- Show an error when a blueprint name contains a dot. The ``.`` has + special meaning, it is used to separate (nested) blueprint names and + the endpoint name. :issue:`4041` Version 2.0.0 diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 5fb84d86..823995f6 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -188,6 +188,10 @@ class Blueprint(Scaffold): template_folder=template_folder, root_path=root_path, ) + + if "." in name: + raise ValueError("'name' may not contain a dot '.' character.") + self.name = name self.url_prefix = url_prefix self.subdomain = subdomain @@ -360,12 +364,12 @@ class Blueprint(Scaffold): """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ - if endpoint: - assert "." not in endpoint, "Blueprint endpoints should not contain dots" - if view_func and hasattr(view_func, "__name__"): - assert ( - "." not in view_func.__name__ - ), "Blueprint view function name should not contain dots" + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options)) def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable: diff --git a/tests/test_basic.py b/tests/test_basic.py index d6ec3fe4..7bdeba1e 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1631,7 +1631,7 @@ def test_url_processors(app, client): def test_inject_blueprint_url_defaults(app): - bp = flask.Blueprint("foo.bar.baz", __name__, template_folder="template") + bp = flask.Blueprint("foo", __name__, template_folder="template") @bp.url_defaults def bp_defaults(endpoint, values): @@ -1644,12 +1644,12 @@ def test_inject_blueprint_url_defaults(app): app.register_blueprint(bp) values = dict() - app.inject_url_defaults("foo.bar.baz.view", values) + app.inject_url_defaults("foo.view", values) expected = dict(page="login") assert values == expected with app.test_request_context("/somepage"): - url = flask.url_for("foo.bar.baz.view") + url = flask.url_for("foo.view") expected = "/login" assert url == expected diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index b986ca02..cfcb43b3 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -1,5 +1,3 @@ -import functools - import pytest from jinja2 import TemplateNotFound from werkzeug.http import parse_cache_control_header @@ -253,28 +251,9 @@ def test_templates_list(test_apps): assert templates == ["admin/index.html", "frontend/index.html"] -def test_dotted_names(app, client): - frontend = flask.Blueprint("myapp.frontend", __name__) - backend = flask.Blueprint("myapp.backend", __name__) - - @frontend.route("/fe") - def frontend_index(): - return flask.url_for("myapp.backend.backend_index") - - @frontend.route("/fe2") - def frontend_page2(): - return flask.url_for(".frontend_index") - - @backend.route("/be") - def backend_index(): - return flask.url_for("myapp.frontend.frontend_index") - - app.register_blueprint(frontend) - app.register_blueprint(backend) - - assert client.get("/fe").data.strip() == b"/be" - assert client.get("/fe2").data.strip() == b"/fe" - assert client.get("/be").data.strip() == b"/fe" +def test_dotted_name_not_allowed(app, client): + with pytest.raises(ValueError): + flask.Blueprint("app.ui", __name__) def test_dotted_names_from_app(app, client): @@ -343,62 +322,19 @@ def test_route_decorator_custom_endpoint(app, client): def test_route_decorator_custom_endpoint_with_dots(app, client): bp = flask.Blueprint("bp", __name__) - @bp.route("/foo") - def foo(): - return flask.request.endpoint + with pytest.raises(ValueError): + bp.route("/", endpoint="a.b")(lambda: "") - try: + with pytest.raises(ValueError): + bp.add_url_rule("/", endpoint="a.b") - @bp.route("/bar", endpoint="bar.bar") - def foo_bar(): - return flask.request.endpoint + def view(): + return "" - except AssertionError: - pass - else: - raise AssertionError("expected AssertionError not raised") + view.__name__ = "a.b" - try: - - @bp.route("/bar/123", endpoint="bar.123") - def foo_bar_foo(): - return flask.request.endpoint - - except AssertionError: - pass - else: - raise AssertionError("expected AssertionError not raised") - - def foo_foo_foo(): - pass - - pytest.raises( - AssertionError, - lambda: bp.add_url_rule("/bar/123", endpoint="bar.123", view_func=foo_foo_foo), - ) - - pytest.raises( - AssertionError, bp.route("/bar/123", endpoint="bar.123"), lambda: None - ) - - foo_foo_foo.__name__ = "bar.123" - - pytest.raises( - AssertionError, lambda: bp.add_url_rule("/bar/123", view_func=foo_foo_foo) - ) - - bp.add_url_rule( - "/bar/456", endpoint="foofoofoo", view_func=functools.partial(foo_foo_foo) - ) - - app.register_blueprint(bp, url_prefix="/py") - - assert client.get("/py/foo").data == b"bp.foo" - # The rule's didn't actually made it through - rv = client.get("/py/bar") - assert rv.status_code == 404 - rv = client.get("/py/bar/123") - assert rv.status_code == 404 + with pytest.raises(ValueError): + bp.add_url_rule("/", view_func=view) def test_endpoint_decorator(app, client): From e93704fbfd5f40e48f8fe9034b6b0fe420d28fb3 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Thu, 13 May 2021 18:26:24 +0800 Subject: [PATCH 36/99] fix url_prefix argument when nesting blueprints --- CHANGES.rst | 2 ++ src/flask/blueprints.py | 16 ++++++++++------ tests/test_blueprints.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c2a42443..db4eff41 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,8 @@ Unreleased - Show an error when a blueprint name contains a dot. The ``.`` has special meaning, it is used to separate (nested) blueprint names and the endpoint name. :issue:`4041` +- Combine URL prefixes when nesting blueprints that were created with + a ``url_prefix`` value. :issue:`4037` Version 2.0.0 diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 823995f6..7bfef84b 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -260,7 +260,7 @@ class Blueprint(Scaffold): """Called by :meth:`Flask.register_blueprint` to register all views and callbacks registered on the blueprint with the application. Creates a :class:`.BlueprintSetupState` and calls - each :meth:`record` callbackwith it. + each :meth:`record` callback with it. :param app: The application this blueprint is being registered with. @@ -344,13 +344,17 @@ class Blueprint(Scaffold): app.cli.add_command(self.cli) for blueprint, bp_options in self._blueprints: - url_prefix = options.get("url_prefix", "") - if "url_prefix" in bp_options: - url_prefix = ( - url_prefix.rstrip("/") + "/" + bp_options["url_prefix"].lstrip("/") + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") ) - bp_options["url_prefix"] = url_prefix bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "." blueprint.register(app, bp_options) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index cfcb43b3..e7724519 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -835,3 +835,36 @@ def test_nested_blueprint(app, client): assert client.get("/parent/no").data == b"Parent no" assert client.get("/parent/child/no").data == b"Parent no" assert client.get("/parent/child/grandchild/no").data == b"Grandchild no" + + +def test_nested_blueprint_url_prefix(app, client): + parent = flask.Blueprint("parent", __name__, url_prefix="/parent") + child = flask.Blueprint("child", __name__, url_prefix="/child") + grandchild = flask.Blueprint("grandchild", __name__, url_prefix="/grandchild") + apple = flask.Blueprint("apple", __name__, url_prefix="/apple") + + @parent.route("/") + def parent_index(): + return "Parent" + + @child.route("/") + def child_index(): + return "Child" + + @grandchild.route("/") + def grandchild_index(): + return "Grandchild" + + @apple.route("/") + def apple_index(): + return "Apple" + + child.register_blueprint(grandchild) + child.register_blueprint(apple, url_prefix="/orange") # test overwrite + parent.register_blueprint(child) + app.register_blueprint(parent) + + assert client.get("/parent/").data == b"Parent" + assert client.get("/parent/child/").data == b"Child" + assert client.get("/parent/child/grandchild/").data == b"Grandchild" + assert client.get("/parent/child/orange/").data == b"Apple" From 6d8b4ce9d037937ef8694067ab5754666240d097 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 16:16:04 -0700 Subject: [PATCH 37/99] set maximum versions of pallets dependencies --- CHANGES.rst | 10 ++++++---- setup.py | 8 ++++---- src/flask/__init__.py | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7fa83f76..510a5475 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,13 +2,15 @@ -Version 1.1.x +Version 1.1.3 ------------- -Not yet released. +Unreleased -- Officially support passing a :class:`pathlib.Path` for - ``static_folder`` which stopped working in 1.1.2. :pr:`3579` +- Set maximum versions of Werkzeug, Jinja, Click, and ItsDangerous. + :issue:`4043` +- Re-add support for passing a ``pathlib.Path`` for ``static_folder``. + :pr:`3579` Version 1.1.2 diff --git a/setup.py b/setup.py index 71c2338e..33ab0b21 100644 --- a/setup.py +++ b/setup.py @@ -53,10 +53,10 @@ setup( include_package_data=True, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", install_requires=[ - "Werkzeug>=0.15", - "Jinja2>=2.10.1", - "itsdangerous>=0.24", - "click>=5.1", + "Werkzeug >= 0.15, < 2.0", + "Jinja2 >= 2.10.1, < 3.0", + "itsdangerous >= 0.24, < 2.0", + "click >= 5.1, < 8.0", ], extras_require={ "dotenv": ["python-dotenv"], diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 1a487e18..97fdfaa9 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -57,4 +57,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "1.1.2" +__version__ = "1.1.3.dev0" From 661bbcdb90ee82baded66ae0733cb87ea5f464ef Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 16:22:57 -0700 Subject: [PATCH 38/99] release version 1.1.3 --- CHANGES.rst | 2 +- src/flask/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 510a5475..d7bc1bcc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Version 1.1.3 ------------- -Unreleased +Released 2021-05-13 - Set maximum versions of Werkzeug, Jinja, Click, and ItsDangerous. :issue:`4043` diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 97fdfaa9..f6da4f9b 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -57,4 +57,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "1.1.3.dev0" +__version__ = "1.1.3" From 3ace642ef32eae3fa096bef85a87545eba3f53fc Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 May 2021 20:19:02 -0400 Subject: [PATCH 39/99] Use compat fspath instead of os.fspath When 7ba35c4 was cherry-picked it introduced the usage of os.fspath which is not supported on Python <3.6 --- CHANGES.rst | 7 +++++++ src/flask/__init__.py | 2 +- src/flask/helpers.py | 2 +- tests/test_basic.py | 1 - 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d7bc1bcc..9c5735a3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,12 @@ .. currentmodule:: flask +Version 1.1.4 +------------- + +Unreleased + +- Update ``static_folder`` to use ``_compat.fspath`` instead of + ``os.fspath`` to continue supporting Python <3.6 :issue:`4050` Version 1.1.3 diff --git a/src/flask/__init__.py b/src/flask/__init__.py index f6da4f9b..72bf6abf 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -57,4 +57,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "1.1.3" +__version__ = "1.1.4.dev0" diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 3e64e3fa..c396b8b1 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -1001,7 +1001,7 @@ class _PackageBoundObject(object): @static_folder.setter def static_folder(self, value): if value is not None: - value = os.fspath(value).rstrip(r"\/") + value = fspath(value).rstrip(r"\/") self._static_folder = value @property diff --git a/tests/test_basic.py b/tests/test_basic.py index 58cbe3b7..8cc69182 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1425,7 +1425,6 @@ def test_static_url_empty_path_default(app): rv.close() -@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python >= 3.6") def test_static_folder_with_pathlib_path(app): from pathlib import Path From 64a5d7a018aadcb5d7d868a782a04d1b599ef89b Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 18:22:56 -0700 Subject: [PATCH 40/99] release version 1.1.4 --- CHANGES.rst | 4 ++-- src/flask/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9c5735a3..3fed126f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,10 +3,10 @@ Version 1.1.4 ------------- -Unreleased +Released 2021-05-13 - Update ``static_folder`` to use ``_compat.fspath`` instead of - ``os.fspath`` to continue supporting Python <3.6 :issue:`4050` + ``os.fspath`` to continue supporting Python < 3.6 :issue:`4050` Version 1.1.3 diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 72bf6abf..f1f6466b 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -57,4 +57,4 @@ from .signals import template_rendered from .templating import render_template from .templating import render_template_string -__version__ = "1.1.4.dev0" +__version__ = "1.1.4" From a7b02b3a07ae292ce376df2459eae3ffb4c06d52 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 14 May 2021 08:11:09 -0700 Subject: [PATCH 41/99] converters have access to session --- CHANGES.rst | 3 +++ src/flask/ctx.py | 8 +++++--- tests/test_converters.py | 8 +++++--- tests/test_session_interface.py | 25 +++++++++++++++---------- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d8a53135..fa24b104 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,9 @@ Unreleased the endpoint name. :issue:`4041` - Combine URL prefixes when nesting blueprints that were created with a ``url_prefix`` value. :issue:`4037` +- Roll back a change to the order that URL matching was done. The + URL is again matched after the session is loaded, so the session is + available in custom URL converters. :issue:`4053` Version 2.0.0 diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 065edd5f..5c064635 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -395,9 +395,6 @@ class RequestContext: _request_ctx_stack.push(self) - if self.url_adapter is not None: - self.match_request() - # Open the session at the moment that the request context is available. # This allows a custom open_session method to use the request context. # Only open a new session if this is the first time the request was @@ -409,6 +406,11 @@ class RequestContext: if self.session is None: self.session = session_interface.make_null_session(self.app) + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the diff --git a/tests/test_converters.py b/tests/test_converters.py index 14c92afd..d94a7658 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -1,6 +1,7 @@ from werkzeug.routing import BaseConverter -from flask import has_request_context +from flask import request +from flask import session from flask import url_for @@ -28,12 +29,13 @@ def test_custom_converters(app, client): def test_context_available(app, client): class ContextConverter(BaseConverter): def to_python(self, value): - assert has_request_context() + assert request is not None + assert session is not None return value app.url_map.converters["ctx"] = ContextConverter - @app.route("/") + @app.get("/") def index(name): return name diff --git a/tests/test_session_interface.py b/tests/test_session_interface.py index aa9ecafa..39562f5a 100644 --- a/tests/test_session_interface.py +++ b/tests/test_session_interface.py @@ -2,21 +2,26 @@ import flask from flask.sessions import SessionInterface -def test_open_session_endpoint_not_none(): - # Define a session interface that breaks if request.endpoint is None +def test_open_session_with_endpoint(): + """If request.endpoint (or other URL matching behavior) is needed + while loading the session, RequestContext.match_request() can be + called manually. + """ + class MySessionInterface(SessionInterface): - def save_session(self): + def save_session(self, app, session, response): pass - def open_session(self, _, request): + def open_session(self, app, request): + flask._request_ctx_stack.top.match_request() assert request.endpoint is not None - def index(): - return "Hello World!" - - # Confirm a 200 response, indicating that request.endpoint was NOT None app = flask.Flask(__name__) - app.route("/")(index) app.session_interface = MySessionInterface() - response = app.test_client().open("/") + + @app.get("/") + def index(): + return "Hello, World!" + + response = app.test_client().get("/") assert response.status_code == 200 From 9c186ccfe8fa4156123e9252a6e8a9cc68bfecab Mon Sep 17 00:00:00 2001 From: Rafael Aviles Date: Fri, 14 May 2021 13:23:34 -0700 Subject: [PATCH 42/99] Use escape function in return statement for username and subpath. --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9418835a..5cd59f41 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -246,7 +246,7 @@ of the argument like ````. :: @app.route('/user/') def show_user_profile(username): # show the user profile for that user - return f'User {username}' + return f'User {escape(username)}' @app.route('/post/') def show_post(post_id): @@ -256,7 +256,7 @@ of the argument like ````. :: @app.route('/path/') def show_subpath(subpath): # show the subpath after /path/ - return f'Subpath {subpath}' + return f'Subpath {escape(subpath)}' Converter types: From 6fbdeb80c76b8a698ab366d21ccd786731b17361 Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 16 May 2021 19:32:29 +0100 Subject: [PATCH 43/99] Fix nested blueprint url_prefix This fixes the case where the blueprint is registered with a url_prefix but any child blueprints have no prefixes. --- src/flask/blueprints.py | 2 ++ tests/test_blueprints.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 7bfef84b..39396ce7 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -354,6 +354,8 @@ class Blueprint(Scaffold): bp_options["url_prefix"] = ( state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") ) + else: + bp_options["url_prefix"] = state.url_prefix bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "." blueprint.register(app, bp_options) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index e7724519..0bae5333 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -868,3 +868,17 @@ def test_nested_blueprint_url_prefix(app, client): assert client.get("/parent/child/").data == b"Child" assert client.get("/parent/child/grandchild/").data == b"Grandchild" assert client.get("/parent/child/orange/").data == b"Apple" + + +def test_nested_blueprint_url_prefix_only_parent_prefix(app, client): + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @child.route("/child-endpoint") + def child_index(): + return "Child" + + parent.register_blueprint(child) + app.register_blueprint(parent, url_prefix="/parent") + + assert client.get("/parent/child-endpoint").data == b"Child" From 1b5f21e015e2c4f36fcdb34a09ce93787d59e181 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Sun, 16 May 2021 22:34:32 -0400 Subject: [PATCH 44/99] Add pathlib in cli tests --- tests/test_cli.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index c3a00f17..ebf8d1f5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,6 +5,7 @@ import ssl import sys import types from functools import partial +from pathlib import Path from unittest.mock import patch import click @@ -29,8 +30,8 @@ from flask.cli import run_command from flask.cli import ScriptInfo from flask.cli import with_appcontext -cwd = os.getcwd() -test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps")) +cwd = Path.cwd() +test_path = (Path(__file__) / ".." / "test_apps").resolve() @pytest.fixture @@ -152,29 +153,25 @@ def test_find_best_app(test_apps): ( ("test", cwd, "test"), ("test.py", cwd, "test"), - ("a/test", os.path.join(cwd, "a"), "test"), + ("a/test", cwd / "a", "test"), ("test/__init__.py", cwd, "test"), ("test/__init__", cwd, "test"), # nested package ( - os.path.join(test_path, "cliapp", "inner1", "__init__"), + test_path / "cliapp" / "inner1" / "__init__", test_path, "cliapp.inner1", ), ( - os.path.join(test_path, "cliapp", "inner1", "inner2"), + test_path / "cliapp" / "inner1" / "inner2", test_path, "cliapp.inner1.inner2", ), # dotted name ("test.a.b", cwd, "test.a.b"), - (os.path.join(test_path, "cliapp.app"), test_path, "cliapp.app"), + (test_path / "cliapp.app", test_path, "cliapp.app"), # not a Python file, will be caught during import - ( - os.path.join(test_path, "cliapp", "message.txt"), - test_path, - "cliapp.message.txt", - ), + (test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"), ), ) def test_prepare_import(request, value, path, result): @@ -193,7 +190,7 @@ def test_prepare_import(request, value, path, result): request.addfinalizer(reset_path) assert prepare_import(value) == result - assert sys.path[0] == path + assert sys.path[0] == str(path) @pytest.mark.parametrize( @@ -278,9 +275,8 @@ def test_scriptinfo(test_apps, monkeypatch): assert obj.load_app() is app # import app with module's absolute path - cli_app_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), "test_apps", "cliapp", "app.py") - ) + cli_app_path = str(test_path / "cliapp" / "app.py") + obj = ScriptInfo(app_import_path=cli_app_path) app = obj.load_app() assert app.name == "testapp" @@ -302,19 +298,13 @@ def test_scriptinfo(test_apps, monkeypatch): pytest.raises(NoAppException, obj.load_app) # import app from wsgi.py in current directory - monkeypatch.chdir( - os.path.abspath( - os.path.join(os.path.dirname(__file__), "test_apps", "helloworld") - ) - ) + monkeypatch.chdir(test_path / "helloworld") obj = ScriptInfo() app = obj.load_app() assert app.name == "hello" # import app from app.py in current directory - monkeypatch.chdir( - os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps", "cliapp")) - ) + monkeypatch.chdir(test_path / "cliapp") obj = ScriptInfo() app = obj.load_app() assert app.name == "testapp" @@ -513,7 +503,7 @@ def test_load_dotenv(monkeypatch): monkeypatch.setenv("EGGS", "3") monkeypatch.chdir(test_path) assert load_dotenv() - assert os.getcwd() == test_path + assert Path.cwd() == test_path # .flaskenv doesn't overwrite .env assert os.environ["FOO"] == "env" # set only in .flaskenv @@ -533,9 +523,8 @@ def test_dotenv_path(monkeypatch): for item in ("FOO", "BAR", "EGGS"): monkeypatch._setitem.append((os.environ, item, notset)) - cwd = os.getcwd() - load_dotenv(os.path.join(test_path, ".flaskenv")) - assert os.getcwd() == cwd + load_dotenv(test_path / ".flaskenv") + assert Path.cwd() == cwd assert "FOO" in os.environ From afc907fd0d4a4e96398829d75ded9711a3046070 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 17 May 2021 16:03:12 -0700 Subject: [PATCH 45/99] use _typeshed.wsgi instead of wsgiref.types --- src/flask/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flask/typing.py b/src/flask/typing.py index 9a664e41..5f27308c 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -2,8 +2,8 @@ import typing as t if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication # noqa: F401 from werkzeug.datastructures import Headers # noqa: F401 - from wsgiref.types import WSGIApplication # noqa: F401 from .wrappers import Response # noqa: F401 # The possible types that are directly convertible or are a Response object. From 10425fb9b13d977128f18c1051fb93edc9aee2de Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 12:35:43 -0700 Subject: [PATCH 46/99] re-add deprecated Config.from_json method --- CHANGES.rst | 2 ++ src/flask/config.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fa24b104..0b5a939d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,6 +23,8 @@ Unreleased - Roll back a change to the order that URL matching was done. The URL is again matched after the session is loaded, so the session is available in custom URL converters. :issue:`4053` +- Re-add deprecated ``Config.from_json``, which was accidentally + removed early. :issue:`4078` Version 2.0.0 diff --git a/src/flask/config.py b/src/flask/config.py index 86f21dc8..c79a558e 100644 --- a/src/flask/config.py +++ b/src/flask/config.py @@ -202,6 +202,31 @@ class Config(dict): return self.from_mapping(obj) + def from_json(self, filename: str, silent: bool = False) -> bool: + """Update the values in the config from a JSON file. The loaded + data is passed to the :meth:`from_mapping` method. + + :param filename: The path to the JSON file. This can be an + absolute path or relative to the config root path. + :param silent: Ignore the file if it doesn't exist. + + .. deprecated:: 2.0.0 + Will be removed in Flask 2.1. Use :meth:`from_file` instead. + This was removed early in 2.0.0, was added back in 2.0.1. + + .. versionadded:: 0.11 + """ + import warnings + from . import json + + warnings.warn( + "'from_json' is deprecated and will be removed in Flask" + " 2.1. Use 'from_file(path, json.load)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.from_file(filename, json.load, silent=silent) + def from_mapping( self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any ) -> bool: From cfda27089932a102953da73fdab17cf9814334e4 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 21:03:02 -0700 Subject: [PATCH 47/99] update pallets-sphinx-themes --- docs/conf.py | 6 +++--- requirements/dev.txt | 2 +- requirements/docs.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index bcea8858..ae2922d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,10 +49,10 @@ html_context = { ] } html_sidebars = { - "index": ["project.html", "localtoc.html", "searchbox.html"], - "**": ["localtoc.html", "relations.html", "searchbox.html"], + "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], + "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], } -singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]} +singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} html_static_path = ["_static"] html_favicon = "_static/flask-icon.png" html_logo = "_static/flask-icon.png" diff --git a/requirements/dev.txt b/requirements/dev.txt index 4a2acbb7..232601f0 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -60,7 +60,7 @@ packaging==20.9 # pytest # sphinx # tox -pallets-sphinx-themes==2.0.0 +pallets-sphinx-themes==2.0.1 # via -r requirements/docs.in pep517==0.10.0 # via pip-tools diff --git a/requirements/docs.txt b/requirements/docs.txt index e99d842e..daf4530b 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -28,7 +28,7 @@ packaging==20.9 # via # pallets-sphinx-themes # sphinx -pallets-sphinx-themes==2.0.0 +pallets-sphinx-themes==2.0.1 # via -r requirements/docs.in pygments==2.9.0 # via From 9889ca8bcabbb1b1290ca4403b0242e01bae4c47 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 21:03:33 -0700 Subject: [PATCH 48/99] update pallets and sphinx requirements --- requirements/dev.txt | 10 +++++----- requirements/docs.in | 2 +- requirements/docs.txt | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 232601f0..1b6998cf 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -18,11 +18,11 @@ blinker==1.4 # via -r requirements/tests.in certifi==2020.12.5 # via requests -cfgv==3.2.0 +cfgv==3.3.0 # via pre-commit chardet==4.0.0 # via requests -click==8.0.0 +click==8.0.1 # via pip-tools distlib==0.3.1 # via virtualenv @@ -44,9 +44,9 @@ imagesize==1.2.0 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.0.0 +jinja2==3.0.1 # via sphinx -markupsafe==2.0.0 +markupsafe==2.0.1 # via jinja2 mypy-extensions==0.4.3 # via mypy @@ -102,7 +102,7 @@ sphinx-issues==1.2.0 # via -r requirements/docs.in sphinx-tabs==3.0.0 # via -r requirements/docs.in -git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 +sphinx==4.0.2 # via # -r requirements/docs.in # pallets-sphinx-themes diff --git a/requirements/docs.in b/requirements/docs.in index d3519043..3ee050af 100644 --- a/requirements/docs.in +++ b/requirements/docs.in @@ -1,5 +1,5 @@ Pallets-Sphinx-Themes -git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 # https://github.com/sphinx-doc/sphinx/issues/921 +Sphinx sphinx-issues sphinxcontrib-log-cabinet sphinx-tabs diff --git a/requirements/docs.txt b/requirements/docs.txt index daf4530b..245f04b6 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -20,9 +20,9 @@ idna==2.10 # via requests imagesize==1.2.0 # via sphinx -jinja2==3.0.0 +jinja2==3.0.1 # via sphinx -markupsafe==2.0.0 +markupsafe==2.0.1 # via jinja2 packaging==20.9 # via @@ -46,7 +46,7 @@ sphinx-issues==1.2.0 # via -r requirements/docs.in sphinx-tabs==3.0.0 # via -r requirements/docs.in -git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 +sphinx==4.0.2 # via # -r requirements/docs.in # pallets-sphinx-themes From 0d594b8c0f13c70507aa61a7666c844c5e2aeda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleksis=20Fraga=20Men=C3=A9ndez?= <44526468+oleksis@users.noreply.github.com> Date: Fri, 21 May 2021 00:33:09 -0400 Subject: [PATCH 49/99] Add update pip and setuptools section (#4061) * Add update pip and setuptools section * Simplify the command to upgrade pip Co-authored-by: Grey Li --- CONTRIBUTING.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 64c8e197..3a9177a4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -112,6 +112,12 @@ First time setup > py -3 -m venv env > env\Scripts\activate +- Upgrade pip and setuptools. + + .. code-block:: text + + $ python -m pip install --upgrade pip setuptools + - Install the development dependencies, then install Flask in editable mode. From bf982718cf30615f2dd17fa71fe17d565ebb1d3e Mon Sep 17 00:00:00 2001 From: Alex Hedges Date: Sat, 15 May 2021 17:38:25 -0400 Subject: [PATCH 50/99] Make add_url_rule() signature consistent This caused a mypy error when I was making another typing improvement, so I am fixing it before committing my other changes. --- src/flask/blueprints.py | 11 ++++++++++- src/flask/scaffold.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 39396ce7..098eca8c 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -365,6 +365,7 @@ class Blueprint(Scaffold): rule: str, endpoint: t.Optional[str] = None, view_func: t.Optional[t.Callable] = None, + provide_automatic_options: t.Optional[bool] = None, **options: t.Any, ) -> None: """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for @@ -376,7 +377,15 @@ class Blueprint(Scaffold): if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: raise ValueError("'view_func' name may not contain a dot '.' character.") - self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options)) + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable: """Register a custom template filter, available application wide. Like diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index f50c9b1b..20654b6b 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -443,7 +443,7 @@ class Scaffold: view_func: t.Optional[t.Callable] = None, provide_automatic_options: t.Optional[bool] = None, **options: t.Any, - ) -> t.Callable: + ) -> None: """Register a rule for routing incoming requests and building URLs. The :meth:`route` decorator is a shortcut to call this with the ``view_func`` argument. These are equivalent: From 8796b2a784bc45fcc6865cd80b2cba0601448727 Mon Sep 17 00:00:00 2001 From: Alex Hedges Date: Mon, 17 May 2021 14:02:27 -0400 Subject: [PATCH 51/99] Use TypeVar for setupmethod() TypeVar is needed to preserve function signatures. The type cast for update_wrapper is needed because wapper_func can not use the full signature that f does. --- src/flask/scaffold.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 20654b6b..f8f94af1 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -33,8 +33,10 @@ if t.TYPE_CHECKING: # a singleton sentinel value for parameter defaults _sentinel = object() +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) -def setupmethod(f: t.Callable) -> t.Callable: + +def setupmethod(f: F) -> F: """Wraps a method so that it performs a check in debug mode if the first request was already handled. """ @@ -53,7 +55,7 @@ def setupmethod(f: t.Callable) -> t.Callable: ) return f(self, *args, **kwargs) - return update_wrapper(wrapper_func, f) + return t.cast(F, update_wrapper(wrapper_func, f)) class Scaffold: From 10a36cb60e10594aa5dbf3c5aba6abbd5f6fcd70 Mon Sep 17 00:00:00 2001 From: Alex Hedges Date: Mon, 17 May 2021 15:30:05 -0400 Subject: [PATCH 52/99] Improve decorator factory type signatures These changes are required to preserve the type signatures of the created decorators. --- src/flask/app.py | 12 +++++++++--- src/flask/blueprints.py | 12 +++++++++--- src/flask/scaffold.py | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/flask/app.py b/src/flask/app.py index f0f31486..cd1c42ad 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1089,7 +1089,9 @@ class Flask(Scaffold): self.view_functions[endpoint] = view_func @setupmethod - def template_filter(self, name: t.Optional[str] = None) -> t.Callable: + def template_filter( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: """A decorator that is used to register custom template filter. You can specify a name for the filter, otherwise the function name will be used. Example:: @@ -1121,7 +1123,9 @@ class Flask(Scaffold): self.jinja_env.filters[name or f.__name__] = f @setupmethod - def template_test(self, name: t.Optional[str] = None) -> t.Callable: + def template_test( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: """A decorator that is used to register custom template test. You can specify a name for the test, otherwise the function name will be used. Example:: @@ -1162,7 +1166,9 @@ class Flask(Scaffold): self.jinja_env.tests[name or f.__name__] = f @setupmethod - def template_global(self, name: t.Optional[str] = None) -> t.Callable: + def template_global( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: """A decorator that is used to register a custom template global function. You can specify a name for the global function, otherwise the function name will be used. Example:: diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 098eca8c..88883ba7 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -387,7 +387,9 @@ class Blueprint(Scaffold): ) ) - def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable: + def app_template_filter( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: """Register a custom template filter, available application wide. Like :meth:`Flask.template_filter` but for a blueprint. @@ -417,7 +419,9 @@ class Blueprint(Scaffold): self.record_once(register_template) - def app_template_test(self, name: t.Optional[str] = None) -> t.Callable: + def app_template_test( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: """Register a custom template test, available application wide. Like :meth:`Flask.template_test` but for a blueprint. @@ -451,7 +455,9 @@ class Blueprint(Scaffold): self.record_once(register_template) - def app_template_global(self, name: t.Optional[str] = None) -> t.Callable: + def app_template_global( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: """Register a custom template global, available application wide. Like :meth:`Flask.template_global` but for a blueprint. diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index f8f94af1..239bc46a 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -644,7 +644,7 @@ class Scaffold: @setupmethod def errorhandler( self, code_or_exception: t.Union[t.Type[Exception], int] - ) -> t.Callable: + ) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]: """Register a function to handle errors by code or exception class. A decorator that is used to register a function given an From a82cc31af847de7250cd489f6080802ce3f5236e Mon Sep 17 00:00:00 2001 From: Alex Hedges Date: Mon, 17 May 2021 17:14:47 -0400 Subject: [PATCH 53/99] Update CHANGES.rst --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0b5a939d..57dbf3dc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,6 +25,8 @@ Unreleased available in custom URL converters. :issue:`4053` - Re-add deprecated ``Config.from_json``, which was accidentally removed early. :issue:`4078` +- Improve typing for some functions using ``Callable`` in their type + signatures, focusing on decorator factories. :issue:`4060` Version 2.0.0 From 36872e7bd45162dc78e789f45e026932a4687434 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Fri, 21 May 2021 16:58:58 +0800 Subject: [PATCH 54/99] Add venv and .venv to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 71dafa39..e50a290e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.pyc *.pyo env/ +venv/ +.venv/ env* dist/ build/ From a541c2ac8b05c2b23e11bd8540088fce1abc2373 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Fri, 21 May 2021 17:46:31 +0800 Subject: [PATCH 55/99] Fix view decorators docs --- docs/patterns/viewdecorators.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst index b0960b2d..0b0479ef 100644 --- a/docs/patterns/viewdecorators.rst +++ b/docs/patterns/viewdecorators.rst @@ -142,7 +142,7 @@ Here is the code for that decorator:: def decorated_function(*args, **kwargs): template_name = template if template_name is None: - template_name = f"'{request.endpoint.replace('.', '/')}.html'" + template_name = f"{request.endpoint.replace('.', '/')}.html" ctx = f(*args, **kwargs) if ctx is None: ctx = {} From 99afbb277d25d3b052e00b9a8da216054d51d62a Mon Sep 17 00:00:00 2001 From: pgjones Date: Tue, 18 May 2021 13:06:21 +0100 Subject: [PATCH 56/99] Fix blueprint nested url_prefix This ensures that the url_prefix is correctly applied, no matter if set during the registration override or when constructing the blueprint. --- src/flask/blueprints.py | 4 ++- tests/test_blueprints.py | 66 +++++++++++++++------------------------- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 88883ba7..85870a90 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -354,7 +354,9 @@ class Blueprint(Scaffold): bp_options["url_prefix"] = ( state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") ) - else: + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: bp_options["url_prefix"] = state.url_prefix bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "." diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 0bae5333..0f9e9db9 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -837,48 +837,32 @@ def test_nested_blueprint(app, client): assert client.get("/parent/child/grandchild/no").data == b"Grandchild no" -def test_nested_blueprint_url_prefix(app, client): - parent = flask.Blueprint("parent", __name__, url_prefix="/parent") - child = flask.Blueprint("child", __name__, url_prefix="/child") - grandchild = flask.Blueprint("grandchild", __name__, url_prefix="/grandchild") - apple = flask.Blueprint("apple", __name__, url_prefix="/apple") - - @parent.route("/") - def parent_index(): - return "Parent" +@pytest.mark.parametrize( + "parent_init, child_init, parent_registration, child_registration", + [ + ("/parent", "/child", None, None), + ("/parent", None, None, "/child"), + (None, None, "/parent", "/child"), + ("/other", "/something", "/parent", "/child"), + ], +) +def test_nesting_url_prefixes( + parent_init, + child_init, + parent_registration, + child_registration, + app, + client, +) -> None: + parent = flask.Blueprint("parent", __name__, url_prefix=parent_init) + child = flask.Blueprint("child", __name__, url_prefix=child_init) @child.route("/") - def child_index(): - return "Child" + def index(): + return "index" - @grandchild.route("/") - def grandchild_index(): - return "Grandchild" + parent.register_blueprint(child, url_prefix=child_registration) + app.register_blueprint(parent, url_prefix=parent_registration) - @apple.route("/") - def apple_index(): - return "Apple" - - child.register_blueprint(grandchild) - child.register_blueprint(apple, url_prefix="/orange") # test overwrite - parent.register_blueprint(child) - app.register_blueprint(parent) - - assert client.get("/parent/").data == b"Parent" - assert client.get("/parent/child/").data == b"Child" - assert client.get("/parent/child/grandchild/").data == b"Grandchild" - assert client.get("/parent/child/orange/").data == b"Apple" - - -def test_nested_blueprint_url_prefix_only_parent_prefix(app, client): - parent = flask.Blueprint("parent", __name__) - child = flask.Blueprint("child", __name__) - - @child.route("/child-endpoint") - def child_index(): - return "Child" - - parent.register_blueprint(child) - app.register_blueprint(parent, url_prefix="/parent") - - assert client.get("/parent/child-endpoint").data == b"Child" + response = client.get("/parent/child/") + assert response.status_code == 200 From 141fde1d8ec8663b4be98777750d2f58c6fe44ad Mon Sep 17 00:00:00 2001 From: pgjones Date: Tue, 18 May 2021 13:33:45 +0100 Subject: [PATCH 57/99] Bugfix blueprint naming Following discussions for Flask we've decided to name blueprints based on how they are registered. This allows for two different blueprints to have the same self-name as long as they are registered in different nested positions. This helps users choose better blueprint names. --- src/flask/app.py | 29 +++++++++++++++-------------- src/flask/blueprints.py | 38 +++++++++++++++++++------------------- src/flask/wrappers.py | 16 ++++++++++++++++ 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/flask/app.py b/src/flask/app.py index cd1c42ad..3bf92ceb 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -747,7 +747,7 @@ class Flask(Scaffold): ] = self.template_context_processors[None] reqctx = _request_ctx_stack.top if reqctx is not None: - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.template_context_processors: funcs = chain(funcs, self.template_context_processors[bp]) orig_ctx = context.copy() @@ -1267,7 +1267,7 @@ class Flask(Scaffold): exc_class, code = self._get_exc_class_and_code(type(e)) for c in [code, None]: - for name in chain(self._request_blueprints(), [None]): + for name in chain(request.blueprints, [None]): handler_map = self.error_handler_spec[name][c] if not handler_map: @@ -1788,9 +1788,16 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None] + if "." in endpoint: - bp = endpoint.rsplit(".", 1)[0] - funcs = chain(funcs, self.url_default_functions[bp]) + bps: t.List[str] = [endpoint.rsplit(".", 1)[0]] + + while "." in bps[-1]: + bps.append(bps[-1].rpartition(".")[0]) + + for bp in bps: + funcs = chain(funcs, self.url_default_functions[bp]) + for func in funcs: func(endpoint, values) @@ -1831,14 +1838,14 @@ class Flask(Scaffold): funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[ None ] - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.url_value_preprocessors: funcs = chain(funcs, self.url_value_preprocessors[bp]) for func in funcs: func(request.endpoint, request.view_args) funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None] - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.before_request_funcs: funcs = chain(funcs, self.before_request_funcs[bp]) for func in funcs: @@ -1863,7 +1870,7 @@ class Flask(Scaffold): """ ctx = _request_ctx_stack.top funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: @@ -1902,7 +1909,7 @@ class Flask(Scaffold): funcs: t.Iterable[TeardownCallable] = reversed( self.teardown_request_funcs[None] ) - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.teardown_request_funcs: funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) for func in funcs: @@ -2074,9 +2081,3 @@ class Flask(Scaffold): wrapped to apply middleware. """ return self.wsgi_app(environ, start_response) - - def _request_blueprints(self) -> t.Iterable[str]: - if _request_ctx_stack.top.request.blueprint is None: - return [] - else: - return reversed(_request_ctx_stack.top.request.blueprint.split(".")) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 85870a90..8fe7d9e9 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -98,7 +98,7 @@ class BlueprintSetupState: defaults = dict(defaults, **options.pop("defaults")) self.app.add_url_rule( rule, - f"{self.name_prefix}{self.blueprint.name}.{endpoint}", + f"{self.name_prefix}.{self.blueprint.name}.{endpoint}".lstrip("."), view_func, defaults=defaults, **options, @@ -266,23 +266,24 @@ class Blueprint(Scaffold): with. :param options: Keyword arguments forwarded from :meth:`~Flask.register_blueprint`. - :param first_registration: Whether this is the first time this - blueprint has been registered on the application. """ - first_registration = False + first_registration = True - if self.name in app.blueprints: - assert app.blueprints[self.name] is self, ( - "A name collision occurred between blueprints" - f" {self!r} and {app.blueprints[self.name]!r}." - f" Both share the same name {self.name!r}." - f" Blueprints that are created on the fly need unique" - f" names." + for blueprint in app.blueprints.values(): + if blueprint is self: + first_registration = False + + name_prefix = options.get("name_prefix", "") + name = f"{name_prefix}.{self.name}".lstrip(".") + + if name in app.blueprints and app.blueprints[name] is not self: + raise ValueError( + f"Blueprint name '{self.name}' " + f"is already registered by {app.blueprints[self.name]}. " + "Blueprints must have unique names." ) - else: - app.blueprints[self.name] = self - first_registration = True + app.blueprints[name] = self self._got_registered_once = True state = self.make_setup_state(app, options, first_registration) @@ -298,12 +299,11 @@ class Blueprint(Scaffold): def extend(bp_dict, parent_dict): for key, values in bp_dict.items(): - key = self.name if key is None else f"{self.name}.{key}" - + key = name if key is None else f"{name}.{key}" parent_dict[key].extend(values) for key, value in self.error_handler_spec.items(): - key = self.name if key is None else f"{self.name}.{key}" + key = name if key is None else f"{name}.{key}" value = defaultdict( dict, { @@ -337,7 +337,7 @@ class Blueprint(Scaffold): if cli_resolved_group is None: app.cli.commands.update(self.cli.commands) elif cli_resolved_group is _sentinel: - self.cli.name = self.name + self.cli.name = name app.cli.add_command(self.cli) else: self.cli.name = cli_resolved_group @@ -359,7 +359,7 @@ class Blueprint(Scaffold): elif state.url_prefix is not None: bp_options["url_prefix"] = state.url_prefix - bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "." + bp_options["name_prefix"] = name blueprint.register(app, bp_options) def add_url_rule( diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index bfa9d7ce..547e68d6 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -1,6 +1,7 @@ import typing as t from werkzeug.exceptions import BadRequest +from werkzeug.utils import cached_property from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Response as ResponseBase @@ -77,6 +78,21 @@ class Request(RequestBase): else: return None + @cached_property + def blueprints(self) -> t.List[str]: + """The names of the current blueprint upwards through parent + blueprints. + """ + if self.blueprint is None: + return [] + + bps: t.List[str] = [self.blueprint] + + while "." in bps[-1]: + bps.append(bps[-1].rpartition(".")[0]) + + return bps + def _load_form_data(self) -> None: RequestBase._load_form_data(self) From c2920e2bd98cdb3dcccc2868c25b695d4780c620 Mon Sep 17 00:00:00 2001 From: pgjones Date: Tue, 18 May 2021 13:37:11 +0100 Subject: [PATCH 58/99] Bugfix allow blueprints to be registered with a different name This allows the same blueprint to be registered multiple times at the same level, but with differing url_prefixes and names. --- src/flask/blueprints.py | 3 ++- tests/test_blueprints.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 8fe7d9e9..61b0ac69 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -274,7 +274,8 @@ class Blueprint(Scaffold): first_registration = False name_prefix = options.get("name_prefix", "") - name = f"{name_prefix}.{self.name}".lstrip(".") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") if name in app.blueprints and app.blueprints[name] is not self: raise ValueError( diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 0f9e9db9..2a93e8fa 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -866,3 +866,16 @@ def test_nesting_url_prefixes( response = client.get("/parent/child/") assert response.status_code == 200 + + +def test_unique_blueprint_names(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp", __name__) + + app.register_blueprint(bp) + app.register_blueprint(bp) # same name, same object, no error + + with pytest.raises(ValueError): + app.register_blueprint(bp2) # same name, different object + + app.register_blueprint(bp2, name="alt") # different name From 67b0b7e30d2304f628e966896748032022ac0257 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 10:32:28 -0700 Subject: [PATCH 59/99] cache blueprint path calculation --- src/flask/app.py | 13 +++++---- src/flask/helpers.py | 11 ++++++++ src/flask/wrappers.py | 62 +++++++++++++++++++++++++++---------------- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/flask/app.py b/src/flask/app.py index 3bf92ceb..315633b5 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -36,6 +36,7 @@ from .globals import _request_ctx_stack from .globals import g from .globals import request from .globals import session +from .helpers import _split_blueprint_path from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_flashed_messages @@ -1790,13 +1791,11 @@ class Flask(Scaffold): funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None] if "." in endpoint: - bps: t.List[str] = [endpoint.rsplit(".", 1)[0]] - - while "." in bps[-1]: - bps.append(bps[-1].rpartition(".")[0]) - - for bp in bps: - funcs = chain(funcs, self.url_default_functions[bp]) + # This is called by url_for, which can be called outside a + # request, can't use request.blueprints. + bps = _split_blueprint_path(endpoint.rpartition(".")[0]) + bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps) + funcs = chain(funcs, bp_funcs) for func in funcs: func(endpoint, values) diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 585b4dea..57ec9ebf 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -6,6 +6,7 @@ import typing as t import warnings from datetime import datetime from datetime import timedelta +from functools import lru_cache from functools import update_wrapper from threading import RLock @@ -821,3 +822,13 @@ def is_ip(value: str) -> bool: return True return False + + +@lru_cache(maxsize=None) +def _split_blueprint_path(name: str) -> t.List[str]: + out: t.List[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index 547e68d6..47dbe5c8 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -1,12 +1,12 @@ import typing as t from werkzeug.exceptions import BadRequest -from werkzeug.utils import cached_property from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Response as ResponseBase from . import json from .globals import current_app +from .helpers import _split_blueprint_path if t.TYPE_CHECKING: import typing_extensions as te @@ -60,38 +60,54 @@ class Request(RequestBase): @property def endpoint(self) -> t.Optional[str]: - """The endpoint that matched the request. This in combination with - :attr:`view_args` can be used to reconstruct the same or a - modified URL. If an exception happened when matching, this will - be ``None``. + """The endpoint that matched the request URL. + + This will be ``None`` if matching failed or has not been + performed yet. + + This in combination with :attr:`view_args` can be used to + reconstruct the same URL or a modified URL. """ if self.url_rule is not None: return self.url_rule.endpoint - else: - return None + + return None @property def blueprint(self) -> t.Optional[str]: - """The name of the current blueprint""" - if self.url_rule and "." in self.url_rule.endpoint: - return self.url_rule.endpoint.rsplit(".", 1)[0] - else: - return None + """The registered name of the current blueprint. - @cached_property - def blueprints(self) -> t.List[str]: - """The names of the current blueprint upwards through parent - blueprints. + This will be ``None`` if the endpoint is not part of a + blueprint, or if URL matching failed or has not been performed + yet. + + This does not necessarily match the name the blueprint was + created with. It may have been nested, or registered with a + different name. """ - if self.blueprint is None: + endpoint = self.endpoint + + if endpoint is not None and "." in endpoint: + return endpoint.rpartition(".")[0] + + return None + + @property + def blueprints(self) -> t.List[str]: + """The registered names of the current blueprint upwards through + parent blueprints. + + This will be an empty list if there is no current blueprint, or + if URL matching failed. + + .. versionadded:: 2.0.1 + """ + name = self.blueprint + + if name is None: return [] - bps: t.List[str] = [self.blueprint] - - while "." in bps[-1]: - bps.append(bps[-1].rpartition(".")[0]) - - return bps + return _split_blueprint_path(name) def _load_form_data(self) -> None: RequestBase._load_form_data(self) From 63b306743fccdd52079034e82cbbeee182a4361d Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 11:05:36 -0700 Subject: [PATCH 60/99] changelog for blueprint registered name --- CHANGES.rst | 7 +++++++ src/flask/app.py | 6 ++++++ src/flask/blueprints.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 57dbf3dc..f50a7ad9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -27,6 +27,13 @@ Unreleased removed early. :issue:`4078` - Improve typing for some functions using ``Callable`` in their type signatures, focusing on decorator factories. :issue:`4060` +- Nested blueprints are registered with their dotted name. This allows + different blueprints with the same name to be nested at different + locations. :issue:`4069` +- ``register_blueprint`` takes a ``name`` option to change the + (pre-dotted) name the blueprint is registered with. This allows the + same blueprint to be registered multiple times with unique names for + ``url_for``. :issue:`1091` Version 2.0.0 diff --git a/src/flask/app.py b/src/flask/app.py index 315633b5..3abce3ce 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1019,6 +1019,12 @@ class Flask(Scaffold): :class:`~flask.blueprints.BlueprintSetupState`. They can be accessed in :meth:`~flask.Blueprint.record` callbacks. + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + .. versionadded:: 0.7 """ blueprint.register(self, options) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 61b0ac69..6b4e2714 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -252,6 +252,12 @@ class Blueprint(Scaffold): arguments passed to this method will override the defaults set on the blueprint. + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + .. versionadded:: 2.0 """ self._blueprints.append((blueprint, options)) @@ -266,6 +272,17 @@ class Blueprint(Scaffold): with. :param options: Keyword arguments forwarded from :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.0.1 + Nested blueprints are registered with their dotted name. + This allows different blueprints with the same name to be + nested at different locations. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. """ first_registration = True From 9409be6e34e153bfdb0aac2c9eb7f60110109172 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 May 2021 13:08:28 -0700 Subject: [PATCH 61/99] warn when registering same blueprint with same name --- CHANGES.rst | 3 ++- src/flask/blueprints.py | 36 ++++++++++++++++++++++++------------ tests/test_blueprints.py | 12 ++++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f50a7ad9..2698276e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -33,7 +33,8 @@ Unreleased - ``register_blueprint`` takes a ``name`` option to change the (pre-dotted) name the blueprint is registered with. This allows the same blueprint to be registered multiple times with unique names for - ``url_for``. :issue:`1091` + ``url_for``. Registering the same blueprint with the same name + multiple times is deprecated. :issue:`1091` Version 2.0.0 diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 6b4e2714..fc999c75 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -283,23 +283,35 @@ class Blueprint(Scaffold): name the blueprint is registered with. This allows the same blueprint to be registered multiple times with unique names for ``url_for``. + + .. versionchanged:: 2.0.1 + Registering the same blueprint with the same name multiple + times is deprecated and will become an error in Flask 2.1. """ - first_registration = True - - for blueprint in app.blueprints.values(): - if blueprint is self: - first_registration = False - + first_registration = not any(bp is self for bp in app.blueprints.values()) name_prefix = options.get("name_prefix", "") self_name = options.get("name", self.name) name = f"{name_prefix}.{self_name}".lstrip(".") - if name in app.blueprints and app.blueprints[name] is not self: - raise ValueError( - f"Blueprint name '{self.name}' " - f"is already registered by {app.blueprints[self.name]}. " - "Blueprints must have unique names." - ) + if name in app.blueprints: + existing_at = f" '{name}'" if self_name != name else "" + + if app.blueprints[name] is not self: + raise ValueError( + f"The name '{self_name}' is already registered for" + f" a different blueprint{existing_at}. Use 'name='" + " to provide a unique name." + ) + else: + import warnings + + warnings.warn( + f"The name '{self_name}' is already registered for" + f" this blueprint{existing_at}. Use 'name=' to" + " provide a unique name. This will become an error" + " in Flask 2.1.", + stacklevel=4, + ) app.blueprints[name] = self self._got_registered_once = True diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 2a93e8fa..2977f69c 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -140,7 +140,7 @@ def test_blueprint_url_defaults(app, client): return str(bar) app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23}) - app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19}) + app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19}) assert client.get("/1/foo").data == b"23/42" assert client.get("/2/foo").data == b"19/42" @@ -873,9 +873,13 @@ def test_unique_blueprint_names(app, client) -> None: bp2 = flask.Blueprint("bp", __name__) app.register_blueprint(bp) - app.register_blueprint(bp) # same name, same object, no error + + with pytest.warns(UserWarning): + app.register_blueprint(bp) # same bp, same name, warning + + app.register_blueprint(bp, name="again") # same bp, different name, ok with pytest.raises(ValueError): - app.register_blueprint(bp2) # same name, different object + app.register_blueprint(bp2) # different bp, same name, error - app.register_blueprint(bp2, name="alt") # different name + app.register_blueprint(bp2, name="alt") # different bp, different name, ok From 714b0a467acab54091d8aeb73403d808abfbcf5b Mon Sep 17 00:00:00 2001 From: pgjones Date: Fri, 21 May 2021 15:01:32 +0100 Subject: [PATCH 62/99] Fix blueprint self registration By raising a ValueError if attempted. I don't see a use case that makes this worth supporting. --- src/flask/blueprints.py | 2 ++ tests/test_blueprints.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index fc999c75..2da4d12b 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -260,6 +260,8 @@ class Blueprint(Scaffold): .. versionadded:: 2.0 """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") self._blueprints.append((blueprint, options)) def register(self, app: "Flask", options: dict) -> None: diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 2977f69c..73c94ade 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -883,3 +883,9 @@ def test_unique_blueprint_names(app, client) -> None: app.register_blueprint(bp2) # different bp, same name, error app.register_blueprint(bp2, name="alt") # different bp, different name, ok + + +def test_self_registration(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + with pytest.raises(ValueError): + bp.register_blueprint(bp) From 3257b7574e7f010686a617197e0fb4596986f7f7 Mon Sep 17 00:00:00 2001 From: pgjones Date: Fri, 21 May 2021 15:02:05 +0100 Subject: [PATCH 63/99] Fix blueprint renaming This ensures that if a blueprint is renamed at the time of registration that name is used when constructing endpoints, as expected. --- src/flask/blueprints.py | 4 +++- tests/test_blueprints.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 2da4d12b..f3913b30 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -67,6 +67,7 @@ class BlueprintSetupState: #: blueprint. self.url_prefix = url_prefix + self.name = self.options.get("name", blueprint.name) self.name_prefix = self.options.get("name_prefix", "") #: A dictionary with URL defaults that is added to each and every @@ -96,9 +97,10 @@ class BlueprintSetupState: defaults = self.url_defaults if "defaults" in options: defaults = dict(defaults, **options.pop("defaults")) + self.app.add_url_rule( rule, - f"{self.name_prefix}.{self.blueprint.name}.{endpoint}".lstrip("."), + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), view_func, defaults=defaults, **options, diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 73c94ade..088ad779 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -889,3 +889,25 @@ def test_self_registration(app, client) -> None: bp = flask.Blueprint("bp", __name__) with pytest.raises(ValueError): bp.register_blueprint(bp) + + +def test_blueprint_renaming(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp2", __name__) + + @bp.get("/") + def index(): + return flask.request.endpoint + + @bp2.get("/") + def index2(): + return flask.request.endpoint + + bp.register_blueprint(bp2, url_prefix="/a", name="sub") + app.register_blueprint(bp, url_prefix="/a") + app.register_blueprint(bp, url_prefix="/b", name="alt") + + assert client.get("/a/").data == b"bp.index" + assert client.get("/b/").data == b"alt.index" + assert client.get("/a/a/").data == b"bp.sub.index2" + assert client.get("/b/a/").data == b"alt.sub.index2" From 7ab934f6bc2380a322c77d92c0b6cfd10b7de991 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 May 2021 08:42:44 -0700 Subject: [PATCH 64/99] improve typing for `stream_with_context` --- CHANGES.rst | 1 + src/flask/helpers.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2698276e..9e987892 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -35,6 +35,7 @@ Unreleased same blueprint to be registered multiple times with unique names for ``url_for``. Registering the same blueprint with the same name multiple times is deprecated. :issue:`1091` +- Improve typing for ``stream_with_context``. :issue:`4052` Version 2.0.0 diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 57ec9ebf..7b8b0870 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -64,8 +64,10 @@ def get_load_dotenv(default: bool = True) -> bool: def stream_with_context( - generator_or_function: t.Union[t.Generator, t.Callable] -) -> t.Generator: + generator_or_function: t.Union[ + t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]] + ] +) -> t.Iterator[t.AnyStr]: """Request contexts disappear when the response is started on the server. This is done for efficiency reasons and to make it less likely to encounter memory leaks with badly written WSGI middlewares. The downside is that if From fe2d744b530283094dd0a2ffa4a9d86f1029b2cd Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 May 2021 08:50:31 -0700 Subject: [PATCH 65/99] release version 2.0.1 --- CHANGES.rst | 2 +- src/flask/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9e987892..d7fe44b9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,7 @@ Version 2.0.1 ------------- -Unreleased +Released 2021-05-21 - Re-add the ``filename`` parameter in ``send_from_directory``. The ``filename`` parameter has been renamed to ``path``, the old name diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 008e1a81..c5da045e 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered as template_rendered from .templating import render_template as render_template from .templating import render_template_string as render_template_string -__version__ = "2.0.1.dev0" +__version__ = "2.0.1" From e22021d531499af87f65d75f1e37e6329e7385ed Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 May 2021 08:55:31 -0700 Subject: [PATCH 66/99] start version 2.0.2.dev0 --- CHANGES.rst | 6 ++++++ src/flask/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index d7fe44b9..006c0a1f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,11 @@ .. currentmodule:: flask +Version 2.0.2 +------------- + +Unreleased + + Version 2.0.1 ------------- diff --git a/src/flask/__init__.py b/src/flask/__init__.py index c5da045e..471ba7a6 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -43,4 +43,4 @@ from .signals import template_rendered as template_rendered from .templating import render_template as render_template from .templating import render_template_string as render_template_string -__version__ = "2.0.1" +__version__ = "2.0.2.dev0" From aa6fbf2f5a6265dd0ecaaca03e646c3c2a8f58d2 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Mon, 24 May 2021 16:33:02 +0800 Subject: [PATCH 67/99] Fix typo in docs/tutorial/index.rst --- docs/tutorial/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 103c27a8..d5dc5b3c 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -55,7 +55,7 @@ this structure and take full advantage of Flask's flexibility. .. image:: flaskr_edit.png :align: center :class: screenshot - :alt: screenshot of login page + :alt: screenshot of edit page :gh:`The tutorial project is available as an example in the Flask repository `, if you want to compare your project From f7adb2c813c5c10dcab5bc81ee173a6f9fbd406a Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Mon, 24 May 2021 00:31:43 +0200 Subject: [PATCH 68/99] improve typing for `teardown_request` --- CHANGES.rst | 2 ++ src/flask/typing.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 006c0a1f..9c374f56 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ Version 2.0.2 Unreleased +- Fix type annotation for ``teardown_request``. :issue:`4093` + Version 2.0.1 ------------- diff --git a/src/flask/typing.py b/src/flask/typing.py index 5f27308c..60c725ee 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -37,7 +37,7 @@ AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints a AfterRequestCallable = t.Callable[["Response"], "Response"] BeforeRequestCallable = t.Callable[[], None] ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue] -TeardownCallable = t.Callable[[t.Optional[BaseException]], "Response"] +TeardownCallable = t.Callable[[t.Optional[BaseException]], None] TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] TemplateFilterCallable = t.Callable[[t.Any], str] TemplateGlobalCallable = t.Callable[[], t.Any] From a960236117442bec67f89c30dfa014e05483da5a Mon Sep 17 00:00:00 2001 From: Marat Sharafutdinov Date: Tue, 25 May 2021 18:04:41 +0300 Subject: [PATCH 69/99] Fix type annotation for `before_request` and `before_app_request` decorators --- CHANGES.rst | 4 +++- src/flask/app.py | 7 +++++-- src/flask/blueprints.py | 5 +++-- src/flask/typing.py | 3 ++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9c374f56..3e79b5cb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,9 @@ Version 2.0.2 Unreleased -- Fix type annotation for ``teardown_request``. :issue:`4093` +- Fix type annotation for ``teardown_request``. :issue:`4093` +- Fix type annotation for ``before_request`` and ``before_app_request`` + decorators. :issue:`4104` Version 2.0.1 diff --git a/src/flask/app.py b/src/flask/app.py index 3abce3ce..cacb40a5 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -59,6 +59,7 @@ from .signals import request_tearing_down from .templating import DispatchingJinjaLoader from .templating import Environment from .typing import AfterRequestCallable +from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable from .typing import ErrorHandlerCallable from .typing import ResponseReturnValue @@ -439,7 +440,7 @@ class Flask(Scaffold): #: :meth:`before_first_request` decorator. #: #: .. versionadded:: 0.8 - self.before_first_request_funcs: t.List[BeforeRequestCallable] = [] + self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = [] #: A list of functions that are called when the application context #: is destroyed. Since the application context is also torn down @@ -1211,7 +1212,9 @@ class Flask(Scaffold): self.jinja_env.globals[name or f.__name__] = f @setupmethod - def before_first_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: + def before_first_request( + self, f: BeforeFirstRequestCallable + ) -> BeforeFirstRequestCallable: """Registers a function to be run before the first request to this instance of the application. diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index f3913b30..883fc2ff 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -6,6 +6,7 @@ from .scaffold import _endpoint_from_view_func from .scaffold import _sentinel from .scaffold import Scaffold from .typing import AfterRequestCallable +from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable from .typing import ErrorHandlerCallable from .typing import TeardownCallable @@ -537,8 +538,8 @@ class Blueprint(Scaffold): return f def before_app_first_request( - self, f: BeforeRequestCallable - ) -> BeforeRequestCallable: + self, f: BeforeFirstRequestCallable + ) -> BeforeFirstRequestCallable: """Like :meth:`Flask.before_first_request`. Such a function is executed before the first request to the application. """ diff --git a/src/flask/typing.py b/src/flask/typing.py index 60c725ee..8d89839f 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -35,7 +35,8 @@ ResponseReturnValue = t.Union[ AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named AfterRequestCallable = t.Callable[["Response"], "Response"] -BeforeRequestCallable = t.Callable[[], None] +BeforeFirstRequestCallable = t.Callable[[], None] +BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]] ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue] TeardownCallable = t.Callable[[t.Optional[BaseException]], None] TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] From 8e589daaf2cec6a10262b8ff88801127f2fa14fd Mon Sep 17 00:00:00 2001 From: default-303 <54715852+default-303@users.noreply.github.com> Date: Sun, 30 May 2021 00:24:35 +0530 Subject: [PATCH 70/99] Fix typing of jinja decorators (#4109) --- CHANGES.rst | 2 ++ src/flask/typing.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3e79b5cb..7920e5ab 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,8 @@ Unreleased - Fix type annotation for ``teardown_request``. :issue:`4093` - Fix type annotation for ``before_request`` and ``before_app_request`` decorators. :issue:`4104` +- Fixed the issue where typing requires template global + decorators to accept functions with no arguments. :issue:`4098` Version 2.0.1 diff --git a/src/flask/typing.py b/src/flask/typing.py index 8d89839f..b1a6cbdc 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -40,8 +40,8 @@ BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]] ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue] TeardownCallable = t.Callable[[t.Optional[BaseException]], None] TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] -TemplateFilterCallable = t.Callable[[t.Any], str] -TemplateGlobalCallable = t.Callable[[], t.Any] -TemplateTestCallable = t.Callable[[t.Any], bool] +TemplateFilterCallable = t.Callable[..., t.Any] +TemplateGlobalCallable = t.Callable[..., t.Any] +TemplateTestCallable = t.Callable[..., bool] URLDefaultCallable = t.Callable[[str, dict], None] URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] From 0ce270d1f3615f222226fb316e505a60c1e3baab Mon Sep 17 00:00:00 2001 From: laggardkernel Date: Wed, 12 May 2021 05:44:00 +0800 Subject: [PATCH 71/99] Update doc about minimal Python version for async support --- docs/async-await.rst | 3 ++- docs/installation.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/async-await.rst b/docs/async-await.rst index 34751d47..ad7cbf38 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -7,7 +7,8 @@ Using ``async`` and ``await`` Routes, error handlers, before request, after request, and teardown functions can all be coroutine functions if Flask is installed with the -``async`` extra (``pip install flask[async]``). This allows views to be +``async`` extra (``pip install flask[async]``). It requires Python 3.7+ +where ``contextvars.ContextVar`` is available. This allows views to be defined with ``async def`` and use ``await``. .. code-block:: python diff --git a/docs/installation.rst b/docs/installation.rst index aef7df0c..a5d105f7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -8,6 +8,8 @@ Python Version We recommend using the latest version of Python. Flask supports Python 3.6 and newer. +``async`` support in Flask requires Python 3.7+ for ``contextvars.ContextVar``. + Dependencies ------------ From 5d0104f67e6e465e5416803062ed53add1e8d333 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jun 2021 08:04:58 +0000 Subject: [PATCH 72/99] Bump pre-commit from 2.12.1 to 2.13.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.12.1 to 2.13.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.12.1...v2.13.0) Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 1b6998cf..54d5e225 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -70,7 +70,7 @@ pluggy==0.13.1 # via # pytest # tox -pre-commit==2.12.1 +pre-commit==2.13.0 # via -r requirements/dev.in py==1.10.0 # via From 491ea32803cb05fb8fc4dfb81ec6c96f94529eb0 Mon Sep 17 00:00:00 2001 From: laggardkernel Date: Tue, 1 Jun 2021 13:43:28 +0800 Subject: [PATCH 73/99] Optimize loop in Flask._find_error_handler() --- src/flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flask/app.py b/src/flask/app.py index cacb40a5..f6656458 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1276,7 +1276,7 @@ class Flask(Scaffold): """ exc_class, code = self._get_exc_class_and_code(type(e)) - for c in [code, None]: + for c in [code, None] if code is not None else [None]: for name in chain(request.blueprints, [None]): handler_map = self.error_handler_spec[name][c] From 270eb2df2a1606383f50ff079a32240966b43fbc Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Fri, 28 May 2021 00:01:48 +0100 Subject: [PATCH 74/99] Support View and MethodView instances with async handlers --- CHANGES.rst | 1 + docs/async-await.rst | 6 ++++++ src/flask/views.py | 5 +++-- tests/test_async.py | 25 ++++++++++++++++++++++++- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7920e5ab..be263970 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,7 @@ Unreleased decorators. :issue:`4104` - Fixed the issue where typing requires template global decorators to accept functions with no arguments. :issue:`4098` +- Support View and MethodView instances with async handlers. :issue:`4112` Version 2.0.1 diff --git a/docs/async-await.rst b/docs/async-await.rst index ad7cbf38..71e5f452 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -18,6 +18,12 @@ defined with ``async def`` and use ``await``. data = await async_db_query(...) return jsonify(data) +Pluggable class-based views also support handlers that are implemented as +coroutines. This applies to the :meth:`~flask.views.View.dispatch_request` +method in views that inherit from the :class:`flask.views.View` class, as +well as all the HTTP method handlers in views that inherit from the +:class:`flask.views.MethodView` class. + .. admonition:: Using ``async`` on Windows on Python 3.8 Python 3.8 has a bug related to asyncio on Windows. If you encounter diff --git a/src/flask/views.py b/src/flask/views.py index 339ffa18..1bd5c68b 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -1,5 +1,6 @@ import typing as t +from .globals import current_app from .globals import request from .typing import ResponseReturnValue @@ -80,7 +81,7 @@ class View: def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: self = view.view_class(*class_args, **class_kwargs) # type: ignore - return self.dispatch_request(*args, **kwargs) + return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs) if cls.decorators: view.__name__ = name @@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType): meth = getattr(self, "get", None) assert meth is not None, f"Unimplemented method {request.method!r}" - return meth(*args, **kwargs) + return current_app.ensure_sync(meth)(*args, **kwargs) diff --git a/tests/test_async.py b/tests/test_async.py index 26a91118..344e9fe6 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -6,6 +6,8 @@ import pytest from flask import Blueprint from flask import Flask from flask import request +from flask.views import MethodView +from flask.views import View pytest.importorskip("asgiref") @@ -18,6 +20,24 @@ class BlueprintError(Exception): pass +class AsyncView(View): + methods = ["GET", "POST"] + + async def dispatch_request(self): + await asyncio.sleep(0) + return request.method + + +class AsyncMethodView(MethodView): + async def get(self): + await asyncio.sleep(0) + return 'GET' + + async def post(self): + await asyncio.sleep(0) + return 'POST' + + @pytest.fixture(name="async_app") def _async_app(): app = Flask(__name__) @@ -53,11 +73,14 @@ def _async_app(): app.register_blueprint(blueprint, url_prefix="/bp") + app.add_url_rule('/view', view_func=AsyncView.as_view('view')) + app.add_url_rule('/methodview', view_func=AsyncMethodView.as_view('methodview')) + return app @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") -@pytest.mark.parametrize("path", ["/", "/home", "/bp/"]) +@pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"]) def test_async_route(path, async_app): test_client = async_app.test_client() response = test_client.get(path) From 5205cd4ea979f6c322e4e6a256a72e7808592818 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Jun 2021 17:56:33 +0000 Subject: [PATCH 75/99] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_async.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_async.py b/tests/test_async.py index 344e9fe6..8276c4a8 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -31,11 +31,11 @@ class AsyncView(View): class AsyncMethodView(MethodView): async def get(self): await asyncio.sleep(0) - return 'GET' + return "GET" async def post(self): await asyncio.sleep(0) - return 'POST' + return "POST" @pytest.fixture(name="async_app") @@ -73,8 +73,8 @@ def _async_app(): app.register_blueprint(blueprint, url_prefix="/bp") - app.add_url_rule('/view', view_func=AsyncView.as_view('view')) - app.add_url_rule('/methodview', view_func=AsyncMethodView.as_view('methodview')) + app.add_url_rule("/view", view_func=AsyncView.as_view("view")) + app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview")) return app From 6a4e7e948d7e5bd5b2b76c1b7f1a0392644bee1e Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Mon, 31 May 2021 12:28:44 +0200 Subject: [PATCH 76/99] improve typing for `app.errorhandler` decorator --- CHANGES.rst | 1 + src/flask/app.py | 6 ++++-- src/flask/blueprints.py | 6 ++++-- src/flask/scaffold.py | 27 +++++++++++++++++++-------- src/flask/typing.py | 11 ++++++++++- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index be263970..d8dd974f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,7 @@ Unreleased - Fixed the issue where typing requires template global decorators to accept functions with no arguments. :issue:`4098` - Support View and MethodView instances with async handlers. :issue:`4112` +- Enhance typing of ``app.errorhandler`` decorator. :issue:`4095` Version 2.0.1 diff --git a/src/flask/app.py b/src/flask/app.py index f6656458..22cc9abc 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -61,7 +61,6 @@ from .templating import Environment from .typing import AfterRequestCallable from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable from .typing import ResponseReturnValue from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable @@ -78,6 +77,7 @@ if t.TYPE_CHECKING: from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner + from .typing import ErrorHandlerCallable if sys.version_info >= (3, 8): iscoroutinefunction = inspect.iscoroutinefunction @@ -1268,7 +1268,9 @@ class Flask(Scaffold): self.shell_context_processors.append(f) return f - def _find_error_handler(self, e: Exception) -> t.Optional[ErrorHandlerCallable]: + def _find_error_handler( + self, e: Exception + ) -> t.Optional["ErrorHandlerCallable[Exception]"]: """Return a registered error handler for an exception in this order: blueprint handler for a specific code, app handler for a specific code, blueprint handler for an exception class, app handler for an exception diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 883fc2ff..8241420b 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -8,7 +8,6 @@ from .scaffold import Scaffold from .typing import AfterRequestCallable from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable from .typing import TemplateFilterCallable @@ -19,6 +18,7 @@ from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .app import Flask + from .typing import ErrorHandlerCallable DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] @@ -581,7 +581,9 @@ class Blueprint(Scaffold): handler is used for all requests, even if outside of the blueprint. """ - def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: + def decorator( + f: "ErrorHandlerCallable[Exception]", + ) -> "ErrorHandlerCallable[Exception]": self.record_once(lambda s: s.app.errorhandler(code)(f)) return f diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 239bc46a..e80e1915 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -21,7 +21,7 @@ from .templating import _default_template_ctx_processor from .typing import AfterRequestCallable from .typing import AppOrBlueprintKey from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable +from .typing import GenericException from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable from .typing import URLDefaultCallable @@ -29,6 +29,7 @@ from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .wrappers import Response + from .typing import ErrorHandlerCallable # a singleton sentinel value for parameter defaults _sentinel = object() @@ -144,7 +145,10 @@ class Scaffold: #: directly and its format may change at any time. self.error_handler_spec: t.Dict[ AppOrBlueprintKey, - t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ErrorHandlerCallable]], + t.Dict[ + t.Optional[int], + t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"], + ], ] = defaultdict(lambda: defaultdict(dict)) #: A data structure of functions to call at the beginning of @@ -643,8 +647,11 @@ class Scaffold: @setupmethod def errorhandler( - self, code_or_exception: t.Union[t.Type[Exception], int] - ) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]: + self, code_or_exception: t.Union[t.Type[GenericException], int] + ) -> t.Callable[ + ["ErrorHandlerCallable[GenericException]"], + "ErrorHandlerCallable[GenericException]", + ]: """Register a function to handle errors by code or exception class. A decorator that is used to register a function given an @@ -674,7 +681,9 @@ class Scaffold: an arbitrary exception """ - def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: + def decorator( + f: "ErrorHandlerCallable[GenericException]", + ) -> "ErrorHandlerCallable[GenericException]": self.register_error_handler(code_or_exception, f) return f @@ -683,8 +692,8 @@ class Scaffold: @setupmethod def register_error_handler( self, - code_or_exception: t.Union[t.Type[Exception], int], - f: ErrorHandlerCallable, + code_or_exception: t.Union[t.Type[GenericException], int], + f: "ErrorHandlerCallable[GenericException]", ) -> None: """Alternative error attach function to the :meth:`errorhandler` decorator that is more straightforward to use for non decorator @@ -708,7 +717,9 @@ class Scaffold: " instead." ) - self.error_handler_spec[None][code][exc_class] = f + self.error_handler_spec[None][code][exc_class] = t.cast( + "ErrorHandlerCallable[Exception]", f + ) @staticmethod def _get_exc_class_and_code( diff --git a/src/flask/typing.py b/src/flask/typing.py index b1a6cbdc..f1c84670 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -33,11 +33,12 @@ ResponseReturnValue = t.Union[ "WSGIApplication", ] +GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True) + AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named AfterRequestCallable = t.Callable[["Response"], "Response"] BeforeFirstRequestCallable = t.Callable[[], None] BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]] -ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue] TeardownCallable = t.Callable[[t.Optional[BaseException]], None] TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] TemplateFilterCallable = t.Callable[..., t.Any] @@ -45,3 +46,11 @@ TemplateGlobalCallable = t.Callable[..., t.Any] TemplateTestCallable = t.Callable[..., bool] URLDefaultCallable = t.Callable[[str, dict], None] URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] + + +if t.TYPE_CHECKING: + import typing_extensions as te + + class ErrorHandlerCallable(te.Protocol[GenericException]): + def __call__(self, error: GenericException) -> ResponseReturnValue: + ... From 1b10e085d821e37b7f7162307046649a721e5587 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 01:54:49 +0000 Subject: [PATCH 77/99] Bump urllib3 from 1.26.4 to 1.26.5 in /requirements Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.4 to 1.26.5. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.4...1.26.5) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 30 +++++++++++++++--------------- requirements/docs.txt | 12 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 54d5e225..42133b83 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -9,13 +9,13 @@ alabaster==0.7.12 appdirs==1.4.4 # via virtualenv asgiref==3.3.4 - # via -r requirements/tests.in + # via -r tests.in attrs==21.2.0 # via pytest babel==2.9.1 # via sphinx blinker==1.4 - # via -r requirements/tests.in + # via -r tests.in certifi==2020.12.5 # via requests cfgv==3.3.0 @@ -35,7 +35,7 @@ filelock==3.0.12 # tox # virtualenv greenlet==1.1.0 - # via -r requirements/tests.in + # via -r tests.in identify==2.2.4 # via pre-commit idna==2.10 @@ -51,7 +51,7 @@ markupsafe==2.0.1 mypy-extensions==0.4.3 # via mypy mypy==0.812 - # via -r requirements/typing.in + # via -r typing.in nodeenv==1.6.0 # via pre-commit packaging==20.9 @@ -61,17 +61,17 @@ packaging==20.9 # sphinx # tox pallets-sphinx-themes==2.0.1 - # via -r requirements/docs.in + # via -r docs.in pep517==0.10.0 # via pip-tools pip-tools==6.1.0 - # via -r requirements/dev.in + # via -r dev.in pluggy==0.13.1 # via # pytest # tox pre-commit==2.13.0 - # via -r requirements/dev.in + # via -r dev.in py==1.10.0 # via # pytest @@ -83,9 +83,9 @@ pygments==2.9.0 pyparsing==2.4.7 # via packaging pytest==6.2.4 - # via -r requirements/tests.in + # via -r tests.in python-dotenv==0.17.1 - # via -r requirements/tests.in + # via -r tests.in pytz==2021.1 # via babel pyyaml==5.4.1 @@ -99,12 +99,12 @@ six==1.16.0 snowballstemmer==2.1.0 # via sphinx sphinx-issues==1.2.0 - # via -r requirements/docs.in + # via -r docs.in sphinx-tabs==3.0.0 - # via -r requirements/docs.in + # via -r docs.in sphinx==4.0.2 # via - # -r requirements/docs.in + # -r docs.in # pallets-sphinx-themes # sphinx-issues # sphinx-tabs @@ -118,7 +118,7 @@ sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-log-cabinet==1.0.1 - # via -r requirements/docs.in + # via -r docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.4 @@ -130,12 +130,12 @@ toml==0.10.2 # pytest # tox tox==3.23.1 - # via -r requirements/dev.in + # via -r dev.in typed-ast==1.4.3 # via mypy typing-extensions==3.10.0.0 # via mypy -urllib3==1.26.4 +urllib3==1.26.5 # via requests virtualenv==20.4.6 # via diff --git a/requirements/docs.txt b/requirements/docs.txt index 245f04b6..f1ee1f27 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -29,7 +29,7 @@ packaging==20.9 # pallets-sphinx-themes # sphinx pallets-sphinx-themes==2.0.1 - # via -r requirements/docs.in + # via -r docs.in pygments==2.9.0 # via # sphinx @@ -43,12 +43,12 @@ requests==2.25.1 snowballstemmer==2.1.0 # via sphinx sphinx-issues==1.2.0 - # via -r requirements/docs.in + # via -r docs.in sphinx-tabs==3.0.0 - # via -r requirements/docs.in + # via -r docs.in sphinx==4.0.2 # via - # -r requirements/docs.in + # -r docs.in # pallets-sphinx-themes # sphinx-issues # sphinx-tabs @@ -62,12 +62,12 @@ sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-log-cabinet==1.0.1 - # via -r requirements/docs.in + # via -r docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.4 # via sphinx -urllib3==1.26.4 +urllib3==1.26.5 # via requests # The following packages are considered to be unsafe in a requirements file: From 63893a427bd022d192febadad0b31ae06fa80776 Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 6 Jun 2021 11:09:03 +0100 Subject: [PATCH 78/99] Improve the changelog entry The fix to the teardown_request also applies to all teardown_* methods. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index d8dd974f..c27b623f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Version 2.0.2 Unreleased -- Fix type annotation for ``teardown_request``. :issue:`4093` +- Fix type annotation for ``teardown_*`` methods. :issue:`4093` - Fix type annotation for ``before_request`` and ``before_app_request`` decorators. :issue:`4104` - Fixed the issue where typing requires template global From 92bed6619459da93f79e85ec674b24cb6fa322f6 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Tue, 8 Jun 2021 19:01:07 +0200 Subject: [PATCH 79/99] Update celery.rst small typo --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index e1f6847e..38a9a025 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -64,7 +64,7 @@ An example task Let's write a task that adds two numbers together and returns the result. We configure Celery's broker and backend to use Redis, create a ``celery`` -application using the factor from above, and then use it to define the task. :: +application using the factory from above, and then use it to define the task. :: from flask import Flask From 34027d8d876c140f84e18d6b56c0aecc5481874c Mon Sep 17 00:00:00 2001 From: Grey Li Date: Mon, 14 Jun 2021 14:20:04 +0800 Subject: [PATCH 80/99] Improve the contributing guide --- CONTRIBUTING.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3a9177a4..a3c8b851 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -12,14 +12,16 @@ to address bugs and feature requests in Flask itself. Use one of the following resources for questions about using Flask or issues with your own code: -- The ``#get-help`` channel on our Discord chat: +- The ``#questions`` channel on our Discord chat: https://discord.gg/pallets - The mailing list flask@python.org for long term discussion or larger issues. - Ask on `Stack Overflow`_. Search with Google first using: ``site:stackoverflow.com flask {search term, exception message, etc.}`` +- Ask on our `GitHub Discussions`_. .. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent +.. _GitHub Discussions: https://github.com/pallets/flask/discussions Reporting issues @@ -92,7 +94,7 @@ First time setup .. code-block:: text - git remote add fork https://github.com/{username}/flask + $ git remote add fork https://github.com/{username}/flask - Create a virtualenv. From a44c7228600044f530856a33c7da147c528644ff Mon Sep 17 00:00:00 2001 From: pgjones Date: Sat, 5 Jun 2021 16:08:51 +0100 Subject: [PATCH 81/99] Fix registering a blueprint twice with differing names Previously the blueprint recorded aspects (before request, after request etc) would only be added to the app if it was the first registration of the blueprint instance. However only the record-once aspects (app-before requests, app-after request) should be added once on registration of the instance, whereas everything else should be added on every unique name registration. This ensures that these trigger under the new name as well as the old. --- CHANGES.rst | 1 + src/flask/blueprints.py | 8 +++++--- tests/test_blueprints.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c27b623f..bdfa9f01 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Unreleased decorators to accept functions with no arguments. :issue:`4098` - Support View and MethodView instances with async handlers. :issue:`4112` - Enhance typing of ``app.errorhandler`` decorator. :issue:`4095` +- Fix registering a blueprint twice with differing names. :issue:`4124` Version 2.0.1 diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 8241420b..c8ce67a4 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -293,7 +293,6 @@ class Blueprint(Scaffold): Registering the same blueprint with the same name multiple times is deprecated and will become an error in Flask 2.1. """ - first_registration = not any(bp is self for bp in app.blueprints.values()) name_prefix = options.get("name_prefix", "") self_name = options.get("name", self.name) name = f"{name_prefix}.{self_name}".lstrip(".") @@ -318,9 +317,12 @@ class Blueprint(Scaffold): stacklevel=4, ) + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + app.blueprints[name] = self self._got_registered_once = True - state = self.make_setup_state(app, options, first_registration) + state = self.make_setup_state(app, options, first_bp_registration) if self.has_static_folder: state.add_url_rule( @@ -330,7 +332,7 @@ class Blueprint(Scaffold): ) # Merge blueprint data into parent. - if first_registration: + if first_bp_registration or first_name_registration: def extend(bp_dict, parent_dict): for key, values in bp_dict.items(): diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 088ad779..a124c612 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -899,6 +899,14 @@ def test_blueprint_renaming(app, client) -> None: def index(): return flask.request.endpoint + @bp.get("/error") + def error(): + flask.abort(403) + + @bp.errorhandler(403) + def forbidden(_: Exception): + return "Error", 403 + @bp2.get("/") def index2(): return flask.request.endpoint @@ -911,3 +919,5 @@ def test_blueprint_renaming(app, client) -> None: assert client.get("/b/").data == b"alt.index" assert client.get("/a/a/").data == b"bp.sub.index2" assert client.get("/b/a/").data == b"alt.sub.index2" + assert client.get("/a/error").data == b"Error" + assert client.get("/b/error").data == b"Error" From f353d126d199071c7933e2f62409e5532333bec5 Mon Sep 17 00:00:00 2001 From: Frank Yu <1032897296@qq.com> Date: Fri, 18 Jun 2021 10:10:29 +0800 Subject: [PATCH 82/99] Update docs of rendering templates (#4153) * Update docs of rendering templates * Improve the grammar Co-authored-by: Grey Li --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5cd59f41..b5071ab0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -444,9 +444,9 @@ Here is an example template:

Hello, World!

{% endif %} -Inside templates you also have access to the :class:`~flask.request`, -:class:`~flask.session` and :class:`~flask.g` [#]_ objects -as well as the :func:`~flask.get_flashed_messages` function. +Inside templates you also have access to the :data:`~flask.Flask.config`, +:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects +as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions. Templates are especially useful if inheritance is used. If you want to know how that works, see :doc:`patterns/templateinheritance`. Basically From 5fa7d2efe757e028a734d3dc56e9f13bb812a92a Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Fri, 18 Jun 2021 20:15:02 +0800 Subject: [PATCH 83/99] Update templating.rst --- docs/templating.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index b0964df8..dcc757c3 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -37,7 +37,7 @@ by default: .. data:: config :noindex: - The current configuration object (:data:`flask.config`) + The current configuration object (:data:`flask.Flask.config`) .. versionadded:: 0.6 From 7b696e076a8ebf22e8ad096b7278051d475c88be Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Fri, 18 Jun 2021 22:18:15 +0800 Subject: [PATCH 84/99] Update testing.rst --- docs/testing.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 2fedc600..73230b4b 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -48,20 +48,21 @@ the application for testing and initializes a new database:: import pytest from flaskr import create_app + from flaskr.db import init_db @pytest.fixture def client(): - db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + db_fd, db_path = tempfile.mkstemp() + app = create_app({'TESTING': True, 'DATABASE': db_path}) - with flaskr.app.test_client() as client: - with flaskr.app.app_context(): - flaskr.init_db() + with app.test_client() as client: + with app.app_context(): + init_db() yield client os.close(db_fd) - os.unlink(flaskr.app.config['DATABASE']) + os.unlink(db_path) This client fixture will be called by each individual test. It gives us a simple interface to the application, where we can trigger test requests to the From 35eb582bf3ddbe995681363167eb3233f539ef8b Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Sun, 20 Jun 2021 23:20:14 +0800 Subject: [PATCH 85/99] Change flask.xxx to plain version in testing docs --- docs/testing.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 73230b4b..061d46d2 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -225,13 +225,13 @@ temporarily. With this you can access the :class:`~flask.request`, :class:`~flask.g` and :class:`~flask.session` objects like in view functions. Here is a full example that demonstrates this approach:: - import flask + from flask import Flask, request - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): - assert flask.request.path == '/' - assert flask.request.args['name'] == 'Peter' + assert request.path == '/' + assert request.args['name'] == 'Peter' All the other objects that are context bound can be used in the same way. @@ -248,7 +248,7 @@ the test request context leaves the ``with`` block. If you do want the :meth:`~flask.Flask.before_request` functions to be called as well, you need to call :meth:`~flask.Flask.preprocess_request` yourself:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): app.preprocess_request() @@ -261,7 +261,7 @@ If you want to call the :meth:`~flask.Flask.after_request` functions you need to call into :meth:`~flask.Flask.process_response` which however requires that you pass it a response object:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): resp = Response('...') @@ -330,7 +330,7 @@ context around for a little longer so that additional introspection can happen. With Flask 0.4 this is possible by using the :meth:`~flask.Flask.test_client` with a ``with`` block:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_client() as c: rv = c.get('/?tequila=42') @@ -354,7 +354,7 @@ keep the context around and access :data:`flask.session`:: with app.test_client() as c: rv = c.get('/') - assert flask.session['foo'] == 42 + assert session['foo'] == 42 This however does not make it possible to also modify the session or to access the session before a request was fired. Starting with Flask 0.8 we From c224832acccbb9b16485c7adeb7714c71fce1ea8 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Sat, 26 Jun 2021 18:58:15 +0800 Subject: [PATCH 86/99] Fix typo in docs/views.rst --- docs/views.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/views.rst b/docs/views.rst index 1d2daec7..63d26c5c 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -113,8 +113,8 @@ Method Based Dispatching For RESTful APIs it's especially helpful to execute a different function for each HTTP method. With the :class:`flask.views.MethodView` you can -easily do that. Each HTTP method maps to a function with the same name -(just in lowercase):: +easily do that. Each HTTP method maps to a method of the class with the +same name (just in lowercase):: from flask.views import MethodView From 6fe9235f866fdc9a76cc086bea1512b6825713e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 08:03:44 +0000 Subject: [PATCH 87/99] Bump sphinx-tabs from 3.0.0 to 3.1.0 Bumps [sphinx-tabs](https://github.com/executablebooks/sphinx-tabs) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/executablebooks/sphinx-tabs/releases) - [Changelog](https://github.com/executablebooks/sphinx-tabs/blob/master/CHANGELOG.md) - [Commits](https://github.com/executablebooks/sphinx-tabs/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: sphinx-tabs dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 34 +++++++++++++++++----------------- requirements/docs.txt | 14 +++++++------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 42133b83..4603468a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -9,13 +9,13 @@ alabaster==0.7.12 appdirs==1.4.4 # via virtualenv asgiref==3.3.4 - # via -r tests.in + # via -r requirements/tests.in attrs==21.2.0 # via pytest babel==2.9.1 # via sphinx blinker==1.4 - # via -r tests.in + # via -r requirements/tests.in certifi==2020.12.5 # via requests cfgv==3.3.0 @@ -35,7 +35,7 @@ filelock==3.0.12 # tox # virtualenv greenlet==1.1.0 - # via -r tests.in + # via -r requirements/tests.in identify==2.2.4 # via pre-commit idna==2.10 @@ -48,10 +48,10 @@ jinja2==3.0.1 # via sphinx markupsafe==2.0.1 # via jinja2 +mypy==0.812 + # via -r requirements/typing.in mypy-extensions==0.4.3 # via mypy -mypy==0.812 - # via -r typing.in nodeenv==1.6.0 # via pre-commit packaging==20.9 @@ -61,17 +61,17 @@ packaging==20.9 # sphinx # tox pallets-sphinx-themes==2.0.1 - # via -r docs.in + # via -r requirements/docs.in pep517==0.10.0 # via pip-tools pip-tools==6.1.0 - # via -r dev.in + # via -r requirements/dev.in pluggy==0.13.1 # via # pytest # tox pre-commit==2.13.0 - # via -r dev.in + # via -r requirements/dev.in py==1.10.0 # via # pytest @@ -83,9 +83,9 @@ pygments==2.9.0 pyparsing==2.4.7 # via packaging pytest==6.2.4 - # via -r tests.in + # via -r requirements/tests.in python-dotenv==0.17.1 - # via -r tests.in + # via -r requirements/tests.in pytz==2021.1 # via babel pyyaml==5.4.1 @@ -98,17 +98,17 @@ six==1.16.0 # virtualenv snowballstemmer==2.1.0 # via sphinx -sphinx-issues==1.2.0 - # via -r docs.in -sphinx-tabs==3.0.0 - # via -r docs.in sphinx==4.0.2 # via - # -r docs.in + # -r requirements/docs.in # pallets-sphinx-themes # sphinx-issues # sphinx-tabs # sphinxcontrib-log-cabinet +sphinx-issues==1.2.0 + # via -r requirements/docs.in +sphinx-tabs==3.1.0 + # via -r requirements/docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 @@ -118,7 +118,7 @@ sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-log-cabinet==1.0.1 - # via -r docs.in + # via -r requirements/docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.4 @@ -130,7 +130,7 @@ toml==0.10.2 # pytest # tox tox==3.23.1 - # via -r dev.in + # via -r requirements/dev.in typed-ast==1.4.3 # via mypy typing-extensions==3.10.0.0 diff --git a/requirements/docs.txt b/requirements/docs.txt index f1ee1f27..e59cd0d4 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -29,7 +29,7 @@ packaging==20.9 # pallets-sphinx-themes # sphinx pallets-sphinx-themes==2.0.1 - # via -r docs.in + # via -r requirements/docs.in pygments==2.9.0 # via # sphinx @@ -42,17 +42,17 @@ requests==2.25.1 # via sphinx snowballstemmer==2.1.0 # via sphinx -sphinx-issues==1.2.0 - # via -r docs.in -sphinx-tabs==3.0.0 - # via -r docs.in sphinx==4.0.2 # via - # -r docs.in + # -r requirements/docs.in # pallets-sphinx-themes # sphinx-issues # sphinx-tabs # sphinxcontrib-log-cabinet +sphinx-issues==1.2.0 + # via -r requirements/docs.in +sphinx-tabs==3.1.0 + # via -r requirements/docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 @@ -62,7 +62,7 @@ sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-log-cabinet==1.0.1 - # via -r docs.in + # via -r requirements/docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.4 From d0f19f790520293c2c40207d492695e582347b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:05:36 +0000 Subject: [PATCH 88/99] Bump asgiref from 3.3.4 to 3.4.0 Bumps [asgiref](https://github.com/django/asgiref) from 3.3.4 to 3.4.0. - [Release notes](https://github.com/django/asgiref/releases) - [Changelog](https://github.com/django/asgiref/blob/main/CHANGELOG.txt) - [Commits](https://github.com/django/asgiref/commits) --- updated-dependencies: - dependency-name: asgiref dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- requirements/tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4603468a..a8908c6d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ alabaster==0.7.12 # via sphinx appdirs==1.4.4 # via virtualenv -asgiref==3.3.4 +asgiref==3.4.0 # via -r requirements/tests.in attrs==21.2.0 # via pytest diff --git a/requirements/tests.txt b/requirements/tests.txt index deb93d62..600ad93a 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -4,7 +4,7 @@ # # pip-compile requirements/tests.in # -asgiref==3.3.4 +asgiref==3.4.0 # via -r requirements/tests.in attrs==21.2.0 # via pytest From ae53c32048dbef4ce1aaf9ecb86925e507118a38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:06:46 +0000 Subject: [PATCH 89/99] Bump pip-tools from 6.1.0 to 6.2.0 Bumps [pip-tools](https://github.com/jazzband/pip-tools) from 6.1.0 to 6.2.0. - [Release notes](https://github.com/jazzband/pip-tools/releases) - [Changelog](https://github.com/jazzband/pip-tools/blob/master/CHANGELOG.md) - [Commits](https://github.com/jazzband/pip-tools/compare/6.1.0...6.2.0) --- updated-dependencies: - dependency-name: pip-tools dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index a8908c6d..85ee227c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -64,7 +64,7 @@ pallets-sphinx-themes==2.0.1 # via -r requirements/docs.in pep517==0.10.0 # via pip-tools -pip-tools==6.1.0 +pip-tools==6.2.0 # via -r requirements/dev.in pluggy==0.13.1 # via @@ -141,6 +141,8 @@ virtualenv==20.4.6 # via # pre-commit # tox +wheel==0.36.2 + # via pip-tools # The following packages are considered to be unsafe in a requirements file: # pip From c9796f85c787b5d01417a50ba25354be3b620afc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:06:57 +0000 Subject: [PATCH 90/99] Bump python-dotenv from 0.17.1 to 0.18.0 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.17.1 to 0.18.0. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.17.1...v0.18.0) --- updated-dependencies: - dependency-name: python-dotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- requirements/tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index a8908c6d..3c510d15 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -84,7 +84,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.4 # via -r requirements/tests.in -python-dotenv==0.17.1 +python-dotenv==0.18.0 # via -r requirements/tests.in pytz==2021.1 # via babel diff --git a/requirements/tests.txt b/requirements/tests.txt index 600ad93a..6bfc63fa 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -24,7 +24,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.4 # via -r requirements/tests.in -python-dotenv==0.17.1 +python-dotenv==0.18.0 # via -r requirements/tests.in toml==0.10.2 # via pytest From 72078148c017e2e3e6d6b6716aa8f9f16e0bf2f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:07:58 +0000 Subject: [PATCH 91/99] Bump mypy from 0.812 to 0.910 Bumps [mypy](https://github.com/python/mypy) from 0.812 to 0.910. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.812...v0.910) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 5 ++--- requirements/typing.txt | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index a8908c6d..d761ff6d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -48,7 +48,7 @@ jinja2==3.0.1 # via sphinx markupsafe==2.0.1 # via jinja2 -mypy==0.812 +mypy==0.910 # via -r requirements/typing.in mypy-extensions==0.4.3 # via mypy @@ -125,14 +125,13 @@ sphinxcontrib-serializinghtml==1.1.4 # via sphinx toml==0.10.2 # via + # mypy # pep517 # pre-commit # pytest # tox tox==3.23.1 # via -r requirements/dev.in -typed-ast==1.4.3 - # via mypy typing-extensions==3.10.0.0 # via mypy urllib3==1.26.5 diff --git a/requirements/typing.txt b/requirements/typing.txt index 0e342aaa..fa04c8ad 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -4,11 +4,11 @@ # # pip-compile requirements/typing.in # +mypy==0.910 + # via -r requirements/typing.in mypy-extensions==0.4.3 # via mypy -mypy==0.812 - # via -r requirements/typing.in -typed-ast==1.4.3 +toml==0.10.2 # via mypy typing-extensions==3.10.0.0 # via mypy From 9fa8b0f7ad64c0cb3729978f88b5ebdc296ba750 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Thu, 1 Jul 2021 18:43:35 +0800 Subject: [PATCH 92/99] Fix typing import issues --- setup.cfg | 3 +++ tox.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 77616866..e25c1e27 100644 --- a/setup.cfg +++ b/setup.cfg @@ -112,3 +112,6 @@ ignore_missing_imports = True [mypy-dotenv.*] ignore_missing_imports = True + +[mypy-cryptography.*] +ignore_missing_imports = True diff --git a/tox.ini b/tox.ini index c24ec0ff..66d1658c 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:typing] deps = -r requirements/typing.txt -commands = mypy +commands = mypy --install-types --non-interactive [testenv:docs] deps = -r requirements/docs.txt From f9ccca97e476a9ffc1fcdf081f9f083a119c5ce2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 22:11:00 +0000 Subject: [PATCH 93/99] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.15.0 → v2.20.0](https://github.com/asottile/pyupgrade/compare/v2.15.0...v2.20.0) - [github.com/psf/black: 21.5b1 → 21.6b0](https://github.com/psf/black/compare/21.5b1...21.6b0) - [github.com/pre-commit/pre-commit-hooks: v3.4.0 → v4.0.1](https://github.com/pre-commit/pre-commit-hooks/compare/v3.4.0...v4.0.1) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d75f3c31..a23c48af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.15.0 + rev: v2.20.0 hooks: - id: pyupgrade args: ["--py36-plus"] @@ -14,7 +14,7 @@ repos: files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 21.5b1 + rev: 21.6b0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 @@ -25,7 +25,7 @@ repos: - flake8-bugbear - flake8-implicit-str-concat - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: fix-byte-order-marker - id: trailing-whitespace From 6e1b72096d5ae1e2cc4d8592ff8271b62548d9cf Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 6 Jul 2021 22:05:31 +0200 Subject: [PATCH 94/99] Fix typo in docs --- src/flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flask/app.py b/src/flask/app.py index 22cc9abc..8c3c39d4 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1306,7 +1306,7 @@ class Flask(Scaffold): .. versionchanged:: 1.0 Exceptions are looked up by code *and* by MRO, so - ``HTTPExcpetion`` subclasses can be handled with a catch-all + ``HTTPException`` subclasses can be handled with a catch-all handler for the base ``HTTPException``. .. versionadded:: 0.3 From bb3217b35017f269e3ce411bf070904e3858d307 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Aug 2021 08:03:36 +0000 Subject: [PATCH 95/99] Bump tox from 3.23.1 to 3.24.1 Bumps [tox](https://github.com/tox-dev/tox) from 3.23.1 to 3.24.1. - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/3.23.1...3.24.1) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4eb84822..2191987d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -130,7 +130,7 @@ toml==0.10.2 # pre-commit # pytest # tox -tox==3.23.1 +tox==3.24.1 # via -r requirements/dev.in typing-extensions==3.10.0.0 # via mypy From 7a3b8bbb897991a791ce51a870866648577054b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Aug 2021 08:04:22 +0000 Subject: [PATCH 96/99] Bump sphinx from 4.0.2 to 4.1.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.2 to 4.1.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.2...v4.1.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 6 +++--- requirements/docs.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4eb84822..445b9405 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -98,7 +98,7 @@ six==1.16.0 # virtualenv snowballstemmer==2.1.0 # via sphinx -sphinx==4.0.2 +sphinx==4.1.2 # via # -r requirements/docs.in # pallets-sphinx-themes @@ -113,7 +113,7 @@ sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -121,7 +121,7 @@ sphinxcontrib-log-cabinet==1.0.1 # via -r requirements/docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx toml==0.10.2 # via diff --git a/requirements/docs.txt b/requirements/docs.txt index e59cd0d4..17730411 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -42,7 +42,7 @@ requests==2.25.1 # via sphinx snowballstemmer==2.1.0 # via sphinx -sphinx==4.0.2 +sphinx==4.1.2 # via # -r requirements/docs.in # pallets-sphinx-themes @@ -57,7 +57,7 @@ sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -65,7 +65,7 @@ sphinxcontrib-log-cabinet==1.0.1 # via -r requirements/docs.in sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx urllib3==1.26.5 # via requests From 3e507a70cafd5bab0acd570ee8ca06d46063b543 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Aug 2021 08:05:05 +0000 Subject: [PATCH 97/99] Bump python-dotenv from 0.18.0 to 0.19.0 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: python-dotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- requirements/tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4eb84822..1d11892f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -84,7 +84,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.4 # via -r requirements/tests.in -python-dotenv==0.18.0 +python-dotenv==0.19.0 # via -r requirements/tests.in pytz==2021.1 # via babel diff --git a/requirements/tests.txt b/requirements/tests.txt index 6bfc63fa..7bf671c0 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -24,7 +24,7 @@ pyparsing==2.4.7 # via packaging pytest==6.2.4 # via -r requirements/tests.in -python-dotenv==0.18.0 +python-dotenv==0.19.0 # via -r requirements/tests.in toml==0.10.2 # via pytest From c0f583fdc1f9153020e05321f4444a2e2d541c2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Aug 2021 08:05:42 +0000 Subject: [PATCH 98/99] Bump asgiref from 3.4.0 to 3.4.1 Bumps [asgiref](https://github.com/django/asgiref) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/django/asgiref/releases) - [Changelog](https://github.com/django/asgiref/blob/main/CHANGELOG.txt) - [Commits](https://github.com/django/asgiref/commits/3.4.1) --- updated-dependencies: - dependency-name: asgiref dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev.txt | 2 +- requirements/tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4eb84822..9a4dd84e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ alabaster==0.7.12 # via sphinx appdirs==1.4.4 # via virtualenv -asgiref==3.4.0 +asgiref==3.4.1 # via -r requirements/tests.in attrs==21.2.0 # via pytest diff --git a/requirements/tests.txt b/requirements/tests.txt index 6bfc63fa..1530a910 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -4,7 +4,7 @@ # # pip-compile requirements/tests.in # -asgiref==3.4.0 +asgiref==3.4.1 # via -r requirements/tests.in attrs==21.2.0 # via pytest From f2b1dc45bd0baf5a5302893bc068e37b4c4d4575 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 18:50:27 +0000 Subject: [PATCH 99/99] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.20.0 → v2.23.1](https://github.com/asottile/pyupgrade/compare/v2.20.0...v2.23.1) - [github.com/asottile/reorder_python_imports: v2.5.0 → v2.6.0](https://github.com/asottile/reorder_python_imports/compare/v2.5.0...v2.6.0) - [github.com/psf/black: 21.6b0 → 21.7b0](https://github.com/psf/black/compare/21.6b0...21.7b0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a23c48af..6ee654a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,19 +2,19 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.20.0 + rev: v2.23.1 hooks: - id: pyupgrade args: ["--py36-plus"] - repo: https://github.com/asottile/reorder_python_imports - rev: v2.5.0 + rev: v2.6.0 hooks: - id: reorder-python-imports name: Reorder Python imports (src, tests) files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 21.6b0 + rev: 21.7b0 hooks: - id: black - repo: https://github.com/PyCQA/flake8