servicewright
One Host, many Entrypoints — a batteries-optional microservice runtime for async Python.
Describe a service once as an AppSpec (DI container, lifecycle, observability, warmup, health) and
run it through any number of pluggable entrypoints — HTTP, gRPC, scheduler, background daemon,
one-shot batch — under one unified lifecycle. The "API vs worker" distinction does not exist at the
core: a cron job and an HTTP request are the same thing — one unit of work in a fresh DI scope.
This is the model behind .NET Generic Host, Spring SmartLifecycle and go-kratos
transport.Server, adapted to async Python.
Why
- One lifecycle for every archetype: Bootstrap → Warmup → Ready → Serve → Drain → Cleanup.
Kubernetes-correct shutdown out of the box: readiness flips to
falsebefore draining, every entrypoint finishes in-flight work within a grace window, the DI scope closes last, and a service that dies mid-serve exits non-zero instead of looking like a graceful stop. - DI-agnostic two-tier scopes: the core depends on no DI library.
AppScopeholds process-lifetime singletons; a freshUnitScopewraps every request / RPC / job / message. A dishka adapter ships in the box; any container fits by implementing two methods. - Pluggable observability add-ons: metrics / tracing / logging / error-tracking are protocols
in the kernel with selectable, extra-gated backends (prometheus, OpenTelemetry, Sentry,
structlog). Adding a backend = one module + one
register_sinkcall, zero core changes. - One error taxonomy, every transport: a
ServiceErrorraised in business code renders as an RFC 9457 problem document over HTTP and as the mappedgrpc.StatusCodeover gRPC, with one masking rule for non-public details and a pluggable renderer when you own the wire format. - Zero hard dependencies:
pip install servicewrightbrings pure Python. Every framework binding lives behind an extra; the kernel never imports an SDK, a vendor, or a transport.
Installation
pip install servicewright # pure kernel, zero dependencies
pip install "servicewright[fastapi]" # + FastAPI/uvicorn entrypoint
pip install "servicewright[grpc]" # + gRPC entrypoint
pip install "servicewright[apscheduler4]" # + cron/scheduler entrypoint
pip install "servicewright[metrics,observability,sentry]" # + prometheus, otel+structlog, sentry
pip install "servicewright[all]" # everything except the conflicting [apscheduler3]
Requirements: Python 3.12+
Quick start — HTTP API + cron in ONE process
import asyncio
from servicewright import AppSpec, ObsConfig, ObservabilityManager, Service, run
from servicewright.adapters.apscheduler4 import ScheduledJob, SchedulerEntrypoint
from servicewright.adapters.fastapi import FastApiEntrypoint
def build_service() -> Service:
spec = AppSpec(
service_name="orders-service",
create_container=build_container, # your DI container factory
observability=ObservabilityManager(
ObsConfig(metrics="prometheus", tracing="otel", logging="structlog"),
),
)
http = FastApiEntrypoint(routers=(router,)) # kind="http"
cron = SchedulerEntrypoint(jobs=[ # kind="scheduler"
ScheduledJob(id="sweep", func=sweep_expired_orders, trigger=interval_trigger),
])
return Service(spec, entrypoints=[http, cron])
if __name__ == "__main__":
asyncio.run(run(build_service(), Settings()))
Both entrypoints share one DI container, one observability setup and one graceful shutdown.
Scaling the worker separately later = the same AppSpec in a second process with a different
entrypoint list.
Entrypoints
| Archetype | Adapter | Extra |
|---|---|---|
| HTTP API | adapters.fastapi / adapters.litestar |
fastapi / litestar |
| gRPC API | adapters.grpc |
grpc |
| Scheduled / cron | adapters.apscheduler4 / adapters.apscheduler3 |
apscheduler4 / apscheduler3 |
| Background daemon | DaemonEntrypoint (built-in) |
— |
| One-shot / batch | OneShotEntrypoint (built-in) |
— |
Writing your own entrypoint = implementing four methods (bind, serve, drain, stop)
with nothing installed.
What's inside
The kernel is core/; everything that touches a third-party SDK is an extra-gated adapter. An
import-linter contract enforces the direction in CI: deleting adapters/ leaves core/ importable.
| Module | Responsibility | Extra |
|---|---|---|
servicewright |
AppSpec, Service, Host, run — the public vocabulary |
— |
core.contracts |
Entrypoint, Plugin, container/settings/health protocols |
— |
core.aio.host |
The lifecycle kernel: warmup → ready → serve → drain → cleanup | — |
core.errors |
ServiceError, ErrorKind, RFC 9457 renderer + renderer seam |
— |
core.context |
Transport-neutral correlation store + outbound propagation | — |
core.health |
HealthRegistry driving both HTTP routes and the gRPC health service |
— |
core.warmup |
Priority-grouped, fail-fast warmup before readiness flips | — |
core.observability |
Sink protocols, NullObjects, backend registry, redaction | — |
adapters.builtin |
DaemonEntrypoint, OneShotEntrypoint — zero-dependency |
— |
adapters.fastapi |
FastAPI entrypoint, middleware stack, problem-details handlers | fastapi |
adapters.litestar |
Litestar entrypoint | litestar |
adapters.grpc |
gRPC entrypoint over grpc-server-kit, error mapping, health bridge | grpc |
adapters.apscheduler4 / apscheduler3 |
Scheduler entrypoints with identical public surfaces | apscheduler4 / apscheduler3 |
adapters.dishka |
dishka ⇄ core scope binding | dishka |
adapters.observability |
prometheus / OpenTelemetry / Sentry / structlog / stdlib sinks | see below |
adapters.warmers, adapters.health |
Redis / Postgres / Kafka warmers and checks | redis, postgres, kafka |
servicewright.testing |
FakeContainer, FakeEntrypoint, FakeScope, FakeSettings |
— |
Optional dependencies
| Extra | Pulls in | Enables |
|---|---|---|
fastapi |
fastapi, uvicorn, deadline-budget, prometheus-fastapi-instrumentator | FastApiEntrypoint + its middleware stack |
litestar |
litestar, uvicorn | LitestarEntrypoint |
grpc |
grpc-server-kit[reflection,channelz,health] | GrpcEntrypoint, error mapping, health bridge |
apscheduler4 / apscheduler3 |
apscheduler 4.x / 3.x | SchedulerEntrypoint (one major per environment) |
dishka |
dishka | DishkaContainer |
observability |
opentelemetry-sdk, OTLP gRPC exporter, structlog | otel tracing + structlog logging sinks |
fastapi-tracing |
the above + opentelemetry-instrumentation-fastapi | HTTP request spans |
metrics |
prometheus-client | prometheus metrics sink + /system/metrics |
sentry |
sentry-sdk | sentry error-tracking sink |
redis / postgres / kafka |
redis / sqlalchemy / aiokafka | matching warmers and health checks |
all |
everything except apscheduler3 |
the full runtime |
Examples
Runnable, self-contained scripts (each exits 0):
examples/minimal_service.py— the smallest real service, narrating every lifecycle phase in order.examples/http_service.py— a real uvicorn server with dishka DI, per-request scopes, health probes, an RFC 9457 error on the wire and a graceful drain.examples/errors_and_context.py— the error taxonomy, a custom renderer, masking, and the correlation store with outbound propagation.examples/warmup_and_health.py— warmup priority groups, fail-fast, and health checks driving readiness.
Documentation
Full documentation: bedrock-python.github.io/servicewright
| Your first service | a complete service with nothing installed but the kernel |
| Tutorial | an HTTP API and a cron job in one process |
| Architecture | the six nouns, the two layers, the dependency rule |
| Lifecycle | phase order, budgets, signals, exit codes |
| Adapters | FastAPI, Litestar, gRPC, scheduler, dishka, observability backends |
| Blueprints | copy-paste skeletons: project layout, HTTP API, gRPC, worker, batch job |
| Writing an entrypoint | four methods, worked end to end |
| Kubernetes | probes, grace periods, exit codes |
| Runbooks | symptom → cause → fix |
| API reference | generated from the source |
The design source-of-truth lives in ARCHITECTURE.md.
License
Apache 2.0 — 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 servicewright-0.3.0.tar.gz.
File metadata
- Download URL: servicewright-0.3.0.tar.gz
- Upload date:
- Size: 105.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c474f6d62f7ab5323f0096a7096211da0998bbdb98db279be1f3613f67bd9b4
|
|
| MD5 |
0b3475902e80a73f7f9b6d3cef86acbc
|
|
| BLAKE2b-256 |
e1eed97fe8c352263d0683b541637d3a720c2bcb62117550382001248895e327
|
File details
Details for the file servicewright-0.3.0-py3-none-any.whl.
File metadata
- Download URL: servicewright-0.3.0-py3-none-any.whl
- Upload date:
- Size: 159.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4ed478889e29c6ca3a54d56ee4a45aee9aff771cb30d423d4d386c3c2004122
|
|
| MD5 |
fbe411f9bcd4e089b698a26af0a731fb
|
|
| BLAKE2b-256 |
5a2522dbff15ef03e43ca7dda9b58a83ea1c26d6206b4b4efe74b63728e01653
|