Pinned
Durable Objects for Python.
A PinnedAPI instance for a given id will exist exactly once in your fleet. It acts as a singleton webserver.
PinnedAPI
from pinned import PinnedAPI, route
class MyAPI(PinnedAPI):
async def lifespan(self):
# setup
yield
# teardown
@route.get("/hello")
async def hello(self):
return {"hello": self.id}
- Handlers must be
async def. - Ids are client-generated; the first request activates.
@route.get/post/websocketsee the path remainder after the id.self.create_task(coro)runs background work bound to the instance: it blocks idle eviction while running, teardown waits for it,abort()cancels it.
PinnedHost
from pinned import PinnedHost, PinnedWorker
app = FastAPI()
worker = PinnedWorker(
pinboard_url="https://pinboard.internal",
advertise_url="http://10.0.3.7:8000", # must uniquely address THIS process
token="the deployment token",
)
app.include_router(worker)
app.include_router(PinnedHost(MyAPI, worker=worker, namespace="/myapi"))
PinnedHostis an APIRouter carrying/{id}that dispatches to the instance.PinnedWorkerholds the pinboard registration; several hosts share one.- A host without a worker runs in local mode: an in-memory directory, same external API —
app.include_router(PinnedHost(MyAPI), prefix="/myapi").
Pinboard
A reverse proxy that routes each request to the backend holding the instance, placing instances on registered backends as needed. Requires Redis and a deployment token shared with the backends.
Protocol
All registry calls carry Authorization: Bearer <deployment token>.
POST /registry/registerat boot —{ clientId, secret, namespaces, advertiseUrl, headroom, draining, challenge, challengeValue }. Before accepting, pinboard callsGET <advertiseUrl>/_pinned/challenge?challenge=<challenge>(authenticated withx-pinned-proxy-secret) and requires the matching{ value }. Response:{ sessionId, expiresInMs }.POST /registry/heartbeatevery 3s —{ sessionId, headroom, draining, drainDeadline?, challenge?, challengeValue? }; this worker always sends a fresh challenge pair per heartbeat, re-proving the advertise URL via the same challenge callback (the pair is optional on the wire, both fields or neither — pairless legacy workers getreregister: truein the ack while unverified). Response: ack{ expiresInMs, messages? }—messagescarries queued drain/wind_down control messages; or a 409 NACK:reregister(unknown session — abort local instances, register fresh) orsuperseded.POST /registry/release—{ sessionId, namespace, id }frees a placement when an instance closes.- Operator:
GET /registry/workersandGET /registry/overviewintrospect;GET /registry/statsfans out to each worker'sGET /_pinned/stats(per-instance connections, tasks, idle time, activation timestamp, and any wire stats the instance'sPinnedAPI.stats()returns, plus per-namespace activation/eviction counters and aprocessblock with uptime and, with thepsutilextra installed, RSS and CPU);GET /registry/eventsreads the capped control-plane event ring;GET /registry/healthreports the control plane's own resolve counters, proxied connections, and placement quarantine;POST /registry/wind_down,/registry/drain, and/registry/undrainmanage lifecycle;GET /healthzis unauthenticated. clientIdandsecretare minted once at process start.sessionIdis the per-registration lease token: placements belong to the session, and the backend aborts all local instances before serving under a new one.- Data-plane requests go over plain HTTP to
<advertiseUrl>/<id><rest>. The proxy setsx-pinned-proxy-secretto the worker's secret; the backend rejects a mismatch with 403. - Placement is per
(namespace, id): first claim wins (RedisSET NX) among live, undrained backends, biased toward headroom. Redis keys and body shapes are documented in pinboard's source (packages/pinned/pinboard).
Lifecycle
- Activation on first request; deactivation by idle sweep. In-flight requests and open streams block eviction.
- A host holds at most
max_instancesids at once (default 10 000); activations beyond the cap answer 503 + Retry-After while existing instances keep serving. self.abort()force-ends an instance: cancels in-flight work and tears down.- Streams are pull-based: the response generator advances only as the client reads — a slow client backpressures its own stream and nothing else, with no per-connection buffer.
- Draining workers answer new activations with 503 + Retry-After while existing requests keep serving; freed ids activate on the new deployment.
- While an instance's close and
/registry/releaseare in flight, requests for that id answer 503 + Retry-After so the proxy re-resolves the placement instead of re-activating locally.
Instance memory is ephemeral. Idle eviction, deploys, lease loss, and crashes all discard it; a re-activation starts from lifespan with nothing. Anything that must survive belongs in the app's own store, written before the response that claims it.
Leases and fencing
- Each acked heartbeat anchors the lease at send time:
self.lease.expires_at= send time +expiresInMs(10s by default, pinboard-supplied) — the moment pinboard may re-place this worker's ids, matching the worker record's Redis TTL. - When
expires_at - bufferpasses without an ack, the worker quits: aborts every local instance and stops registering before the ids can move; the host app keeps running. The buffer (default 5s) is the worker's local safety margin;self.lease.configure(...)changes it and the handlers. - A frozen process (VM pause, long GC) that wakes after its lease lapsed fails its next heartbeat (unknown session) and aborts everything before serving again.
- Registration failures retry with exponential backoff (capped at 30s); only a
supersededNACK — another session took over this worker — stops the loop for good.host.readyis False while the worker is unregistered or past its lease; wire it to the app's readiness probe (pinned_registeredis the matching gauge). - Guarantee: at most one live instance per id outside a lease-bounded failure window.
Observability
Lifecycle events (activation, eviction/abort, drain, lease health, registration) log on the stdlib pinned.* logger hierarchy with instance/namespace fields; configure it like any library logger. When prometheus_client is importable, pinned.metrics registers pinned_* counters, gauges, and a per-namespace request duration histogram on the default registry — a host app exposing /metrics picks them up automatically. Without the package they are no-ops. No Sentry or OpenTelemetry wiring; that stays in the host app.
Metadata
Release files for pinned 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pinned-0.13.0.tar.gz | 147.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pinned-0.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 237.5 kB
Release files / pinned-0.13.0.tar.gz
| Download URL | pinned-0.13.0.tar.gz |
|---|---|
| Size | 147.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
245b9c7bf14e5d0f82238e9c53ae2be7c4f4b6042cb5d37f45e2a933180bd490
|
|
BLAKE2b-256 checksum How to use checksums |
c597a303e8d06ee80865ad5dd52c26db6899b6dbb77051d8b40937072ef56254
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency logRelease files / pinned-0.13.0-py3-none-any.whl
| Download URL | pinned-0.13.0-py3-none-any.whl |
|---|---|
| Size | 90.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4e36fc7ec03f4ad51f35bac42d8880dd54cd017b9857e09e4dbde286a2e29b15
|
|
BLAKE2b-256 checksum How to use checksums |
684bed782dffafb4ce730d9cace3d11ea97ebccbf0832523a57463dcb5ee24c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency log