Jinja2¶
Jinja2 is the code-rendering layer. Every generator class the tool emits, every fallback generator, and every team-contribution template is a Jinja2 template rendered to a string of Python (or YAML, or Markdown) source. This chapter is about that specific, slightly unusual use of a templating engine: rendering code, not markup.
Inline Template, not a loader environment¶
Both src/test_data_workbench/adaptation/generator_factory.py and
src/test_data_workbench/core/template_generator.py use Jinja2’s
Template class directly, constructed from an inline triple-quoted string,
rather than a configured Environment with a FileSystemLoader pointed
at template files on disk:
from jinja2 import Template
template = Template('''
from faker import Faker
...
class {{ class_name }}Generator:
...
''')
return template.render(class_name=..., columns=..., ...)
Every generator-producing method (_create_user_generator,
_create_product_generator, _create_order_generator,
_create_review_generator, _create_generic_generator,
_create_fallback_generator) follows this same shape: build the template
string, compute a small context dict from the table’s columns, render, return
the string. Keeping the templates as string literals next to the Python that
computes their context keeps each generator strategy self-contained in one
method, at the cost of losing syntax highlighting for the embedded template
and of Jinja2 parsing the template fresh on every call rather than once at
import time. For templates this small and infrequently invoked (once per
table, per tdw deploy), that cost is negligible.
Whitespace control shapes the emitted Python¶
Because the output has to be syntactically valid Python, not just plausible
text, whitespace-trimming tags ({%- %} / {%- -%}) do real work
throughout these templates. A block that should not appear at all when its
condition is false, like the sequential primary-key counter, is written so
its absence leaves no stray blank line or dangling comma:
def __init__(self, seed: Optional[int] = None):
self.fake = Faker()
self._generated_emails = set()
{%- if has_sequential_pk %}
self._pk_counter = 0
{%- endif %}
if seed is not None:
Faker.seed(seed)
random.seed(seed)
Without the trim markers, Jinja2’s default block behavior leaves the newline
around {% if %}/{% endif %} tags in the output, which would still be
syntactically valid here but would accumulate into visibly uneven generated
code across the many conditional sections. Since the entire point of
generated-code-as-product (Design Principles) is that a
person reads this output, tidy whitespace is not cosmetic, it is part of the
“inspectable” property the docs claim.
Optional constructor shape via conditional blocks¶
The generic generator template (_create_generic_generator) is the
clearest example of the templates doing real structural work, not just
value substitution. Whether the generated class’s __init__ takes a
reference_data parameter at all, whether it needs a self-reference
tracking dict, and whether the class gets a _get_reference_id or
_self_reference_id helper method are each decided by a boolean computed
in Python (uses_reference_helper, uses_self_reference_helper) and
threaded into the template as conditionals. A table with no foreign keys
gets a generator with a plain no-argument constructor; a table with a
self-referential foreign key (categories.parent_id -> categories.id)
gets a materially different class shape. The template is doing dispatch on
structure, not just filling in blanks in one fixed skeleton.
Rendered code is validated after the fact, not trusted¶
Nothing about Jinja2 guarantees the rendered text is valid Python; it is a text templating engine and has no idea what Python syntax is. That check happens downstream, in the AST-based validators described in Architecture, which parse every generated file before it is trusted. Jinja2’s job ends at producing the string; correctness of that string is someone else’s concern by design, which keeps the template code itself simple string substitution rather than tangled with validation logic.
Three-tier contribution templates¶
core/template_generator.py’s TeamTemplateGenerator uses the same
inline-Template approach for a different purpose: producing
copy-paste-and-edit starting points for team members at three explicit skill
levels (beginner, intermediate, advanced), each with a different generated
class shape, a plain generate() with TODO markers for beginners, explicit
relationship handling for intermediates, batching and caching scaffolding for
advanced contributors. It also renders the project’s generated
CONTRIBUTING.md and scenario YAML through the same mechanism, which is
why Jinja2 is listed as a general code/config renderer for the project rather
than narrowly “the thing that makes generator classes.”