Pytest plugin to lint Jinja2 templates in FastAPI applications
Project description
pytest-jinja-check
A pytest plugin that lints Jinja2 templates used in FastAPI applications. Catches common issues at test time:
- Syntax validation — templates parse without errors
- Hardcoded route detection —
href="/foo"should beurl_for('foo') - Endpoint validation —
url_for('endpoint')references actually exist in your app - Context variable checking — routes pass all variables their templates need
Installation
pip install pytest-jinja-check
Or with uv:
uv add pytest-jinja-check
For endpoint validation against a live FastAPI app, install with the fastapi extra:
pip install "pytest-jinja-check[fastapi]"
# or
uv add "pytest-jinja-check[fastapi]"
Quick start
The plugin auto-registers with pytest via entry points. Add configuration to your pyproject.toml:
[tool.pytest-jinja-check]
template_dir = "templates"
python_dir = "app"
auto_check = true
app = "app.main:app" # optional — enables url_for() endpoint validation
With auto_check = true, all lint checks run at session start — no boilerplate tests required:
pytest
If lint issues are found, pytest exits before your tests run with a single aggregated report.
You can also enable checks for one run without changing config:
pytest --jinja-check
Use --no-jinja-check to skip checks when auto_check is enabled in config.
Manual checks with fixtures
If you prefer explicit tests or need to validate against a specific app instance, use the provided fixtures:
# tests/test_templates.py
def test_template_syntax(template_syntax_errors):
assert not template_syntax_errors, "\n".join(str(e) for e in template_syntax_errors)
def test_no_hardcoded_routes(hardcoded_routes):
assert not hardcoded_routes, "\n".join(str(e) for e in hardcoded_routes)
def test_context_variables(missing_context_variables):
assert not missing_context_variables, "\n".join(
str(e) for e in missing_context_variables
)
def test_url_for_endpoints(validate_endpoints):
from myapp.main import app
errors = validate_endpoints(app)
assert not errors, "\n".join(str(e) for e in errors)
Fixtures
All analysis fixtures are session-scoped (run once per test session).
| Fixture | Returns | Description |
|---|---|---|
template_linter_config |
LinterConfig |
Resolved configuration |
template_variables |
dict[str, TemplateInfo] |
Template name -> extracted variables (with inheritance) |
route_contexts |
list[RouteContext] |
All TemplateResponse calls found via AST |
template_syntax_errors |
list[LintError] |
Templates that fail to parse |
hardcoded_routes |
list[LintError] |
Hardcoded URLs that should use url_for() |
missing_context_variables |
list[LintError] |
Routes missing required template variables |
validate_endpoints |
callable(app) -> list[LintError] |
Factory: validates url_for() refs against a live app |
Configuration
All settings in [tool.pytest-jinja-check] in your pyproject.toml:
| Key | Default | Description |
|---|---|---|
template_dir |
"templates" |
Template directory relative to project root |
python_dir |
"." |
Directory to scan for Python route files |
route_file_patterns |
["**/*.py"] |
Glob patterns for route files |
ignore_variables |
["request", "url_for", ...] |
Variables to skip (framework-provided) |
allowed_url_prefixes |
["#", "http://", ...] |
URL prefixes that aren't hardcoded routes |
auto_check |
false |
Run all lint checks at pytest session start |
app |
(none) | Import string for FastAPI app (e.g. "app.main:app") — enables endpoint validation in auto-check mode |
You can also pass pytest CLI options:
| Flag | Description |
|---|---|
--jinja-check |
Run lint checks for this session (overrides auto_check = false) |
--no-jinja-check |
Skip lint checks (overrides auto_check = true) |
--template-lint-config <path> |
Directory containing pyproject.toml with [tool.pytest-jinja-check] config |
Programmatic API
The analysis functions can also be used outside of pytest:
from pytest_jinja_check import (
analyze_all_templates,
check_syntax,
check_hardcoded_routes,
check_context_variables,
validate_url_for_references,
extract_all_route_contexts,
run_all_checks,
load_config,
)
from pathlib import Path
# Run all checks in one call (same as auto-check mode)
config = load_config(Path("."))
errors = run_all_checks(config)
# Or run checks individually
# Analyze templates
templates = analyze_all_templates(Path("templates"))
for name, info in templates.items():
print(f"{name}: needs {info.variables}")
# Find issues
syntax_errors = check_syntax(Path("templates"))
hardcoded = check_hardcoded_routes(Path("templates"))
routes = extract_all_route_contexts(Path("app"))
missing = check_context_variables(Path("templates"), routes)
How it works
- Template analysis uses
jinja2.meta.find_undeclared_variables()on parsed ASTs, recursively resolving{% extends %}to capture inherited variable requirements. - Route analysis uses Python's
astmodule to findTemplateResponse(...)calls and extract template names and context dict keys. Handles both the old positional API and newer keyword API. - Endpoint validation introspects a live FastAPI
app.routesto get registered endpoint names.
Known limitations
- Template variables from dynamic
{% extends variable %}can't be resolved. - Context dicts built outside the
TemplateResponse(...)call (e.g.,ctx = {...}; TemplateResponse("t.html", ctx)) are flagged as dynamic and skipped rather than producing false positives. - The variable union from template inheritance may over-report when a child block completely replaces a parent block that used a variable.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_jinja_check-1.1.0.tar.gz.
File metadata
- Download URL: pytest_jinja_check-1.1.0.tar.gz
- Upload date:
- Size: 50.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4df166a69113aa557763014ada041a94f97403e9160b75c6ab2d413b01aca787
|
|
| MD5 |
38e3b4be42ed8730aa4ab90992402ef4
|
|
| BLAKE2b-256 |
b132ed90d7ab256c264ffcaabca269354b448486f2d97ce687555edda5a778a8
|
Provenance
The following attestation bundles were made for pytest_jinja_check-1.1.0.tar.gz:
Publisher:
release.yml on Promptly-Technologies-LLC/pytest-jinja-check
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_jinja_check-1.1.0.tar.gz -
Subject digest:
4df166a69113aa557763014ada041a94f97403e9160b75c6ab2d413b01aca787 - Sigstore transparency entry: 1928922669
- Sigstore integration time:
-
Permalink:
Promptly-Technologies-LLC/pytest-jinja-check@8dcfda41a380c2cd0dc05b563284e1185fac5675 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Promptly-Technologies-LLC
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8dcfda41a380c2cd0dc05b563284e1185fac5675 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_jinja_check-1.1.0-py3-none-any.whl.
File metadata
- Download URL: pytest_jinja_check-1.1.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f89c41fc88772608ca8ef3d92ad54dc53d79dcb50a5106f02abd8cf04ad01eb
|
|
| MD5 |
84cf126168da99732ee9baf784b6f021
|
|
| BLAKE2b-256 |
4e367be3b6f7c4c51c47eb034444b972766c639cb0889c9ba47a1ad7b4093ef0
|
Provenance
The following attestation bundles were made for pytest_jinja_check-1.1.0-py3-none-any.whl:
Publisher:
release.yml on Promptly-Technologies-LLC/pytest-jinja-check
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_jinja_check-1.1.0-py3-none-any.whl -
Subject digest:
8f89c41fc88772608ca8ef3d92ad54dc53d79dcb50a5106f02abd8cf04ad01eb - Sigstore transparency entry: 1928922824
- Sigstore integration time:
-
Permalink:
Promptly-Technologies-LLC/pytest-jinja-check@8dcfda41a380c2cd0dc05b563284e1185fac5675 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Promptly-Technologies-LLC
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8dcfda41a380c2cd0dc05b563284e1185fac5675 -
Trigger Event:
push
-
Statement type: