Jinja template context reference#
Every string value in a support YAML file — pre_init/post_init text,
databases argument values, env_vars values, and parameter/pre_defines/
post_defines defaults — is treated as a Jinja2
template. This page enumerates everything you can reference inside those
templates when writing a support YAML file.
For the meaning of each parameter or define type, see Support module YAML reference (*.ibek.support.yaml); this page only covers what is in scope at render time and how a declared type drives coercion.
The render entrypoint#
Every per-entity string field goes through a single method, Utils.render,
which compiles the string with jinja2.StrictUndefined. (The top-level assembly
templates st.cmd.jinja and ioc.subst.jinja are rendered separately and
directly via Template(...).render() in gen_scripts.py, not through
Utils.render.) For the per-entity renders the practical consequences are:
Unknown names raise. Referencing a name that is not in scope is an error, not an empty string. The original Jinja exception is re-wrapped and raised as a
ValueErrorthat quotes the offending template text, so a typo in a variable name fails theibekrun rather than producing silent garbage.No custom Jinja extensions are registered. ibek does not add any custom filters, globals or tests. Only stock Jinja2 builtins are available (e.g.
range,format,sort,list,int,dict, slicing and arithmetic), plus the_globalhelper object described below. If a filter or function is not part of standard Jinja2, it is not available here.
A single Utils instance (the UTILS singleton) is shared across one whole
ibek generate run, so any state it accumulates (counters, stored variables)
persists from one entity to the next in render order.
Top-level names#
The entity’s own fields#
Inside a template for a given entity, all of that entity’s fields are available as bare names:
every declared parameter,
every
pre_definesvalue,every
post_definesvalue,the always-present
type(the entity model name, e.g.motorSim.simMotorController) andentity_enabled(a bool, defaultTrue).
Fields become available in a fixed order — pre_defines → parameters →
post_defines — and each is rendered in that order. A later field may
reference an earlier one, but not the other way round. For example, in the
motorSim.simMotorAxis model the start parameter defaults to another
parameter’s value:
start:
type: str # int or jinja string
description: The starting position of the axis (in counts)
default: "{{home}}"
Here home (declared earlier) is in scope when start’s default is rendered.
Variables created with set/incrementor are also bare names#
Whenever you call _global.set(...) or _global.incrementor(...), the stored
variable is also spread into the template context as a bare top-level name
for all subsequent renders. So after a post_defines stores a value with
set, a later pre_init line can read it directly by name. From
tests/samples/support/utils.ibek.support.yaml:
post_defines:
test_global_var:
description: test global variable setter
value: '{{ _global.set("magic_global", 42) }}'
get_global:
description: test global variable getter
value: '{{ _global.get("magic_global") }}'
pre_init:
- type: comment
value: global "magic" is {{ get_global }}
- type: comment
value: counter "InterruptVector" is now {{ _global.incrementor("InterruptVector", start=192, stop=255) }}
Here get_global (a post_defines field) is referenced as a bare name in
pre_init. The same counter name reused in a different entity continues from
where it left off, because UTILS is shared across the run.
Special variables#
Two extra names are injected into every template:
Name |
Value |
|---|---|
|
The stem (filename without extension) of the first IOC instance YAML file being processed. |
|
The IOC instance name, already rendered (its own |
Type coercion#
Jinja always produces a string. ibek coerces that string back to a typed value in two ways:
Declared types coerce automatically. When a parameter or define is declared as
int,float,bool,listordict, the rendered string is passed throughast.literal_evaland cast to that type. (Typesstr,object,idandenumare not coerced — they stay as the rendered string, withobject/idthen resolved as described below.) Note thatpre_defines/post_definessupport onlystr,float,int,boolandlist; thedict,object,idandenumtypes are parameter-only.Inline coercion suffix. You can force coercion inside a single expression by ending it with a
| <typ> }}suffix, where<typ>is one ofint,float,bool,list,dict. This is a genuine Jinja builtin filter that Jinja itself applies during rendering — ibek does not strip it. After rendering, ibek separately inspects the template text with a regular expression (re_get_type) and, if the suffix names one of those five types, applies the sameast.literal_eval-based cast to the result.
Warning
Because the suffix is a real Jinja filter, it only works for filter names Jinja
actually knows. Writing a suffix that is not a Jinja filter (e.g.
{{ x | foo }}) makes Jinja raise “No filter named ‘foo’”, which ibek
re-wraps as a ValueError. A suffix that is a real Jinja filter but not one of
the five coercion types (e.g. | upper) is applied by Jinja and then ignored by
ibek’s coercion step, so no extra cast happens.
Referencing another entity#
A parameter declared with type: object does not hold a string — after
validation it holds the actual referenced Entity. Two things follow:
Rendering the object itself (
{{ port }}) callsEntity.__str__, which returns the value of the target entity’sidfield. This raises if the referenced entity has noid-typed parameter.Attribute access (
{{ port.field }}) reaches into the target entity’s fields.
From motorSim.simMotorAxis, the controller parameter is an object reference
to a simMotorController:
databases:
- file: basic_asyn_motor.db
args:
P: "{{controller.P}}" # the controller's P field
PORT: "{{controller}}" # the controller's id (its controllerName)
Note
In sub_entities/shared anchors there is no type information, so an object
parameter there is just a plain dict. As the quadEM.Plugins model notes, you
must use an explicit field such as {{DEVICE.P}} / {{DEVICE.PORT}} rather than
{{DEVICE}} in that context.
The JinjaString rule for non-string defaults#
A default for an int, float, bool, list or dict parameter may be
written as a string, but only if that string actually contains a {{ ... }}
expression. This is enforced by schema validation against the JINJA pattern
(.*\{\{.*\}\}.*). A bare literal string in one of these slots is rejected — use
the native YAML literal instead (e.g. default: 20, not default: "20"). The
rule applies only to typed parameter defaults; a pre_defines/post_defines
value is typed Any with no pattern and is not subject to it.
To include literal {{ / }} characters that Jinja should not evaluate,
wrap them in {% raw %} ... {% endraw %}, as the simMotorController.DESC
default does:
DESC:
type: str
default: "Simulated Motion Controller testing escaping: {% raw %} {{enclosed in escaped curly braces}} {% endraw %}"
The st.cmd (phase 2) render#
The boot script is rendered in a second pass from st.cmd.jinja. That
top-level render sees:
_global(the same sharedUTILSsingleton), andevery variable spread from
UTILS.variables(everything created withset/incrementor),plus the already-rendered per-entity blocks: the environment-variable entries, the pre-
iocInitscript entries, and the post-iocInitentries.
Because the per-entity blocks are already rendered before this pass, counters
and stored globals reflect the totals accumulated across all entities by the
time the st.cmd template runs.