Skip to content

Instantly share code, notes, and snippets.

@defnull
Created September 14, 2011 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save defnull/1216497 to your computer and use it in GitHub Desktop.
Save defnull/1216497 to your computer and use it in GitHub Desktop.
Subclassable ViewPlugin
class ViewPlugin(object):
''' Automatically renders a template for callbacks that return a dictionary.
Subclasses may overrule some methods to implement engine specific
features. They must implement :meth:`prepare_file` and
:meth:`prepare_source`, should implement :meth:`prepare_factory` and may
implement :meth:`assemble_config`, :meth:`is_source`, :meth:`locate` and
additional methods.
'''
name = 'view'
api = 2
def __init__(self, view=None, **conf):
self.view = view
self.conf = conf
def apply(self, callback, route):
view = route.config.get('view', self.view)
if not view: return callback
config = self.assemble_config(route)
app = route.app
render = self.prepare(view, **config)
def wrapper(*a, **ka):
rv = callback(*a, **ka)
if isinstance(rv, (dict, UserDict)):
rv.update(_view=self, _tpl=tpl, _app=app)
return render(rv)
return rv
return wrapper
def is_source(self, view):
''' Return True if the supplied string looks like template source code,
False otherwise. '''
return "\n" in view or "{" in view or "%" in view or '$' in view
def locate(self, name, config):
''' Given a template or file name and the assembled config dictionary,
return the path to the template file, or None. The default
implementation tries every search path in config['lookup'] to find
a matching file. If the path contains `%s` (e.g. `./views/%s.tpl`),
it is used as a format string to get the full path. '''
for path in ['%s'] + (config.get('lookup') or []):
if '%s' in path: path %= name
else: path = os.path.join(path, name)
if os.path.isfile(path): return fname
def assemble_config(self, route):
''' Merge config settings from four sources: Application, plugin class,
plugin instance and route config. The default implementation merges
all four configurations and overwrites existing keys. Subclasses
may decide to merge or check certain values. '''
config = (route.app.config.get('view') or {}).copy()
config.update(self.conf)
config.update(route.config.get('view_conf') or {})
return config
def prepare(self, view, **config):
''' Given a view identifier (which might be a factory callable, a source
string or a template name) and optional configuration as
additional keyword arguments, this method returns a callable
or raises an exception. The returned callable accepts a dictionary
with template vars and returns the rendered template as a string or
string iterator. '''
if callable(view):
return self.prepare_factory(view, **config)
elif self.is_source(view):
return self.prepare_source(view, **config)
else:
filepath = self.locate(view, config)
if not filepath: raise TemplateError('Template %r not found.'%view)
return self.prepare_file(filepath, **config)
def prepare_file(self, filepath, **config):
''' :meth:`prepare` implementation that excepts a file path. '''
raise NotImplementedError('View plugin does not support named views.')
def prepare_source(self, source, **config):
''' :meth:`prepare` implementation that excepts a source string. '''
raise NotImplementedError('View plugin does not support source views.')
def prepare_factory(self, factory, **config):
''' :meth:`prepare` implementation that excepts a factory callable
(usually a pre-configured template instance). '''
raise NotImplementedError('View plugin does not support factory views.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment