Fresh, organic health checks for Python.
Project description
Castana
Health check library for Python that handles async and sync probes together.
Features
- Runs async and sync probes in the same health check
- Auto-detects database drivers (SQLAlchemy, asyncpg, psycopg, redis-py)
- IETF-compliant JSON responses
- No dependencies for core library
- Thread pool isolation for health checks
Installation
pip install castana
Optional extras:
pip install "castana[http]" # httpx for HTTP probes
pip install "castana[system]" # psutil for memory checks
pip install "castana[redis]" # redis-py
pip install "castana[postgres]" # asyncpg/psycopg
pip install "castana[all]" # all extras
Basic Usage
from castana import HealthCheck
from castana.probes import PostgresProbe, RedisProbe, DiskProbe
health = HealthCheck(name="my-service", version="1.0.0")
health.add_probe(PostgresProbe(conn=db_pool, name="db"))
health.add_probe(RedisProbe(client=redis_client, name="cache"))
health.add_probe(DiskProbe(warning_mb=500))
FastAPI
from fastapi import FastAPI
from castana.adapters.fastapi import create_health_router, create_health_lifespan
app = FastAPI(lifespan=create_health_lifespan(health))
app.include_router(create_health_router(health))
Flask
from flask import Flask
from castana.adapters.flask import FlaskHealth
app = Flask(__name__)
FlaskHealth(app, health_check=health)
Django
urls.py:
from django.urls import path
from castana.adapters.django import DjangoHealthView
urlpatterns = [
path('health/', DjangoHealthView.as_view(health_check=health)),
]
apps.py:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
from castana.adapters.django import register_shutdown
register_shutdown(health)
Response Format
{
"status": "pass",
"version": "1.0.0",
"checks": {
"db": [{
"status": "pass",
"componentType": "datastore",
"observedValue": 1,
"time": "2026-01-05T14:32:07+00:00",
"metadata": {"latency_ms": 2.34}
}]
},
"metrics": {
"duration_ms": 2.5,
"probe_count": 1
}
}
HTTP status codes:
200- status ispassorwarn503- status isfail
Available Probes
| Probe | Description | Dependencies |
|---|---|---|
| DiskProbe | Disk space check | None |
| MemoryProbe | Memory usage check | psutil |
| HttpProbe | HTTP endpoint check | httpx |
| RedisProbe | Redis ping check | redis-py |
| PostgresProbe | PostgreSQL check | asyncpg/psycopg/SQLAlchemy |
Configuration
health = HealthCheck(
name="my-api",
version="1.0.0",
global_timeout=30.0,
max_workers=4,
cache_ttl=5.0,
)
Options:
name: Service identifierversion: Service version stringglobal_timeout: Maximum time for entire health check suitemax_workers: Dedicated thread pool sizecache_ttl: Cache results for N seconds
Caching
Set cache_ttl to cache results and reduce probe execution frequency:
health = HealthCheck(cache_ttl=5.0)
Concurrent requests wait for the first result instead of all running probes.
Critical Probes
By default, probe failures cause global fail status. Mark non-critical probes:
health.add_probe(RedisProbe(client=redis, name="cache")) # Critical
health.add_probe(DiskProbe(name="backup-disk", critical=False)) # Optional
Kubernetes Probes
Separate liveness and readiness checks:
health = HealthCheck()
health.add_probe(DiskProbe(name="disk"), groups=["liveness", "readiness"])
health.add_probe(PostgresProbe(conn=db_pool, name="db"), groups=["readiness"])
Enable separate endpoints:
# FastAPI
app.include_router(create_health_router(health, include_live_ready=True))
# Flask
FlaskHealth(app, health_check=health, include_live_ready=True)
# Django
urlpatterns = get_health_urlpatterns(health, include_live_ready=True)
Endpoints:
/health- All probes/health/live- Liveness group only/health/ready- Readiness group only
Retry Logic
health.add_probe(HttpProbe(
name="external-api",
url="https://api.example.com/health",
retry_attempts=2,
retry_delay=0.3,
))
Global defaults:
health = HealthCheck(
default_retry_attempts=3,
default_retry_delay=1.0,
)
Failures and timeouts trigger retries. WarnCondition does not retry.
Custom Probes
Decorator
from castana import HealthCheck, WarnCondition
health = HealthCheck()
@health.probe(name="config-check", timeout=1.0)
def check_config():
if not config.IS_LOADED:
raise ValueError("Config not loaded")
return {"env": "production"}
@health.probe(name="db-ping")
async def check_database():
return await db.ping()
Class-based
from castana import BaseProbe, WarnCondition
class QueueProbe(BaseProbe):
def __init__(self, queue):
super().__init__(name="queue-depth", timeout=5.0)
self.queue = queue
async def check(self):
depth = await self.queue.get_depth()
if depth > 1000:
raise WarnCondition(f"Queue depth: {depth}")
return depth
class DiskSpaceProbe(BaseProbe):
def check(self):
return "OK"
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file castana-1.0.0.tar.gz.
File metadata
- Download URL: castana-1.0.0.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b83781346370347097a721a4c5363a669dc315533486f02fc5a525093e16c35f
|
|
| MD5 |
1c6a62b6b7934d80d01ef6cc8770f361
|
|
| BLAKE2b-256 |
3de876562e235bc3e6fca680c05b95fe8a1642a6fa7b430b5e11489eeaf40715
|
File details
Details for the file castana-1.0.0-py3-none-any.whl.
File metadata
- Download URL: castana-1.0.0-py3-none-any.whl
- Upload date:
- Size: 24.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79637ab145b7ede05eee2e3aa48d2e2c0816b22092dca011097c1a6dccb172d3
|
|
| MD5 |
a8bf6fd7bc2c7ed41a93d54dbd143d9b
|
|
| BLAKE2b-256 |
baa03ff2370082224fe26ee160d9a3cd4e2aadb8df185e3fc94d3b1357a0ef61
|