Source

vlastic / vlastic / rom / resource.py

Full commit
from .. import http

__all__ = ["Resource", "ObjectResource", "DictionaryResource"]

METHOD_HANDLER_ATTRIBUTE_NAME = "\nrom_methods\n"

def dispatch_method_handler(resource, method):
    """Dispatch a member function which is mapped to given method. Returns
    a tuple (method_handler, view_function) if it is found, or None.

    """
    if hasattr(resource, METHOD_HANDLER_ATTRIBUTE_NAME):
        cache = getattr(resource, METHOD_HANDLER_ATTRIBUTE_NAME)
        if method in cache:
            return cache[method]
    else:
        cache = {}
        setattr(resource, METHOD_HANDLER_ATTRIBUTE_NAME, cache)
    for attr in dir(resource):
        attr = getattr(resource, attr)
        if hasattr(attr, METHOD_HANDLER_ATTRIBUTE_NAME):
            names = getattr(attr, METHOD_HANDLER_ATTRIBUTE_NAME)
            if method in names:
                cache[method] = attr, names[method]
                return attr, names[method]


class Resource:
    """Resource accepts a request and returns a response.

        class SessionStore(vlastic.rom.Resource):
            @vlastic.rom.get
            def get_user(self):
                return {"name": ...}

            @vlastic.rom.post
            @vlastic.rom.put
            def login(self, name, password):
                ...

            @vlastic.rom.delete
            def logout(self):
                ...

    When sess = SessionStore(), it RESTfully maps to HTTP. See also following
    table:

        +----------------------------+----------------------+
        | HTTP                       | Python object        |
        +----------------------------+----------------------+
        | GET /                      | sess.get_user()      |
        | POST /?name=a&password=b   | sess.login("a", "b") |
        | PUT /?name=a&password=b    | sess.login("a", "b") |
        | DELETE /                   | sess.logout()        |
        +----------------------------+----------------------+

    """

    def __call__(self, request):
        if tuple(request.path) != ():
            return http.NotFoundError()
        method = request.method.upper()
        dispatch_result = dispatch_method_handler(self, method)
        if dispatch_result:
            method_handler, view = dispatch_result
            result = method_handler()
            try:
                response = view(result, request)
            except TypeError:
                response = view(result)
            if not isinstance(response, http.Response):
                response = http.Response(
                    (b"HTTP/1.1", 200, b"OK"),
                    {b"Content-Type": b"text/html"},
                    response
                )
            return response
        return http.MethodNotAllowedError()


class ObjectResource(Resource):
    """ObjectResource is the resource which be able access childs with
    same way of handling object attributes. 

    """

    def __call__(self, request):
        path = request.path
        try:
            child_name = next(path)
        except StopIteration:
            return Resource.__call__(self, request)
        if hasattr(self, child_name):
            return getattr(self, child_name)(request)
        return http.NotFoundError()


class DictionaryResource(Resource):
    """DictionaryResource is the resource which be able access childs by
    same way of handling dict.
    
    """

    def __call__(self, request):
        path = request.path
        try:
            child_name = next(path)
        except StopIteration:
            return Resource.__call__(self, request)
        try:
            child = self[child_name]
        except KeyError:
            return http.NotFoundError()
        return child(request)

    def __getitem__(self, child_name):
        return self.child[str(child_name)]

    def __setitem__(self, child_name, child):
        if not hasattr(self, "child"):
            self.child = {}
        self.child[str(child_name)] = child

    def __delitem__(self, child_name):
        del self.child[str(child_name)]