Changelog
All notable changes to pytest-async-benchmark will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.4.1] - 2026-11-12
📝 Documentation & Links
- Complete documentation website built with VitePress and deployed to GitHub Pages at https://mihailtd.github.io/pytest-async-benchmark/
- Added
[project.urls]inpyproject.toml(Documentation, Repository, Issues, Changelog, Homepage) - Interactive guides for Concurrency Benchmarking, Pedantic Mode, Task Leak Detection, and CI Regressions
- Real-world framework guides for FastAPI, FastMCP, Quart, and Aiohttp
- Architectural deep dive linking to https://mihai.ltd/blog/why-i-built-pytest-async-benchmark/
[0.4.0] - 2026-11-12
🚀 Major Features
- Concurrency Benchmarking:
async_benchmark.concurrent(func, concurrency=..., total=..., timeout=...)runs calls with N in flight at once and reports throughput, latency percentiles under load, errors, timeouts and event loop lag - Regression Detection:
--async-bench-autosavesaves numbered runs;--async-bench-comparecompares against a saved run;--async-bench-compare-fail=median:10%fails the session only when a slowdown exceeds the threshold and is statistically significant (one-sided Mann-Whitney U test, pure Python) - JSON Export:
--async-bench-json=PATHwrites every result, including per-call samples, machine info and git commit info - Task Leak Detection:
leak_check=True/"warn"(or--async-bench-leak-check) fails or warns when a benchmark leaves asyncio tasks running
🔧 Improvements
- Per-call timing: every call is timed individually; statistics are computed from all calls instead of from per-round averages
- Real percentiles: results now include
p50,p75,p90,p95,p99, plusops,count,total_time,samplesandmode - Auto calibration: without
rounds, measurement runs for a time budget (max_time, default 1.0s, at least 5 rounds) instead of a fixed 5 calls; configurable per call, via the marker, or with--async-bench-max-time - Config-file settings:
async_bench_max_timeandasync_bench_leak_checkcan be set once inpyproject.toml/pytest.iniinstead of passing flags every run - Comparison tables now show p95 and p99
- New examples:
examples/concurrency_examples.py, plus concurrency tests in the FastAPI and FastMCP examples
⚠️ Breaking changes
No public function, fixture or result key was removed or renamed, but some behavior changed:
- Python 3.9 is no longer supported (end of life since October 2025). pip on Python 3.9 keeps installing 0.3.0.
- Default number of calls: benchmarks without
roundsnow run for ~1 second (at least 5 rounds, at most 10,000) instead of exactly 5 rounds. A fast function is now called hundreds of times instead of 6 (5 rounds + 1 warmup). This makes suites slower and matters if the benchmarked function has side effects (writes rows, sends requests, increments mocks). To restore the old behavior passrounds=5, use@pytest.mark.async_benchmark(rounds=5), or set--async-bench-max-time=0(orasync_bench_max_time = "0"in your pytest config). - Default
result["rounds"]is no longer5whenroundsisn't given; it's however many rounds fit the time budget. - Statistics with
iterations > 1:min,max,medianandstddevnow describe individual calls instead of round averages, somaxcan be higher andminlower than before (meanis unchanged). Latency assertions such asassert result["max"] < 0.01may need loosening.raw_timeskeeps its old meaning (one mean per round); per-call durations are in the newsampleskey. AsyncBenchmarkRunner().roundsis nowNonewhen not given (was5).rounds=0/iterations=0now raiseValueError. Previously they were silently replaced with 5 and 1.- Terminal output: the per-benchmark table and comparison tables show different rows (percentiles, ops/sec, samples). Anything parsing that output needs updating; use
--async-bench-jsonfor machine-readable results.
🏗️ Project & CI
- Tested on Python 3.10-3.14; type annotations modernized (
X | None) tytype checking, ruff lint and format checks enforced in pre-commit and CI- GitHub releases are created automatically from the tag, with this changelog as the release notes
- Dependabot for GitHub Actions and uv dependencies; GitHub Actions bumped to current major versions
📝 Documentation
- FastAPI and FastMCP examples and README sections
- Removed
gradeandgrade_scorefrom the documented result keys; they were never returned
[0.3.0] - 2026-11-10
🚀 Major Features
- Pedantic Mode: Added
async_benchmark.pedantic()(andAsyncBenchmarkRunner.run_pedantic()) allowing per-iteration setup and teardown while excluding their duration from benchmark measurements (addresses issue #1) - Flexible Hook Support: Setup and teardown functions can be synchronous or asynchronous coroutines
- Argument Passing: Return values from
setup(individual values or unpacked tuples) are passed as arguments to both the target function andteardown - Dual Interface Pedantic Support: Works seamlessly with
await async_benchmark.pedantic(...)or synchronousasync_benchmark.pedantic(...)
🔧 Improvements & Upgrades
- Dependency Upgrades: Upgraded dependencies to latest versions (pytest 9.x, rich 15.x, ruff 0.17.x, aiohttp 3.14.x, quart 0.23.x, pre-commit 4.7.x)
- Modern Python Compatibility: Fixed
asyncio.iscoroutinefunctiondeprecations slated for removal in Python 3.16 by switching toinspect.iscoroutinefunctionacross all modules - Pytest-Asyncio v1.x Compatibility: Added explicit
asyncio_mode = "auto"andasyncio_default_fixture_loop_scope = "function"configuration inpyproject.toml - CI Matrix: Extended GitHub Actions CI matrix to test on Python 3.13
[0.2.0] - 2025-05-28
🚀 Major Features
- Backwards Compatibility: Automatic interface detection supporting both sync and async usage patterns
- pytest-asyncio Integration: Full compatibility with pytest-asyncio plugin when available
- Dual Interface Support: Works with
await async_benchmark(...)(with pytest-asyncio) orresult = async_benchmark(...)(without) - Smart Detection: Automatically detects pytest-asyncio availability and provides appropriate interface
✨ Added
- Automatic pytest-asyncio detection and interface switching
- Legacy sync interface support for backwards compatibility with v0.1.x
- Optional dependency configuration for pytest-asyncio
- Comprehensive backwards compatibility test suite
_SyncResultWrapperclass for seamless sync interface- Enhanced documentation covering both usage patterns
🔧 Changed
- NON-BREAKING: pytest-asyncio moved from required to optional dependency
AsyncBenchmarkFixture.__call__now returns different types based on environment- Enhanced fixture to support both sync and async interfaces automatically
- Updated examples to show both usage patterns
- README updated with backwards compatibility documentation
🛠️ Fixed
- RuntimeError issues when pytest-asyncio not available
- Event loop management conflicts in different environments
- Compatibility issues with projects not using pytest-asyncio
📚 Documentation
- Added pytest-asyncio compatibility section to README
- Updated all code examples to demonstrate async/await usage
- Created dedicated pytest-asyncio showcase example
- Enhanced Quick Start guide with modern async patterns
🧪 Testing
- All 40 tests passing with pytest-asyncio integration
- Examples thoroughly tested and validated
- Comprehensive test coverage maintained
[0.1.1] - 2025-05-27
Initial Features
- Core async benchmarking functionality
- Rich console output with beautiful formatting
- Statistical analysis and comparison tools
- Multiple examples and comprehensive testing
Core Components
AsyncBenchmarkFixturefor pytest integrationAsyncBenchmarkRunnerfor timing executionBenchmarkComparatorfor performance analysis- Rich-based display formatting
Examples
- Basic pytest examples
- Quart API comparison
- Comparison analysis tools
- Statistical benchmarking patterns