[Web-SIG] Standardized template API

Ian Bicking ianb at colorstudy.com
Wed Feb 1 00:01:36 CET 2006

Phillip J. Eby wrote:
> At 02:03 PM 1/31/2006 -0500, Clark C. Evans wrote:
>> I'd stick with the notion of a "template_name" that is neither the
>> template file nor the template body.  Then you'd want a template factory
>> method that takes the name and produces the template body (complied if
>> necessary).  This needs to be once indirect since a template may refer
>> to other sub-templates. This way your template could be stored
>> in-memory, on-disk, or in a database, or even remotely using an HTTP
>> cashe.  The actual storage mechanism for the template source code should
>> not be part of this interface.
> I'd go even further than this, to note that frameworks need to be able 
> to cache "compiled" versions of templates.  The template "engine" should 
> be able to return a "compiled" template that the framework can use to do 
> rendering in future.

The plugin instance itself can manage the caching.  I believe current 
implementations do that (at least TurboCheetah does).  Is there anything 
the framework can do for caching that the plugin instance can't?

> Note too that frameworks such as Zope 3 and peak.web have concepts like 
> localization and "skinning" that require the ability to switch out the 
> actual provider of a named template or "view", and in at least the case 
> of peak.web this can be polymorphic.  That is, one "skin" could 
> implement template "foo" using Kid and another one using ZPT.  So the 
> name of a template needs to be independent of its implementation 
> language, or the ability to plug in different engines is moot.  
> (Currently, TurboGears embeds the chosen template language in code, 
> which means a deployer can't skin the application effectively.)

I believe that custom find_template implementations could handle these 
cases.  Maybe there also needs to be a relative_to_template_name 
argument to find_template, so that it can resolve template names 
relative to the calling template (so, for instance, you can use relative 
names to the template you inherit from).

> Finally, I'd note that an increasingly common use case for template 
> storage is likely to be via pkg_resources lookup, so that applications 
> can be deployed as eggs.  Ideally, future versions of Zope 3 and 
> peak.web would perhaps allow one egg to provide skins or "layers" for 
> views defined in other eggs, so that users can plug in third-party skins 
> for applications.

If find_template returns a template object instead of a filename, this 
would be easier.  As it is it could produce an actual filename (with 
pkg_resources.resource_filename), but that seems unnecessary as I think 
about it.

> Actually, if we're going to come up with something really useful here, 
> it would probably be a good idea to expand the scope from just defining 
> templates, to defining a "resource access" protocol to cover both static 
> resource files and templates that may need to be localized or skinned.  
> That would be a much bigger win, IMO, since it would put more control in 
> the hands of deployers and customizers, instead of just making it 
> possible for developers to use whatever template language they fancy.  :)

That seems to be an extension of find_template, which isn't specified 
much in this proposal.  But we could specify that, I suppose:

def find_resource(resource_type, resource_name, relative_to=None):
     Return a resource_type kind of resource, with the given name.  If
     relative_to (a resource name) is given and relative names are
     supported, then do so.

class IResourceType:
     def load_resource(resource_name, finder, resource_filename=None,
         Returns the resource.  resource_name is an opaque identifier,
         but should be kept.  finder is an implementation of
         find_resource.  The resource content can be identified with a
         string or filename [other kinds?].  If both are given, filename
         can be used for debugging output, but string should be used as
         the actual content.  If the resource refers to other resources
         then the finder should be used for retrieving those resources.

class ITemplatePlugin(IResourceType):
     def __init__(extra_vars_func=None, options=None):
         Create a template plugin instance.  extra_vars_func is a
         function that returns a dictionary of "standard" variables
         for the environment.  options is a dictionary of options
         for the template (e.g., compiler settings) with no defined

     def load_resource(...):
         Return an instance of the template.  No interface for the
         return value is defined, but the return value can be used
         with .render()

         Note that some templating languages don't work well with
         render(), so the returned resource may be the more useful
         interface, and frameworks should allow users to get at this
         return value directly.

     def render(template_instance, vars, format="html", fragment=False):
         Render the template instance (as returned by load_resource)
         to a string [unicode?].  vars is typically a dictionary (-like
         object) of variables for use in substitution.  format may be
         "xhtml", "plain", etc., but plugins may ignore that value.
         "fragment" indicates if an incomplete document should be created
         (e.g., with no DOCTYPE); again templates may ignore this.

         [instead of transform, can we have this not necessarily return
         strings, e.g., format="elementtree"?]

So now you'd use it like:

   tmpl_plugin = CheetahPlugin()
   tmpl = my_finder(tmpl_plugin, 'foo.tmpl')
   print tmpl_plugin.render(tmpl, locals())

Where before it would be:

   tmpl_plugin = CheetahPlugin()
   print tmpl_plugin.render(locals(), template='foo.tmpl')

Ian Bicking  /  ianb at colorstudy.com  /  http://blog.ianbicking.org

More information about the Web-SIG mailing list