This release is a pre-release and may not be stable for production use.
voci
A concurrent test runner for async Python. voci runs your whole suite in one process on one
event loop, with every test as a concurrent asyncio task. For a suite that spends its time
waiting — on a database, on an ASGI app, on a network stub — that turns wall-clock time from the
sum of your tests into roughly the slowest one, with a single connection pool and a single set of
imports behind it.
Tests declare what they need as parameter defaults, the way FastAPI routes do. There is no
conftest.py and no name-based lookup: a fixture is a function you import, so "go to definition"
works, renames are safe, and a typo is an ImportError at collection rather than a mystery at run
time. When a fixture is a side effect no test needs the value of, a module — or a package, for
everything under it — declares it once: voci.use(reset_cache), the way a FastAPI router
declares dependencies for every route on it. Assertions keep the introspection you already know —
voci vendors pytest's assertion rewriter, so assert a == b still prints a real diff.
# tests/fixtures.py
from typing import Annotated
import voci
from voci import Depends
@voci.fixture(scope="session")
async def engine() -> AsyncIterator[AsyncEngine]:
engine = create_async_engine("sqlite+aiosqlite:///./test.db")
yield engine
await engine.dispose()
@voci.fixture()
async def session(engine: Annotated[AsyncEngine, Depends(engine)]) -> AsyncIterator[AsyncSession]:
"""A transaction per test, always rolled back — so tests share one engine safely."""
async with engine.connect() as conn:
transaction = await conn.begin()
yield AsyncSession(bind=conn)
await transaction.rollback()
# tests/test_users.py
from typing import Annotated
from voci import Depends
from tests.fixtures import session
async def test_create_user(db: Annotated[AsyncSession, Depends(session)]) -> None:
db.add(User(email="alice@example.com"))
await db.flush()
assert await db.scalar(select(func.count()).select_from(User)) == 1
$ voci
PASS tests/test_users.py 10 tests Σ 3.84s
PASS tests/test_orders.py 12 tests Σ 7.29s
22 tests · 22 passed · 1.14s wall (9.8x concurrency)
The last line is the one to watch: the multiplier is what running concurrently bought you over
the sum of every test's own duration (each file's Σ, above).
Install
Python 3.13+. The core package has no dependencies.
uv pip install voci # or: pip install voci
Configure
Everything is optional. voci reads [tool.voci] from the nearest pyproject.toml:
[tool.voci]
testpaths = ["tests"]
concurrency = 16 # tests in flight at once
timeout = 60 # per-test budget, in seconds
env = { ENVIRONMENT = "test" }
CLI flags win over config, which wins over the defaults:
voci # run everything
voci tests/test_users.py # run one file
voci tests/test_users.py::test_create[admin] # run one test, or one of its cases
voci -k "users and not slow" # select by substring of the test id
voci -m "smoke" # select by @voci.tag
voci --serial # exactly serial — the first debugging step
voci -x # stop at the first failure, cancelling what is in flight
voci --durations 10 # the slowest tests, to tune --concurrency by
voci --loop-watchdog 10 # how long the loop may block before voci names the call
voci --collect-only # print the ids that would run, and stop
voci -s # live, id-prefixed output instead of captured
Where to go next
- examples/ — three worked suites: a FastAPI + async SQLAlchemy CRUD API, a pure-async library, and shared resources under concurrency. Each is runnable.
- ROADMAP.md — what isn't built yet, and what's next.
- plans/rationale.md — why voci is shaped the way it is. Read this before changing anything load-bearing.
Status
Pre-release, ahead of v0.1. The core is real and exercised by voci's own suite and the three examples: discovery, dependency injection with four scopes and inverted teardown, concurrent execution with per-test timeouts, capture, assertion introspection, selection, and the reporter. Fair scheduling around shared resources, the migration tool, and machine-readable reports are still ahead — see ROADMAP.md. The public API is not frozen yet.
Trade-offs
voci is not pytest and does not aim to be compatible with it. There is no plugin ecosystem and no hook system — dependency injection is the extension point. Adopting it means rewriting your fixture wiring, and it requires Python 3.13+. Linux and macOS are supported; Windows is best-effort.
unittest.mock keeps working, at a price: mock.patch writes to a module or class, which every
concurrently running test would see, so voci runs a patch-decorated test alone and reports what
that cost. A patch opened inside a test body fails that test unless it is marked @voci.solo.
examples/02-async-library walks the alternatives.
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 voci-0.1.0rc3.tar.gz.
File metadata
- Download URL: voci-0.1.0rc3.tar.gz
- Upload date:
- Size: 983.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0de0f972f066e2b9c87faa41edfa163cd5a631695a5d6289f831c398bccf4ecd
|
|
| MD5 |
aeaf1caa4feb9f31a1cd0998372e09c8
|
|
| BLAKE2b-256 |
5e974219571f3b95dcd9958b18189fa0375047ae9d3b6726adc3fdb388c85e12
|
Provenance
The following attestation bundles were made for voci-0.1.0rc3.tar.gz:
Publisher:
release.yml on Ddedalus/voci
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voci-0.1.0rc3.tar.gz -
Subject digest:
0de0f972f066e2b9c87faa41edfa163cd5a631695a5d6289f831c398bccf4ecd - Sigstore transparency entry: 2762131437
- Sigstore integration time:
-
Permalink:
Ddedalus/voci@6bbed8914a55bfcf67b2001a0c524ef25fb81959 -
Branch / Tag:
refs/tags/v0.1.0rc3 - Owner: https://github.com/Ddedalus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6bbed8914a55bfcf67b2001a0c524ef25fb81959 -
Trigger Event:
release
-
Statement type:
File details
Details for the file voci-0.1.0rc3-py3-none-any.whl.
File metadata
- Download URL: voci-0.1.0rc3-py3-none-any.whl
- Upload date:
- Size: 259.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ce22291771be8e4958d827bd676f5cee1dc073a43f86eafe4ebdc5b05b0849f
|
|
| MD5 |
b629a766a445366482506942423eda00
|
|
| BLAKE2b-256 |
4ffef9185bd5cd8d5d070632795d021470a4957fc2bfe569c4132e516698ea6c
|
Provenance
The following attestation bundles were made for voci-0.1.0rc3-py3-none-any.whl:
Publisher:
release.yml on Ddedalus/voci
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voci-0.1.0rc3-py3-none-any.whl -
Subject digest:
3ce22291771be8e4958d827bd676f5cee1dc073a43f86eafe4ebdc5b05b0849f - Sigstore transparency entry: 2762131585
- Sigstore integration time:
-
Permalink:
Ddedalus/voci@6bbed8914a55bfcf67b2001a0c524ef25fb81959 -
Branch / Tag:
refs/tags/v0.1.0rc3 - Owner: https://github.com/Ddedalus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6bbed8914a55bfcf67b2001a0c524ef25fb81959 -
Trigger Event:
release
-
Statement type: