Template Tools¶
The → tdi.tools.template module deals with templates on a higher
level and can help you organizing your template lists.
Overlay Discovery¶
Practically any non-trivial project consists of a collection of recurring template pieces. Be it the basic document layout or tiny boxes appearing here and there. Every outsourced piece is eventually placed in the final template via the overlay mechanism. That way, a template definition is always a list of template sources, which are overlayed one by one (starting with the first one) – and might look like this:
tpl = [
'layout.html', # overall frame
'index.html', # page specific frame and main content
'header.html', # recurring stuff...
'footer.html',
'login.html',
'widget1.html',
'widget2.html',
# and so on
]
Creating these lists is easy, as you typically create one page at a time. Maintaining them is harder, especially in bigger projects. Suppose, you place a new snippet into the footer (via overlay) – you have to modify all template lists. Both tasks – creating and maintaining – are very suitable for automating.
The following sections explain the automating mechanism in a bottom-up
manner. The code typically used by library users is the Layout
class, configured with a Loader instance.
discover Function¶
-
discover(loader, names, use=None, ignore=None)¶ Find templates to use and order them topologically correct
The → discover function is the heart of the
template tools helping you organizing your templates.
The function takes a base set of template names and finds all additional template sources needed to create the final template – all on the basis of overlay dependencies. The base set in the example above might be, for example:
['layout.html', 'index.html']
discover calculates a list of template names, a set of unresolved
overlays and a dict of ambiguous overlays (mapping overlay names to
tuples of template names). Overlay cycles are handled by the following
algorithm:
- Immediate cycles, i.e. overlays provided and required by a single template are ignored. Those overlays simply not contribute to the dependencies.
- In all other cases an exception is raised.
The use and ignore parameters are for fine tuning the
resulting list. They can handle manual overrides and resolve
ambiguities (when overlays are available in more than one template).
In order to execute the discover() function, we need another
abstraction – the loader, which is responsible for actually finding
and loading the templates by their names.
Loader Class¶
-
class
Loader(list, load, select)¶ Find, load and select templates
The → Loader is a container class, an interface
between the discover() function and the actual code working with
the templates. The latter is passed to the constructor and split into
three functions:
listThis is the template lister. The function is called without parameters and should return a list of all template names. A template name is typically a string, but it can be anything, as long as it’s (a) hashable and (b) uniquely identifies a template. The returned list can be filtered later using the
selectfunction. A simple lister function walking through the file system might look like this:import os, posixpath DIR = '/path/to/templates' IGNORE = ('.svn', 'CVS') def onerror(_): """ Bail out in case of error """ raise def lister(): # prepare for stripping the path prefix and normalizing to / baselen = len(os.path.join(DIR, '')) slashed = os.path.sep == '/' def norm(path): path = path[baselen:] if not slashed: path = path.replace(os.path.sep, '/') return path for path, dirs, files in os.walk(DIR, onerror=onerror): # strip unwanted sub directories dirs[:] = [dir for dir in dirs if dir not in IGNORE] # yield all but hidden .html files path = norm(path) for name in files: if name.startswith('.') or not name.endswith('.html'): continue if path: name = posixpath.join(path, name) yield name
It yields relative normalized paths (to all html files below a specified directory) and ignores version control directories. Note that case-sensitivity matters are not addressed here for clarity.
loadThis function actually should load a single template. It takes one parameter (a template name as returned by the lister). The function is expected to return an actual
→ Templateobject. A trivial file system template loader (working together with the lister above) could be:import os from tdi import html DIR = '/path/to/templates' def loader(name): return html.from_file(os.path.join(DIR, os.path.normpath(name)))
It is usually a good idea, to use a memoized factory for that.
selectThe selector decides if a particular template source is available for automatic disovery at all (layout and page definition snippets, i.e. the base templates passed to the discover function are usually not suitable for auto discovery).
selectis called with two parameters – the loader instance and the template name to check. If it returns a true value, the template should be auto-discovered, otherwise it won’t.How you select the templates is completely your decision. It could be by name or by content or something completely different, like a flag in a database. The following example selects all templates, which provide a particular (source) overlay named “autowidget”:
def selector(loader, name): return 'autowidget' in loader.load(name).source_overlay_names
With this selector, all HTML templates, which should be considered for automatic disovery may look like this:
<html tdi:overlay="<autowidget"> ... </html>
Layout Class¶
-
class
Layout(loader, *base, **kwargs)¶ Create template lists based on a start configuration
The → Layout class is tailored for day to day
template list maintenance. Its task is to eventually emit template
lists. It aims to do so based on input as minimal as possible (so you
can focus on more important things). In order to achieve this, it is
possible to derive new layout instances from existing ones, using the
Layout.extend() method. For bigger setups it is actually
recommended to use this technique to start with a single layout, which
defines the loader and then build up a hierarchy from there.
The layout constructor takes the following parameters (which are passed to every derivation, too):
loader- The
Loaderobject as described above. *base- The list of base template names. This might be, for example
layout.html. Or empty. **kwargsThe keyword parameters further configure the layout object:
use- Defines a mapping from overlay names to template names. This
parameter allows resolving ambiguities on the one hand and may
disable resolution for certain overlays by accepting
Noneas the mapped template name. ignore- Accepts an iterable containg template names to ignore completely.
cls- The factory for the template list. If not specified or
None,TemplateListis used. lazy- Specifies, when the template resolution should happen. If true, the
templates are discovered, when the template list is accessed. If
false, the templates are discovered when the template list is
created. If omitted or
None, it defaults to true.
The layout object then provides the following methods:
-
Layout.__call__(*names, **kwargs)¶ Create a template list from this layout
The
→ Layout.__call__method is used to actually produce a template list. The parameters are:*names- This is a list of base template names, which extends the list provided by the layout already. So, you cannot reset the base templates. If you need a different base, then use a different layout.
**kwargsuse- This updates the
usemapping. So you may extend or overwrite here. ignore- This updates the
ignoreset. You only can add new ignorables here. considerconsiderremoves template names from the ignore list again. It accepts an iterable of template names.considertakes precedence over names listed inignore.
Depending on the laziness the
__call__()method callsTemplateList.discoveror returns a proxy which defers this call until the first access. (TemplateListmay be changed via theclsargument of the constructor.)Whenever
TemplateList.discoveris called, it may raiseTemplateUndecidedorDependencyCycleexceptions, in case something goes wrong.Example:
# at a central place: loader = Loader(...) layout = Layout(loader, 'layout.html') # and later somewhere else: tpl_index = layout('index.html') # and for another page: tpl_imprint = layout('imprint.html')
-
Layout.extend(*base, **kwargs)¶ Extend the layout and create a new one.
→ extendis to derive from a layout in order to create a new one, typically more specialized than the original one. The parameters are similar to the ones ofLayout.__call__().*base- This is a list of base template names, which extends the list we’re deriving from. So, you cannot reset the base templates. If you need a different base, then extend a different layout.
**kwargsuse- This updates the inherited
usemapping. So you may extend or overwrite here. ignore- This updates the inherited
ignoreset. You only can add new ignorables here. considerconsiderremoves template names from the ignore list again. It accepts an iterable of template names.considertakes precedence over names listed inignore.
The
Layout.extend()method returns a newLayoutobject.Example:
# at a central place: loader = Loader(...) html_layout = Layout(loader) # main layout fetching headers & footers & stuff main_layout = html_layout.extend('layout.html') # iframe layout starting blank iframe_layout = html_layout.extend('iframe_layout.html') # and later somewhere else: tpl_index = main_layout('index.html') tpl_imprint = main_layout('imprint.html') # and for an included page tpl_some_frame = iframe_layout('some_frame.html')
TemplateList Class¶
-
class
TemplateList(*args, **kwargs)¶ Container for template names
-
class
TemplateListProxy(creator, autoreload)¶ Proxy
TemplateListinstances for lazy creation
→ TemplateList instances are created by
Layout objects. Depending on the configured laziness they are
either returned directly or indirectly via a
→ TemplateListProxy object. Since the latter
proxies all relevant methods to the TemplateList, both classes
effectively behave the same way.
TemplateList extends python’s list by the following items:
- The constructor accepts a
MISSINGargument, which should contain an iterable of all unresolved overlay names (this is the list, thediscover()function returns). The argument is copied into a list and stored in themissingattribute. - The
__repr__method emits theMISSINGargument and also always pretty-prints the list contents. So if you’d like to see the resulting template list of a particular page, just print the template list object in order to get all relevant information. - It provides a
discoverclassmethod which is called by theLayoutclass. Thediscovermethod is a small wrapper around the globaldiscover()function and works as an alternative constructor for theTemplateListclass. Thisdiscovermethod is also responsible for raisingTemplateUndecidedexceptions.