Automatic code generation from Jinja templates
Illustration by Narcisse Navellier (obtained from Wikimedia Commons)StempelWerk has been created to prevent copy-and-paste errors and reduce tedious manual work. This can lead to a tremendous saving in development time and cost.
- Lean and easy to use
- Template system is easy to understand and lightweight
- Templates can be introduced gradually
- Any file can be switched back to manual editing by deleting its template
- Mature and powerful
- All functionality is covered by unit tests
- The template language Jinja2 is very mature, and has an excellent Template Designer Documentation
- A single template can create multiple files
- Templates can undertake surprisingly complex tasks
- Jinja2 (and thus StempelWerk) can be extended using Python
- Permissive licensing
- StempelWerk: BSD 3-Clause License
- Jinja2: BSD 3-Clause License
- Python: PSF licencse
- uv: MIT License
I have used StempelWerk in a professional project to generate most code (SQL) and part of the documentation (Markdown). StempelWerk even helped me during a migration by creating code for two different types of database. I logged and offset time gained against time spent: after nine months, I had saved 100+ hours of working time!
This ensures that you can update StempelWerk, its libraries, and your templates with peace of mind. Just execute a full StempelWerk run and see whether the output files change (or do not change) in the way you expect. Any incompatibilities will become immediately obvious.
There are many other benefits as well, such as the detection (and retention) of accidental manual changes to your output files.
It is quite possible to store templates and output files directly next to each other. However, I have found that a designated output directory simplifies setting up, and working with, StempelWerk. It is hard to describe - you just feel less "resistance"...
One example: by design, StempelWerk does not store state. In consequence, it does not keep track of generated files and cannot detect superfluous output files (such as after deletion or renaming). However, you can easily delete the output directory and run StempelWerk again. All superfluous files will be gone, whereas your templates and other files are not affected.
StempelWerk is currently developed in Python 3.12. The tests also run successfully on Python 3.10 and Python 3.14.
- For flexibility, use
pip:
# bash
python3 -m pip install stempelwerk- For simplicity, use
pipx:
# bash
pipx install --upgrade stempelwerk-
Install uv
-
Open the project directory
-
Setup the development environment
- Linux shell:
./script/bootstrap
- Windows PowerShell:
.\script\bootstrap.ps1
- Anywhere:
# edit `.python-version` to match your installed Python version, # or remove `--no-managed-python` to let uv install Python uv sync --no-managed-python
pipandpipx:
stempelwerk [ARGUMENTS] SETTINGS_FILE_PATHuv:
uv run stempelwerk [ARGUMENTS] SETTINGS_FILE_PATH- Python package:
from stempelwerk.StempelWerk import StempelWerk
StempelWerk(...)For help on the command line, simply call:
[uv run] stempelwerk --helpPath to a JSON file or a JSON-formatted string containing a dictionary of global variables:
{
"spam": "eggs"
}StempelWerk forces explicit use of globals by grouping them under the
environment variable globals. In other words, you have to use globals.spam
or globals['spam'] to access your global variable spam.
Jinja supports several approaches of loading global variables. In case it
matters, StempelWerk loads globals when calling
jinja2.Environment.get_template.
For a simple demonstration of globals, please render the provided example
templates with --globals '{"NO_cast": true}'.
By default, StempelWerk renders all template files it finds in the specified template directory.
When you use the command line argument --only-modified, however, StempelWerk
tries to process only the template files that have changed since the last
successful run.
This logic is not infallible: some file systems update modification times in a
weird manner, and changes to master templates (called "stencils" in StempelWerk)
are currently not handled. However, in such a case you can simply use
StempelWerk without the --only-modified argument.
Do not use this command line argument in CI/CD pipelines!
Adding one of these command line arguments will display less information. Great when working on slow consoles.
Adding this command line argument will display additional information, such as settings, loaded templates and added extensions. Very useful for debugging.
StempelWerk reads its settings from a JSON file (see settings_example.json for
an example) . The path to this file is specified as command line argument, and
is relative to the current working directory.
For cross-platform compatibility, I recommend using forward slashes in settings
for path separators: /spam/eggs. StempelWerk will handle path separator
conversions for you.
Path to root directory, relative to the current working directory. All other paths are relative to this directory. This simplifies the setting up of paths.
Path to root of template directory, relative to root_dir. This directory is
scanned recursively, and all files matching the setting included_file_names
will be rendered using Jinja.
Path to root of output directory, relative to root_dir. Rendered files will be
saved in this directory.
Default value: None
Name of the directory containing stencils (master templates). The name must not contain slashes or backslashes.
Files in directories matching this name will not be rendered. If this setting is specified and no stencils are found, StempelWerk will exit with an error.
There may be one or more directories with this name, and they must be located
somewhere under template_dir. This allows stencils to be loaded into Jinja,
and to be referenced from templates at runtime.
Default value: False
StempelWerk ensures that template_dir and output_dir exist. In case they are
not found, StempelWerk will exit with an error.
Previously, these directories were created automatically, but that interfered with debugging.
When this option is set to yes, all missing directories in the output directory will be created automatically. This ensures that rendered files can always be written.
Depending on your use case, automatically created directories may be just awkward or a full-blown security issue. This option is therefore disabled by default, and I encourage you to leave it that way.
List containing file specifications such as *.sql.jinja. Only files with a
matching glob are considered
to be templates and will be passed to Jinja.
Default value: {}
Dictionary containing initialization parameters for the Jinja environment.
Most default values work well for me, but I always enable
trim_blocks:
"jinja_options": {
"trim_blocks": true
},Default value: []
List containing Jinja extensions that will be loaded into the Jinja environment.
Default value: []
List of Python modules, each containing a CustomCode class inheriting from
StempelWerk.CustomCodeTemplate. See directory tests/tintin/custom for
examples.
After creating the Jinja environment and loading Jinja extensions, each module
will be imported. An instance of CustomCode will be created and its method
update_environment() will be called. This method must return a Jinja
environment.
Use custom modules to add filters and tests to the environment, or perform any other task Python is capable of.
Warning: there are no security checks to prevent you from deleting all of your files and doing other mischief, so please be careful!
Default value: .last_run
Path to the file for storing a time stamp of the last successful run. The path
is relative to root_dir.
If your operating system handles temporary directories correctly (Windows does
not), you could store this file in one of them (e.g. /tmp/). With
--only-modified, all template files would be rendered once after starting the
system, and afterwards only when they are updated.
Default values: ### New file: and ### Content:
Each time these strings are encountered in the rendered output of a template, a new file is created. This allows you to create multiple files from a single template.
The code relies on the following order: new file marker, optional whitespace, path to the output file, optional whitespace, content marker, optional whitespace, and contents of the output file:
### New file: spam/eggs.py
### Content:
def spam():
return 'eggs'
You can insert these markers by using a template filter provided by StempelWerk:
{{- ('directory/' ~ filename) | start_new_file -}}or adding a macro to the template:
{%- macro add_file_markers(filename) %}
### New file: {{ filename }}
### Content:
{% endmacro -%}
{{- add_file_markers('directory/' ~ filename) -}}Good file separators strike a balance between performance (brevity) and reliability (uniqueness). Please see the example files to see them in action.
Default value: None
Rendered files will be written using the operating system's standard newline
character. Change this setting to use another newline character, such as \r\n.
StempelWerk overrides this setting for certain files, such as Windows Batch
files. If you want to change this behaviour, please create an instance of
StempelWerk in Python and override its public member variable
newline_exceptions.
Please read the code of conduct before asking for help, filing bug reports or contributing to this project. Thanks!
Copyright (c) 2020-2026 Martin Zuther
This program is free software and licensed under the terms of the BSD 3-Clause License.
Thank you for using free software!
