Contributing¶
Thanks for your interest in django-edge-isr! This document explains how to set up your dev environment, run tests, follow our style guides, and open high-quality pull requests.
Table of contents¶
- Getting started
- Project layout
- Run the example app
- Testing
- Linting & formatting
- Pre-commit hooks
- Git & PR conventions
- Design guidelines
- Performance & safety checks
- Good first issues
- Opening issues
- Security
- License
Getting started¶
# 1) Create & activate virtualenv
python -m venv .venv
source .venv/bin/activate
# 2) Install in editable mode with dev extras
pip install -e ".[dev]"
# 3) Optional: start a local Redis (tests use fakeredis by default)
redis-server # (optional)
We support Python 3.10+ and Django 4.2 / 5.x in the 0.x series.
Project layout¶
├─ src/edge_isr/ # package code
│ ├─ connectors/ # CDN connectors (cloudflare, cloudfront)
│ ├─ revalidate/ # queue adapters + tasks
│ ├─ admin/ # minimal admin endpoints (JSON)
│ ├─ middleware.py # default SWR policy + tag binding
│ ├─ decorators.py # @isr(...) for views/fragments
│ └─ ...
├─ tests/ # pytest + pytest-django (in-memory DB)
│ ├─ django_settings.py # isolated settings for tests
│ ├─ urls.py, views.py # tiny test endpoints
│ └─ ... # unit & integration tests
├─ example/ # demo Django project (manual e2e)
├─ docs/ # documentation sources
└─ pyproject.toml # build + tooling config
Run the example app¶
The example/
project helps you test the package manually:
cd example
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
# Visit http://127.0.0.1:8000/post/1/ (see README for quick steps)
Testing¶
We use pytest + pytest-django. Tests are isolated from your system Redis via fakeredis.
- Run the full suite:
python -m pytest -q
- Run a subset:
python -m pytest tests/test_graph.py::test_bind_and_urls_for -q
- Useful envs:
# show prints/logs on failure
pytest -q -s
# stop on first failure
pytest -q -x
Writing tests¶
- Place tests in
tests/
and import the package asedge_isr
. - If you need Redis, don’t hit a real server — use the already-configured
fakeredis
fixture (tests/conftest.py
patchesedge_isr.graph.redis.from_url
automatically). - HTTP tests can use Django’s
client
fixture. - Prefer small, focused tests; add one failing test per bug before fixing it.
Minimal example:
# tests/test_my_feature.py
from edge_isr import tag, graph
def test_something():
url = "http://testserver/x/"
t = tag("post", 123)
graph.bind(url, [t])
assert url in graph.urls_for([t])
Linting & formatting¶
We follow the common Python toolchain:
- Black – the only formatter (opinionated, deterministic).
- Ruff – linter (fast, catches style & small logic mistakes). We use
ruff --fix
for safe auto-fixes, not for formatting.
Run locally:
# format
black .
# lint
ruff check .
# or apply safe auto-fixes:
ruff check . --fix
Black & Ruff settings live in pyproject.toml
([tool.black]
, [tool.ruff]
).
Pre-commit hooks¶
We recommend installing pre-commit to run Black/Ruff and basic hygiene checks before every commit.
pip install pre-commit
pre-commit install
pre-commit run --all-files
.pre-commit-config.yaml
includes:
end-of-file-fixer
,trailing-whitespace
,check-yaml
,check-toml
,debug-statements
- Black
- Ruff (lint only; no ruff-format)
Git & PR conventions¶
- Branching: open feature/bug branches off
develop
(e.g.feat/connector-cloudfront
,fix/tag-ttl
). -
Commits: use Conventional Commits:
-
feat:
,fix:
,docs:
,test:
,refactor:
,chore:
… -
PR checklist:
-
[ ] Title uses Conventional Commits
- [ ] Tests added/updated and pass locally (
pytest -q
) - [ ] Lint & format pass (
pre-commit run --all-files
) - [ ] Docs updated (
README
,docs/
, examples if applicable) - [ ] No breaking changes unless explicitly discussed
Small, focused PRs are merged faster than “mega PRs”.
Design guidelines¶
- Opt-in: ISR is explicit (
@isr
) — never cache private/personalized endpoints by default. - Headers first: SWR semantics via
Cache-Control: s-maxage=..., stale-while-revalidate=...
. - Tag graph is the source of truth: always bind
url ↔ tags
for targeted invalidations. - Connectors are optional: package must work without Cloudflare/CloudFront (reverse proxy or just origin cache).
- Safe-by-default: no infinite loops (idempotent warmups), respect
Vary
, handle non-200 responses gracefully. - DX matters: clear errors, good defaults, minimal boilerplate.
Performance & safety checks¶
-
Avoid origin stampede:
-
serve stale within SWR window, revalidate once in background
- queue warmups and throttle them
- Keep Redis keys small (we hash long URL keys internally).
- Don’t bind on non-200 responses; don’t purge on empty resolution sets.
- Add tests around headers,
Vary
, and tag resolution for new features.
Good first issues¶
- Connector coverage: add more tests for CloudFront invalidations (batching, quotas).
- Fragment caching: prototype
@isr_fragment
/{% isrcache %}
with a tiny API and tests. - Admin UX: filters, pagination, and simple replay actions in
edge_isr.admin
. - Metrics: Prometheus exporter (warmup latency, purge latency, hit ratio if available).
- Docs: short “recipes” for Django REST Framework, HTMX, Wagtail.
If you’re unsure where to start, comment on an issue and we’ll help you scope it.
Opening issues¶
Please include:
- Environment (Python/Django versions), package version/commit
- Minimal reproduction (view/model snippet, headers observed)
- Expected vs. actual behavior
- Logs/tracebacks (trim to essentials)
Bug reports with a failing test case are ❤️.
Security¶
If you believe you’ve found a security issue, do not open a public issue. Please email the maintainers privately: hamabarhamou@gmail.com. We’ll respond quickly and coordinate a fix.
License¶
By contributing, you agree that your contributions are licensed under the project’s MIT license.