Skip to content

v0.1.0

Latest

Choose a tag to compare

@vilyam vilyam released this 30 Jul 00:56

Initial public release.

Added

  • lock(key, *, backend, lease, on_duplicate=None) — the core context manager. Runs the
    with body at most once for a key: a duplicate raises Duplicate on enter (body skipped),
    a live holder raises InProgress, a body exception releases the key for retry, and success
    records the key DONE. Ownership is a monotonic fencing token: complete() commits on a
    fence match even after the lease lapsed (an unreclaimed lapsed holder is still the only
    party that ran the work), and release() advances the fence so a released claim can never
    be completed afterwards.
  • mutex(key, *, backend, lease) — the serialize-without-deduplicating sibling, built on the
    same backend port. Always release()s and never complete()s, so a mutex key is never
    DONE: distinct messages for one entity (e.g. successive status updates for one purchase
    order) each run, one at a time, none dropped. Mutual exclusion, not ordering.
  • @idempotent(backend=..., key=..., lease=...) — the pub/sub layer: wraps a
    functions_framework CloudEvent handler and maps the outcome to ack/nack (duplicate → ack,
    contention → quiet 503 with a warning log carrying the message's deliveryAttempt, work
    error → 500). Key defaults to the Pub/Sub messageId; override with key=. Accepts a
    backend instance, a zero-arg factory, or a backend class (built lazily, once). Fails closed
    on unkeyed messages unless allow_unguarded=True; rejects async handlers at decoration
    and at runtime.
  • on_duplicate observation hook (DuplicateHook) on lock() and @idempotent — called
    with the key when a delivery is a duplicate; best-effort and payload-agnostic, it never
    changes the outcome.
  • Lock backends implementing the LockBackend port: InMemoryLock, DatastoreLock
    (production default; serializable transactions, claim-token retry re-affirmation),
    RedisLock (single-key Lua CAS, server-side clock, cluster-safe), SqlLock (any DB-API
    2.0 driver, epoch-ms timestamps, FOR UPDATE + PK tiebreak, deadlock-code retries). Each
    owns its ttl with lazy read-time expiry — no cron, no background sweeper.
  • message_id() and delivery_attempt() routers resolving the id and the dead-letter
    attempt counter across push, Eventarc CloudEvent, raw push body and streaming-pull shapes —
    envelope metadata only, never payload.
  • GCP_PUBSUB_IDEMPOTENCY_DISABLED env switch: @idempotent becomes a warning pass-through
    (backend never built) for unit-testing handler bodies.
  • Backend contract suite run against every backend (memory / SQLite / PostgreSQL / Redis /
    Datastore emulator, auto-started in Docker), a docker-compose end-to-end harness (real
    Pub/Sub + Datastore emulators, real push subscriptions, multi-instance crash storm), Redis
    server-clock tests, and a @idempotent+functions_framework integration test.
  • examples/ (Cloud Function, Cloud Run, local dev), COMPARISON.md (alternatives & prior
    art), DECISIONS.md, and CI/release workflows (PyPI Trusted Publishing).