You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
import typing as t
|
|
|
|
from .globals import current_app
|
|
from .globals import request
|
|
from .typing import ResponseReturnValue
|
|
|
|
|
|
http_method_funcs = frozenset(
|
|
["get", "post", "head", "options", "delete", "put", "trace", "patch"]
|
|
)
|
|
|
|
|
|
class View:
|
|
"""Alternative way to use view functions. A subclass has to implement
|
|
:meth:`dispatch_request` which is called with the view arguments from
|
|
the URL routing system. If :attr:`methods` is provided the methods
|
|
do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
|
|
method explicitly::
|
|
|
|
class MyView(View):
|
|
methods = ['GET']
|
|
|
|
def dispatch_request(self, name):
|
|
return f"Hello {name}!"
|
|
|
|
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
|
|
|
When you want to decorate a pluggable view you will have to either do that
|
|
when the view function is created (by wrapping the return value of
|
|
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
|
|
|
class SecretView(View):
|
|
methods = ['GET']
|
|
decorators = [superuser_required]
|
|
|
|
def dispatch_request(self):
|
|
...
|
|
|
|
The decorators stored in the decorators list are applied one after another
|
|
when the view function is created. Note that you can *not* use the class
|
|
based decorators since those would decorate the view class and not the
|
|
generated view function!
|
|
"""
|
|
|
|
#: A list of methods this view can handle.
|
|
methods: t.Optional[t.List[str]] = None
|
|
|
|
#: Setting this disables or force-enables the automatic options handling.
|
|
provide_automatic_options: t.Optional[bool] = None
|
|
|
|
#: The canonical way to decorate class-based views is to decorate the
|
|
#: return value of as_view(). However since this moves parts of the
|
|
#: logic from the class declaration to the place where it's hooked
|
|
#: into the routing system.
|
|
#:
|
|
#: You can place one or more decorators in this list and whenever the
|
|
#: view function is created the result is automatically decorated.
|
|
#:
|
|
#: .. versionadded:: 0.8
|
|
decorators: t.List[t.Callable] = []
|
|
|
|
def dispatch_request(self) -> ResponseReturnValue:
|
|
"""Subclasses have to override this method to implement the
|
|
actual view function code. This method is called with all
|
|
the arguments from the URL rule.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@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.
|
|
|
|
The arguments passed to :meth:`as_view` are forwarded to the
|
|
constructor of the class.
|
|
"""
|
|
|
|
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
|
|
self = view.view_class(*class_args, **class_kwargs) # type: ignore
|
|
return current_app.ensure_sync(self.dispatch_request)(*args, **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 MethodViewType(type):
|
|
"""Metaclass for :class:`MethodView` that determines what methods the view
|
|
defines.
|
|
"""
|
|
|
|
def __init__(cls, name, bases, d):
|
|
super().__init__(name, bases, d)
|
|
|
|
if "methods" not in d:
|
|
methods = set()
|
|
|
|
for base in 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 we have no method at all in there we don't want to add a
|
|
# method list. This is for instance the case for the base class
|
|
# or another subclass of a base method view that does not introduce
|
|
# new methods.
|
|
if methods:
|
|
cls.methods = methods
|
|
|
|
|
|
class MethodView(View, metaclass=MethodViewType):
|
|
"""A class-based view that dispatches request methods to the corresponding
|
|
class methods. For example, if you implement a ``get`` method, it will be
|
|
used to handle ``GET`` requests. ::
|
|
|
|
class CounterAPI(MethodView):
|
|
def get(self):
|
|
return session.get('counter', 0)
|
|
|
|
def post(self):
|
|
session['counter'] = session.get('counter', 0) + 1
|
|
return 'OK'
|
|
|
|
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
|
"""
|
|
|
|
def dispatch_request(self, *args: t.Any, **kwargs: t.Any) -> 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)(*args, **kwargs)
|