The → tdi.tools.template module deals with templates on a higher level and can help you organizing your template lists.
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.
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:
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.
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:
This 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 select function. 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.
This 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 → Template object. 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.
The 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). select is 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>
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):
The keyword parameters further configure the layout object:
The layout object then provides the following methods:
Create a template list from this layout
The → Layout.__call__ method is used to actually produce a template list. The parameters are:
Depending on the laziness the __call__() method calls TemplateList.discover or returns a proxy which defers this call until the first access. (TemplateList may be changed via the cls argument of the constructor.)
Whenever TemplateList.discover is called, it may raise TemplateUndecided or DependencyCycle exceptions, 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')
Extend the layout and create a new one.
→ extend is 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 of Layout.__call__().
The Layout.extend() method returns a new Layout object.
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')
Container for template names
Proxy TemplateList instances 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: