Parallel test execution for free-threaded Python builds
Project description
pytest-threadpool
Status: Beta · Parallel test execution for free-threaded Python builds (3.13t+).
Runs test bodies concurrently in a ThreadPoolExecutor while keeping
fixture setup/teardown sequential (pytest internals are not thread-safe).
Installation
pip install pytest-threadpool
Quick start
pytest --threadpool auto
Mark tests for parallel execution:
from pytest_threadpool import parallelizable, not_parallelizable
import pytest
@parallelizable("children") # all nested tests run in parallel
class TestMyFeature:
def test_a(self): ...
def test_b(self): ...
@parallelizable("parameters") # parametrized variants run in parallel
@pytest.mark.parametrize("x", [1, 2, 3])
def test_with_params(x): ...
@parallelizable("all") # children + parameters combined
class TestEverything:
@pytest.mark.parametrize("n", [1, 2])
def test_param(self, n): ...
def test_plain(self): ...
@not_parallelizable # opt out of inherited parallelism
def test_must_be_sequential(): ...
Scopes
| Scope | Effect |
|---|---|
children |
All nested tests run concurrently (children, grandchildren, etc.) |
parameters |
Parametrized variants of the same test run concurrently |
all |
Combines children + parameters |
Marker levels
Markers can be applied at function, class, module (pytestmark), or
package (__init__.py pytestmark) level. Priority (most specific wins):
not_parallelizable > own marker > class > module > package
Shared state between tests
Unlike pytest-xdist, which uses subprocesses and requires all test data to
be pickleable, pytest-threadpool runs tests in threads within a single
process. This means tests can share common non-pickleable, thread-safe
objects — both within a parallel group and across sequential groups:
import threading
import pytest
class SharedState:
lock = threading.Lock() # not pickleable
event = threading.Event() # not pickleable
results = {}
@pytest.mark.parallelizable("children")
class TestGroupA:
def test_a1(self):
with SharedState.lock:
SharedState.results["a1"] = True
def test_a2(self):
with SharedState.lock:
SharedState.results["a2"] = True
@pytest.mark.parallelizable("children")
class TestGroupB:
def test_b1(self):
SharedState.event.set()
with SharedState.lock:
SharedState.results["b1"] = True
def test_b2(self):
assert SharedState.event.wait(timeout=10)
with SharedState.lock:
SharedState.results["b2"] = True
Objects like threading.Lock, threading.Event, logging.Logger, database
connections, and other non-pickleable resources can live as class attributes
and be accessed freely from any test — parallel or sequential — without
serialization overhead or workarounds.
Usage
# Auto-detect thread count
pytest --threadpool auto
# Fixed thread count
pytest --threadpool 8
# Normal sequential run (no flag)
pytest
Tested versions
| Component | Versions |
|---|---|
| Python | 3.13t, 3.14t, 3.15t |
| pytest | >=9.0.2 |
Known limitations
- Private pytest API usage — The plugin relies on internal
_pytestAPIs (fixture finalizers, setup state, terminal writer) that have no public equivalents. These may break across pytest releases without warning. - No plugin compatibility guarantees — Interactions with other pytest
plugins (e.g.
pytest-xdist,pytest-timeout,pytest-randomly) are untested and may conflict. - No captured stdout in parallel — Standard output from tests running
concurrently is written directly to the terminal. Pytest's built-in capture
(
capsys/capfd) is not supported during parallel execution.
License
MIT
Project details
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_threadpool-0.2.0.tar.gz.
File metadata
- Download URL: pytest_threadpool-0.2.0.tar.gz
- Upload date:
- Size: 50.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
296b6c7b9eb1179917311eb7516e31085f7ab125c5db8d9171985458ae2bde81
|
|
| MD5 |
730998a65fbd88351b8bdd18e2a6c5e9
|
|
| BLAKE2b-256 |
f4c89366925e9d6df948a6866ee3ccb01a902ba9edc49ccaf009d7ed23cd7cf7
|
File details
Details for the file pytest_threadpool-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pytest_threadpool-0.2.0-py3-none-any.whl
- Upload date:
- Size: 19.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
842c5150871dedb542c58faf19116aa420bbc058782ae40ff6ca4717cd4db22e
|
|
| MD5 |
62671bf97101932450d41b46ddef6e8a
|
|
| BLAKE2b-256 |
1cd22d71a50e489733271a792701ad6b546bebc7e7095c975fc472c72a37595c
|