Maintainer guide
This document is intended for maintainers of the template.
Automated updates
We use Dependabot to keep dependencies up-to-date, including Python packages, GitHub actions, npm packages, and Docker images.
GitHub Actions workflows
CI
ci.yml
The CI workflow runs on pushes to main and on pull requests. It covers two main aspects:
Tests job: Runs pytest across Ubuntu, Windows, and macOS to validate template generation works correctly on all platforms.
Docker job: Builds and tests Docker configurations for both the basic setup and with Celery enabled.
Issue manager
issue-manager.yml
Uses tiangolo/issue-manager to automatically close issues and pull requests after a delay when labeled appropriately.
Runs daily at 00:12 UTC, and also triggers on issue comments, issue labeling, and PR labeling.
Configured labels and their behavior (all with 10-day delay):
Label |
Message |
|---|---|
|
Assuming the question was answered, this will be automatically closed now. |
|
Assuming the original issue was solved, it will be automatically closed now. |
|
Automatically closing after waiting for additional info. To re-open, please provide the additional information requested. |
|
As discussed, we won’t be implementing this. Automatically closing. |
UV lock regeneration
dependabot-uv-lock.yml
Automatically regenerates uv.lock when pyproject.toml changes in PRs from Dependabot or PyUp. This ensures the lock file stays in sync with dependency updates.
Triggers on:
Pull requests that modify
pyproject.toml(from dependabot[bot] or pyup-bot)Manual workflow dispatch
Align versions
align-versions.yml
Keeps version numbers synchronized across the template when Dependabot updates certain files. Runs the scripts/node_version.py and scripts/ruff_version.py scripts to propagate version changes.
Triggers on Dependabot PRs that modify:
template/.nvmrctemplate/requirements/local.txt
Template testing
Copier Python API
Template tests use the Copier Python API directly:
# tests/test_copier_generation.py
from copier import run_copy
def test_project_generation(template_path, tmp_path, context):
"""Test template generation with default options."""
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
assert tmp_path.is_dir()
def test_generation_with_celery(template_path, tmp_path, context):
"""Test template generation with Celery enabled."""
context["use_celery"] = True
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
assert (tmp_path / "config" / "celery_app.py").exists()
def test_generation_without_drf(template_path, tmp_path, context):
"""Test template generation without DRF."""
context["use_drf"] = False
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
assert not (tmp_path / "config" / "api_router.py").exists()
The template_path and context fixtures are defined in conftest.py.
Matrix testing
We test multiple option combinations to catch interaction bugs. The CI runs tests across:
Operating systems: Ubuntu, Windows, macOS
Feature combinations: Default, Celery, async, Heroku
Example parametrized test:
import pytest
@pytest.mark.parametrize("use_celery,use_async,use_heroku", [
(False, False, False), # Defaults
(True, False, False), # With Celery
(False, True, False), # With async
(False, False, True), # With Heroku
(True, True, False), # Celery + async
])
def test_combinations(template_path, tmp_path, context, use_celery, use_async, use_heroku):
context.update({
"use_celery": use_celery,
"use_async": use_async,
"use_heroku": use_heroku,
})
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
assert tmp_path.is_dir()
Conditional file exclusion testing
Copier uses _exclude patterns in copier.yaml to conditionally exclude files. Tests verify this behavior:
def test_celery_files_excluded_when_disabled(template_path, tmp_path, context):
"""Verify Celery files are excluded when use_celery=False."""
context["use_celery"] = False
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
# These files should be excluded by _exclude patterns in copier.yaml
assert not (tmp_path / "config" / "celery_app.py").exists()
assert not (tmp_path / "docker" / "local" / "django" / "celery").exists()
def test_post_generation_generates_secrets(template_path, tmp_path, context):
"""Verify post_generation.py generates random secrets in .env."""
run_copy(str(template_path), str(tmp_path), data=context, unsafe=True, vcs_ref="HEAD")
env_file = tmp_path / ".env"
content = env_file.read_text()
# Secret should be present and not a placeholder
assert "DJANGO_SECRET_KEY=" in content
assert "{{" not in content # No unexpanded template vars
Template updates with Copier
This template uses Copier for template management with built-in update support:
# In a generated project, update to the latest template version
copier update --trust
# Apply a specific template version
copier update --trust --vcs-ref v2.0.0
Copier stores template metadata in .copier-answers.yml, including the template URL and the commit hash used to generate the project.
For maintainers: When making breaking changes, consider using Copier’s migration scripts feature to handle upgrades automatically.