Skip to content

mzuther/StempelWerk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

306 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

PyPI - Version PyPI - License

StempelWerk

Automatic code generation from Jinja templates

Illustration of an old printing machine

Illustration by Narcisse Navellier (obtained from Wikimedia Commons)

Introduction

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

Case study

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!

Best practices

Keep output files under source control

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.

Store output files in a designated directory

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.

Installation

StempelWerk is currently developed in Python 3.12. The tests also run successfully on Python 3.10 and Python 3.14.

PyPI package

  • For flexibility, use pip:
# bash
python3 -m pip install stempelwerk
  • For simplicity, use pipx:
# bash
pipx install --upgrade stempelwerk

Repository

  1. Install uv

  2. Open the project directory

  3. 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

Execution

  • pip and pipx:
stempelwerk [ARGUMENTS] SETTINGS_FILE_PATH
  • uv:
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 --help

Command line argument --globals

Path 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}'.

Command line argument --only-modified

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!

Command line argument --ultraquiet and --quiet

Adding one of these command line arguments will display less information. Great when working on slow consoles.

Command line argument --verbose

Adding this command line argument will display additional information, such as settings, loaded templates and added extensions. Very useful for debugging.

Settings

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.

root_dir

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.

template_dir

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.

output_dir

Path to root of output directory, relative to root_dir. Rendered files will be saved in this directory.

stencil_dir_name

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.

create_directories

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.

included_file_names

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.

jinja_options

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
  },

jinja_extensions

Default value: []

List containing Jinja extensions that will be loaded into the Jinja environment.

custom_modules

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!

last_run_file

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.

marker_new_file and marker_content

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.

newline

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.

Code of conduct

Please read the code of conduct before asking for help, filing bug reports or contributing to this project. Thanks!

License

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!

About

Automatic code generation from Jinja2 templates

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published