add View.init_every_request attribute

This commit is contained in:
David Lord 2022-06-06 11:04:04 -07:00
parent aab1d9935e
commit 6e23239567
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
3 changed files with 61 additions and 13 deletions

View File

@ -22,7 +22,9 @@ Unreleased
:issue:`4571`
- ``before_first_request`` is deprecated. Run setup code when creating
the application instead. :issue:`4605`
- Added the ``View.init_every_request`` class attribute. If a view
subclass sets this to ``False``, the view will not create a new
instance on every request. :issue:`2520`.
Version 2.1.3
-------------

View File

@ -59,6 +59,18 @@ class View:
#: .. versionadded:: 0.8
decorators: t.List[t.Callable] = []
#: Create a new instance of this view class for every request by
#: default. If a view subclass sets this to ``False``, the same
#: instance is used for every request.
#:
#: A single instance is more efficient, especially if complex setup
#: is done during init. However, storing data on ``self`` is no
#: longer safe across requests, and :data:`~flask.g` should be used
#: instead.
#:
#: .. versionadded:: 2.2
init_every_request: t.ClassVar[bool] = True
def dispatch_request(self) -> ft.ResponseReturnValue:
"""Subclasses have to override this method to implement the
actual view function code. This method is called with all
@ -69,19 +81,35 @@ class View:
@classmethod
def as_view(
cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
) -> t.Callable:
"""Converts the class into an actual view function that can be used
with the routing system. Internally this generates a function on the
fly which will instantiate the :class:`View` on each request and call
the :meth:`dispatch_request` method on it.
) -> ft.ViewCallable:
"""Convert the class into a view function that can be registered
for a route.
The arguments passed to :meth:`as_view` are forwarded to the
constructor of the class.
By default, the generated view will create a new instance of the
view class for every request and call its
:meth:`dispatch_request` method. If the view class sets
:attr:`init_every_request` to ``False``, the same instance will
be used for every request.
The arguments passed to this method are forwarded to the view
class ``__init__`` method.
.. versionchanged:: 2.2
Added the ``init_every_request`` class attribute.
"""
if cls.init_every_request:
def view(*args: t.Any, **kwargs: t.Any) -> ft.ResponseReturnValue:
self = view.view_class(*class_args, **class_kwargs) # type: ignore
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
self = view.view_class( # type: ignore[attr-defined]
*class_args, **class_kwargs
)
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
else:
self = cls(*class_args, **class_kwargs)
def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
if cls.decorators:
view.__name__ = name
@ -146,7 +174,7 @@ class MethodView(View, metaclass=MethodViewType):
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
"""
def dispatch_request(self, *args: t.Any, **kwargs: t.Any) -> ft.ResponseReturnValue:
def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it
@ -155,4 +183,4 @@ class MethodView(View, metaclass=MethodViewType):
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
return current_app.ensure_sync(meth)(*args, **kwargs)
return current_app.ensure_sync(meth)(**kwargs)

View File

@ -240,3 +240,21 @@ def test_remove_method_from_parent(app, client):
assert client.get("/").data == b"GET"
assert client.post("/").status_code == 405
assert sorted(View.methods) == ["GET"]
def test_init_once(app, client):
n = 0
class CountInit(flask.views.View):
init_every_request = False
def __init__(self):
nonlocal n
n += 1
def dispatch_request(self):
return str(n)
app.add_url_rule("/", view_func=CountInit.as_view("index"))
assert client.get("/").data == b"1"
assert client.get("/").data == b"1"