Skip to content

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 14:17
· 10 commits to main since this release
v2.0.0
ab5e143

This release addresses the findings of the September 2026 project review. Several fixes change public behavior, so the major version is bumped. Downstream projects, chapkit in particular, need coordinated updates before moving their pin.

Breaking changes

  • Dependencies are scoped to the application. get_database, get_scheduler, and get_app_manager now take the request and read request.app.state. The module-level set_database, set_scheduler, and set_app_manager functions are removed. Code running inside the lifespan should capture the objects it needs instead of calling a global getter (#34).
  • POST is create-only. CRUD routers call the new Manager.create(), which raises ConflictError (HTTP 409) when the supplied id already exists. save() keeps its upsert semantics for library callers. create is a new abstract method on the Manager protocol; flush and rollback are new abstract methods on Repository (#31).
  • Explicit nulls clear nullable fields. save, save_all, and create dump input with exclude_unset=True instead of exclude_none=True. Omitted fields are left alone; an explicit null is assigned. PUT preserves the set of fields the client sent (#31).
  • Pagination bounds are enforced. List endpoints validate page >= 1 and 1 <= size <= 100 at the request boundary and return 422 otherwise. Listings are ordered by id (#31).
  • Health returns 503 when unhealthy. GET /health sets HTTP 503 for an unhealthy aggregate state; degraded and healthy return 200. Probes and the registration readiness check now fail for a broken service (#33).
  • Database errors no longer leak SQL. SQLAlchemy errors return an RFC 9457 Problem Details body with a generic detail and a trace_id; the full error is logged with the same id. IntegrityError maps to 409, other errors to 500. The previous {"detail": ..., "error": ...} body is gone. Request validation errors use the same shape with a structured errors extension (#33).
  • An empty auth allowlist is honored. with_auth(unauthenticated_paths=[]) protects every path. Only None selects the defaults (/, /docs, /redoc, /openapi.json, /health) (#33).
  • Scheduler API changes. JobStatus.canceling is new: cancelling a synchronous job reports canceling until its thread finishes, and the capacity slot is held until then. Scheduler.shutdown(timeout=...) is a new abstract method. set_max_concurrency validates its argument (None or at least 1) and its parameter is renamed to max_concurrency. max_concurrency must be at least 1 when set (#34, #35).
  • Keepalive uses a handle. start_keepalive requires service_id and returns a KeepaliveHandle; stop_keepalive(handle) takes it. The module globals are removed (#34).
  • fail_on_error=True shuts the process down. A failed deferred registration or a readiness timeout marks the service unhealthy, logs at critical level, and raises SIGTERM so the server shuts down gracefully. Startup itself is never aborted (#34).
  • make lint is check-only. Use make format to apply ruff formatting and fixes (#32).

Added

  • Migrations ship inside the package at servicekit/alembic; get_alembic_dir() returns the path. A root alembic.ini plus make migrate and make upgrade targets support the checkout. A warning is logged when the bundled migration runs for an application that defines its own tables (#30).
  • with_jobs(shutdown_timeout=...) and ServiceBuilder._create_scheduler() for subclasses that supply their own scheduler (#34).
  • InMemoryScheduler._make_record() and _on_job_result() extension hooks, so subclasses no longer copy add_job (#35).
  • EntryStaticFiles serves the manifest entry file at an app's mount root (#33).
  • Custom ServicekitException extensions are included in Problem Details responses; reserved member names are dropped with a warning (#33).
  • A built-in registration health check when registration is configured (#34).
  • Wheel smoke test and Docker example builds in CI (#30, #32).

Fixed

  • Installed file-based databases could not find the default migrations (#30).
  • Keepalive recovery after a 404 never re-registered because the service info was serialized to an empty dict (#34).
  • Cancelling a queued job left it pending forever, and cancellation errors reached the event loop exception handler (#35).
  • Changing max_concurrency at runtime created an independent semaphore (#35).
  • Bulk-save hooks could not see earlier new entities in the same batch, and a repeated explicit id in one batch raised instead of upserting (#31).
  • Application shutdown left scheduled jobs running; startup and shutdown hook errors skipped cleanup (#34).
  • setup_monitoring returned a detached Prometheus reader on repeated calls (#34).
  • SSE poll_interval query parameters are bounded to (0, 60] (#35).
  • The registration example Dockerfile could not build from a clean checkout (#32).
  • Stale Vega tests, scheduler guide endpoints, and README class names (#32).

What's Changed

  • chore(deps): bump actions/checkout from 6 to 7 in the github-actions group by @dependabot[bot] in #23
  • chore(deps): bump the python-dependencies group across 1 directory with 2 updates by @dependabot[bot] in #24
  • chore(deps): bump actions/setup-python from 6 to 7 in the github-actions group by @dependabot[bot] in #28
  • chore(deps): bump the python-dependencies group across 1 directory with 12 updates by @dependabot[bot] in #29
  • fix: ship Alembic migrations inside the servicekit package by @mortenoh in #30
  • fix: create-only POST, explicit-null updates, batch flush, bounded pagination by @mortenoh in #31
  • chore: fix registration example build, make lint check-only, refresh stale docs by @mortenoh in #32
  • fix: harden auth allowlist, error responses, health status codes and app entry by @mortenoh in #33
  • refactor: scope database, scheduler, and lifecycle state to each application by @mortenoh in #34
  • fix: correct job cancellation, sync capacity accounting, and concurrency resizing by @mortenoh in #35
  • chore: release 2.0.0 by @mortenoh in #36

Full Changelog: v1.0.1...v2.0.0