infoxtractor/src/ix/adapters/rest/schemas.py
Dirk Riemann e46c44f1e0
All checks were successful
tests / test (push) Successful in 1m7s
tests / test (pull_request) Successful in 1m5s
feat(rest): FastAPI adapter + /jobs, /healthz, /metrics routes (spec §5)
Routes:
- POST /jobs: 201 on first insert, 200 on idempotent re-submit.
- GET /jobs/{id}: full Job envelope or 404.
- GET /jobs?client_id=&request_id=: correlation lookup or 404.
- GET /healthz: {postgres, ollama, ocr}; 200 iff all ok (degraded counts
  as non-200 per spec). Postgres probe guarded by a 2 s wait_for.
- GET /metrics: pending/running counts + 24h done/error counters +
  per-use-case avg seconds. Plain JSON, no Prometheus.

create_app(spawn_worker=bool) parameterises worker spawning so tests that
only need REST pass False. Worker spawn is tolerant of the loop module not
being importable yet (Task 3.5 fills it in).

Probes are a DI bundle — production wiring swaps them in at startup
(Chunk 4); tests inject canned ok/fail callables. Session factory is also
DI'd so tests can point at a per-loop engine and sidestep the async-pg
cross-loop future issue that bit the jobs_repo fixture.

9 new integration tests; unit suite unchanged. Forgejo Actions trigger is
flaky; local verification is the gate (unit + integration green locally).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 11:47:04 +02:00

52 lines
1.5 KiB
Python

"""REST-adapter request / response bodies.
Most payloads reuse the core contracts directly (:class:`RequestIX`,
:class:`Job`). The only adapter-specific shape is the lightweight POST /jobs
response (`job_id`, `ix_id`, `status`) — callers don't need the full Job
envelope back on submit; they poll ``GET /jobs/{id}`` for that.
"""
from __future__ import annotations
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class JobSubmitResponse(BaseModel):
"""What POST /jobs returns: just enough to poll or subscribe to callbacks."""
model_config = ConfigDict(extra="forbid")
job_id: UUID
ix_id: str
status: Literal["pending", "running", "done", "error"]
class HealthStatus(BaseModel):
"""Body of GET /healthz.
Each field reports per-subsystem state. Overall HTTP status is 200 iff
every field is ``"ok"`` (spec §5). ``ollama`` can be ``"degraded"``
when the backend is reachable but the default model isn't pulled —
monitoring surfaces that as non-200.
"""
model_config = ConfigDict(extra="forbid")
postgres: Literal["ok", "fail"]
ollama: Literal["ok", "degraded", "fail"]
ocr: Literal["ok", "fail"]
class MetricsResponse(BaseModel):
"""Body of GET /metrics — plain JSON (no Prometheus format for MVP)."""
model_config = ConfigDict(extra="forbid")
jobs_pending: int
jobs_running: int
jobs_done_24h: int
jobs_error_24h: int
by_use_case_seconds: dict[str, float]