Skip to content
rpytest GitHub

About

A faster pytest you don't have to migrate to.

rpytest exists because the slow part of running pytest, for most teams, is not the tests. It's the interpreter starting up, the plugins importing, and the suite re-collecting on every single invocation. We don't fix that by rewriting pytest. We fix it by keeping it warm.

What it actually is

rpytest is two things: a small Rust CLI, and a Python daemon. The Rust binary is what you invoke; it's lightweight (the README benchmark measures it at around 6 MB resident) and it knows how to parse pytest's flag surface. The daemon is a long-lived Python process that hosts a real pytest. It imports your test files once, caches the collected node IDs, and waits for RPCs from the CLI.

When you run rpytest tests/test_api.py -k auth, the CLI sends a small message to the daemon describing the selector. The daemon filters its cached inventory, runs the matching tests in-process, and streams results back. The cost of importing your conftest.py, of loading your plugins, of building the test inventory — you pay that once, when the daemon starts. After that, every invocation is small.

The drop-in promise

"Drop-in" is the load-bearing word in the tagline. Concretely:

And to make this checkable rather than promised, rpytest --verify-dropin runs both pytest and rpytest against your suite and diffs the collection, results, and exit codes. If your suite uses a plugin that assumes a fresh process every invocation (some coverage tools, some hypothetical plugins that monkey-patch sys.path during collection), the verifier is how you find out before CI does.

Where the speed comes from

The benchmark in the repo (BENCHMARK.md) shows the daemon model removing a class of overhead that's invisible until you measure it: interpreter startup, plugin import graph, test collection, and worker spawn-up under xdist. On the 500-test synthetic suite measured there, wall-clock with startup drops from 0.63 s to 0.32 s, and the -n 4 case beats pytest -n 4 (xdist) by roughly 3.5x because xdist re-pays worker startup every time. We're publishing those numbers because they're the ones we have; we're not extrapolating them to your suite.

Your suite will be different. A suite dominated by slow integration tests will see a smaller percentage improvement (because the daemon overhead it removes is a smaller share of total time). A unit-test-heavy CI hot path will see a larger one. The honest framing is: rpytest removes the fixed cost per invocation. Whether that matters depends on how often you invoke and how big the fixed cost is for you.

What it isn't

rpytest is not a pytest replacement in the "we wrote it from scratch" sense. The pytest process inside the daemon is real pytest. Your plugins load. Your fixtures resolve. Your conftest.py does its thing. We're sitting in front of pytest, not under it.

rpytest is also not a remote test runner, a CI orchestrator, or a managed service. The daemon runs locally next to your CLI. There is no control plane, no cloud, no account. The CI deployment story is mostly about sharding (--shard 0 --total-shards 4) and built-in -n, not about a hosted backend.

Who's building it

rpytest is built by Neul Labs. It's MIT-licensed, the source is at github.com/neul-labs/rpytest, and the docs live at rpytest.docs.neullabs.com. The crates are rpytest (CLI), rpytest-core (shared types), rpytest-ipc (the daemon RPC), and rpytest-daemon (the Python-hosting process).

How to try it

The fastest path is pip install rpytest, then run rpytest in a repository where pytest already passes. The first run will be cold (the daemon has to start). The second run will be the one worth measuring. If you want to be sure the swap is safe before you change CI, run rpytest --verify-dropin and read the diff.