grelmicro
Async-first toolkit. Microservice patterns inside.
A Python toolkit for distributed systems: microservices, modular monoliths, and self-contained systems.
Documentation: https://grelmicro.grel.info/
Source Code: https://github.com/grelinfo/grelmicro
What your service gains
Your FastAPI, Starlette, Litestar or FastStream service keeps its routes, its lifespan and its database code. grelmicro adds what a service needs once it runs on more than one replica:
- a scheduled job that runs once per interval across replicas, not on each of them,
- a lock, a cache and a rate limit that every replica shares,
- a retry, a circuit breaker and a timeout around the calls that fail,
- health probes, logs, traces and metrics that Kubernetes and your dashboards read.
Use one pattern, a few, or all of them. Each one you add works with the ones you have. Pay only for what you import: grelmicro needs three dependencies, each backend is an extra, and a module loads only what it uses.
One line says where the shared state lives: Redis, Valkey, PostgreSQL or SQLite. Change that line and the rest of the code stays the same.
Every pattern behaves the same on each framework, and a parity test holds the claim. The hot paths run in a compiled Rust core: a JWT signature check is about four times faster than in a pure-Python library.
Coming from another stack? See the mapping for Spring Boot, Quarkus, Django, Laravel or Symfony.
Start with one pattern
Run a job once a minute across all your replicas, not once per replica. Create a file main.py with:
from fastapi import FastAPI
from grelmicro import Grelmicro
from grelmicro.providers.redis import RedisProvider
from grelmicro.task import Tasks
app = FastAPI()
tasks = Tasks()
micro = Grelmicro(uses=[RedisProvider("redis://localhost:6379/0"), tasks])
micro.install(app)
@tasks.every(interval=60, gate="claim")
async def send_report() -> None:
print("report sent by this copy")
Start Redis, then run each copy of the app in its own terminal:
docker run -d -p 6379:6379 redis
pip install "grelmicro[fastapi,redis]" "fastapi[standard]"
# Terminal 1
fastapi run main.py
# Terminal 2
fastapi run main.py --port 8001
Each minute the report is sent once, by whichever copy claims it. micro.install(app) opens grelmicro when the app starts and closes it when the app stops. Already pass a lifespan to FastAPI(...)? It keeps running.
Add the next one
The same provider line serves every pattern you add. Here a lock keeps two inventory syncs from running at once across replicas, and a circuit breaker stops calling a warehouse service that keeps failing. The job from above stays:
from fastapi import FastAPI
from grelmicro import Grelmicro
from grelmicro.coordination import Lock
from grelmicro.providers.redis import RedisProvider
from grelmicro.resilience import CircuitBreaker
from grelmicro.task import Tasks
app = FastAPI()
tasks = Tasks()
micro = Grelmicro(uses=[RedisProvider("redis://localhost:6379/0"), tasks])
micro.install(app)
sync_lock = Lock("inventory-sync")
warehouse = CircuitBreaker("warehouse")
@app.post("/inventory/sync")
async def sync_inventory() -> dict[str, str]:
async with sync_lock, warehouse:
return {"status": "synced"} # your warehouse call
@tasks.every(interval=60, gate="claim")
async def send_report() -> None:
print("report sent by this copy")
Both copies now share the lock and the breaker's state as well as the job.
What each module guarantees
| Module | Guarantee |
|---|---|
| Cache | @cached(ttl=30) memoizes in one process. @cached(TTLCache(...)) shares entries across replicas, and concurrent misses on one key run the function once per process. With a Coordination backend, lock="cluster" runs it once across replicas. |
| Idempotency | A repeated key within ttl replays the stored response without running the operation again. |
| Coordination | A Lock has one holder at a time for as long as its lease, and a LeaderElection one leader. A lost extension metric tells you when the work outran the lease. |
| Outbox | A message published inside your transaction runs its handler at least once, and never for a transaction that rolled back. |
| Task Scheduler | With gate="claim", one worker runs each interval or cron fire. A claimed cron fire runs at most once, and a fire missed while every worker was down replays once. |
| Resilience | An open circuit breaker fails calls fast until reset_timeout, then lets a few probe calls through. A Rate Limiter on a shared backend counts one budget for every replica. Retry, Timeout, Bulkhead and Fallback compose through a Shield. |
| Health | Each check runs at most once per cache_ttl, however many probes arrive. |
| Security | A JWT exp, and with aud and iss when you name them. Signing keys refresh from the JWKS when they rotate. Client IP trusts only your own proxies. |
| Logging, Tracing, Metrics | A log line written inside a traced request carries that request's trace and span id. |
| Configuration | ExternalConfig changes a running component's settings from a mounted ConfigMap, Secret or file, without a restart. |
grelmicro is not a task queue (reach for Celery, Dramatiq, or taskiq), not a message broker client (reach for FastStream to publish and subscribe over Kafka, RabbitMQ, NATS, or Redis), and not a web framework (it plugs into FastAPI, Starlette, Litestar, and FastStream). It fills the gap between the web framework you picked and the infrastructure you run.
Already using aiocache, slowapi, pybreaker, tenacity, or aioredlock? See the comparison page for a per-domain breakdown.
Pre-1.0, the API may change on a minor release. 1.x follows standard semver.
Installation
pip install grelmicro
See the Installation guide for uv and poetry commands, plus optional extras for Redis, PostgreSQL, SQLite, Kubernetes, OpenTelemetry, structlog, and JWT verification.
Run the demo
Want to see every pattern running against real Redis and Postgres? The FastAPI demo starts in three commands:
cd examples/fastapi-demo
docker compose up --wait
open http://localhost:8000/docs
It wires a cached endpoint, a rate-limited endpoint, a circuit-breaker-protected endpoint, a distributed lock, a leader-gated task, and /healthz / /readyz probes. Read app.py to see each one.
The User Guide covers multiple Redis instances, separate names and test overrides.
Contributing
Report bugs and request features in GitHub issues. The reporting guide lists what to include and what happens after you file.
To contribute code or docs, read the contributing guide. It explains the pull request process and the requirements for acceptable contributions: the development setup, the code style, and the pre-merge checklist.
Report security issues privately through the security policy, not a public issue.
License
This project is licensed under the terms of the MIT license.