Smart fixture caching for pytest with SQLite storage
Project description
pytest-fixture-cache
Smart fixture caching for pytest - Speed up your test suite by caching expensive fixtures with SQLite storage.
Why pytest-fixture-cache?
Testing with expensive fixtures (API calls, database seeding, file generation) can slow down your test suite significantly. This plugin automatically caches fixture results to SQLite, reducing test execution time by up to 10x for fixture-heavy test suites.
Key Features:
- ✅ Drop-in decorator - Just add
@cached_fixtureto your fixtures - ✅ SQLite storage - Fast, reliable, zero-config persistence
- ✅ Smart invalidation - Mark fixtures dirty when data changes
- ✅ Scope-aware - Respects pytest fixture scopes (function/class/module/session)
- ✅ CLI integration - Clear cache, disable caching, view stats
- ✅ Result monad support - Works with
returns,resultlibraries - ✅ Hit tracking - Monitor cache effectiveness with statistics
Installation
pip install pytest-fixture-cache
For Result monad support:
pip install pytest-fixture-cache[monad]
Quick Start
Basic Usage
import pytest
from pytest_fixture_cache import cached_fixture
@cached_fixture
@pytest.fixture
def expensive_fixture():
"""This will be cached after first execution"""
# Expensive operation (API call, DB setup, etc.)
import time
time.sleep(5) # Simulating slow operation
return {"data": "value", "id": 123}
def test_something(expensive_fixture):
# First run: 5 seconds (executes fixture)
# Subsequent runs: <0.1 seconds (loads from cache)
assert expensive_fixture["id"] == 123
CLI Usage
# Run tests with caching (default)
pytest
# Clear cache before running tests
pytest --clear-cache
# Disable caching for this run
pytest --no-cache
# Show cache statistics
pytest --cache-stats
Cache Statistics Output
================================================================================
FIXTURE CACHE STATISTICS
================================================================================
Found 3 cached fixture(s):
Fixture: expensive_api_fixture
Scope: session
Status: clean
Hits: 42
Created: 2025-01-25 10:30:15
Updated: 2025-01-25 10:30:15
Fixture: database_fixture
Scope: module
Status: clean
Hits: 15
Created: 2025-01-25 10:31:20
Updated: 2025-01-25 10:31:20
================================================================================
Advanced Usage
Programmatic Cache Management
from pytest_fixture_cache import (
clear_fixture_cache,
mark_fixture_dirty,
get_cache_stats
)
# Clear specific fixture
clear_fixture_cache("my_fixture")
# Clear all fixtures
clear_fixture_cache()
# Mark fixture dirty (invalidate)
mark_fixture_dirty("my_fixture")
# Get statistics
stats = get_cache_stats()
for entry in stats:
print(f"{entry['fixtureName']}: {entry['hitCount']} hits")
Mark Cache Dirty in Tests
def test_modify_data(expensive_fixture, mark_dirty):
"""Test that modifies fixture data"""
# Use fixture
data_id = expensive_fixture['id']
# Modify the data (making cache invalid)
delete_data(data_id)
# Mark cache dirty so next test gets fresh data
mark_dirty('expensive_fixture')
Result Monad Integration
For functional programming patterns using returns or result libraries:
from returns.result import Result, Success, Failure
from pytest_fixture_cache.monad import cached_result
def create_order():
"""Returns Result[OrderInfo, str]"""
# ... API call that returns Result type
return Success({"order_id": "123", "status": "created"})
@pytest.fixture
def order_fixture():
"""Cached fixture with Result monad"""
result = cached_result("order_fixture", create_order)
assert isinstance(result, Success)
return result.unwrap()
Configuration
Set cache directory via environment variable:
# Use custom cache directory
export PYTEST_FIXTURE_CACHE_DIR="/path/to/cache"
pytest
# Default: .pytest_cache/fixtures/
In your tests:
from pytest_fixture_cache import get_cache_location
def test_cache_location():
cache_path = get_cache_location()
print(f"Cache stored at: {cache_path}")
How It Works
- First Run: Fixture executes normally, result is cached to SQLite
- Subsequent Runs: Cached data is loaded (skips expensive operations)
- Invalidation: Mark fixtures dirty when data changes
- Smart Loading: Only loads if status is "clean"
flowchart LR
A[Test Runs] --> B{Cache Exists?}
B -->|No| C[Execute Fixture]
B -->|Yes| D{Status Clean?}
D -->|Yes| E[Load from Cache]
D -->|No| C
C --> F[Save to Cache]
F --> G[Return Result]
E --> G
Cache Storage Schema
SQLite database: .pytest_cache/fixtures/cache.db
CREATE TABLE fixture_cache (
id INTEGER PRIMARY KEY,
fixtureName TEXT UNIQUE,
fixtureScope TEXT,
fixtureData TEXT, -- JSON serialized
status TEXT, -- 'clean' or 'dirty'
createdAt TEXT,
updatedAt TEXT,
hitCount INTEGER
)
Use Cases
1. API Integration Tests
@cached_fixture
@pytest.fixture(scope="session")
def api_auth_token():
"""Cache authentication token for entire test session"""
response = requests.post("https://api.example.com/auth",
json={"user": "test", "pass": "secret"})
return {"token": response.json()["access_token"]}
2. Database Seeding
@cached_fixture
@pytest.fixture(scope="module")
def seeded_database():
"""Cache database seed data for all tests in module"""
db = Database()
db.seed_test_data(1000_rows=True) # Expensive!
return {"db": db, "record_count": 1000}
3. File Generation
@cached_fixture
@pytest.fixture
def generated_report():
"""Cache generated PDF report"""
report = ReportGenerator().create_pdf(complex_data=True)
return {"path": report.path, "size": report.size}
Performance Impact
Example benchmark (100 tests, 3 expensive fixtures):
| Scenario | Without Cache | With Cache | Speedup |
|---|---|---|---|
| First run | 45.2s | 45.5s | 1.0x (cache creation overhead) |
| Second run | 45.1s | 4.8s | 9.4x faster |
| Third run | 44.9s | 4.7s | 9.5x faster |
Best Practices
-
Cache Expensive Operations Only
- Don't cache trivial fixtures (overhead > savings)
- Best for: API calls, DB operations, file I/O
-
Use Appropriate Scope
session: Data shared across all testsmodule: Data shared within a modulefunction: Per-test data (least caching benefit)
-
Invalidate When Needed
- Mark dirty after tests that modify data
- Clear cache when backend data changes
-
Monitor Cache Effectiveness
- Use
--cache-statsto check hit counts - Low hits = fixture not reused enough to benefit
- Use
Comparison to Alternatives
| Feature | pytest-fixture-cache | Built-in pytest.cache | pytest-lazy-fixture |
|---|---|---|---|
| Automatic caching | ✅ Decorator | ❌ Manual | ❌ No caching |
| Dirty/clean status | ✅ Yes | ❌ No | N/A |
| Hit tracking | ✅ Yes | ❌ No | N/A |
| CLI integration | ✅ Yes | ✅ Limited | ❌ No |
| Result monad support | ✅ Yes | ❌ No | ❌ No |
| Scope awareness | ✅ Yes | ❌ No | ✅ Yes |
Troubleshooting
Cache not working?
-
Check fixture returns a dict:
@cached_fixture @pytest.fixture def my_fixture(): return {"key": "value"} # ✅ Works # return "string" # ❌ Won't cache
-
Verify cache directory permissions:
ls -la .pytest_cache/fixtures/
-
Enable verbose output:
pytest -v # Shows "[CACHE HIT]" or "[CACHE MISS]" messages
Clear stuck cache:
pytest --clear-cache
# or
rm -rf .pytest_cache/fixtures/
Contributing
Contributions welcome! Please check the issues page.
Development Setup
git clone https://github.com/yourusername/pytest-fixture-cache.git
cd pytest-fixture-cache
pip install -e ".[dev]"
pytest tests/
License
MIT License - see LICENSE file for details.
Changelog
See CHANGELOG.md for version history.
Credits
Developed for the WMS testing framework, extracted as a standalone library to benefit the pytest community.
Links
Project details
Release history Release notifications | RSS feed
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_fixture_cache-0.1.0.tar.gz.
File metadata
- Download URL: pytest_fixture_cache-0.1.0.tar.gz
- Upload date:
- Size: 25.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0b3c2bb64f1a5d7a33392d493f45b9095d2f0a3348343b548622f92cf84a674
|
|
| MD5 |
01efa4e827afbb48ae5129da40d8d1d5
|
|
| BLAKE2b-256 |
e5cd34f04cc04c610324f3b06e71ffb57a238dc4644309182eb9184706a03299
|
File details
Details for the file pytest_fixture_cache-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pytest_fixture_cache-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d997e4fd1b37da29973cd3060795c035cfd9cb4ab53fc35b66bb1b56bed3696
|
|
| MD5 |
816f7c9db638ca027227478fbd546803
|
|
| BLAKE2b-256 |
b1bf354b9c9f447953edd751da47f67bc8ae2f3041d2ddd51c882ab2970159be
|