pytest-async-benchmark 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/.github/workflows/ci.yml +3 -3
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/CHANGELOG.md +14 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/PKG-INFO +139 -89
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/README.md +137 -87
- pytest_async_benchmark-0.3.0/examples/pedantic_examples.py +86 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/pyproject.toml +6 -1
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/scripts/release-check.py +1 -1
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/__init__.py +1 -1
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/display.py +2 -2
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/plugin.py +79 -1
- pytest_async_benchmark-0.3.0/src/pytest_async_benchmark/runner.py +142 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/utils.py +2 -1
- pytest_async_benchmark-0.3.0/tests/test_pedantic.py +220 -0
- pytest_async_benchmark-0.3.0/uv.lock +2871 -0
- pytest_async_benchmark-0.2.0/src/pytest_async_benchmark/runner.py +0 -56
- pytest_async_benchmark-0.2.0/uv.lock +0 -1279
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/.github/workflows/release.yml +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/.github/workflows/test-publish.yml +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/.gitignore +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/.pre-commit-config.yaml +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/LICENSE +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/PYPI_SETUP.md +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/RELEASE_GUIDE.md +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/backwards_compatibility_examples.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/comparison_examples.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/marker_syntax_examples.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/pytest_asyncio_showcase.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/pytest_examples.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/examples/quart_api_comparison.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/screenshot.png +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/scripts/quality-check.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/analytics.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/comparison.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/src/pytest_async_benchmark/stats.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/conftest.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/test_async_bench.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/test_async_compatibility.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/test_backwards_compatibility.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/test_comparison.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0}/tests/test_demo.py +0 -0
- {pytest_async_benchmark-0.2.0 → pytest_async_benchmark-0.3.0/tests}/test_marker_functionality.py +0 -0
|
@@ -11,13 +11,13 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
strategy:
|
|
13
13
|
matrix:
|
|
14
|
-
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
17
|
- uses: actions/checkout@v4
|
|
18
18
|
|
|
19
19
|
- name: Install uv
|
|
20
|
-
uses: astral-sh/setup-uv@
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
21
|
with:
|
|
22
22
|
version: "latest"
|
|
23
23
|
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
|
|
40
40
|
- name: Test examples
|
|
41
41
|
run: |
|
|
42
|
-
uv run python -m pytest examples/pytest_examples.py -v
|
|
42
|
+
uv run python -m pytest examples/pytest_examples.py examples/pedantic_examples.py -v
|
|
43
43
|
uv run python examples/comparison_examples.py
|
|
44
44
|
|
|
45
45
|
- name: Test package build
|
|
@@ -5,6 +5,20 @@ All notable changes to pytest-async-benchmark will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-11-10
|
|
9
|
+
|
|
10
|
+
### 🚀 Major Features
|
|
11
|
+
- **Pedantic Mode**: Added `async_benchmark.pedantic()` (and `AsyncBenchmarkRunner.run_pedantic()`) allowing per-iteration setup and teardown while excluding their duration from benchmark measurements (addresses issue #1)
|
|
12
|
+
- **Flexible Hook Support**: Setup and teardown functions can be synchronous or asynchronous coroutines
|
|
13
|
+
- **Argument Passing**: Return values from `setup` (individual values or unpacked tuples) are passed as arguments to both the target function and `teardown`
|
|
14
|
+
- **Dual Interface Pedantic Support**: Works seamlessly with `await async_benchmark.pedantic(...)` or synchronous `async_benchmark.pedantic(...)`
|
|
15
|
+
|
|
16
|
+
### 🔧 Improvements & Upgrades
|
|
17
|
+
- **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)
|
|
18
|
+
- **Modern Python Compatibility**: Fixed `asyncio.iscoroutinefunction` deprecations slated for removal in Python 3.16 by switching to `inspect.iscoroutinefunction` across all modules
|
|
19
|
+
- **Pytest-Asyncio v1.x Compatibility**: Added explicit `asyncio_mode = "auto"` and `asyncio_default_fixture_loop_scope = "function"` configuration in `pyproject.toml`
|
|
20
|
+
- **CI Matrix**: Extended GitHub Actions CI matrix to test on Python 3.13
|
|
21
|
+
|
|
8
22
|
## [0.2.0] - 2025-05-28
|
|
9
23
|
|
|
10
24
|
### 🚀 Major Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pytest-async-benchmark
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: pytest-async-benchmark: Modern pytest benchmarking for async code. 🚀
|
|
5
5
|
Author-email: Mihai Farcas <contact@mihai.ltd>
|
|
6
6
|
License: MIT License
|
|
@@ -87,11 +87,12 @@ async def test_my_api():
|
|
|
87
87
|
# Your existing async test code
|
|
88
88
|
pass
|
|
89
89
|
|
|
90
|
+
|
|
90
91
|
# Then just add pytest-async-benchmark and use:
|
|
91
92
|
@pytest.mark.asyncio
|
|
92
93
|
async def test_my_api_performance(async_benchmark):
|
|
93
94
|
result = await async_benchmark(my_async_function)
|
|
94
|
-
assert result[
|
|
95
|
+
assert result["mean"] < 0.01
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
## 🚀 Quick Start
|
|
@@ -105,18 +106,20 @@ When pytest-asyncio is installed, use the natural async/await syntax:
|
|
|
105
106
|
import asyncio
|
|
106
107
|
import pytest
|
|
107
108
|
|
|
109
|
+
|
|
108
110
|
async def slow_async_operation():
|
|
109
111
|
await asyncio.sleep(0.01) # 10ms
|
|
110
112
|
return "result"
|
|
111
113
|
|
|
114
|
+
|
|
112
115
|
@pytest.mark.asyncio
|
|
113
116
|
@pytest.mark.async_benchmark(rounds=5, iterations=10)
|
|
114
117
|
async def test_async_performance(async_benchmark):
|
|
115
118
|
# Use await with pytest-asyncio for best experience
|
|
116
119
|
result = await async_benchmark(slow_async_operation)
|
|
117
|
-
|
|
120
|
+
|
|
118
121
|
# Your assertions here
|
|
119
|
-
assert result[
|
|
122
|
+
assert result["mean"] < 0.02
|
|
120
123
|
```
|
|
121
124
|
|
|
122
125
|
### Simple Interface (without pytest-asyncio)
|
|
@@ -126,17 +129,19 @@ For simpler setups, the sync interface works automatically:
|
|
|
126
129
|
import asyncio
|
|
127
130
|
import pytest
|
|
128
131
|
|
|
132
|
+
|
|
129
133
|
async def slow_async_operation():
|
|
130
134
|
await asyncio.sleep(0.01)
|
|
131
135
|
return "result"
|
|
132
136
|
|
|
137
|
+
|
|
133
138
|
@pytest.mark.async_benchmark(rounds=5, iterations=10)
|
|
134
139
|
def test_sync_performance(async_benchmark):
|
|
135
140
|
# No await needed - sync interface
|
|
136
141
|
result = async_benchmark(slow_async_operation)
|
|
137
|
-
|
|
142
|
+
|
|
138
143
|
# Your assertions here
|
|
139
|
-
assert result[
|
|
144
|
+
assert result["mean"] < 0.02 # Should complete in under 20ms
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
### Flexible Configuration Syntax
|
|
@@ -148,14 +153,14 @@ pytest-async-benchmark supports **two syntax options** for configuring benchmark
|
|
|
148
153
|
@pytest.mark.async_benchmark(rounds=5, iterations=10)
|
|
149
154
|
async def test_with_marker(async_benchmark):
|
|
150
155
|
result = await async_benchmark(slow_async_operation)
|
|
151
|
-
assert result[
|
|
156
|
+
assert result["rounds"] == 5 # From marker
|
|
152
157
|
```
|
|
153
158
|
|
|
154
159
|
#### Option 2: Function Parameter Syntax
|
|
155
160
|
```python
|
|
156
161
|
async def test_with_parameters(async_benchmark):
|
|
157
162
|
result = await async_benchmark(slow_async_operation, rounds=5, iterations=10)
|
|
158
|
-
assert result[
|
|
163
|
+
assert result["rounds"] == 5 # From function parameters
|
|
159
164
|
```
|
|
160
165
|
|
|
161
166
|
## 🔧 Interface Detection & Flexibility
|
|
@@ -170,6 +175,7 @@ When pytest-asyncio is installed, use natural async/await syntax:
|
|
|
170
175
|
[tool.pytest.ini_options]
|
|
171
176
|
asyncio_mode = "auto"
|
|
172
177
|
|
|
178
|
+
|
|
173
179
|
# Then use await syntax
|
|
174
180
|
@pytest.mark.asyncio
|
|
175
181
|
async def test_my_benchmark(async_benchmark):
|
|
@@ -211,7 +217,7 @@ def test_my_benchmark(async_benchmark):
|
|
|
211
217
|
async def test_with_marker(async_benchmark):
|
|
212
218
|
"""Use marker for consistent, visible configuration."""
|
|
213
219
|
result = await async_benchmark(my_async_function)
|
|
214
|
-
assert result[
|
|
220
|
+
assert result["rounds"] == 10 # Configuration is explicit and visible
|
|
215
221
|
```
|
|
216
222
|
|
|
217
223
|
#### Simple Interface (without pytest-asyncio)
|
|
@@ -220,7 +226,7 @@ async def test_with_marker(async_benchmark):
|
|
|
220
226
|
def test_with_marker_sync(async_benchmark):
|
|
221
227
|
"""Use marker for consistent, visible configuration - simple style."""
|
|
222
228
|
result = async_benchmark(my_async_function) # No await needed
|
|
223
|
-
assert result[
|
|
229
|
+
assert result["rounds"] == 10 # Configuration is explicit and visible
|
|
224
230
|
```
|
|
225
231
|
|
|
226
232
|
### Two Configuration Syntaxes
|
|
@@ -232,13 +238,14 @@ def test_with_marker_sync(async_benchmark):
|
|
|
232
238
|
@pytest.mark.async_benchmark(rounds=10, iterations=100, warmup_rounds=2)
|
|
233
239
|
async def test_with_marker_async(async_benchmark):
|
|
234
240
|
result = await async_benchmark(my_async_function)
|
|
235
|
-
assert result[
|
|
241
|
+
assert result["rounds"] == 10
|
|
242
|
+
|
|
236
243
|
|
|
237
244
|
# Without pytest-asyncio
|
|
238
245
|
@pytest.mark.async_benchmark(rounds=10, iterations=100, warmup_rounds=2)
|
|
239
246
|
def test_with_marker_sync(async_benchmark):
|
|
240
247
|
result = async_benchmark(my_async_function)
|
|
241
|
-
assert result[
|
|
248
|
+
assert result["rounds"] == 10
|
|
242
249
|
```
|
|
243
250
|
|
|
244
251
|
#### Function Parameter Syntax (Dynamic)
|
|
@@ -247,22 +254,17 @@ def test_with_marker_sync(async_benchmark):
|
|
|
247
254
|
@pytest.mark.asyncio
|
|
248
255
|
async def test_with_parameters_async(async_benchmark):
|
|
249
256
|
result = await async_benchmark(
|
|
250
|
-
my_async_function,
|
|
251
|
-
rounds=10,
|
|
252
|
-
iterations=100,
|
|
253
|
-
warmup_rounds=2
|
|
257
|
+
my_async_function, rounds=10, iterations=100, warmup_rounds=2
|
|
254
258
|
)
|
|
255
|
-
assert result[
|
|
259
|
+
assert result["rounds"] == 10
|
|
260
|
+
|
|
256
261
|
|
|
257
262
|
# Without pytest-asyncio
|
|
258
263
|
def test_with_parameters_simple(async_benchmark):
|
|
259
264
|
result = async_benchmark(
|
|
260
|
-
my_async_function,
|
|
261
|
-
rounds=10,
|
|
262
|
-
iterations=100,
|
|
263
|
-
warmup_rounds=2
|
|
265
|
+
my_async_function, rounds=10, iterations=100, warmup_rounds=2
|
|
264
266
|
)
|
|
265
|
-
assert result[
|
|
267
|
+
assert result["rounds"] == 10
|
|
266
268
|
```
|
|
267
269
|
|
|
268
270
|
#### Combined Syntax (Override)
|
|
@@ -270,15 +272,17 @@ Both interfaces support parameter precedence where function parameters override
|
|
|
270
272
|
|
|
271
273
|
```python
|
|
272
274
|
@pytest.mark.async_benchmark(rounds=5, iterations=50) # Default config
|
|
273
|
-
async def test_with_override(
|
|
275
|
+
async def test_with_override(
|
|
276
|
+
async_benchmark,
|
|
277
|
+
): # Works with or without @pytest.mark.asyncio
|
|
274
278
|
"""Function parameters override marker settings."""
|
|
275
279
|
result = await async_benchmark( # Use 'await' only with pytest-asyncio
|
|
276
280
|
my_async_function,
|
|
277
|
-
rounds=20 # This overrides marker's rounds=5
|
|
281
|
+
rounds=20, # This overrides marker's rounds=5
|
|
278
282
|
# iterations=50 comes from marker
|
|
279
283
|
)
|
|
280
|
-
assert result[
|
|
281
|
-
assert result[
|
|
284
|
+
assert result["rounds"] == 20 # Function parameter wins
|
|
285
|
+
assert result["iterations"] == 50 # From marker
|
|
282
286
|
```
|
|
283
287
|
|
|
284
288
|
### Basic Benchmarking
|
|
@@ -302,6 +306,42 @@ async def test_my_async_function(async_benchmark):
|
|
|
302
306
|
|
|
303
307
|
### Advanced Configuration
|
|
304
308
|
|
|
309
|
+
### 🔬 Pedantic Mode (Setup & Teardown Excluded from Timing)
|
|
310
|
+
|
|
311
|
+
When benchmarking async operations that require per-iteration setup (e.g. creating fresh databases, initializing connection pools, or preparing test fixtures) and teardown, setup/cleanup overhead pollutes the timing results.
|
|
312
|
+
|
|
313
|
+
`async_benchmark.pedantic()` runs setup before and teardown after each iteration, but strictly measures **only the target operation**:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
@pytest.mark.asyncio
|
|
317
|
+
async def test_database_indexing(async_benchmark):
|
|
318
|
+
async def setup():
|
|
319
|
+
db = await create_fresh_database()
|
|
320
|
+
return db
|
|
321
|
+
|
|
322
|
+
async def teardown(db):
|
|
323
|
+
await db.close()
|
|
324
|
+
|
|
325
|
+
async def index_records(db):
|
|
326
|
+
await db.execute("CREATE INDEX idx_user ON users(email)")
|
|
327
|
+
|
|
328
|
+
result = await async_benchmark.pedantic(
|
|
329
|
+
index_records,
|
|
330
|
+
setup=setup,
|
|
331
|
+
teardown=teardown,
|
|
332
|
+
rounds=10,
|
|
333
|
+
iterations=1,
|
|
334
|
+
)
|
|
335
|
+
assert result["mean"] < 0.05
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Key Features:**
|
|
339
|
+
- ⚡ **Zero Setup/Teardown Overhead**: Setup and teardown times are excluded from benchmark measurements.
|
|
340
|
+
- 🔄 **Sync or Async Hooks**: Both `setup` and `teardown` can be synchronous functions or `async def` coroutines.
|
|
341
|
+
- 📦 **Argument Passing**: Return value from `setup` is passed as argument(s) to the target function and `teardown`. Unpacks tuples automatically if returned.
|
|
342
|
+
- 🛡️ **Safe Cleanup**: Teardown runs in a `finally` block even if the target encounters an error.
|
|
343
|
+
- 🌓 **Dual Interface**: Supports `await async_benchmark.pedantic(...)` or synchronous `async_benchmark.pedantic(...)`.
|
|
344
|
+
|
|
305
345
|
## 🎯 Two Configuration Syntaxes
|
|
306
346
|
|
|
307
347
|
pytest-async-benchmark offers **two flexible ways** to configure your benchmarks:
|
|
@@ -316,10 +356,10 @@ Use pytest markers for **declarative, visible configuration**:
|
|
|
316
356
|
async def test_high_precision_benchmark(async_benchmark):
|
|
317
357
|
"""High precision benchmark with marker configuration."""
|
|
318
358
|
result = await async_benchmark(my_async_function)
|
|
319
|
-
|
|
359
|
+
|
|
320
360
|
# Configuration is visible and consistent
|
|
321
|
-
assert result[
|
|
322
|
-
assert result[
|
|
361
|
+
assert result["rounds"] == 10
|
|
362
|
+
assert result["iterations"] == 100
|
|
323
363
|
```
|
|
324
364
|
|
|
325
365
|
**Benefits:**
|
|
@@ -338,12 +378,9 @@ async def test_dynamic_benchmark(async_benchmark):
|
|
|
338
378
|
"""Dynamic benchmark with runtime configuration."""
|
|
339
379
|
# Configuration can be computed or conditional
|
|
340
380
|
rounds = 20 if is_production else 5
|
|
341
|
-
|
|
381
|
+
|
|
342
382
|
result = await async_benchmark(
|
|
343
|
-
my_async_function,
|
|
344
|
-
rounds=rounds,
|
|
345
|
-
iterations=50,
|
|
346
|
-
warmup_rounds=1
|
|
383
|
+
my_async_function, rounds=rounds, iterations=50, warmup_rounds=1
|
|
347
384
|
)
|
|
348
385
|
```
|
|
349
386
|
|
|
@@ -361,19 +398,19 @@ async def test_dynamic_benchmark(async_benchmark):
|
|
|
361
398
|
@pytest.mark.async_benchmark(rounds=5, iterations=50, warmup_rounds=1)
|
|
362
399
|
async def test_with_overrides(async_benchmark):
|
|
363
400
|
"""Use marker defaults with selective overrides."""
|
|
364
|
-
|
|
401
|
+
|
|
365
402
|
# Quick test with marker defaults
|
|
366
403
|
quick_result = await async_benchmark(fast_function)
|
|
367
|
-
|
|
404
|
+
|
|
368
405
|
# Precision test with overridden rounds
|
|
369
406
|
precise_result = await async_benchmark(
|
|
370
407
|
slow_function,
|
|
371
|
-
rounds=20 # Overrides marker's rounds=5
|
|
408
|
+
rounds=20, # Overrides marker's rounds=5
|
|
372
409
|
# iterations=50 and warmup_rounds=1 come from marker
|
|
373
410
|
)
|
|
374
|
-
|
|
375
|
-
assert quick_result[
|
|
376
|
-
assert precise_result[
|
|
411
|
+
|
|
412
|
+
assert quick_result["rounds"] == 5 # From marker
|
|
413
|
+
assert precise_result["rounds"] == 20 # From function override
|
|
377
414
|
```
|
|
378
415
|
|
|
379
416
|
### Traditional Configuration
|
|
@@ -383,9 +420,9 @@ async def test_with_overrides(async_benchmark):
|
|
|
383
420
|
async def test_with_custom_settings(async_benchmark):
|
|
384
421
|
result = await async_benchmark(
|
|
385
422
|
my_async_function,
|
|
386
|
-
rounds=10,
|
|
387
|
-
iterations=5,
|
|
388
|
-
warmup_rounds=2
|
|
423
|
+
rounds=10, # Number of rounds to run
|
|
424
|
+
iterations=5, # Iterations per round
|
|
425
|
+
warmup_rounds=2, # Warmup rounds before measurement
|
|
389
426
|
)
|
|
390
427
|
```
|
|
391
428
|
|
|
@@ -398,12 +435,12 @@ async def test_with_args(async_benchmark):
|
|
|
398
435
|
# Process the data
|
|
399
436
|
await asyncio.sleep(0.01)
|
|
400
437
|
return len(data) * multiplier
|
|
401
|
-
|
|
438
|
+
|
|
402
439
|
result = await async_benchmark(
|
|
403
440
|
process_data,
|
|
404
|
-
"test_data",
|
|
405
|
-
multiplier=2,
|
|
406
|
-
rounds=3
|
|
441
|
+
"test_data", # positional arg
|
|
442
|
+
multiplier=2, # keyword arg
|
|
443
|
+
rounds=3,
|
|
407
444
|
)
|
|
408
445
|
```
|
|
409
446
|
|
|
@@ -414,14 +451,17 @@ async def test_with_args(async_benchmark):
|
|
|
414
451
|
```python
|
|
415
452
|
from pytest_async_benchmark import quick_compare
|
|
416
453
|
|
|
454
|
+
|
|
417
455
|
async def algorithm_v1():
|
|
418
456
|
await asyncio.sleep(0.002) # 2ms
|
|
419
457
|
return "v1_result"
|
|
420
458
|
|
|
459
|
+
|
|
421
460
|
async def algorithm_v2():
|
|
422
461
|
await asyncio.sleep(0.0015) # 1.5ms - optimized
|
|
423
462
|
return "v2_result"
|
|
424
463
|
|
|
464
|
+
|
|
425
465
|
# Quick one-liner comparison
|
|
426
466
|
def test_algorithm_comparison():
|
|
427
467
|
winner, results = quick_compare(algorithm_v1, algorithm_v2, rounds=5)
|
|
@@ -433,12 +473,16 @@ def test_algorithm_comparison():
|
|
|
433
473
|
```python
|
|
434
474
|
from pytest_async_benchmark import a_vs_b_comparison
|
|
435
475
|
|
|
476
|
+
|
|
436
477
|
def test_detailed_comparison():
|
|
437
478
|
# Compare with beautiful terminal output
|
|
438
479
|
a_vs_b_comparison(
|
|
439
|
-
"Original Algorithm",
|
|
440
|
-
|
|
441
|
-
|
|
480
|
+
"Original Algorithm",
|
|
481
|
+
algorithm_v1,
|
|
482
|
+
"Optimized Algorithm",
|
|
483
|
+
algorithm_v2,
|
|
484
|
+
rounds=8,
|
|
485
|
+
iterations=20,
|
|
442
486
|
)
|
|
443
487
|
```
|
|
444
488
|
|
|
@@ -447,25 +491,30 @@ def test_detailed_comparison():
|
|
|
447
491
|
```python
|
|
448
492
|
from pytest_async_benchmark import BenchmarkComparator
|
|
449
493
|
|
|
494
|
+
|
|
450
495
|
def test_multi_scenario():
|
|
451
496
|
comparator = BenchmarkComparator()
|
|
452
|
-
|
|
497
|
+
|
|
453
498
|
# Add multiple scenarios
|
|
454
499
|
comparator.add_scenario(
|
|
455
|
-
"Database Query v1",
|
|
456
|
-
|
|
457
|
-
|
|
500
|
+
"Database Query v1",
|
|
501
|
+
db_query_v1,
|
|
502
|
+
rounds=5,
|
|
503
|
+
iterations=10,
|
|
504
|
+
description="Original database implementation",
|
|
458
505
|
)
|
|
459
|
-
|
|
506
|
+
|
|
460
507
|
comparator.add_scenario(
|
|
461
|
-
"Database Query v2",
|
|
462
|
-
|
|
463
|
-
|
|
508
|
+
"Database Query v2",
|
|
509
|
+
db_query_v2,
|
|
510
|
+
rounds=5,
|
|
511
|
+
iterations=10,
|
|
512
|
+
description="Optimized with connection pooling",
|
|
464
513
|
)
|
|
465
|
-
|
|
514
|
+
|
|
466
515
|
# Run comparison and get results
|
|
467
516
|
results = comparator.run_comparison()
|
|
468
|
-
|
|
517
|
+
|
|
469
518
|
# Beautiful comparison table automatically displayed
|
|
470
519
|
# Access programmatic results
|
|
471
520
|
fastest = results.get_fastest_scenario()
|
|
@@ -478,20 +527,20 @@ Each benchmark returns detailed statistics:
|
|
|
478
527
|
|
|
479
528
|
```python
|
|
480
529
|
{
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
530
|
+
"min": 0.001234, # Minimum execution time
|
|
531
|
+
"max": 0.005678, # Maximum execution time
|
|
532
|
+
"mean": 0.002456, # Mean execution time
|
|
533
|
+
"median": 0.002123, # Median execution time
|
|
534
|
+
"stddev": 0.000234, # Standard deviation
|
|
535
|
+
"p50": 0.002123, # 50th percentile (median)
|
|
536
|
+
"p90": 0.003456, # 90th percentile
|
|
537
|
+
"p95": 0.004123, # 95th percentile
|
|
538
|
+
"p99": 0.004789, # 99th percentile
|
|
539
|
+
"rounds": 5, # Number of rounds executed
|
|
540
|
+
"iterations": 1, # Number of iterations per round
|
|
541
|
+
"raw_times": [...], # List of raw timing measurements
|
|
542
|
+
"grade": "A", # Performance grade (A-F)
|
|
543
|
+
"grade_score": 87.5, # Numeric grade score (0-100)
|
|
495
544
|
}
|
|
496
545
|
```
|
|
497
546
|
|
|
@@ -596,22 +645,24 @@ import pytest
|
|
|
596
645
|
|
|
597
646
|
app = FastAPI()
|
|
598
647
|
|
|
648
|
+
|
|
599
649
|
@app.get("/api/data")
|
|
600
650
|
async def get_data():
|
|
601
651
|
# Simulate database query
|
|
602
652
|
await asyncio.sleep(0.005)
|
|
603
653
|
return {"data": "example"}
|
|
604
654
|
|
|
655
|
+
|
|
605
656
|
@pytest.mark.asyncio
|
|
606
657
|
async def test_fastapi_endpoint_performance(async_benchmark):
|
|
607
658
|
async def make_request():
|
|
608
659
|
with TestClient(app) as client:
|
|
609
660
|
response = client.get("/api/data")
|
|
610
661
|
return response.json()
|
|
611
|
-
|
|
662
|
+
|
|
612
663
|
result = await async_benchmark(make_request, rounds=10)
|
|
613
|
-
assert result[
|
|
614
|
-
assert result[
|
|
664
|
+
assert result["mean"] < 0.1 # Should respond within 100ms
|
|
665
|
+
assert result["grade"] in ["A", "B"] # Should have good performance grade
|
|
615
666
|
```
|
|
616
667
|
|
|
617
668
|
### Quart API Endpoint Comparison
|
|
@@ -623,21 +674,22 @@ from pytest_async_benchmark import a_vs_b_comparison
|
|
|
623
674
|
import asyncio
|
|
624
675
|
import aiohttp
|
|
625
676
|
|
|
677
|
+
|
|
626
678
|
async def test_api_v1():
|
|
627
679
|
async with aiohttp.ClientSession() as session:
|
|
628
|
-
async with session.get(
|
|
680
|
+
async with session.get("http://localhost:5000/api/v1/data") as resp:
|
|
629
681
|
return await resp.json()
|
|
630
682
|
|
|
683
|
+
|
|
631
684
|
async def test_api_v2():
|
|
632
685
|
async with aiohttp.ClientSession() as session:
|
|
633
|
-
async with session.get(
|
|
686
|
+
async with session.get("http://localhost:5000/api/v2/data") as resp:
|
|
634
687
|
return await resp.json()
|
|
635
688
|
|
|
689
|
+
|
|
636
690
|
# Compare API versions
|
|
637
691
|
a_vs_b_comparison(
|
|
638
|
-
"API v1", test_api_v1,
|
|
639
|
-
"API v2 (Optimized)", test_api_v2,
|
|
640
|
-
rounds=10, iterations=5
|
|
692
|
+
"API v1", test_api_v1, "API v2 (Optimized)", test_api_v2, rounds=10, iterations=5
|
|
641
693
|
)
|
|
642
694
|
```
|
|
643
695
|
|
|
@@ -648,13 +700,11 @@ a_vs_b_comparison(
|
|
|
648
700
|
async def test_database_query_performance(async_benchmark):
|
|
649
701
|
async def fetch_user_data(user_id):
|
|
650
702
|
async with database.connection() as conn:
|
|
651
|
-
return await conn.fetch_one(
|
|
652
|
-
|
|
653
|
-
)
|
|
654
|
-
|
|
703
|
+
return await conn.fetch_one("SELECT * FROM users WHERE id = ?", user_id)
|
|
704
|
+
|
|
655
705
|
result = await async_benchmark(fetch_user_data, 123, rounds=5)
|
|
656
|
-
assert result[
|
|
657
|
-
assert result[
|
|
706
|
+
assert result["mean"] < 0.05 # Should complete within 50ms
|
|
707
|
+
assert result["p95"] < 0.1 # 95% of queries under 100ms
|
|
658
708
|
```
|
|
659
709
|
|
|
660
710
|
## 🎯 Using Markers
|