Why I Built thread-order: Deterministic, Ordered Concurrency for I/O-Bound Workflows
Most Python test frameworks assume that tests should be independent. In many cases, that's exactly the right approach: it enables easy parallelism, faster feedback, and simpler reasoning.
But sometimes, independence just isn't realistic.
I ran into this while testing an API where operations form a lifecycle. Resources are created, updated, referenced by other resources, and eventually torn down. Each step depends on state produced by the previous one. If something fails early, downstream operations must not run.
This wasn’t a hypothetical edge case — it was my day-to-day workflow.
The problem with 'just use pytest'
I initially modeled this using pytest. I relied on ordering plugins, fixtures, and shared state. It worked, but it quickly became fragile.
As the test suite grew, ordering constraints spread across files. Execution became effectively serialized. Failures caused confusing cascades. Test runs slowed down, and understanding what actually happened during a run required digging through logs and mentally reconstructing execution order.
At some point it became clear: I wasn’t really writing independent tests anymore. I was orchestrating a workflow.
Pytest isn't built for that. And that's not a flaw — it's a design choice. Pytest optimizes for independent tests and fast feedback, not deterministic task graphs with shared state.
Why not pytest-xdist?
A common suggestion is pytest-xdist. That's a solid tool, especially for CPU-bound workloads and independent tests.
My workload was different. It was mostly I/O-bound: HTTP calls, waiting on external systems, validating responses. Threads were a better fit than processes.
More importantly, multiprocessing breaks shared in-memory state. My workflow relied on passing structured results from one step to the next. Rebuilding that on top of IPC would have added complexity without solving the core problem: "explicit ordering with shared state".
I needed concurrency, but I also needed control.
The real-world constraint
In my case, tests represented a real lifecycle: creating resources, updating them, attaching related objects, and then tearing everything down in reverse order. Each step depended on state produced by the previous one, and failures had to prevent downstream operations from running.
This pattern is common when testing APIs that manage infrastructure, networking, or other stateful systems.
The requirements were clear:
- Deterministic execution order
- Shared, mutable state
- Safe parallelism where possible
- Clear visibility into what ran, what didn’t, and why
I couldn't find a tool that handled all of that cleanly.
So I built one.
Introducing thread-order
thread-order is a lightweight framework for running Python callables concurrently while respecting explicit ordering constraints.
Internally, execution is modeled as a directed acyclic graph (DAG). Each task declares what it depends on. The scheduler submits tasks to a thread pool only when their dependencies have completed successfully.
Because execution happens in threads, shared state is simple: a dictionary passed to all tasks. No serialization. No IPC. No ceremony.
The scheduler guarantees:
- A task never runs before its dependencies
- Downstream tasks can be skipped if a dependency fails
- Execution is deterministic and repeatable
It's intentionally designed for I/O-bound workflows.
Visibility matters
One of the biggest pain points in my earlier pytest-based approach was visibility. When something failed, it wasn't always obvious what ran, what was skipped, or how concurrency behaved at runtime.
To address that, thread-order includes built-in runtime visualization.
The CLI can optionally run with a text-based UI (TUI) powered by thread-viewer, which shows:
- Live thread utilization
- Which task is running on which thread
- A real-time progress bar
- Clear signals when tasks complete, fail, or are skipped
You can literally watch the scheduler make decisions as it runs.
On top of that, there's an optional GUI, installable via thread-order[ui]. It exposes the same execution model visually, with additional affordances for loading tasks, managing state, inspecting execution history, and monitoring progress.
Both interfaces exist for the same reason: deterministic execution is only useful if you can see it happening.
When should you use this?
thread-order is not a replacement for pytest.
If your tests are independent, fast, and CPU-bound, pytest (with or without xdist) is the right tool.
But if you're orchestrating a workflow — especially one that interacts with external systems — and execution order matters, this model fits naturally. You stop fighting the framework and start expressing intent directly.
Final thoughts
This framework exists because I couldn't express my real-world constraints cleanly in pytest without turning my test suite into a fragile execution engine.
thread-order is intentionally small. It does one thing well: deterministic, ordered concurrency for I/O-bound workflows.
It's a niche — but it's a niche a lot of infrastructure and platform engineers quietly run into.
https://pypi.org/project/thread-order/
https://github.com/soda480/thread-order
Top comments (0)