capio
Composable capabilities for Python: resilience, caching, observability, and AI behavior.
A capability runtime, not a decorator library.
Capio is the composable capability layer for Python applications. Apply cross-cutting behavior to functions and methods — retries, caching, timeouts, circuit breaking, rate limiting, tracing, metrics, logging, and more — with one uniform, typed API.
Designed for sync and async Python, generator-friendly, backend-agnostic, and fail-safe by default. The architecture is specified in the RFC documents (RFC-000…033).
Table of contents
- Features
- Installation
- Quick start
- How it works
- Capabilities
- Custom capabilities
- Context & events
- CLI
- Documentation
- Development
- License
Features
- 8 batteries-included capabilities — retry, cache, timeout, circuit breaker, rate limit, trace, metrics, log — plus a plugin SDK for your own.
- One uniform API —
@use.<name>(...)chained form and the equivalent@use(...)composite form. - Sync, async, and generators — one decorator works for
def,async def, generator, and async-generator functions. - Lazy pipelines — decorating costs microseconds and does no I/O; the pipeline is built on the first call and memoized.
- Fail-safe by default — cache, trace, metrics, and log degrade gracefully
when their backend fails; opt in to hard errors with
strictmode. - Cancellation-safe — timeouts and cancellations are
BaseExceptions, so retry and circuit-breaker never swallow them. - Event bus — subscribe to structured events (
cache.hit,retry.attempt,circuit.open, …) without touching your functions. - Context injection — read invocation IDs, environment, and deadlines via
use.context(). - CLI —
capio doctor,inspect,graph, andbenchmark.
Installation
pip install capio
For development, clone the repo and install with the dev extra:
git clone https://github.com/shashi3070/capio.git
cd capio
pip install -e ".[dev]"
Capio has no third-party runtime dependencies beyond the CLI (Typer).
Quick start
from capio import use
@use.retry(max_attempts=3, backoff="exponential", jitter=True)
@use.cache(ttl="5m")
@use.timeout(seconds=2)
@use.trace()
def search(query: str) -> list[str]:
...
Capabilities compose as nested scopes; the decorator written highest runs outermost. The composite form is equivalent (and sorts by priority):
from capio import use
@use(
retry={"max_attempts": 3, "backoff": "exponential", "jitter": True},
cache={"ttl": "5m"},
timeout={"seconds": 2},
trace=True,
)
def search2(query: str) -> list[str]:
...
Async works with the same API:
@use.retry(max_attempts=3)
@use.cache(ttl="30s")
@use.circuit_breaker(failure_threshold=5, reset_timeout="30s")
async def fetch(url: str) -> bytes:
...
Every capability and every option is documented in the usage guide.
How it works
Decorating a function attaches metadata (fn.__capio__) and a thin wrapper —
nothing else. On the first call, Capio builds the execution pipeline
(validating configuration, running the capability lifecycle, resolving
backends) and memoizes it. Every call then runs the wrapped function through
that pipeline.
Inside the pipeline, capabilities wrap each other like an onion — each runs,
delegates to the next via call_next(ctx), and resumes on the way back out.
Ordering is outermost-first; the composite form sorts by priority:
Capabilities are fail-safe by default: if the cache, trace, metrics, or log
backend fails, the invocation proceeds untouched (the failure is emitted as an
event). Under strict mode the same failures raise.
A deep, module-by-module code walkthrough lives in the architecture document.
Error handling
Capio raises from the capio.exceptions module. Two rules to remember:
- Timeouts and cancellations are
BaseExceptionsubclasses —except Exceptionwill not catch them. CatchCapioTimeoutErroror the baseCapioCancelledBaseexplicitly. - Everything else derives from
CapabilityException(anException), with structured attributescapability,code, andextra.
Timeout
from capio import use
from capio.exceptions import CapioTimeoutError
@use.timeout(seconds=1)
def slow() -> str:
...
try:
slow()
except CapioTimeoutError as exc:
print(f"timed out after {exc.seconds}s") # exc.seconds == 1.0
Prefer returning a sentinel over raising? Set return_on:
@use.timeout(seconds=1, return_on="timeout") # returns "timeout" instead of raising
def slow2() -> str:
...
return_on and raise_on=True are mutually exclusive (config error).
Retry exhaustion
from capio import use
from capio.exceptions import RetryExhaustedError
@use.retry(max_attempts=3)
def flaky() -> None:
raise ValueError("boom")
try:
flaky()
except RetryExhaustedError as exc:
last_error = exc.__cause__ # the final ValueError
print(exc.capability) # "retry"
print(exc.code) # "capio.retry.exhausted"
Use on_final="reraise_original" to re-raise the first failure instead of
wrapping it.
Circuit breaker and rate limit
from capio.exceptions import CircuitOpenError, RateLimitExceededError
@use.circuit_breaker(failure_threshold=3)
def call_api() -> dict: ...
@use.rate_limit(limit=1, window="1s")
def tick() -> None: ...
try:
call_api()
except CircuitOpenError:
... # dependency is unhealthy; fail fast or serve a fallback
try:
tick()
except RateLimitExceededError as exc:
print("retry after", exc.retry_after) # seconds
With use.retry, these two are not retried by default (they are
non-retryable unless you explicitly list them in retry_on).
Configuration errors
These are raised at decoration / first-call time and are all
CapabilityExceptions: ConfigurationError, UnknownCapabilityError,
DuplicateCapabilityError, ConflictingPipelineError,
UnsupportedExecutionKindError, CacheKeyError, BackendUnavailableError
(strict mode).
Capabilities
| Decorator | Capability | Purpose | RFC |
|---|---|---|---|
use.retry |
Retry | Retry failures with backoff + jitter | RFC-017 |
use.cache |
Cache | In-memory cache with TTL | RFC-016 |
use.timeout |
Timeout | Bound execution time | RFC-018 |
use.circuit_breaker |
Circuit Breaker | Fail fast when a dependency is unhealthy | RFC-018 |
use.rate_limit |
Rate Limit | Admission control | RFC-018 |
use.trace |
Trace | Span recording | RFC-019 |
use.metrics |
Metrics | Counters + histograms | RFC-019 |
use.log |
Log | Structured invocation logging | RFC-020 |
See the usage guide for every option of every capability (types, defaults, examples).
Custom capabilities
from capio import Capability, use
from capio.registry import registry
class Audit(Capability):
name = "audit"
priority = 550
def run(self, ctx, call_next):
result = call_next(ctx)
print("audit:", ctx.fn_name, result)
return result
registry.register(Audit)
@use.audit()
def handler(x: int) -> int:
return x * 2
For async-aware capabilities, override run_async and await call_next(ctx).
Context & events
Inject the per-invocation Context into any decorated function:
from capio import use
@use.context()
def handler(ctx):
return ctx.invocation_id, ctx.env, ctx.strict
Subscribe to capability events:
from capio import default_runtime
default_runtime().event_bus.subscribe("cache.hit", lambda e: print("hit", e.data))
CLI
capio doctor # environment + plugin smoke check
capio inspect mod.fn # show a decorated function's pipeline
capio graph mod.fn # render pipeline order
capio benchmark # run micro-benchmarks against RFC-027 budgets
capio version # print version
If your OS blocks pip-generated console scripts, use python -m capio.cli ....
Documentation
- Architecture guide — how each part is built: code walkthrough, snippets, and the invocation flow
- Usage guide — the full manual: every capability and configuration option
- RFCs — the normative architecture: RFC-000 index, RFC-001 vision, RFC-002 core concepts, RFC-003
useAPI, RFC-004…024 architecture, RFC-025 errors, RFC-026 security, RFC-027 performance, RFC-028 CLI, RFC-029 testing, RFC-030 AI/agents/LLM/MCP, RFC-031 reference implementation, RFC-032 roadmap, RFC-033 migration/FAQ - Changelog
Development
pip install -e ".[dev]"
pytest tests/ -v
ruff check .
Status: v0.1.0 — MVP reference implementation (RFC-031). Core capabilities
implemented: retry, cache, timeout, circuit_breaker, rate_limit,
trace, metrics, log. 67 tests, ruff clean.
License
MIT — see LICENSE.
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 capio-0.1.1.tar.gz.
File metadata
- Download URL: capio-0.1.1.tar.gz
- Upload date:
- Size: 44.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d592897c590c8fa84b30106adcff2362cd8a4b08b0f09042d79b94654cbe3e91
|
|
| MD5 |
25865444a24abaf8ebdd93fd25a03fc2
|
|
| BLAKE2b-256 |
28fafc1af0b9be80db2ce940a218112fb41cd5ec7533f5ffdd9dad0b73fac1af
|
File details
Details for the file capio-0.1.1-py3-none-any.whl.
File metadata
- Download URL: capio-0.1.1-py3-none-any.whl
- Upload date:
- Size: 44.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a608bc474444a88bf5d8559a9b0cb37583929500ec56adf88758d332f1db4582
|
|
| MD5 |
1a2b878f4d388e1dbfa6225d7e10fa5c
|
|
| BLAKE2b-256 |
1d5f18ded07bbb5b3f34f0f11a9d4377807c6f803e795d9f2bb27bea58e8211a
|