From duplicated /health endpoints to a published PyPI package - an engineering deep dive. ...
From duplicated
/healthendpoints to a published PyPI package - an engineering deep dive.
In a typical microservice architecture, health endpoints start simple:
GET /health
GET /ready
But over time, reality sets in.
Some services use:
Django + PostgreSQL + Redis + Celery
FastAPI + SQLAlchemy + Redis
BFFs that depend on upstream HTTP services
RabbitMQ + background workers
Async stacks mixed with sync stacks
Each service needs:
Liveness checks
Readiness checks
Dependency verification
Timeouts
Structured JSON output
Correct HTTP status codes
And before long, every service has its own slightly different HealthService.
Different thresholds.
Different response formats.
Different timeout logic.
Different readiness semantics.
That's when I realized:
Health checks are infrastructure. They should not be rewritten per service.
So I built PulseCheck - a framework-agnostic health and readiness library for Python.
Before writing a single line of code, I defined constraints:
This wasn't just about code reuse.
It was about architectural consistency.
The key design decision was separation of concerns.
pulsecheck/
│
├── core/ ← Framework-agnostic health engine
├── fastapi/ ← FastAPI adapter
└── django/ ← Django adapter
The core layer contains:
It has zero framework dependencies.
The core doesn't know what FastAPI or Django is.
Each dependency is implemented as a check:
SQLAlchemyAsyncCheck
DjangoDBCheck
RedisAsyncCheck
RedisSyncCheck
RabbitMQKombuCheck
CeleryInspectCheck
HttpDependencyCheck
Each check:
Has a name
Has a timeout
Has a degraded threshold
Returns structured results
Example:
registry = HealthRegistry(environment="prod")
registry.register(SQLAlchemyAsyncCheck(engine))
registry.register(RedisAsyncCheck(redis_url))
registry.register(CeleryInspectCheck(celery_app))
No monolithic service class.
Just composition.
FastAPI is async.
Django is traditionally sync.
Instead of creating two engines, the core is async-first.
Sync checks are wrapped using:
asyncio.to_thread(...)
This gives:
This avoids duplicating the health engine.
This is often misunderstood.
Liveness:
"Is the process alive?"
Readiness:
"Can this service safely receive traffic?"
PulseCheck separates them cleanly:
registry.liveness()
await registry.readiness()
Readiness runs dependency checks.
Liveness does not.
This mirrors Kubernetes probe behavior.
Health isn't binary.
Instead of just UP or DOWN, PulseCheck supports:
HEALTHY DEGRADED UNHEALTHY
If a dependency is slow but responding:
{
"status": "DEGRADED",
"response_time_ms": 750
}
This gives operational insight without triggering restarts.
One of the most important design decisions was dependency management.
FastAPI projects already have FastAPI.
Django projects already have Django.
The library must not force unnecessary installations.
In pyproject.toml:
[project.optional-dependencies]
fastapi = ["fastapi>=0.100"]
django = ["Django>=4.2"]
redis_async = ["redis>=5.0"]
rabbitmq = ["kombu>=5.3"]
celery = ["celery>=5.3"]
Now:
FastAPI service:
pip install pulsecheck[fastapi,redis_async]
Django service:
pip install pulsecheck[django,redis_sync]
Clean. Explicit. Controlled.
Health endpoints are infrastructure endpoints.
In FastAPI:
@router.get("/health", include_in_schema=False)
They exist.
They work.
They don't pollute public API docs.
Small detail. Big professionalism signal.
Before uploading to PyPI, I tested:
pip install -e .) python -m build) I also learned something important:
TestPyPI contains junk packages that can interfere with dependency resolution. Always use:
--extra-index-url https://test.pypi.org/simple/
Not --index-url.
Small ecosystem lesson.
Publishing was straightforward:
python -m build
python -m twine upload dist/*
Important rule:
You cannot overwrite a version on PyPI.
Every change requires a version bump.
This enforces discipline.
Microservices suffer from invisible duplication.
Health checks are often treated as boilerplate.
But consistency in infrastructure code improves:
Operational clarity
Monitoring integration
Kubernetes reliability
Onboarding speed
Codebase maintainability
PulseCheck turned copy-paste health logic into a reusable, composable abstraction.
Publishing a library is not about writing code.
It's about:
PulseCheck started as internal cleanup.
It became a reusable infrastructure layer.
If you're duplicating health logic across services, consider abstracting it.
Your future self will thank you.
PyPI: https://pypi.org/project/pulsecheck-py/
GitHub: https://github.com/tasosCDR/pulsecheck-py
If you'd like feedback on the architecture or want to contribute, feel free to reach out.
"PulseCheck is intentionally minimal today. But its architecture allows deeper observability and resilience integrations"