#!/usr/bin/env python
"""
No app built with Seshat does much without controllers. This module provides a
base controller class which can be used right away in its current state, or can
be inherited from to create more advanced or custom controllers.
Basic use is like so::
from seshat.controller import Controller
class index(Controller):
def GET(self):
return "<h1>WAT</h1>"
By default all controllers have HEAD and GET methods. HEAD simply calls GET but
strips the reponse body. (HEAD is probably broken for now but I'll fix it
eventually).
"""
"""
For more information and licensing, see: https://github.com/JoshAshby/seshat
http://xkcd.com/353/
Josh Ashby
2014
http://joshashby.com
joshuaashby@joshashby.com
"""
import traceback
import actions
import logging
from response import Response
from request import Request
from headers import ResponseHeaders
logger = logging.getLogger("seshat.controller")
[docs]class Controller(object):
"""
The parent of all controllers which Seshat will serve.
To use this to make a controller, override or add the request method (in
all caps) which will be called for this controller. Eg::
from seshat.controller import Controller
class index(Controller):
def GET(self):
return "<h1>WAT</h1>"
then all GET based requests to this controller will return with the text
`<h1>WAT</h1>` however all POST, PUT, DELETE calls will a 405 Method Not
Supported error.
"""
def __init__(self, request=None, session=None):
self.request = request or Request()
self.post_init_hook()
self.headers = ResponseHeaders()
self.session = session
self.finish_init()
def finish_init(self):
pass
[docs] def post_init_hook(self):
"""
Called at the end of `__init__` this allows you to customize the
creation process of your controller, without having to override
`__init__` itself.
This should accept nothing and return nothing.
"""
pass
def __call__(self):
try:
c = self.pre_content_hook()
if c is not None:
if isinstance(c, actions.Action):
return c()
if hasattr(self, self.request.method):
c = getattr(self, self.request.method)()
if isinstance(c, actions.Action):
return c()
if isinstance(c, Response):
return c
self.post_content_hook(c)
return Response(200, self.headers, c)
else:
return actions.MethodNotSupported()()
# TODO: Add code to make this not crash
except Exception as e:
tb = str(traceback.format_exc())
logger.exception(e)
logger.error(tb)
return actions.InternalServerError(e, tb)()
[docs] def pre_content_hook(self):
"""
Called before the request method is called and should return either
`None` or :py:class:`.Action` object.
If there is a returned value other than None, this will skip calling
the request method and simply return directly to dispatch, so make sure
it returns an :py:class:`.Action`.
A good example of the use for this hook would be for authentication.
You could for example, check a parameter set through a cookie and
return something like a 401 Unauthorized if the param doesn't represent
a logged in user::
return actions.Unauthorized()
:rtype: :py:class:`.Action` or `None`
"""
return None
[docs] def post_content_hook(self, content):
"""
Gets called after the content generating request method has been
called. This can be to further modify the content which is returned, or
perform some other action after each request.
:param content: the content from the content generating request method
that was called.
:type content: `str`
:return: The original or modified content
:rtype: `str`
"""
return content
[docs] def HEAD(self):
"""
Will be called if the request method is HEAD
By default this will call `GET()` but return nothing, so that only the
Headers are returned to the client.
"""
self.GET()
[docs] def GET(self):
"""
Will be called if the request method is GET
"""
pass