123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- 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``.
- """
-
-
-
- methods: t.ClassVar[t.Optional[t.Collection[str]]] = None
-
-
-
- provide_automatic_options: t.ClassVar[t.Optional[bool]] = None
-
-
-
-
-
-
- decorators: t.ClassVar[t.List[t.Callable]] = []
-
-
-
-
-
-
-
-
-
-
- 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.
- 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(**kwargs: t.Any) -> ft.ResponseReturnValue:
- self = view.view_class(
- *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)
-
-
-
-
-
- view.view_class = cls
- view.__name__ = name
- view.__doc__ = cls.__doc__
- view.__module__ = cls.__module__
- view.methods = cls.methods
- view.provide_automatic_options = cls.provide_automatic_options
- 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)
- 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 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)
|