pytest-http-cache
Record real HTTP traffic the first time your tests run, replay it from disk every time after. Tests run offline, and a third party changing their API can't break your suite until you decide to re-record.
Works with httpx, requests, and aiohttp — and doesn't depend on any of them. Traffic is captured by a local record/replay proxy, so requests go through each library's real stack and its own proxy support instead of being mocked.
Install
pip install pytest-http-cache
The plugin registers itself; there is nothing to enable.
Use
def test_users():
users = httpx.get("https://api.example.com/v1/users").json()
assert users[0]["name"] == "Ada"
First run: the request goes out and the response is written to .http-cache/. Every later run: the
response is served from disk, no network involved. No fixture, decorator, or import is needed —
installing the plugin is enough, and http_cache_mode = off opts out.
The cache mirrors the URL, so you can read and diff it:
.http-cache/
api.example.com/
v1/users/
GET.response.http # raw wire format: status line, headers, blank line, body
cacheconfig.json # optional, see "Varying the key"
Configure
# pytest.ini / pyproject.toml
[pytest]
http_cache_dir = .http-cache # where responses live
http_cache_mode = auto # auto | record | replay | refresh | off
http_cache_local = false # cache localhost / 127.0.0.1 / 0.0.0.0 traffic too
http_cache_exclude_hosts = # hosts to never cache
| Mode | Behaviour |
|---|---|
auto (default) |
Replay what's cached, record what isn't |
record |
Always call upstream, always overwrite |
replay |
Replay only; a miss fails the test (use in CI to prove the suite is offline) |
refresh |
Drop the entry, then re-record |
off |
Pass everything through, store nothing |
Anything can be set on the command line, and per test:
pytest --http-cache-mode=refresh
pytest --http-cache-clear # wipe the cache first
pytest --http-cache-clear=https://api.example.com # wipe one subtree first
@pytest.mark.http_cache(mode="record", local=True)
def test_against_a_live_api(): ...
Local requests
localhost, 127.0.0.1, 0.0.0.0, and ::1 are passed straight through and never cached — your
own test server should stay live. Set http_cache_local = true (or the marker) when you do want
them recorded.
Clearing the cache
The store is plain files with no index, so all of these are equivalent and safe:
rm -rf .http-cache # everything
rm -rf .http-cache/api.example.com/v1/users # one endpoint
pytest --http-cache-clear
def test_fresh(http_cache):
http_cache.clear("https://api.example.com/v1/users")
Anything deleted is simply a miss on the next run.
Varying the key
By default the key is method + host + path — query parameters and headers are ignored, so
?page=1 and ?page=2 share one entry. When an endpoint really does vary, opt in per directory:
def test_paged(http_cache):
http_cache.vary("https://api.example.com/v1/users", query=["page"])
or write it by hand after the first run:
{"vary": {"query": ["page"], "headers": ["Accept-Language"]}}
Each combination is then stored as GET.<hash>.response.http beside the default entry, and
cacheconfig.json records what each hash stands for.
Certificate authority
HTTPS is intercepted with a CA generated on first use in ~/.cache/pytest-http-cache/ca, reused
from then on. Point PYTEST_HTTP_CACHE_CA_DIR somewhere else for hermetic or read-only-home
environments (CI containers, sandboxes).
The http_cache fixture
| Member | Purpose |
|---|---|
clear(url_pattern=None) |
Delete everything, or a URL prefix / glob subtree |
vary(url, query=[...], headers=[...]) |
Write vary rules for a URL's directory |
has(url, method="GET") |
Is this response cached? |
path_for(url, method="GET") |
Where the entry lives |
hits / misses |
Counters for the session |
root / mode / settings |
Resolved configuration |
How it works
A mitmproxy instance starts once per session on an ephemeral loopback port. The session's proxy and
CA environment variables point client libraries at it; aiohttp additionally gets a small shim
because ClientSession ignores the environment by default (trust_env=False). On a hit the proxy
answers from disk without opening an upstream connection; on a miss it forwards the request and
writes the response. See SPEC.md for the design.
Dependencies
pytest and mitmproxy (>=11,<12 — 12.x currently breaks TLS setup with recent pyOpenSSL).
Client libraries are never imported unless you use them.
Tested on CPython 3.11–3.13, Linux and macOS. Windows is not supported: cache directories are
named host:port, and : is not a legal path character there.
Status
Working end to end: httpx, requests, and aiohttp all record and replay over both HTTP and HTTPS. See SPEC.md for the design. Streaming responses (server-sent events) and WebSocket traffic pass through uncached.
Release files for pytest-http-cache 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_http_cache-0.1.0.tar.gz | 20.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_http_cache-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.3 kB
Release files / pytest_http_cache-0.1.0.tar.gz
| Download URL | pytest_http_cache-0.1.0.tar.gz |
|---|---|
| Size | 20.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6f9934c8c4df3ee07dbad4abdd8523f84c459ef8c134bcd83026794b7564cb08
|
|
BLAKE2b-256 checksum How to use checksums |
7499fb57d9b791564a0e5ddedf35fc44614ad88ba532d8628ce69dbf3482ef6e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency logRelease files / pytest_http_cache-0.1.0-py3-none-any.whl
| Download URL | pytest_http_cache-0.1.0-py3-none-any.whl |
|---|---|
| Size | 23.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf26a797f0a276c2aaa569f8542ba77c7e49aa08c7af4451ac99fba9a5010fae
|
|
BLAKE2b-256 checksum How to use checksums |
eceb0e7021fe5b3975d8777f8ae836153a969279dab5bc12c8f9be04abbaba90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency log