Mini Shell
# -*- test-case-name: twisted.web.test.test_template -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from typing import TYPE_CHECKING, Callable, List, Optional, TypeVar, Union, overload
from zope.interface import implementer
from twisted.web.error import (
MissingRenderMethod,
MissingTemplateLoader,
UnexposedMethodError,
)
from twisted.web.iweb import IRenderable, IRequest, ITemplateLoader
if TYPE_CHECKING:
from twisted.web.template import Flattenable, Tag
T = TypeVar("T")
class Expose:
"""
Helper for exposing methods for various uses using a simple decorator-style
callable.
Instances of this class can be called with one or more functions as
positional arguments. The names of these functions will be added to a list
on the class object of which they are methods.
"""
def __call__(self, *funcObjs: Callable) -> Callable:
"""
Add one or more functions to the set of exposed functions.
This is a way to declare something about a class definition, similar to
L{zope.interface.implementer}. Use it like this::
magic = Expose('perform extra magic')
class Foo(Bar):
def twiddle(self, x, y):
...
def frob(self, a, b):
...
magic(twiddle, frob)
Later you can query the object::
aFoo = Foo()
magic.get(aFoo, 'twiddle')(x=1, y=2)
The call to C{get} will fail if the name it is given has not been
exposed using C{magic}.
@param funcObjs: One or more function objects which will be exposed to
the client.
@return: The first of C{funcObjs}.
"""
if not funcObjs:
raise TypeError("expose() takes at least 1 argument (0 given)")
for fObj in funcObjs:
exposedThrough: List[Expose] = getattr(fObj, "exposedThrough", [])
exposedThrough.append(self)
setattr(fObj, "exposedThrough", exposedThrough)
return funcObjs[0]
_nodefault = object()
@overload
def get(self, instance: object, methodName: str) -> Callable:
...
@overload
def get(self, instance: object, methodName: str, default: T) -> Union[Callable, T]:
...
def get(
self, instance: object, methodName: str, default: object = _nodefault
) -> object:
"""
Retrieve an exposed method with the given name from the given instance.
@raise UnexposedMethodError: Raised if C{default} is not specified and
there is no exposed method with the given name.
@return: A callable object for the named method assigned to the given
instance.
"""
method = getattr(instance, methodName, None)
exposedThrough = getattr(method, "exposedThrough", [])
if self not in exposedThrough:
if default is self._nodefault:
raise UnexposedMethodError(self, methodName)
return default
return method
def exposer(thunk: Callable) -> Expose:
expose = Expose()
expose.__doc__ = thunk.__doc__
return expose
@exposer
def renderer() -> None:
"""
Decorate with L{renderer} to use methods as template render directives.
For example::
class Foo(Element):
@renderer
def twiddle(self, request, tag):
return tag('Hello, world.')
<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
<span t:render="twiddle" />
</div>
Will result in this final output::
<div>
<span>Hello, world.</span>
</div>
"""
@implementer(IRenderable)
class Element:
"""
Base for classes which can render part of a page.
An Element is a renderer that can be embedded in a stan document and can
hook its template (from the loader) up to render methods.
An Element might be used to encapsulate the rendering of a complex piece of
data which is to be displayed in multiple different contexts. The Element
allows the rendering logic to be easily re-used in different ways.
Element returns render methods which are registered using
L{twisted.web._element.renderer}. For example::
class Menu(Element):
@renderer
def items(self, request, tag):
....
Render methods are invoked with two arguments: first, the
L{twisted.web.http.Request} being served and second, the tag object which
"invoked" the render method.
@ivar loader: The factory which will be used to load documents to
return from C{render}.
"""
loader: Optional[ITemplateLoader] = None
def __init__(self, loader: Optional[ITemplateLoader] = None):
if loader is not None:
self.loader = loader
def lookupRenderMethod(
self, name: str
) -> Callable[[Optional[IRequest], "Tag"], "Flattenable"]:
"""
Look up and return the named render method.
"""
method = renderer.get(self, name, None)
if method is None:
raise MissingRenderMethod(self, name)
return method
def render(self, request: Optional[IRequest]) -> "Flattenable":
"""
Implement L{IRenderable} to allow one L{Element} to be embedded in
another's template or rendering output.
(This will simply load the template from the C{loader}; when used in a
template, the flattening engine will keep track of this object
separately as the object to lookup renderers on and call
L{Element.renderer} to look them up. The resulting object from this
method is not directly associated with this L{Element}.)
"""
loader = self.loader
if loader is None:
raise MissingTemplateLoader(self)
return loader.load()
Zerion Mini Shell 1.0