[Answered ]-CherryPy with Cheetah as plugin + tool – blank pages

2👍

In general, to me it’s some sort of over-engineering happened in your snippets. CherryPy plugins are usually used for a system task (e.g. put PID-file on engine start, remove it on stop) or for an asynchronous task (e.g. sending email in separate thread). Template rendering happens clearly synchronously to the request handling, so I don’t see the point of extracting this logic out of CherryPy tool. There’s a class in CherryPy, cherrypy._cptools.HandlerWrapperTool, which demonstrate the suggested approach to wrapping handler return values.

I haven’t ever used Cheetah, so my example is Jinja2-based. You will just have to change the templating engine instance (to Cheetah) and correct its calls. The rest is the same.

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import os
import types

import cherrypy
import jinja2


path   = os.path.abspath(os.path.dirname(__file__))
config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 4
  }
}


class TemplateTool(cherrypy.Tool):

  _engine = None
  '''Jinja environment instance'''


  def __init__(self):
    viewLoader   = jinja2.FileSystemLoader(os.path.join(path, 'view'))
    self._engine = jinja2.Environment(loader = viewLoader)

    cherrypy.Tool.__init__(self, 'before_handler', self.render)

  def __call__(self, *args, **kwargs):
    if args and isinstance(args[0], (types.FunctionType, types.MethodType)):
      # @template
      args[0].exposed = True
      return cherrypy.Tool.__call__(self, **kwargs)(args[0])
    else:
      # @template()
      def wrap(f):
        f.exposed = True
        return cherrypy.Tool.__call__(self, *args, **kwargs)(f)
      return wrap

  def render(self, name = None):
    cherrypy.request.config['template'] = name

    handler = cherrypy.serving.request.handler
    def wrap(*args, **kwargs):
      return self._render(handler, *args, **kwargs)
    cherrypy.serving.request.handler = wrap

  def _render(self, handler, *args, **kwargs):
    template = cherrypy.request.config['template']
    if not template:
      parts = []
      if hasattr(handler.callable, '__self__'):
        parts.append(handler.callable.__self__.__class__.__name__.lower())
      if hasattr(handler.callable, '__name__'):
        parts.append(handler.callable.__name__.lower())
      template = u'/'.join(parts)

    data     = handler(*args, **kwargs) or {}
    renderer = self._engine.get_template(u'{0}.html'.format(template))

    return renderer.render(**data)


cherrypy.tools.template = TemplateTool()


class App:

  @cherrypy.expose
  def index(self):
    '''No renderer applied, CherryPy outputs dict keys'''
    return {'user': 123}

  @cherrypy.tools.template
  def auto(self):
    return {'user': 123}

  @cherrypy.tools.template(name = 'app/auto')
  def manual(self):
    return {'user': 234}


if __name__ == '__main__':
  cherrypy.quickstart(App(), '/', config)

Along the python file, create directory view/app and put the following in file named auto.html there.

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv='content-type' content='text/html; charset=utf-8' />
    <title>Test</title>
  </head>
  <body>
    <p>User: <em>{{ user }}</em></p>
  </body>
</html>

Some notes on the TemplateTool. First, you can use it as a decorator in two ways: not making a call, and making a call with template name argument. You can use the tool as any other CherryPy tool in the configuration (e.g. make all controller methods to render templates). Second, following convention-over-configuration principle, the tool when not provided with template name will use classname/methodname.html. Third, the decorator exposes the method, so you don’t need to add @cherrypy.expose on top.

👤saaj

Leave a comment