Package: slapos.recipe.template
Contentsdefault recipeOptionsmd5sum- check file integrityinlinemode- specify filesystem permissionsjinja2Optionsmd5sum,modeonce- avoiding file re-creationcontext- template variables and section dependencyextensions- Jinja2 extensionsimport-delimiter,import-list- template importsupdate- force rerendering on updateencodingFAQErrors in templateHistory5.1 (2022-10-24)5.0 (2022-02-03)4.6 (2021-06-08)4.5 (2020-01-08)4.4 (2019-01-24)4.3 (2018-01-25)4.2 (2017-12-12)4.1 (2017-10-18)4.0 (2017-10-13)3.0 (2017-05-23)2.10 (2017-01-18)2.9 (2015-11-18)2.8 (2015-06-25)2.7 (2015-05-18)2.6 (2014-11-26)2.5 (2013-08-07)2.4.3 (2013-08-02)2.4.2 (2012-08-21)2.4.1 (2012-08-01)2.4 (2012-06-01)2.3 (2012-03-29)2.2 (2011-10-12)2.1 (2011-10-12)2.0 (2011-10-12)1.1 (2011-05-30)Collection of recipes to generate a file from a template that can be
either inline or fetched with the buildout download API.Inspired bycollective.recipe.template.default recipeThe default recipe generates a file (optionoutput) from a template using
buildout expansion. The template is specified with eitherurl(optionally
combined withmd5sum) orinline.Here is a simple buildout:>>> base = """
... [buildout]
... parts = template
...
... [section]
... option = value
...
... [template]
... recipe = slapos.recipe.template
... url = template.in
... output = template.out
... """
>>> write('buildout.cfg', base)A simple template:>>> write('template.in', '${section:option}')And the output file has been parsed by buildout itself:>>> run_buildout()
Installing template.
>>> cat('template.out')
valueThe recipe relies on buildout expansion to pull sections it depends on, which
implies that the rendering (including the download if requested) is done during
the initialization phase.Optionsmd5sum- check file integrityIf the template is specified with theurloption, an MD5 checksum can be
given to check the contents of the template:>>> base += """
... md5sum = 1993226f57db37c4a19cb785f826a1aa
... """
>>> write(sample_buildout, 'buildout.cfg', base)
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('template.out')
valueIn such case, updating the part does nothing:>>> write('template.out', 'altered')
>>> run_buildout()
Updating template.
>>> cat('template.out')
alteredIn case of checksum mismatch:>>> run_buildout('template:md5sum=00000000000000000000000000000000')
While:
  Installing.
  Getting section template.
  Initializing section template.
