Source code for gimme.controller

import sys
import traceback
import types
from jinja2 import Environment, PackageLoader

from .renderers import (
    BaseRenderer,
    Template,
    Json,
    Compress,
    Format,
    BulkRenderer
)


[docs]class MethodRenderer(list): ''' The ``MethodRenderer`` provides an interface for multiple renderers to all manipulate the controller method's output in an orderly fashion. It should be noted that the ``MethodRenderer`` class inherits from :func:`list`. The first item in the list should always be an instance of :class:`gimme.controller.ControllerMethod`. Every other item should inherit from :class:`gimme.renderers.BaseRenderer`. ''' def __init__(self, items=[]): if len(items) < 1 or not isinstance(items[0], ControllerMethod): raise ValueError("First argument to MethodRenderer must be " "an instance of ControllerMethod") self.im_class = items[0].im_class self.__name__ = items[0].__name__ list.__init__(self, items)
[docs] def __call__(self): ''' Calls the :class:`ControllerMethod <gimme.controller.ControllerMethod>` and then calls :meth:`render` on each of the :class:`BaseRenderer <gimme.renderers.BaseRenderer>` objects. :return: The final output of the execution chain described above. ''' controller = self[0].controller_instance data = self[0]() for i in self[1:]: data = i.render(controller, data) return data
[docs] def __add__(self, other): ''' Adds another :class:`BaseRenderer <gimme.renderers.BaseRenderer>` to the list. :param BaseRenderer other: The renderer to add. :return: self ''' if not isinstance(other, BaseRenderer): other = Template(other) self.append(other) return self
def __repr__(self): return "<MethodRenderer([%s])>" % ', '.join(map(str, self))
[docs] def __eq__(self, content_type): ''' Returns a :class:`Format <gimme.renderers.Format>` object with all of the renderers in this object inside it. Sets the ``Format`` content type to ``content_type``. This is typically used in something like the following:: app.routes.get('/', SomeController.index + ( (SomeController.index == 'text/plain') | # <-- (SomeController.index.json() == 'application/json') | (SomeController.index.template('template.html') == 'text/html') )) :param str content_type: The content type to set the ``Format`` object to. :return: A :class:`Format <gimme.renderers.Format>` object. ''' return Format(BulkRenderer(list(self[1:])), content_type)
[docs] def template(self, template_path): ''' Adds a :class:`Template <gimme.renderers.Template>` object with the provided path to the list. :param str template_path: The path to the template :return: self ''' self.append(Template(template_path)) return self
[docs] def json(self): ''' Adds a :class:`Json <gimme.renderers.Json>` object to the list. :return: self ''' self.append(Json()) return self
[docs] def compress(self): ''' Adds a :class:`Compress <gimme.renderers.Compress>` object to the list. :return: self ''' self.append(Compress()) return self
[docs]class ControllerMethod(object): ''' ControllerMethod objects wrap regular controller methods, as defined by the application programmer. In order to allow greater flexibility and expressiveness, all controller methods whose name do not start with ``_`` are replaced with instances of ``ControllerMethod``. These objects can be called just as the controller methods can, but in addition, other methods and operators are made available. What all this means, in practice, is that you can do things like this in your route definitions:: app.routes.get('/', RootController.index + 'index.html') I hope to elaborate on this and how it works in the future. ''' def __init__(self, cls, fn): self.im_class = cls self.fn = fn self.__name__ = fn.__name__ def __call__(self): return self.fn(self.controller_instance)
[docs] def __add__(self, other): ''' Creates a new :class:`gimme.controller.MethodRenderer` object with the ``other`` object as the first renderer. Or, if ``other`` is a string, instantiates a :class:`gimme.renderers.Template` object with the string and passes that to the :class:`gimme.controller.MethodRenderer` instead. Example:: method_renderer = SomeController.some_method + gimme.Template('index.html') # or, a shortcut: method_renderer = SomeController.some_method + 'index.html' :param other: Either a string or an instance of :class:`gimme.renderers.BaseRenderer`. ''' if not isinstance(other, BaseRenderer): other = Template(other) return MethodRenderer([self, other])
[docs] def __eq__(self, content_type): ''' Returns a :class:`gimme.renderers.Format` object that responds to HTTP Accept headers as specified by ``content_type``. Example:: format_obj = YourController.your_method == 'text/html' :param str content_type: The content type to customize format to. ''' return Format(BulkRenderer([]), content_type)
[docs] def template(self, template_path): ''' Returns a :class:`gimme.controller.MethodRenderer` object with a :class:`gimme.renderers.Template` renderer directed at the path provided by ``template_path``. :param str template_path: Where to point the template to. ''' return MethodRenderer([self, Template(template_path)])
[docs] def json(self): ''' Returns a :class:`gimme.controller.MethodRenderer` object with a :class:`gimme.renderers.Json` renderer. ''' return MethodRenderer([self, Json()])
[docs] def compress(self): ''' Returns a :class:`gimme.controller.MethodRenderer` object with a :class:`gimme.renderers.Compress` renderer. ''' return MethodRenderer([self, Compress()])
class ControllerMeta(type): def __init__(mcs, name, bases, attrs): for key, value in [(k, v) for (k, v) in attrs.iteritems() if not k.startswith('_')]: if hasattr(value, '__call__'): setattr(mcs, key, ControllerMethod(mcs, value)) type.__init__(mcs, name, bases, attrs)
[docs]class Controller(object): ''' Like most frameworks, the controller is what bridges the gap between the model (business logic) and the view. In Gimme, one thing that is a little different from the classic MVC methodology is that controllers are not directly tied to views. This is explained further in the :class:`gimme.routes.Routes` documentation. Controllers are instantiated automatically by Gimme on an as-needed basis. If a controller's method is needed to fulfill a request, the controller will be instantiated and the method called and its data returned. This means that controllers are instantiated per request, as opposed to once, as the application is started. One thing to note is that, while Gimme instantiates controllers automatically, in order to perform unit tests, you must instantiate controllers in your tests. This can be done like so:: controller = YourController(your_app, request_obj, response_obj) Of course, to do the above, you must have request and response objects. To fetch these objects, the easiest way is generally via the :meth:`gimme.routes.Routes.match` method:: request, response = your_app.routes.match(wsgi_environ) (Where the ``wsgi_environ`` variable is a dictionary-like object with necessary WSGI environ parameters, such as ``PATH_INFO``, ``REQUEST_METHOD``, etc.) One other thing that may be worth noting is that this class uses a custom metaclass that, upon definition, scans for any method whose name does not start with a ``_``, and replaces it with an instance of :class:`gimme.controller.ControllerMethod`. :ivar app: The gimme app. :ivar request: The request object. :ivar response: The response object. ''' __metaclass__ = ControllerMeta def __init__(self, app, request, response): self.app = app self.request = request self.response = response def __new__(cls, *args): obj = object.__new__(cls, *args) for i in dir(obj): attr = getattr(obj, i) if isinstance(attr, ControllerMethod): attr.controller_instance = obj return obj
class ErrorController(Controller): def __init__(self, *args, **kwargs): Controller.__init__(self, *args, **kwargs) self.environment = Environment( loader=PackageLoader('gimme', 'templates')) def http404(self): self.response.status = 404 return self.environment.get_template('errors/404.html').render({ 'headers': self.request.headers, }) def http500(self): self.response.status = 500 e_type, e_value, e_traceback = sys.exc_info() traceback.print_exception(e_type, e_value, e_traceback) return self.environment.get_template('errors/500.html').render({ 'message': "Oh snap! Something's borked. :(", 'headers': self.request.headers, 'traceback': traceback.format_exception( e_type, e_value, e_traceback) }) def generic(self): return self.environment.get_template('errors/generic.html').render({ 'status': self.response._status })