Skip to content

grelmicro

grelmicro

Async-first toolkit. Microservice patterns inside.

A Python toolkit for distributed systems: microservices, modular monoliths, and self-contained systems.

PyPI - Version PyPI - Python Version License: MIT codecov uv Ruff ty OpenSSF Scorecard OpenSSF Best Practices SLSA Build Level 2

A FastAPI route protected by a grelmicro rate limiter and health check


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 Rust powered is accepted only with a valid signature and 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.