without¶
A decoupled-IO substrate for connecting streams of events to stateful processors
backed by contexts, aiming for maximum concurrency from testable,
dependency-injected code. I/O is not banned, it is separated into the right
abstractions (sources at the edge, behaviors via sample, effects contained in
a processor's step) so the parts stay reusable.
The bet¶
Python has many frameworks with similar-but-subtly-different shapes (ASGI apps,
Kafka consumers, asyncio protocols, config reloaders) that do not interoperate
because none of them names the shared lower layer. without names that layer as
a narrow interface, so the pieces compose. It is meant to feel like a library
(your control flow stays visible) rather than a framework.
The Philosophy page rests on two ideas: the stateful stream processor as a universal way to model computation, and an ecosystem of thin layers with narrow interfaces, so you meet one altitude, descend when you need to, and can replace a layer without rewriting the rest. Read it first to get the mindset the code is shaped around.
The substrate¶
Three types carry the whole model (without.interfaces):
- A
Stream[T]is an asynchronous sequence of values: the one shape every connection takes, whoever does the I/O. A socket, a file watcher, a clock, and an in-memory list are all just streams. - A
Processor[In, Out]transforms a stream of inputs into a stream of outputs. It is the only node type and the only thing a user writes. - A
Context[T]is a stream viewed as its latest sampled value:current()reads the latest and never blocks, the way long-lived state (config, a pool) is read.
The packages¶
This is a uv workspace of flat, version-locked
packages. Each is its own top-level import.
without: the core interfaces every plugin speaks, the stream connectors, and awith-scoped background task helper.without-env: a staticContextparsed from environment variables withpydantic-settings.without-configmap: config from a Kubernetes mount, the context-updated-by-a-stream half of the model.without-asgi: adapters that turn an ASGI app'sreceive/sendinto typed event streams and back, in both directions.without-web: an opinionated HTTP/WebSocket router with trie matching, typed path params, mounting, scoped middleware, exception handlers, and OpenAPI.without-http: anasyncioASGI server and HTTP client built on the sans-IOh11/h2/wsprotostate machines.without-dag: bounded-concurrency execution of DAG-shaped async workflows, liftable straight into aProcessor.without-durability: durable workflows over a checkpoint any process can read, with the store interfaces that make one writer at a time enforceable. Its stores are Redis, Postgres, and SQLite.
The package dependency graph is derived from the
declared dependencies, and each package's API reference (its Reference page,
e.g. without) is recovered from the source docstrings.
Installing¶
pip install without-core # the core interfaces (imported as `without`)
pip install without-web # plus whichever plugins you need
Development¶
uv sync
just test # start the services in compose.yaml, then mypy + pytest
just docs # serve this site with live reload
A few tests drive a real backing service rather than a fake. just test starts the
services in compose.yaml with docker or
podman, whichever it finds, and stops them again when it exits;
install either to run those tests, or let them skip.
For contributing to without itself, see the
Releasing runbook for how the workspace is
versioned and published, and the
Mutation testing guide for driving each
package's suite to zero surviving mutants.