Error: MD5 checksum mismatch for local resource at 'template.in'.inlineYou may prefer to inline small templates:>>> write('buildout.cfg', """
... [buildout]
... parts = template
...
... [section]
... option = inlined
...
... [template]
... recipe = slapos.recipe.template
... inline = ${section:option}
... output = template.out
... """)
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('template.out')
inlinedNote that in such case, the rendering is done by buildout itself:
it just creates a file with the value ofinline.mode- specify filesystem permissionsBy default, executable permissions are set if the content of the output file
looks like an executable script, i.e. it has a shebang that points to an
executable file. This is done by respecting umask:>>> import os, stat
>>> os.access('template.out', os.X_OK)
False
>>> run_buildout('section:option=#!/bin/sh')
Uninstalling template.
Installing template.
>>> os.access('template.out', os.X_OK)
TrueFile permissions can be forced using themodeoption in octal representation
(no need for 0-prefix):>>> run_buildout('template:mode=627')
Uninstalling template.
Installing template.
>>> print("0%o" % stat.S_IMODE(os.stat('template.out').st_mode))
0627jinja2Similar to the default recipe but the template syntax is Jinja2 instead of
buildout. Other significant differences are:Rendering, and download if requested, is done during the install phase.Dependencies are explicit (seecontextoption) instead of deduced from
the template.Some extra features (options detailed below).For backward compatibility, the following old options are still supported:The generated file can be specified withrenderedinstead ofoutput.The template can be specified withtemplateinstead ofurl/inline.
An inline template is prefixed withinline:+ an optional newline.Example demonstrating some types:>>> write('buildout.cfg',
... '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = foo.in
... output = foo
... context =
...     key     bar          section:key
...     key     recipe       :recipe
...     raw     knight       Ni !
...     import  json_module  json
...     section param_dict   parameter-collection
...
... [parameter-collection]
... foo = 1
... bar = bar
...
... [section]
... key = value
... ''')And according Jinja2 template (kept simple, control structures are possible):>>> write('foo.in',
...     '{{bar}}\n'
...     'Knights who say "{{knight}}"\n'
...     '${this:is_literal}\n'
...     '${foo:{{bar}}}\n'
...     'swallow: {{ json_module.dumps(("african", "european")) }}\n'
...     'parameters from section: {{ param_dict | dictsort }}\n'
...     'Rendered with {{recipe}}\n'
...     'UTF-8 text: привет мир!\n'
...     'Unicode text: {{ "你好世界" }}\n'
... )We run buildout:>>> run_buildout()
Installing template.And the template has been rendered:>>> cat('foo')
value
Knights who say "Ni !"
${this:is_literal}
${foo:value}
swallow: ["african", "european"]
parameters from section: [('bar', 'bar'), ('foo', '1')]
Rendered with slapos.recipe.template:jinja2
UTF-8 text: привет мир!
Unicode text: 你好世界Optionsmd5sum,modeSame as for the default recipe.once- avoiding file re-creationPath of a marker file to prevents rendering altogether.Normally, each time the section is installed/updated the file gets
re-generated. This may be undesirable in some cases.onceallows specifying a marker file, which when present prevents template
rendering:>>> write('buildout.cfg',
... '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... inline = dummy
... output = foo_once
... once = foo_flag
... ''')
>>> run_buildout()
Uninstalling template.
Installing template.
The template install returned None.  A path or iterable os paths should be returned.Template was rendered:>>> cat('foo_once')
dummyAnd canary exists:>>> import os
>>> os.path.exists('foo_flag')
TrueRemove rendered file and re-render:>>> os.unlink('foo_once')
>>> with open('buildout.cfg', 'a') as f:
...     f.writelines(['extra = useless'])
>>> run_buildout()
Uninstalling template.
Installing template.
The template install returned None.  A path or iterable os paths should be returned.
Unused options for template: 'extra'.Template was not rendered:>>> os.path.exists('foo_once')
FalseRemoving the canary allows template to be re-rendered:>>> os.unlink('foo_flag')
>>> with open('buildout.cfg', 'a') as f:
...     f.writelines(['moreextra = still useless'])
>>> run_buildout()
Uninstalling template.
Installing template.
The template install returned None.  A path or iterable os paths should be returned.
Unused options for template: 'extra'.
>>> cat('foo_once')
dummyIt’s also possible to use the same file forrenderedandonce:>>> write('buildout.cfg',
... '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... inline = initial content
... output = rendered
... once = ${:output}
... ''')
>>> run_buildout() # doctest: +ELLIPSIS
Uninstalling template.
Installing template.
The template install returned None.  A path or iterable os paths should be returned.Template was rendered:>>> cat('rendered')
initial contentWhen buildout options are modified, the template will not be rendered again:>>> with open('buildout.cfg', 'a') as f:
...     f.writelines(['inline = something different'])

>>> run_buildout()
Uninstalling template.
Installing template.
The template install returned None.  A path or iterable os paths should be returned.Even though we used a different template, the file still contain the first template:>>> cat('rendered')
initial contentcontext- template variables and section dependencyJinja2 context specification, one variable per line, with 3 whitespace-separated
parts: type, name and expression. Available types are described below. “name” is
the variable name to declare. Expression semantic varies depending on the type.Available types:rawImmediate literal string.keyIndirect literal string.importImport a python module.sectionMake a whole buildout section available to template, as a dictionary.Indirection targets are specified as: [section]:key .
It is possible to use buildout’s buit-in variable replacement instead instead
ofkeytype, but keep in mind that different lines are different
variables for this recipe. It might be what you want (factorising context
chunk declarations), otherwise you should use indirect types.You can use other part of buildout in the template. This way this parts
will be installed as dependency:>>> write('buildout.cfg', '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... inline = {{bar}}
... output = foo
... context = key bar dependency:foobar
...
... [dependency]
... foobar = dependency content
... recipe = zc.buildout:debug
... ''')

>>> run_buildout()
Uninstalling template.
Installing dependency.
  foobar='dependency content'
  recipe='zc.buildout:debug'
