Skip to content

Releases: dhis2-chap/servicekit

v2.0.2

Choose a tag to compare

@github-actions github-actions released this 06 Sep 18:16
v2.0.2
9e6f6ff

Patch release completing the RFC 9457 error surface. No breaking changes.

Fixed

  • FastAPI request validation failures (a malformed request body, a missing required field, or an out-of-range query parameter such as ?page=0 on a list route) were still returned as FastAPI's default {"detail": [...]} body with application/json, because only pydantic.ValidationError was routed through the Problem Details handler. RequestValidationError now produces the same 422 application/problem+json response as other validation errors: generic detail, a trace_id, and the structured error list under the errors extension with loc, type, and msg preserved.

Upgrading

Drop-in for 2.0.x users. Clients that parsed the top-level detail list of a 422 should read the errors member of the Problem Details body instead; the entries are unchanged.

What's Changed

  • fix(api): return Problem Details for FastAPI request validation errors by @mortenoh in #40
  • chore: release 2.0.2 by @mortenoh in #41

Full Changelog: v2.0.1...v2.0.2

v2.0.1

Choose a tag to compare

@github-actions github-actions released this 06 Sep 17:50
v2.0.1
7f332a6

Patch release fixing issues found while verifying 2.0.0 end to end with chapkit. No new breaking changes.

Fixed

  • BaseManager.create() reported every database integrity error as a duplicate id, producing 409 "Entity with id None already exists" for a foreign-key violation such as an unknown parent_id. It now re-checks for a real duplicate and otherwise returns 409 with the detail Entity violates a database constraint and, when it can be derived safely from the driver error, a constraint extension (foreign_key, unique, not_null, check). The SQL statement never reaches the response. The database error handler in the API layer classifies IntegrityError the same way through the new servicekit.classify_integrity_error helper.
  • The job router (/api/v1/jobs) returned plain {"detail": ...} bodies; it now raises servicekit exceptions, so its errors are RFC 9457 Problem Details like the rest of the API. A malformed job id is now 400 (it was 404), matching the CRUD routes.

Added

  • 409 is documented in OpenAPI for the CRUD create route and 404 for the single-entity routes, both with the ProblemDetail schema.
  • JobOptions is a public export of servicekit.api; ServiceBuilder._create_scheduler(job_options: JobOptions) is the supported hook for subclasses that supply their own scheduler. The private _JobOptions name remains as an alias.

Upgrading

Drop-in for 2.0.0 users. Clients that matched the exact detail string of a create-time conflict should match on the 409 status instead; the detail now differs between a duplicate id and another constraint violation.

What's Changed

  • fix: map create-time integrity errors to accurate conflict details by @mortenoh in #37
  • chore: build GitHub release notes from the annotated tag message by @mortenoh in #38
  • chore: release 2.0.1 by @mortenoh in #39

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

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 14:17
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

v1.0.1

Choose a tag to compare

@mortenoh mortenoh released this 24 Jun 12:42
b5b0995

Fixes

  • Self-registration port now follows the bind port. run_app exports its resolved port as SERVICEKIT_PORT before starting uvicorn, so service registration advertises and probes the same port the app actually binds. Previously a non-default run_app port (e.g. 9090) left registration pointing at 8000 — timing out, or worse, registering an unrelated service squatting on 8000. An explicitly set SERVICEKIT_PORT (e.g. an externally advertised port behind a proxy) remains authoritative. (#25)

Full changelog: v1.0.0...v1.0.1

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 17 Jun 10:42
v1.0.0
37c38ca

What's Changed

Full Changelog: v0.12.1...v1.0.0

v0.12.1

Choose a tag to compare

@github-actions github-actions released this 17 Jun 10:20
v0.12.1
89b7391

What's Changed

  • chore(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #14
  • chore(deps): bump the python-dependencies group across 1 directory with 4 updates by @dependabot[bot] in #16
  • chore(deps): bump locked dependencies by @mortenoh in #17
  • chore(deps): add httpx2 for starlette TestClient by @mortenoh in #18
  • chore(deps): bump codecov/codecov-action from 6 to 7 in the github-actions group by @dependabot[bot] in #19
  • chore: update repository references to dhis2-chap org by @mortenoh in #20
  • docs: left sidebar nav, author email, release 0.12.1 by @mortenoh in #21

New Contributors

Full Changelog: v0.12.0...v0.12.1

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 27 May 12:34
v0.12.0
e30607e

Full Changelog: v0.11.0...v0.12.0

v0.11.0

Choose a tag to compare

@mortenoh mortenoh released this 27 May 11:53
ccc35c4

What's Changed

Dependency Updates (#13)

  • fastapi[standard] 0.121.2 → 0.136.3
  • starlette 0.49.3 → 1.1.0 (new explicit floor starlette>=1.0.1, transitive via fastapi)
  • Transitive bumps via uv lock --upgrade: pyright, mypy, ruff, pydantic, sqlalchemy, opentelemetry, uvicorn, typer, websockets, and others

Internal

  • Switched Database.session and BaseServiceBuilder lifespan return types from AsyncIterator to AsyncGenerator to satisfy pyright 1.1.409's new deprecation check on @asynccontextmanager-decorated functions

Full Changelog: v0.10.0...v0.11.0

v0.10.0

Choose a tag to compare

@mortenoh mortenoh released this 13 Apr 11:29
968fc85

What's Changed

Bug Fixes

  • Re-register service when keepalive ping returns 404 (#11) -- When the orchestrator loses track of a service (e.g. after restart), the keepalive loop now automatically re-registers instead of silently failing.
  • Defer service registration until app is serving requests (#12) -- Registration now runs in a background task that waits for the app to be fully ready before announcing to the orchestrator. Fixes the race condition where the orchestrator calls back to fetch configs before uvicorn is accepting connections.

Details on deferred registration (#12)

  • Registration always defers to a background task (both fail_on_error=True and False)
  • Readiness check uses the actual health endpoint path from the builder; falls back to TCP connect check when no health endpoint is configured
  • Registration aborts if the app never becomes ready
  • Registration call is shielded from task cancellation for proper cleanup on shutdown
  • Registration state stored on app.state instead of module globals

Full Changelog: v0.8.2...v0.10.0

v0.8.2

Choose a tag to compare

@mortenoh mortenoh released this 08 Apr 19:03
v0.8.2
57d331a

Bug Fixes

  • fix: Respect SERVICEKIT_HOST env var in service registration -- the env var was previously ignored because socket.gethostname() auto-detection always succeeded first. Resolution order changed from parameter -> auto-detect -> env var to parameter -> env var -> auto-detect, matching how SERVICEKIT_PORT already works.

Chores

  • Widen uv_build version range to include 0.11
  • Remove deprecated license classifier in favor of PEP 639 project.license
  • Update lockfile