Copier Template-Extensions
Special Jinja2 extension for Copier that allows to load extensions using file paths relative to the template root instead of Python dotted paths.
Installation
With pip:
pip install copier-template-extensions
With uv:
uv tool install copier --with copier-template-extensions
With pipx:
pipx install copier
pipx inject copier copier-template-extensions
Usage
In your template configuration, first add our loader extension, then add your templates extensions using relative file paths, and the class name after a colon:
_jinja_extensions:
- copier_template_extensions.TemplateExtensionLoader
- extensions/context.py:ContextUpdater
- extensions/slugify.py:SlugifyExtension
With this example, you are supposed to have an extensions
directory at the root of your template containing two modules:
context.py and slugify.py.
📁 template_root
├── 📄 abc.txt.jinja
├── 📄 copier.yml
└── 📁 extensions
├── 📄 context.py
└── 📄 slugify.py
See Context hook extension
to see how the ContextUpdater class can be written.
The SlugifyExtension class could be written like this:
import re
import unicodedata
from jinja2.ext import Extension
# taken from Django
# https://github.com/django/django/blob/main/django/utils/text.py
def slugify(value, allow_unicode=False):
"""
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
dashes to single dashes. Remove characters that aren't alphanumerics,
underscores, or hyphens. Convert to lowercase. Also strip leading and
trailing whitespace, dashes, and underscores.
"""
value = str(value)
if allow_unicode:
value = unicodedata.normalize('NFKC', value)
else:
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub(r'[^\w\s-]', '', value.lower())
return re.sub(r'[-\s]+', '-', value).strip('-_')
class SlugifyExtension(Extension):
def __init__(self, environment):
super().__init__(environment)
environment.filters["slugify"] = slugify
Context hook extension
This package also provides a convenient extension class allowing template writers to update the context used to render templates, in order to add, modify or remove items of the context.
In one of your relative path extensions modules,
create a class that inherits from ContextHook,
and override its hook method:
from copier_template_extensions import ContextHook
class ContextUpdater(ContextHook):
def hook(self, context):
context["say"] = "hello " + context["name"]
In your Jinja templates, you will now have access
to the {{ say }} variable directly.
This can be extremely useful in template projects where you don't want to ask too many questions to the users and instead infer some values from their answers.
Consider the following example:
you ask your users if they want to generate
a CLI app or a web API.
Depending on their answer,
the main Python module should be named
cli.py or app.py.
Without the context hook, you would need to write a Jinja macro somewhere, or update the context directly in Jinja, and import this file (still using Jinja) in the filename of the module:
{# using macros #}
{%- macro module_name() %}
{%- if project_type == "webapi" %}app{% else %}cli{% endif %}
{%- endmacro %}
{# or enhancing the context #}
{#- Initiate context with a copy of Copier answers -#}
{%- set ctx = _copier_answers.copy() -%}
{#- Populate our new variables -#}
{%- set _ = ctx.update({"module_name": ("app" if project_type == "webapi" else "cli") -%}
📁 template_root
├── 📄 copier.yml
├── 📄 macros # the macros file
├── 📄 context # the context file
├── 📁 extensions
│ └── 📄 slugify.py
└── 📁 {{project_name|slugify}}
│
│ # using the macros
├── 📄 {% import 'macros' as macros with context %}{{macros.module_name()}}.py.jinja
│
│ # or using the enhanced context
└── 📄 {% from 'context' import ctx with context %}{{ctx.module_name}}.py.jinja
As you can see, both forms are really ugly to write:
- the
macrosorcontextcan only be placed in the root, as slashes/are not allowed in filenames - you must use spaces and single-quotes (double-quotes are not valid filename characters on Windows) in your templated filenames, which is not clean
- filenames are very long
Using our context hook instead makes it so easy and clean!
from copier_template_extensions import ContextHook
class ContextUpdater(ContextHook):
def hook(self, context):
context["module_name"] = "app" if context["project_type"] == "webapi" else "cli"
📁 template_root
├── 📄 copier.yml
├── 📁 extensions
│ ├── 📄 slugify.py
│ └── 📄 context.py
└── 📁 {{project_name|slugify}}
└── 📄 {{module_name}}.py.jinja
You can do many more things with a context hook, like downloading data from external resources to include it in the context, etc.
Sponsors
And 4 more private sponsor(s).
Metadata
Release files for copier-template-extensions 0.3.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| copier_template_extensions-0.3.4.tar.gz | 40.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| copier_template_extensions-0.3.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 55.1 kB
Release files / copier_template_extensions-0.3.4.tar.gz
| Download URL | copier_template_extensions-0.3.4.tar.gz |
|---|---|
| Size | 40.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ec9d2c50c6d6cdd54f11d1bedffcc01ca40cf74cc3035e171cb4d3fef38bb3ea
|
|
BLAKE2b-256 checksum How to use checksums |
acf55a67301e6da8729c06b583de290c9840d8c89d6cddd3b47a347bef2a767a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|
Release files / copier_template_extensions-0.3.4-py3-none-any.whl
| Download URL | copier_template_extensions-0.3.4-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
81c66adf2dbf77bedf8caf86d4494548b294671cdb276f6370eb111f40f38b39
|
|
BLAKE2b-256 checksum How to use checksums |
6772058b8db6813d6390daddfa5ae7684e7b6bdb044bc0c1e1f0da2d0c3bacd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|