Installing template.This way you can get options which are computed in the__init__of
the dependent recipe.Let’s create a sample recipe modifying its option dict:>>> write('setup.py',
... '''
... from setuptools import setup
...
... setup(name='samplerecipe',
...       entry_points = {
...           'zc.buildout': [
...                'default = main:Recipe',
...           ],
...       }
...      )
... ''')
>>> write('main.py',
... '''
... class Recipe(object):
...
...     def __init__(self, buildout, name, options):
...         options['data'] = 'foobar'
...
...     def install(self):
...         return []
... ''')Let’s just usebuildout.cfgusing this egg:>>> write('buildout.cfg',
... '''
... [buildout]
... develop = .
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... inline =
...   {{bar}}
... output = foo
... context = key bar sample:data
...
... [sample]
... recipe = samplerecipe
... ''')
>>> run_buildout()
Develop: '/sample-buildout/.'
Uninstalling template.
Uninstalling dependency.
Installing sample.
Installing template.
>>> cat('foo')
foobarextensions- Jinja2 extensionsJinja2 extensions to enable when rendering the template,
whitespace-separated. By default, none is loaded.>>> write('foo.in',
... '''{% set foo = ['foo'] -%}
... {% do foo.append(bar) -%}
... {{ foo | join(', ') }}''')
>>> write('buildout.cfg',
... '''
... [buildout]
... develop = .
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = foo.in
... output = foo
... context = key bar buildout:parts
... # We don't actually use all those extensions in this minimal example.
... extensions = jinja2.ext.do jinja2.ext.loopcontrols
...   jinja2.ext.with_
... ''')
>>> run_buildout()
Develop: '/sample-buildout/.'
Uninstalling template.
Uninstalling sample.
Installing template.

>>> cat('foo')
foo, templateimport-delimiter,import-list- template importsimport-delimiterspecifies the delimiter character for in-temlate imports.
Defaults to/.import-listis a list of import paths. Format is similar tocontext.
“name” becomes import’s base name. Available types:rawfileLiteral path of a file.fileIndirect path of a file.rawfolderLiteral path of a folder. Any file in such folder can be imported.folderIndirect path of a folder. Any file in such folder can be imported.Here is a simple template importing an equaly-simple library:>>> write('template.in', '''
... {%- import "library" as library -%}
... {{ library.foo() }}
... ''')
>>> write('library.in', '{% macro foo() %}FOO !{% endmacro %}')To import a template from rendered template, you need to specify what can be
imported:>>> write('buildout.cfg', '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = template.in
... output = bar
... import-list = rawfile library library.in
... ''')
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('bar')
FOO !Just like context definition, it also works with indirect values:>>> write('buildout.cfg', '''
... [buildout]
... parts = template
...
... [template-library]
... path = library.in
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = template.in
... output = bar
... import-list = file library template-library:path
... ''')
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('bar')
FOO !This also works to allow importing from identically-named files in different
directories:>>> write('template.in', '''
... {%- import "dir_a/1.in" as a1 -%}
... {%- import "dir_a/2.in" as a2 -%}
... {%- import "dir_b/1.in" as b1 -%}
... {%- import "dir_b/c/1.in" as bc1 -%}
... {{ a1.foo() }}
... {{ a2.foo() }}
... {{ b1.foo() }}
... {{ bc1.foo() }}
... ''')
>>> mkdir('a')
>>> mkdir('b')
>>> mkdir(join('b', 'c'))
>>> write(join('a', '1.in'), '{% macro foo() %}a1foo{% endmacro %}')
>>> write(join('a', '2.in'), '{% macro foo() %}a2foo{% endmacro %}')
>>> write(join('b', '1.in'), '{% macro foo() %}b1foo{% endmacro %}')
>>> write(join('b', 'c', '1.in'), '{% macro foo() %}bc1foo{% endmacro %}')All templates can be accessed inside both folders:>>> write('buildout.cfg', '''
... [buildout]
... parts = template
...
... [template-library]
... path = library.in
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = template.in
... output = bar
... import-list =
...     rawfolder dir_a a
...     rawfolder dir_b b
... ''')
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('bar')
a1foo
a2foo
b1foo
bc1fooIt is possible to override default path delimiter (without any effect on final
path):>>> write('template.in', r'''
... {%- import "dir_a\\1.in" as a1 -%}
... {%- import "dir_a\\2.in" as a2 -%}
... {%- import "dir_b\\1.in" as b1 -%}
... {%- import "dir_b\\c\\1.in" as bc1 -%}
... {{ a1.foo() }}
... {{ a2.foo() }}
... {{ b1.foo() }}
... {{ bc1.foo() }}
... ''')
>>> write('buildout.cfg', r'''
... [buildout]
... parts = template
...
... [template-library]
... path = library.in
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = template.in
... output = bar
... import-delimiter = \
... import-list =
...     rawfolder dir_a a
...     rawfolder dir_b b
... ''')
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('bar')
a1foo
a2foo
b1foo
bc1fooupdate- force rerendering on updateBy default, and like the default recipe, nothing is done on update if the
template is known in advance to be the same, either because it’s inline or
a md5sum is given:>>> write('buildout.cfg',
... '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... inline = {{ os.environ['FOO'] }}
... output = foo
... context = import os os
... ''')
>>> os.environ['FOO'] = '1'
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('foo')
1
>>> os.environ['FOO'] = '2'
>>> run_buildout()
Updating template.
>>> cat('foo')
1But Jinja2 is such that the output may depend on other things than buildout
data and it may be wanted to force update in such case:>>> with open('buildout.cfg', 'a') as f:
...     f.writelines(['update = true'])
>>> run_buildout()
Uninstalling template.
Installing template.
>>> cat('foo')
2
>>> os.environ['FOO'] = '1'
>>> run_buildout()
Updating template.
>>> cat('foo')
1
>>> del os.environ['FOO']encodingEncoding for input template and output file.
Defaults toutf-8.FAQQ: How do I generate ${foo:bar} where foo comes from a variable ?A:{{'${'~ foo_var ~ ':bar}' }}This is required as jinja2 fails parsing “${{{ foo_var }}:bar}”. Though,
jinja2 succeeds at parsing “${foo:{{ bar_var }}}” so this trick isn’t
needed for that case.Errors in template>>> write('template.in', '''\
... foo
... {%
... bar
... ''')
>>> write('buildout.cfg', '''
... [buildout]
... parts = template
...
... [template]
... recipe = slapos.recipe.template:jinja2
... url = template.in
... output = foo
... ''')
>>> 0; run_buildout() # doctest: +ELLIPSIS
0...
While:
  Installing template.
