Skip to main content

Structured health and readiness check system for FastAPI

Reason this release was yanked:

The _alert_loop deadlock introduced in 1.6.0 can hang any user's test suite that uses alerters or webhook_url with fastapi-watch.

Project description

fastapi-watch

Structured health and readiness checks for FastAPI.

Test, Build & Publish PyPI version Supported Python versions PyPI Downloads


Add /health/* endpoints to any FastAPI app in minutes. Probes observe real traffic — no synthetic requests — and stream live results to a built-in dashboard and Prometheus endpoint.

For full documentation see DOCS.md.


Installation

pip install fastapi-watch

# With service-specific extras
pip install "fastapi-watch[postgres,redis]"
pip install "fastapi-watch[all]"

zsh users: quote extras to avoid glob expansion: pip install "fastapi-watch[redis]"

Available extras: postgres, mysql, sqlalchemy, redis, memcached, rabbitmq, kafka, mongo, celery


Quick start

from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi_watch import HealthRegistry
from fastapi_watch.probes import PostgreSQLProbe, RedisProbe

app = FastAPI()
registry = HealthRegistry(app)

# Infrastructure probes
registry.add(PostgreSQLProbe(url="postgresql://user:pass@localhost/mydb"))
registry.add(RedisProbe(url="redis://localhost:6379"), critical=False)

@asynccontextmanager
async def lifespan(app):
    # Automatically monitor every route — no decorators needed
    registry.discover_routes(tags=["api"], max_error_rate=0.05)
    registry.set_started()
    yield

app = FastAPI(lifespan=lifespan)

Every route is now monitored with real-traffic data. Health endpoints are live at /health/*.


Endpoints

Endpoint Purpose Status
GET /health/live Liveness — process is alive always 200
GET /health/ready Readiness — all critical probes passing 200 / 503
GET /health/status Full probe detail 200 / 207
GET /health/dashboard Live HTML dashboard (SSE) 200
GET /health/metrics Prometheus text format 0.0.4 200
GET /health/history Rolling result history per probe 200
GET /health/alerts Probe state-change log 200
GET /health/startup Startup gate; 503 until set_started() 200 / 503
GET /health/ready/stream SSE stream of readiness stream
GET /health/status/stream SSE stream of full probe detail stream
GET /health/maintenance Maintenance mode status 200
POST /health/maintenance Enable maintenance mode 200
DELETE /health/maintenance Disable maintenance mode 200

The prefix defaults to /health and is configurable: HealthRegistry(app, prefix="/ops/health").


Route monitoring

The fastest way to monitor your API is auto-discovery. fastapi-watch monitors real traffic — no synthetic polling, no wasted requests.

discover_routes — monitor everything at once

One call after all routers are included. Every route gets a passive probe with no decorators required.

@asynccontextmanager
async def lifespan(app):
    registry.discover_routes(
        tags=["api"],              # tag all probes for filtering
        max_error_rate=0.05,       # alert if error rate exceeds 5%
        include_paths=["/api/*"],  # whitelist — only monitor these routes
        exclude_paths=["/api/admin"],  # exclude even if include_paths matches
    )
    registry.set_started()
    yield

Auto-discovered probes use GET /items/{id} style descriptions so they're immediately recognizable in the dashboard.

watch_router — monitor a specific router

Scope monitoring to one router with its own tags, thresholds, and criticality. Call it after app.include_router.

app.include_router(users_router, prefix="/users")
app.include_router(orders_router, prefix="/orders")

registry.watch_router(users_router, tags=["users"], max_error_rate=0.01)
registry.watch_router(orders_router, tags=["orders"], critical=False)

Then filter health checks by router: GET /health/ready?tag=users

@probe.watch — full control on one route

For routes that need custom thresholds, a specific name, or tight SLOs.

from fastapi_watch import FastAPIRouteProbe

checkout_probe = FastAPIRouteProbe(
    name="checkout",
    description="Payment processing",
    tags=["payments"],
    max_error_rate=0.001,
    slow_call_threshold_ms=200,
)

@app.post("/checkout")
@checkout_probe.watch
async def checkout():
    ...

registry.add(checkout_probe)

Priority system

When all three approaches are used together, each route is monitored exactly once — the highest-priority wins:

Priority Method When to use
1 — highest @probe.watch One route needs its own thresholds or name
2 watch_router A whole router shares settings
3 — lowest discover_routes Catch-all for everything not handled explicitly

discover_routes and watch_router skip any route already covered by a higher-priority method — no conflicts, no double counting.


Tag-based filtering

All probes accept tags=[...]. FastAPI route tags (@app.get("/items", tags=["store"])) are automatically merged in. Filter any endpoint by tag:

GET /health/ready?tag=payments         # only payment probes
GET /health/status?tag=users,orders    # users OR orders probes
GET /health/status/stream?tag=payments # filtered live stream

The dashboard shows tag chips on each probe card and a clickable filter bar to isolate groups at a glance.


Infrastructure probes

from fastapi_watch.probes import PostgreSQLProbe, RedisProbe, HttpProbe, TCPProbe

registry.add(PostgreSQLProbe(url="postgresql://..."))
registry.add(RedisProbe(url="redis://..."), critical=False)
registry.add(HttpProbe(name="payments-api"), critical=True)
registry.add(TCPProbe(host="kafka.internal", port=9092))

Passive probes observe real calls — use @probe.watch on any function to track its latency, error rate, and throughput without making synthetic requests.


Alerting

from fastapi_watch.alerts import SlackAlerter, PagerDutyAlerter

registry = HealthRegistry(
    app,
    alerters=[
        SlackAlerter(webhook_url="https://hooks.slack.com/..."),
        PagerDutyAlerter(routing_key="your-routing-key"),
    ],
)

Webhook URLs are validated at construction time — private/loopback/link-local IP targets are rejected to prevent SSRF.


Security

Health endpoints are publicly accessible by default — set auth in production:

registry = HealthRegistry(app, auth={"username": "ops", "password": "secret"})

To skip all route registration entirely and expose health status on your own endpoint with custom auth:

registry = HealthRegistry(app, serve_routes=False)

@app.get("/internal/health", dependencies=[Depends(my_auth)])
async def my_health():
    report = await registry.get_report()
    return report.model_dump()

See DOCS.md — Security for auth callables, SSRF protection on webhook alerters, probe error message handling, and probe name restrictions.


License

MIT


Claude used to write README, code annotation, help with test case coverage, and clean up my messy thoughts into readable code.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_watch-1.6.0.tar.gz (119.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_watch-1.6.0-py3-none-any.whl (76.0 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_watch-1.6.0.tar.gz.

File metadata

  • Download URL: fastapi_watch-1.6.0.tar.gz
  • Upload date:
  • Size: 119.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for fastapi_watch-1.6.0.tar.gz
Algorithm Hash digest
SHA256 f8a1ff9d1e3f472e96ce1e01fa41455b77aeb6946c0bf0b556b7c52e82fe1d0d
MD5 a33f0da79f8ea4ee502b2605063ece8a
BLAKE2b-256 362be1e95bb1789b47e6b085638e8a71e03fd7639abcbf97e1e4aa8ba05a60c4

See more details on using hashes here.

File details

Details for the file fastapi_watch-1.6.0-py3-none-any.whl.

File metadata

  • Download URL: fastapi_watch-1.6.0-py3-none-any.whl
  • Upload date:
  • Size: 76.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for fastapi_watch-1.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 110a505e0b194d0cbdfd4ea36398b35ad9553efe3e52160347c4a6add99910ab
MD5 acaad9b75e8f550e57e490c8c84453ab
BLAKE2b-256 8062b142ff3c2ffe6d410df0afcd7ebfe220f806d940ad022b0ea07ef440dc06

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page