|
|
import typing as t
from . import typing as ft from .globals import current_app from .globals import request
http_method_funcs = frozenset( ["get", "post", "head", "options", "delete", "put", "trace", "patch"] )
class View: """Subclass this class and override :meth:`dispatch_request` to
create a generic class-based view. Call :meth:`as_view` to create a view function that creates an instance of the class with the given arguments and calls its ``dispatch_request`` method with any URL variables.
See :doc:`views` for a detailed guide.
.. code-block:: python
class Hello(View): init_every_request = False
def dispatch_request(self, name): return f"Hello, {name}!"
app.add_url_rule( "/hello/<name>", view_func=Hello.as_view("hello") )
Set :attr:`methods` on the class to change what methods the view accepts.
Set :attr:`decorators` on the class to apply a list of decorators to the generated view function. Decorators applied to the class itself will not be applied to the generated view function!
Set :attr:`init_every_request` to ``False`` for efficiency, unless you need to store request-global data on ``self``. """
#: The methods this view is registered for. Uses the same default #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and #: ``add_url_rule`` by default. methods: t.ClassVar[t.Optional[t.Collection[str]]] = None
#: Control whether the ``OPTIONS`` method is handled automatically. #: Uses the same default (``True``) as ``route`` and #: ``add_url_rule`` by default. provide_automatic_options: t.ClassVar[t.Optional[bool]] = None
#: A list of decorators to apply, in order, to the generated view #: function. Remember that ``@decorator`` syntax is applied bottom #: to top, so the first decorator in the list would be the bottom #: decorator. #: #: .. versionadded:: 0.8 decorators: t.ClassVar[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: """The actual view function behavior. Subclasses must override
this and return a valid response. Any variables from the URL rule are passed as keyword arguments. """
raise NotImplementedError()
@classmethod def as_view( cls, name: str, *class_args: t.Any, **class_kwargs: t.Any ) -> ft.RouteCallable: """Convert the class into a view function that can be registered
for a route.
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.
Except for ``name``, all other 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(**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 view.__module__ = cls.__module__ for decorator in cls.decorators: view = decorator(view)
# We attach the view class to the view function for two reasons: # first of all it allows us to easily figure out what class-based # view this thing came from, secondly it's also used for instantiating # the view class so you can actually replace it with something else # for testing purposes and debugging. view.view_class = cls # type: ignore view.__name__ = name view.__doc__ = cls.__doc__ view.__module__ = cls.__module__ view.methods = cls.methods # type: ignore view.provide_automatic_options = cls.provide_automatic_options # type: ignore return view
class MethodView(View): """Dispatches request methods to the corresponding instance methods.
For example, if you implement a ``get`` method, it will be used to handle ``GET`` requests.
This can be useful for defining a REST API.
:attr:`methods` is automatically set based on the methods defined on the class.
See :doc:`views` for a detailed guide.
.. code-block:: python
class CounterAPI(MethodView): def get(self): return str(session.get("counter", 0))
def post(self): session["counter"] = session.get("counter", 0) + 1 return redirect(url_for("counter"))
app.add_url_rule( "/counter", view_func=CounterAPI.as_view("counter") ) """
def __init_subclass__(cls, **kwargs: t.Any) -> None: super().__init_subclass__(**kwargs)
if "methods" not in cls.__dict__: methods = set()
for base in cls.__bases__: if getattr(base, "methods", None): methods.update(base.methods) # type: ignore[attr-defined]
for key in http_method_funcs: if hasattr(cls, key): methods.add(key.upper())
if methods: cls.methods = methods
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 # retry with GET. if meth is None and request.method == "HEAD": meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}" return current_app.ensure_sync(meth)(**kwargs)
|