...
Traceback (most recent call last):
  ...
  File "template.in", line 3, in template
    bar
...TemplateSyntaxError: Encountered unknown tag 'bar'.History5.1 (2022-10-24)Allow an overriding section to unset ‘url’ in order to instead define
‘inline’.Fix incorrect initialization of buildout download API.5.0 (2022-02-03)jinja2: annotate compiled source with source path rather than download
destination path.default: bring some of the jinja2 improvements by refactoring the 2 recipes.default: add support for inline templates.Improve condition to determine if the output file is executable or not.jinja2: fix default context (range missing, Py2/Py3 inconsistencies).jinja2: new options to specify source (template -> url/inline) and
destination (rendered -> output) the same way as the default recipe.
rendered & template are deprecated.jinja2: by default, rerender on update only if the template may have changed,
and new boolean option to force update like it used to be.4.6 (2021-06-08)Fix leaking temporary files with templates from URLs4.5 (2020-01-08)jinja2: Prevent ‘once’ from overwriting ‘rendered’4.4 (2019-01-24)jinja2: add bytes and six4.3 (2018-01-25)jinja2: Compile the same source only once and use compiled source next time.4.2 (2017-12-12)jinja2: try to not rewrite on update if there’s no change4.1 (2017-10-18)Fix $$ escaping in basic templates.4.0 (2017-10-13)jinja2: read template at install/update and fix ‘mode’ optionAdd support for Python 3.3.0 (2017-05-23)jinja2: make ‘import’ return the leaf module instead of the root one.2.10 (2017-01-18)jinja2: Add support for render-once.2.9 (2015-11-18)jinja2: Add support for non-ascii templates.
Encoding for input/output and imported files can be set via new “encoding”
parameter which defaults to utf-8.2.8 (2015-06-25)jinja2: new assert function.2.7 (2015-05-18)jinja2: fix display of source in traceback when there is an error in the
root template (or in instance parameters).2.6 (2014-11-26)jinja2: add many built-in functions from Python.2.5 (2013-08-07)Fix file import with Jinja2 >= 2.72.4.3 (2013-08-02)jinja2: add support for inline templates.2.4.2 (2012-08-21)jinja2: Mode shall be used instead of umask. [Vincent Pelletier]jinja2: Add jinja2 “import” directive support. [Vincent Pelletier,
Timothee Lacroix]Added rawfile and rawfolder types. [Vincent Pelletier, Timothee Lacroix]Reworked loader classes [Vincent Pelletier]2.4.1 (2012-08-01)jinja2: Make “context” parameter really optional. [Vincent Pelletier]2.4 (2012-06-01)Provide access to zc.buildout.buildout.dumps when it exists. [Vincent
Pelletier]Fix missing jinja2 entry point documentation in pacakge description [Vincent
Pelletier]2.3 (2012-03-29)Add jinja2 entry point with jinja2 template support. [Vincent Pelletier]2.2 (2011-10-12)Include missing files in package. [Łukasz Nowak]2.1 (2011-10-12)Description update. [Łukasz Nowak]2.0 (2011-10-12)Dropping collective.recipe.template dependency. [Romain Courteaud, Antoine
Catton]1.1 (2011-05-30)Moved out from slapos.cookbook in order to minimise depenencies [Łukasz
Nowak]