Repository navigation
v2.0.0
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, andget_app_managernow take the request and readrequest.app.state. The module-levelset_database,set_scheduler, andset_app_managerfunctions 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 raisesConflictError(HTTP 409) when the supplied id already exists.save()keeps its upsert semantics for library callers.createis a new abstract method on theManagerprotocol;flushandrollbackare new abstract methods onRepository(#31). - Explicit nulls clear nullable fields.
save,save_all, andcreatedump input withexclude_unset=Trueinstead ofexclude_none=True. Omitted fields are left alone; an explicitnullis assigned. PUT preserves the set of fields the client sent (#31). - Pagination bounds are enforced. List endpoints validate
page >= 1and1 <= size <= 100at the request boundary and return 422 otherwise. Listings are ordered by id (#31). - Health returns 503 when unhealthy.
GET /healthsets HTTP 503 for anunhealthyaggregate state;degradedandhealthyreturn 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.IntegrityErrormaps to 409, other errors to 500. The previous{"detail": ..., "error": ...}body is gone. Request validation errors use the same shape with a structurederrorsextension (#33). - An empty auth allowlist is honored.
with_auth(unauthenticated_paths=[])protects every path. OnlyNoneselects the defaults (/,/docs,/redoc,/openapi.json,/health) (#33). - Scheduler API changes.
JobStatus.cancelingis new: cancelling a synchronous job reportscancelinguntil its thread finishes, and the capacity slot is held until then.Scheduler.shutdown(timeout=...)is a new abstract method.set_max_concurrencyvalidates its argument (Noneor at least 1) and its parameter is renamed tomax_concurrency.max_concurrencymust be at least 1 when set (#34, #35). - Keepalive uses a handle.
start_keepaliverequiresservice_idand returns aKeepaliveHandle;stop_keepalive(handle)takes it. The module globals are removed (#34). fail_on_error=Trueshuts 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 lintis check-only. Usemake formatto apply ruff formatting and fixes (#32).
Added
- Migrations ship inside the package at
servicekit/alembic;get_alembic_dir()returns the path. A rootalembic.iniplusmake migrateandmake upgradetargets 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=...)andServiceBuilder._create_scheduler()for subclasses that supply their own scheduler (#34).InMemoryScheduler._make_record()and_on_job_result()extension hooks, so subclasses no longer copyadd_job(#35).EntryStaticFilesserves the manifestentryfile at an app's mount root (#33).- Custom
ServicekitExceptionextensions are included in Problem Details responses; reserved member names are dropped with a warning (#33). - A built-in
registrationhealth 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
pendingforever, and cancellation errors reached the event loop exception handler (#35). - Changing
max_concurrencyat 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_monitoringreturned a detached Prometheus reader on repeated calls (#34).- SSE
poll_intervalquery 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