Source code for kyoukai.routegroup

"""
Route groups are classes that allow you to group a set of routes together.

.. currentmodule:: kyoukai.routegroup
"""
import collections
import inspect
import typing


[docs]def get_rg_bp(group: 'RouteGroup'): """ Gets the :class:`~.Blueprint` created from a :class:`~.RouteGroup`. """ return getattr(group, "_{0.__name__}__blueprint".format(type(group)))
[docs]class RouteGroupType(type): """ The metaclass for a route group. This is responsible for passing the keyword arguments to the metaclass. """ def __new__(mcs, name, bases, class_body, **kwargs): """ Override of `__new__` to ensure the __init__ signature is compatible. """ return super().__new__(mcs, name, bases, class_body) def __init__(self, name, bases, class_body, **kwargs): """ Override of `__init__` to store the blueprint params. """ super().__init__(name, bases, class_body) self._bp_kwargs = kwargs
[docs] def _init_blueprint(self, obb): """ Initializes the Blueprint used by this route group. :param obb: The route group instance to intialize. """ # circular imports tm from kyoukai.blueprint import Blueprint bp = Blueprint(self.__name__, **self._bp_kwargs) # get all the method types that have a `.route` attr on them for name, value in inspect.getmembers(obb): # unwrap methods if not hasattr(value, "__func__"): continue func = value.__func__ if getattr(func, "in_group", False) is True: # check the delegate type if func.rg_delegate == "route": # wrap value, but use func attrs # this preserves the method and `self` rtt = bp.wrap_route(value, **func.route_kwargs) rtt.routes = func.routes rtt.bp = bp # copy hooks for type_, hooks in func.route_hooks.items(): for hook in hooks: rtt.add_hook(type_, hook) bp.routes.append(rtt) elif func.rg_delegate == "errorhandler": # add the error handler using `errorhandler_code` for code in func.errorhandler_codes: bp.add_errorhandler(value, code) elif func.rg_delegate == "hook": # add the hook bp.add_hook(func.hook_type, value) setattr(obb, "_{.__name__}__blueprint".format(self), bp)
def __call__(self, *args, **kwargs): obb = object.__new__(self) obb.__init__(*args, **kwargs) self._init_blueprint(obb) return obb
[docs]def route(url: str, methods: typing.Iterable[str] = ("GET", "HEAD"), **kwargs): """ A companion function to the RouteGroup class. This follows :meth:`.Blueprint.route` in terms of arguments, and marks a function as a route inside the class. This will return the original function, with some attributes attached: - ``in_group``: Marks the function as in the route group. - ``rg_delegate``: Internal. The type of function inside the group this is. - ``route_kwargs``: Keyword arguments to provide to ``wrap_route``. - ``route_url``: The routing URL to provide to ``add_route``. - ``route_methods``: The methods for the route. - ``route_hooks``: A defaultdict of route-specific hooks. Additionally, the following methods are added. - ``hook``: A decorator that adds a hook of type ``type_``. - ``before_request``: A decorator that adds a ``pre`` hook. - ``after_request``: A decorator that adds a ``post`` hook. .. versionadded:: 2.1.1 .. versionchanged:: 2.1.3 Added the ability to add route-specific hooks. .. versionchanged:: 2.2.0 Now accepts an already edited function as the function to decorate - this will add a new \ routing url and method pair to the :attr:`.Route.routes`. .. versionchanged:: 2.2.2 Default methods changed to GET and HEAD. :param url: The routing URL of the route. :param methods: An iterable of methods for the route. """ def inner(func): # add the required attrs which are used on a scan later func.in_group = True func.rg_delegate = "route" func.route_kwargs = kwargs # try and append to the routes # failing that, create a new list try: func.routes.append((url, methods)) except AttributeError: func.routes = [(url, methods)] if not hasattr(func, "route_hooks"): func.route_hooks = collections.defaultdict(lambda: []) # helper for route-specific hooks. def hook(type_: str): def _inner2(hookfunc): func.route_hooks[type_].append(hookfunc) return hookfunc return _inner2 func.hook = hook func.before_request = hook("pre") func.after_request = hook("post") return func return inner
[docs]def errorhandler(startcode: int, endcode: int = None, step: int = None): """ A companion function to the RouteGroup class. This follows :meth:`.Blueprint.errorhandler` in terms of arguments. :param startcode: The error code to handle, for example 404. This also represents the start of an error range, if endcode is not None. :param endcode: The end of the error code range to handle. Error handlers will be added for all requests between startcode and endcode. :param step: The step for the error handler range. """ def inner(func): func.in_group = True func.rg_delegate = "errorhandler" # less code here if endcode is None: codes = [startcode] else: codes = range(startcode, endcode, step or 1) for code in codes: try: func.errorhandler_codes.append(code) except AttributeError: func.errorhandler_codes = [code] return func return inner
# hooks
[docs]def hook(type_: str): """ Marks a function as a hook. :param type_: The type of hook to mark. """ def inner(func): func.in_group = True func.rg_delegate = "hook" func.hook_type = type_ return func return inner
[docs]def before_request(func): """ Helper decorator to mark a function as a pre-request hook. """ return hook("pre")(func)
[docs]def after_request(func): """ Helper decorator to mark a function as a post-request hook. """ return hook("post")(func)
[docs]class RouteGroup(object, metaclass=RouteGroupType): """ A route group is a class that contains multiple methods that are decorated with the route decorator. They produce a blueprint that can be added to the tree that includes all methods in the route group. .. code-block:: python class MyGroup(RouteGroup, prefix="/api/v1"): def __init__(self, something: str): self.something = something @route("/ping") async def ping(self, ctx: HTTPRequestContext): return '{"response": self.something}' Blueprint parameters can be passed in the class call. To add the route group as a blueprint, use :meth:`.Blueprint.add_route_group(MyGroup, *args, **kwargs)`. """