API reference
Loading
tplx.Loader(roots=(), *, engines=None, undefined='strict', globals=None, encoder=None, configure=None)
Finds templates under roots and renders them with the engine each one needs.
A name is resolved against every root in order. In each directory an
exact file name wins; otherwise, among the files whose stem is the name
(boot.cfg finds boot.cfg.j2), the one whose suffix the highest-priority
engine claims, then the lowest name. The engines are consulted highest
PRIORITY first and the first whose can_process accepts the file builds
it, so the suffix picks the engine.
engines defaults to the registry as it is when the loader is built.
undefined is one of UNDEFINED_MODES. globals are names every render
sees, under the data passed to render. encoder presents the data to
the engines that cannot evaluate Python (a DataEncoder by default).
configure maps an engine class to a function called once with that
engine's own environment whenever a lookup builds one, for the class and
its subclasses: {JinjaTemplate: lambda env: env.filters.update(q=quote)}.
Source code in src/tplx/_loader.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | |
find(name, **lookup)
self.lookup(**lookup).find(name).
Source code in src/tplx/_loader.py
258 259 260 | |
get(name, **lookup)
self.lookup(**lookup).get(name).
Source code in src/tplx/_loader.py
262 263 264 | |
lookup(*, candidates=None, paths=())
The loader bound to one caller's file names and search paths.
candidates(file_name) lists the file names to try for the last part
of a name, most specific first; by default only that file name. The
directory part of the name is kept. paths are searched before the
roots: an anchored path as it is, a relative one inside each root.
Includes inside a template resolve through the same Lookup. The same
arguments return the same Lookup, which caches what it compiles,
while it is among the LOOKUP_CACHE_SIZE most recently used; pass a
candidates function that is defined once, not a new one per call.
Source code in src/tplx/_loader.py
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
render(name, data=None, /, *, candidates=None, paths=(), **overrides)
self.lookup(candidates=..., paths=...).render(name, data, **overrides).
A data name that is candidates or paths goes in data.
Source code in src/tplx/_loader.py
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | |
tplx.Lookup(loader, *, candidates=None, paths=())
A Loader bound to one set of candidates and search paths.
Build one with Loader.lookup. It caches compiled templates by name, and
reuses one while its file is unchanged.
Source code in src/tplx/_loader.py
292 293 294 295 296 297 298 299 300 301 302 303 | |
clear()
Forget the compiled templates and engine state; the next render resolves afresh.
A cached template is reused while its own file is unchanged, so a more specific file added later is found only after this.
Source code in src/tplx/_loader.py
367 368 369 370 371 372 373 374 | |
find(name)
Find the file for name and read its bytes.
name may be a list of names: each is tried in turn, with all of its
candidates in every search path, and the first file found wins.
:raises TemplateNotFound: nothing matches, or a name has a .. segment.
Source code in src/tplx/_loader.py
317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
get(name)
The compiled template for name, from the cache while its file is unchanged.
:raises TemplateNotFound: no file matches. :raises NoEngineError: no engine claims the file, or it is not UTF-8 and the engine that claims it needs text. :raises EngineUnavailableError: the claiming engine's extra or program is missing. :raises RenderError: the engine could not compile it.
Source code in src/tplx/_loader.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
include(name, engine)
Resolve an include written in an engine template.
name is found and given an engine exactly as get would. When that
engine is engine itself, Include.text holds the source for the
including engine to compile natively (keeping its blocks, macros and
context); otherwise it is None, and the engine inserts
render_include(name, values) instead.
Source code in src/tplx/_loader.py
442 443 444 445 446 447 448 449 450 451 452 453 454 | |
render(name, data=None, /, **overrides)
Render name; its names are the loader's globals, then data, then overrides.
Text, or bytes from a BINARY engine.
:raises RenderError: the engine failed; UndefinedError for a missing
name in strict mode. Also everything get raises.
Source code in src/tplx/_loader.py
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | |
render_include(name, values)
Render name with its own engine and values, as text to insert.
:raises NoEngineError: the result is bytes that are not UTF-8.
Source code in src/tplx/_loader.py
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 | |
resolve(name)
The file a render of name reads and the engine that renders it.
:raises TemplateNotFound: no file matches. :raises NoEngineError: no engine claims the file.
Source code in src/tplx/_loader.py
433 434 435 436 437 438 439 440 | |
search_paths()
The directories searched, in order: paths first, then the roots.
Source code in src/tplx/_loader.py
305 306 307 308 309 310 311 312 313 314 315 | |
state(key, factory)
Per-lookup state for an engine, built by factory on first use.
An engine keeps what its includes need here (a jinja Environment, a
mako TemplateLookup), so every template one lookup builds shares it.
Source code in src/tplx/_loader.py
497 498 499 500 501 502 503 504 505 506 507 | |
tplx.Source
Bases: NamedTuple
A template file as found: its bytes, where it came from, and a freshness check.
Presenting data
tplx.DataEncoder(*, expose=(), max_depth=8)
Presents render data to the engines that cannot evaluate Python.
view(values)for Liquid, Handlebars and Mustache: structures become lazy read-only mappings, lists stay lists, values stay as they are.json(values)for ERB and EPP: a JSON-serialisable copy.flatten(values)for the shell engine: one level of text, keyed bykey(path).
One rule decides what is a structure, default(value); subclass and
override it (and key, text) to change how values are encoded, as with
json.JSONEncoder.default.
expose lists _-prefixed names that json and flatten keep; they drop
every other one, since such names are commonly back-references (an owner, a
cache) that would drag a whole program in. max_depth bounds how deep
json and flatten go; a value deeper than that is written as its text.
Source code in src/tplx/_encoder.py
136 137 138 | |
default(value)
What value stands for: a Mapping when it is a structure, else a value.
Called for anything that is not a scalar, a mapping, a list or a tuple.
The default: an object whose class defines its own __str__ is a value
(written as that text); any other object with attributes is a structure
of its attributes; anything else is a value.
Source code in src/tplx/_encoder.py
142 143 144 145 146 147 148 149 150 151 152 153 154 | |
flatten(values)
values as one level of text, keyed by key(path).
List items are keyed by their index. Two paths that give one key log a warning naming both values; the later one wins.
Source code in src/tplx/_encoder.py
236 237 238 239 240 241 242 243 244 | |
json(values)
A JSON-serialisable copy of values; anything else is written as its text.
Source code in src/tplx/_encoder.py
210 211 212 | |
key(path)
The flattened name of the value at path: joined with _, upper-cased.
Source code in src/tplx/_encoder.py
156 157 158 | |
structure(value)
value as a Mapping, a list, or a value that is neither.
Source code in src/tplx/_encoder.py
170 171 172 173 174 175 176 177 178 | |
text(value)
How flatten writes a value: None empty, a boolean true/false.
Source code in src/tplx/_encoder.py
160 161 162 163 164 165 166 | |
view(values)
The top-level names of a data template.
Source code in src/tplx/_encoder.py
191 192 193 | |
wrap(value)
value for a data template: a view for a structure, a value as it is.
Source code in src/tplx/_encoder.py
180 181 182 183 184 185 186 187 188 189 | |
tplx.DataView(obj, encoder)
Bases: Mapping
A lazy, read-only mapping over an object's items or attributes.
Nothing is copied, so a view never goes stale against what it wraps. Values are presented through the encoder that made the view. Dunder names are hidden; a single leading underscore is not.
Source code in src/tplx/_encoder.py
37 38 39 | |
The engine contract
tplx.Template(source, *, lookup=None, origin='<string>')
A template an engine has compiled, ready to render.
A subclass sets EXT (the suffixes it claims), optionally PRIORITY and
BINARY, and implements render_values. The loader builds it with the file's
contents, the Lookup that found it and the file's origin; an engine with
an include syntax resolves includes through self.lookup, so they search
the same roots as the file that includes them.
Source code in src/tplx/_template.py
66 67 68 69 70 71 72 73 74 75 76 | |
encoder
property
The DataEncoder this template presents its data with.
is_up_to_date
property
Is the file this was loaded from unchanged? True when there is no file.
undefined
property
The UNDEFINED_MODES entry this template renders with.
can_process(file, source)
classmethod
Does this engine render file? By default, when it claims the suffix.
Source code in src/tplx/_template.py
132 133 134 135 | |
configure_native(native)
Apply the loader's configure functions for this engine to native.
An engine calls it once, where it builds its own environment for a
lookup; every function whose key is this engine's class or a base of it
runs, in the order given. Returns native.
Source code in src/tplx/_template.py
90 91 92 93 94 95 96 97 98 99 100 101 | |
render(data=None, /, **overrides)
Render with data, then overrides, as the template's top-level names.
A keyword wins over the same name in data. An engine's own exception
is raised as RenderError, with it as the __cause__.
Source code in src/tplx/_template.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
render_values(values)
What an engine implements: render with values as the top-level names.
Source code in src/tplx/_template.py
128 129 130 | |
tplx.register_engine(cls=None, *, priority=None)
register_engine(
cls: _Engine, *, priority: Optional[int] = None
) -> _Engine
register_engine(
cls: None = None, *, priority: Optional[int] = None
) -> Callable[[_Engine], _Engine]
Add an engine to the registry and return it; usable as a decorator.
priority overrides the class's PRIORITY. Registering a class that is
already registered does nothing beyond that. A class that would claim a
suffix another registered engine claims at the same priority raises
ValueError and changes nothing: give one of them a higher priority to say
which wins.
Source code in src/tplx/_registry.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
tplx.unregister_engine(cls)
Remove an engine from the registry; True if it was registered.
Source code in src/tplx/_registry.py
108 109 110 111 112 113 | |
tplx.registered_engines()
The registered engines, highest PRIORITY first.
Source code in src/tplx/_registry.py
121 122 123 124 | |
Engines
tplx.engines
The shipped engines, registered on import.
An optional engine is registered whether or not its library is installed: a
.mako file without the extra raises EngineUnavailableError naming it,
rather than being copied as bytes because nothing claimed it. Importing this
package imports no engine library.
CheetahTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Cheetah template, rendered by CT3. Needs the cheetah extra.
The data is the template's search list, as it is: $target.hostname reads
a key or an attribute. #include "name" resolves through the Lookup that
loaded the file, exactly as a render would: a Cheetah file is compiled by
Cheetah (#include raw inserts its text as it is), any other is rendered by
its own engine with the render's data and its output inserted; a name
found nowhere raises TemplateNotFound under strict and renders empty
otherwise. strict raises an undefined name as UndefinedError, lenient
renders it empty and debug writes it back as $name.
Loader(configure={CheetahTemplate: function}) receives the dict of
Cheetah compiler settings.
Source code in src/tplx/engines/_cheetah.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
CopyTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
Return the file's bytes, unchanged; claims every suffix, consulted last.
BINARY, so the file is never decoded: a CRLF file, latin-1 text or an
EFI binary all come back exactly as stored, and render returns bytes.
A text file that still holds %{NAME} placeholders logs a warning on its
first render, since it was probably meant for the shell engine.
Source code in src/tplx/engines/_copy.py
45 46 47 48 49 50 51 52 53 54 55 | |
EPPTemplate(source, *, lookup=None, origin='<string>')
Bases: SubprocessTemplate
A Puppet EPP template, rendered by puppet epp render (TPLX_PUPPET names another).
Every top-level name is an EPP parameter: <%= $target['hostname'] %>.
The output is the template's own bytes: no newline is added at the end.
CRLF in puppet's output is turned into LF, so the result does not depend on
the platform; a .epp template cannot emit CRLF on purpose.
Source code in src/tplx/engines/_external.py
123 124 125 126 127 128 129 130 131 132 | |
ERBTemplate(source, *, lookup=None, origin='<string>')
Bases: SubprocessTemplate
An ERB template, rendered by the system ruby (TPLX_RUBY names another).
@context is the whole data hash and each other top-level name is also an
instance variable: <%= @target['hostname'] %>; a name context is
@context['context']. <%- -%> trims.
Source code in src/tplx/engines/_external.py
123 124 125 126 127 128 129 130 131 132 | |
EnvsubstTemplate(source, *, lookup=None, origin='<string>')
Bases: ShellTemplate
$NAME and ${NAME} as envsubst writes them (.envsubst, .template).
DIALECT picks which envsubst it reads like, from DIALECTS: gnu (the
default: no defaults, no $$ escape, an expression it does not know is
left as it is), a8m (POSIX defaults and alternates, $$ escapes) or
drone (braced only; defaults, slices, replacements, trims, case, length).
Setting PATTERN or FEATURES in a subclass overrides the dialect's.
Values come from the render's data, flattened as for the shell engine
(pass os.environ as data, or tplx render --env); names match
case-insensitively, and an undefined name follows the undefined mode
(debug keeps it, as nginx's own $uri should be).
class DroneEnvsubst(EnvsubstTemplate):
DIALECT = "drone"
Source code in src/tplx/_template.py
66 67 68 69 70 71 72 73 74 75 76 | |
syntax()
classmethod
PATTERN and FEATURES, each the dialect's unless the class sets it.
:raises ValueError: an unknown DIALECT, PATTERN or feature.
Source code in src/tplx/engines/_shell.py
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 | |
GoTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Go text/template template, rendered by gotpl. Needs the go extra
(Python 3.11 or later).
The data is passed as it is: .Name reads a key or an attribute. Go's
builtin functions only; Loader(configure={GoTemplate: function}) receives
the dict of functions, so Sprig is
functions.update(gotpl.funcs.sprig.text_func_map()).
{{template "name" .}} and {{block}} names that the file does not
define resolve through the Lookup that loaded it, exactly as a render
would: a Go file joins the template set, any other is rendered by its own
engine with the pipeline's data (a mapping, or nothing) and its output
inserted. A name found nowhere raises TemplateNotFound when it executes
under strict and renders empty otherwise. strict raises a missing key
as UndefinedError; Go has no empty mode, so lenient and debug both
write <no value>. Output keeps the template's text as it is.
Source code in src/tplx/engines/_go.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
is_up_to_date
property
Is the file unchanged, and every file it includes?
HandlebarsTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Handlebars template, rendered by pybars3. Needs the handlebars extra.
{{> name}} partials resolve through the Lookup that loaded the file,
exactly as a render would: a Handlebars file is compiled by Handlebars, any
other is rendered by its own engine with the template's data and its
output inserted. Handlebars has no strict mode: an
undefined name renders empty whatever the undefined mode says. A partial
that cannot be found raises TemplateNotFound under strict and renders
empty otherwise, as in Mustache.
As Handlebars specifies, {{x}} is HTML-escaped; {{{x}}} writes the
value as it is.
Source code in src/tplx/engines/_handlebars.py
43 44 45 46 47 48 49 50 51 52 | |
JinjaTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Jinja template. Needs the jinja extra.
{% include %}, {% extends %}, {% import %} and {% from %} resolve
through the Lookup that loaded the file, exactly as a render would: a
Jinja file is compiled by Jinja, any other is rendered by its own engine
with the current context and its output inserted. All three undefined
modes apply. Output keeps the template's trailing newline and, as Jinja
does by default, writes every line ending as LF.
Source code in src/tplx/engines/_jinja.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
LiquidTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Liquid template, rendered by python-liquid. Needs the liquid extra.
Liquid reads mappings, not attributes, so objects in the data are presented
as read-only mappings of their attributes. {% include %} and
{% render %} resolve through the Lookup that loaded the file, exactly
as a render would: a Liquid file is parsed by Liquid, any other is rendered
by its own engine with the render's data and its output inserted. All three undefined modes apply.
Source code in src/tplx/engines/_liquid.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
MakoTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A mako template. Needs the mako extra.
<%include file=.../>, <%inherit file=.../> and <%namespace file=.../>
resolve through the Lookup that loaded the file, exactly as a render
would: a mako file is compiled by mako, any other is rendered by its own
engine with the template's data and its output inserted. Names are
root-relative. strict is mako's own strict_undefined
(raising UndefinedError); lenient renders an undefined name empty and
logs it. debug behaves as lenient: compiled mako cannot re-emit the
${...} it came from. A data name in RESERVED_NAMES is dropped with a
warning.
Source code in src/tplx/engines/_mako.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
MustacheTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
A Mustache template, rendered by chevron. Needs the mustache extra.
{{> name}} partials resolve through the Lookup that loaded the file,
exactly as a render would: a Mustache file is parsed by Mustache, any other
is rendered by its own engine with the template's data and its output
inserted. Under strict a partial that cannot be
found raises TemplateNotFound, otherwise it renders empty. chevron has
no strict mode for names: an undefined name renders empty whatever the
undefined mode says.
As Mustache specifies, {{x}} is HTML-escaped; {{{x}}} or {{& x}}
writes the value as it is.
Source code in src/tplx/engines/_mustache.py
47 48 49 50 51 52 53 54 55 | |
ShellTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
%{UPPER_SNAKE} placeholders substituted from the flattened data.
The data is flattened by the loader's DataEncoder.flatten: by default
nested names join with _ and are upper-cased ({"target": {"hostname":
h}} is %{TARGET_HOSTNAME}), None renders empty and a boolean as
true/false. Placeholders match names case-insensitively.
%{NAME:-default} and %{NAME-default} work as in POSIX shells, as do
%{NAME:1:3} (a slice) and %{NAME//pattern/text} (a replacement), and
%% is a literal %. Only the braced form is on by default, so a bare %
is text: a kickstart keeps %packages and a script keeps date +%Y%m%d.
An expression it does not know (%{NAME:?x}) is text: its % is written
and the scan goes on inside it. All three undefined modes apply.
Configured by subclassing:
class DollarTemplate(ShellTemplate):
EXT = (".tpl", ".cfg")
DELIMITER = "$"
PATTERN = "all" # ${NAME} and $NAME
FEATURES = ShellTemplate.FEATURES | {"trim", "case"}
Source code in src/tplx/_template.py
66 67 68 69 70 71 72 73 74 75 76 | |
expand(text, variables)
text with every placeholder substituted from variables (UPPER names).
Source code in src/tplx/engines/_shell.py
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
pattern()
classmethod
A pattern finding where a placeholder starts (escaped, braced, named).
Cached in the class's own __dict__, so a subclass with another
delimiter never reuses its parent's pattern.
Source code in src/tplx/engines/_shell.py
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | |
syntax()
classmethod
This class's (PATTERN, FEATURES), checked.
:raises ValueError: an unknown PATTERN or feature.
Source code in src/tplx/engines/_shell.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
variables(values)
values as the placeholders read them: the encoder's flatten, by UPPER name.
Source code in src/tplx/engines/_shell.py
280 281 282 | |
SubprocessTemplate(source, *, lookup=None, origin='<string>')
Bases: Template
Base for an engine that runs another language's renderer.
A subclass sets COMMAND, ENV_VAR, EXT and implements command().
The data reaches the program as JSON, made by the loader's
DataEncoder.json: structures become objects, anything else its text, and
_-prefixed names are dropped unless the encoder's expose lists them. Undefined modes do not apply;
missing data is the other language's own null.
Source code in src/tplx/engines/_external.py
123 124 125 126 127 128 129 130 131 132 | |
command(program, template_path, values_path)
The argument list to run.
Source code in src/tplx/engines/_external.py
150 151 152 | |
executable()
classmethod
The program's full path.
:raises EngineUnavailableError: it is not installed.
Source code in src/tplx/engines/_external.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
Exceptions
tplx.exceptions
The errors tplx raises. Every one derives from TplxError.
EngineUnavailableError
Bases: TplxError
The engine that claims a file cannot run here.
Its library is not installed (the message names the extra) or the program
it runs is not on PATH (the message names the program).
NoEngineError
Bases: TplxError
No engine claims the file found, or the file is not text its engine can read.
RenderError
Bases: TplxError
An engine failed to compile or render a template.
The engine's own exception is the __cause__; the message names the engine
and the file.
RenderTimeoutError
Bases: RenderError, TimeoutError
A program-backed engine did not finish within its timeout.
TemplateNotFound(name, message=None)
Bases: TplxError, LookupError
No search path holds a file for the name, or the name escapes them.
names holds every name tried, in order; name is the first.
Source code in src/tplx/exceptions.py
28 29 30 31 32 33 34 35 36 37 38 39 | |
TplxError
Bases: Exception
Base class of every error tplx raises.
UndefinedError
Bases: RenderError, NameError
The template used a name the data does not define, in strict mode.
Testing an engine
tplx.testing
The suite every engine passes, shipped so a third-party engine runs it too.
Subclass EngineContract in a pytest test module, name the engine and a
source that writes the name value, and pytest collects the checks:
from tplx.testing import EngineContract
class TestShoutyEngine(EngineContract):
engine = ShoutyTemplate
source = "{{ name }}"
expected = "TPLX"
expected is what rendering source with name="tplx" returns. Every test
takes pytest's tmp_path fixture.
EngineContract
Checks one engine against the Template contract.