Source code for tet.view

"""
View utilities and base classes for Tet applications.

This module provides view-related utilities including:

- :class:`view_config` - Extended Pyramid view configuration decorator
- :class:`expose` - Decorator for exposing controller methods as views
- :class:`BaseController` - Base class for traversal-based controllers
- :class:`ServiceViews` - Base class for service-based view classes

Example
-------

Using the expose decorator with controllers::

    from tet.view import BaseController, expose

    class UserController(BaseController):
        @expose(renderer="json")
        def index(self):
            return {"users": []}

        @expose(renderer="json")
        def profile(self):
            return {"user": "john"}

Using ServiceViews for dependency injection::

    from tet.view import ServiceViews
    from pyramid.view import view_config

    class UserViews(ServiceViews):
        @view_config(route_name="users", renderer="json")
        def list_users(self):
            # self.request and self.context are available
            return {"users": []}
"""
from inspect import isclass

from pyramid.request import Request
from pyramid.view import *
from pyramid.view import view_config as _pyramid_view_config
from pyramid_di import RequestScopedBaseService


[docs] class view_config(_pyramid_view_config): """Extended Pyramid view_config decorator.""" def __init__(self, **settings): super(view_config, self).__init__(**settings)
[docs] class expose(object): """ Decorator for exposing controller methods as views. Use on methods of :class:`BaseController` subclasses. The method name becomes the view name (``index`` becomes the default view). """ venusian = venusian def __init__(self, **settings): self.__dict__.update(settings) def __call__(self, wrapped): settings = self.__dict__.copy() def callback(context, name, ob): config = context.config.with_package(info.module) name = attr_name if name == "index": name = "" def view_wrapper(request): # TODO: should we stack the request? return getattr(request.context, attr_name)() config.add_view(view=view_wrapper, name=name, context=ob, **settings) info = self.venusian.attach(wrapped, callback, category="pyramid") if info.scope != "class": # if the decorator was attached to a method in a class, or # otherwise executed at class scope, we need to set an # 'attr' into the settings if one isn't already in there raise ValueError("expose can be only applied to instance methods!") attr_name = wrapped.__name__ settings["_info"] = info.codeinfo # fbo "action_method" return wrapped
[docs] class BaseController(object): """ Base class for traversal-based controllers. Supports nested controllers as class attributes and custom lookup via ``_lookup`` method. """ def __getitem__(self, name): """Look up child controller by name.""" if hasattr(self, "_lookup"): try: return self._lookup(name) except KeyError: pass child_controller = getattr(self, name, None) if isclass(child_controller) and issubclass(child_controller, BaseController): child = child_controller(self.request) child.__parent__ = self child.__name__ = name return child raise KeyError("Child not found: %s" % name)
[docs] class ServiceViews(RequestScopedBaseService): """ Base class for view classes with dependency injection support. Provides ``self.request`` and ``self.context`` attributes. """ def __init__(self, request: Request): super().__init__(request=request) self.context = getattr(request, "context", None)