Skip to main content

Parallel test execution for free-threaded Python builds

Project description

pytest-threadpool

PyPI - Version Python License GitHub CI codecov

Status: Beta · Parallel test execution using threads.

Runs test bodies, function-scoped fixture setup, and function-scoped fixture teardown concurrently in a thread pool while keeping shared fixtures (module/class/session scope) sequential.

Works on any Python 3.13+. Free-threaded builds (3.13t, 3.14t, 3.15t) get true parallelism for CPU-bound tests. Standard builds still benefit from parallel execution of I/O-bound tests (network, database, file operations).

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

Fixture handling

Function-scoped fixtures are cloned per-item and set up in parallel alongside test calls. Each worker gets independent fixture instances — no shared mutable state between concurrent fixture setups.

Shared fixtures (module, class, and session scope) are resolved once sequentially before workers launch and served from cache to all items.

Scope Behavior
function Cloned per-item, setup and teardown in parallel workers
class Resolved once, cached, shared across items
module Resolved once, cached, shared across items
session Resolved once, cached, shared across items

Shared fixture teardown runs sequentially after the parallel group completes.

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.13, 3.13t, 3.14, 3.14t, 3.15, 3.15t
pytest 9.0.2

Note: On standard (GIL-enabled) builds, the GIL limits parallel speedup for CPU-bound tests. I/O-bound tests still run concurrently.

Examples

The examples/ directory contains runnable usage patterns:

  • DI container — dependency injection with Singleton, ThreadLocal, ContextLocal, and Factory scopes
  • Event bus — shared in-memory test double with concurrent producers and aggregate verification
  • Parallel logging — shared thread-safe log collector (caplog alternative)
  • Shared state — barriers, atomic counters, and cross-group coordination
  • User pool — custom thread pool with LIFO queue recycling

The tests/integration_tests/cases/ and tests/integration_tests/ directories are also worth browsing for real-world grouping, fixture, and reporting scenarios.

Known limitations

  • Private pytest API usage — The plugin relies on internal _pytest APIs (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 capsys/capfd/caplog in parallel — These fixtures are not thread-safe. capsys/capfd fail with "cannot use capsys and capsys at the same time" when requested by parallel tests. caplog leaks records across fixtures and its at_level() context managers race on the shared root logger. Alternatives:
    • print() — Worker output is suppressed by default (buffered by thread-local stream proxies). Pass -s (--capture=no) to disable suppression and see interleaved output.
    • Logging — Use a per-test FileHandler writing to tmp_path, or collect structured records in a shared thread-safe list (see examples/test_logging/).

License

Apache 2.0

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

pytest_threadpool-0.3.5.tar.gz (74.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_threadpool-0.3.5-py3-none-any.whl (25.8 kB view details)

Uploaded Python 3

File details

Details for the file pytest_threadpool-0.3.5.tar.gz.

File metadata

  • Download URL: pytest_threadpool-0.3.5.tar.gz
  • Upload date:
  • Size: 74.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pytest_threadpool-0.3.5.tar.gz
Algorithm Hash digest
SHA256 164707575aa30ea91b08c3ea57da05bbcc4c174c3b742ff5fd468bf399be780b
MD5 375859824fdc06dc69dfe9ee739039e9
BLAKE2b-256 9337cc39bcbb1ad7671f2d09ffbd0d89f3a3295d99c9f1072313a7ba74b941ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_threadpool-0.3.5.tar.gz:

Publisher: publish.yml on pytest-threadpool/pytest-threadpool

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pytest_threadpool-0.3.5-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_threadpool-0.3.5-py3-none-any.whl
Algorithm Hash digest
SHA256 396c4e1b68b3955a14f05c2dd5fdd092b9ccc9dfef21c4428c48b85e7d607253
MD5 c5bd670ec87dd3b1e4a37a3ef2e55c79
BLAKE2b-256 b6fd794a3f503e01e1893f2c8cb30f7e83d2719e9c272af256f6afb2f2fd3c6e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_threadpool-0.3.5-py3-none-any.whl:

Publisher: publish.yml on pytest-threadpool/pytest-threadpool

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page