Skip to main content

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


Why grelmicro

grelmicro is an async Python toolkit for microservices and distributed systems: shared locks, caching, rate limits, circuit breakers, retries, and scheduled tasks.

It ships them as small, composable modules with pluggable backends, alongside idempotency keys, the transactional outbox, logging, health checks, metrics, and tracing. Async-first, type-safe, and fully tested.

It is built for any Python application that coordinates work across processes, workers, or replicas. The same primitives serve microservices, a modular monolith, or a self-contained system, and fit naturally into containerized and Kubernetes deployments.

  • Micro: one focused primitive per module. Each covers a microservice pattern (distributed lock, leader election, rate limiter, circuit breaker, health check API, externalised configuration).
  • Fast: small footprint by design. We keep the layers thin so your code stays quick.
  • Async-first: every I/O call is async / await. Drops into FastAPI, FastStream, and any asyncio-based stack.
  • Backend-agnostic: each primitive is a protocol. Swap Redis for PostgreSQL or SQLite without touching application code.
  • Framework-agnostic: every pattern behaves the same on FastAPI, Starlette, Litestar, and FastStream. micro.install(app) handles the difference, and a parity test holds the claim.
  • Railguarded: fully tested, type-checked, and validated. Pre-1.0 the API may change on a minor release. 1.x follows standard semver.

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.

Modules

Module Summary
Cache TTLCache and a @cached decorator with local and distributed stampede protection. Redis, Valkey, PostgreSQL, SQLite, in-memory.
Idempotency Idempotency keys that make a retried operation safe. Store the response once, replay it on repeat, single-flight across replicas.
Coordination Distributed Lock, ReadWriteLock, TaskLock, and LeaderElection. Redis, Valkey, PostgreSQL, SQLite, Kubernetes, in-memory.
Outbox Transactional outbox. publish a message inside your database transaction and a background relay delivers it at least once with retries and dead-lettering. PostgreSQL, in-memory.
Task Scheduler Interval and cron tasks with durable, distributed at-most-once execution. A modern, lightweight alternative to APScheduler and Celery beat.
Resilience Shield, Stack, Circuit Breaker, Rate Limiter, Retry, Timeout, Bulkhead, and Fallback, with pluggable algorithms and backends.
Logging 12-factor logging with JSON, LOGFMT, TEXT, or PRETTY output, structured error rendering, and OpenTelemetry trace context.
Tracing Unified instrumentation. @instrument creates OpenTelemetry spans and enriches log records with structured context.
Metrics OpenTelemetry metrics with a @measure decorator, a Prometheus /metrics router, and built-in instrumentation across components.
Health Health checks with concurrent runners and FastAPI liveness / readiness integration.
Client IP Resolve the real caller behind a reverse proxy, trusting only the X-Forwarded-For entries your own proxies appended.
Configuration ExternalConfig reconfigures live components from a mounted ConfigMap, Secret, or .env / JSON / YAML / TOML file.

Installation

pip install grelmicro

See the Installation guide for uv and poetry commands, plus optional extras for Redis, PostgreSQL, SQLite, Kubernetes, OpenTelemetry, and structlog.

Example

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.

One route, one primitive

The smallest grelmicro program: a FastAPI route protected by a process-local rate limiter. No Grelmicro(...), no Redis, no lifespan.

from fastapi import FastAPI

from grelmicro.providers.memory import MemoryProvider
from grelmicro.resilience import RateLimitExceededError, RateLimiter

app = FastAPI()
api_limiter = RateLimiter.sliding_window(
    "api", limit=100, window=60, backend=MemoryProvider().ratelimiter()
)


@app.get("/ping")
async def ping() -> str:
    try:
        await api_limiter.acquire_or_raise()
    except RateLimitExceededError:
        return "throttled"
    return "ok"

That is the whole thing. Pick a primitive, name it, give it a backend, call it. The memory backend says per-process on purpose. Make it fleet-wide when you need to.

FastAPI with one provider

To make the rate limiter fleet-wide, put one provider in a Grelmicro container and install it into FastAPI. The provider wires a component for every kind it serves, so there is nothing else to list.

from fastapi import FastAPI

from grelmicro import Grelmicro
from grelmicro.providers.redis import RedisProvider
from grelmicro.resilience import RateLimitExceededError, RateLimiter

redis = RedisProvider("redis://localhost:6379/0")
micro = Grelmicro(uses=[redis])

api_limiter = RateLimiter.sliding_window("api", limit=100, window=60)

app = FastAPI()
micro.install(app)


@app.get("/ping")
async def ping() -> str:
    try:
        await api_limiter.acquire_or_raise()
    except RateLimitExceededError:
        return "throttled"
    return "ok"

Adding more primitives is the same shape: they resolve through the same provider. micro.install(app) opens the app on startup, closes it on shutdown, and lets request handlers resolve backends without passing backend=.

FastAPI integration

Create a file main.py with:

import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel

from grelmicro import Grelmicro
from grelmicro.cache import TTLCache, cached
from grelmicro.security import TrustedProxies, resolve_client_address
from grelmicro.health import HealthChecks
from grelmicro.log import configure as configure_logging
from grelmicro.providers.redis import RedisProvider
from grelmicro.resilience import (
    CircuitBreaker,
    RateLimitExceededError,
    RateLimiter,
)
from grelmicro.coordination import LeaderElection, Lock, TaskLock
from grelmicro.task import Tasks

logger = logging.getLogger(__name__)

# === grelmicro app: one container, one lifespan ===
tasks = Tasks()
health = HealthChecks()

# One line says where the shared state lives.
redis = RedisProvider("redis://localhost:6379/0")

leader = LeaderElection("leader-election")
tasks.add_task(leader)

micro = Grelmicro(uses=[redis, tasks, health])

class User(BaseModel):
    id: int
    name: str


# === Patterns declared once at module load, no backend wiring ===
ttl_cache = TTLCache[User](ttl=300)
lock = Lock("shared-resource")
cb = CircuitBreaker("my-service")
api_limiter = RateLimiter.sliding_window("api", limit=100, window=60)


# === FastAPI ===
@asynccontextmanager
async def lifespan(app):
    configure_logging()
    yield


app = FastAPI(lifespan=lifespan)
micro.install(app)


# --- Cache: avoid redundant database queries ---
@cached(ttl_cache)
async def get_user(user_id: int) -> User:
    return User(id=user_id, name="Alice")


@app.get("/users/{user_id}")
async def read_user(user_id: int) -> User:
    return await get_user(user_id)


# --- Circuit Breaker: protect calls to an unreliable service ---
@app.get("/")
async def read_root() -> str:
    async with cb:
        return "Hello World"


# --- Rate Limiter: protect endpoints from overload ---
# Behind a proxy, `request.client.host` is the proxy, so every caller would
# share one bucket. Resolve the real client instead, trusting only your own
# proxies. Drop the `trusted`/`client_key` lines if nothing fronts the app.
trusted = TrustedProxies(["10.0.0.0/8"])


def client_key(request: Request) -> str:
    client = resolve_client_address(request.scope, trusted)
    return client.key if client else "unknown"


@app.get("/api")
async def api_endpoint(request: Request) -> str:
    try:
        await api_limiter.acquire_or_raise(key=client_key(request))
    except RateLimitExceededError as exc:
        raise HTTPException(
            status_code=429,
            detail="Too many requests",
            headers={"Retry-After": str(int(exc.retry_after))},
        )
    return "ok"


# --- Distributed Lock: synchronize access to a shared resource ---
@app.get("/protected")
async def protected() -> str:
    async with lock:
        return "ok"


# --- Interval Task: run locally on every worker ---
@tasks.every(seconds=5)
def heartbeat():
    logger.info("heartbeat")


# --- Distributed Task: run once per interval across all workers ---
@tasks.every(seconds=60, lock=TaskLock(lease_duration=300))
def cleanup():
    logger.info("cleanup")


# --- Leader-gated Task: only the leader executes ---
@tasks.every(seconds=10, leader=leader)
def leader_only_task():
    logger.info("leader task")

The key shape:

  • One container, one lifespan. Grelmicro(uses=[...]) lists every Component and active manager. async with micro: opens them all in order, closes in reverse.
  • One Provider, many Components. Grelmicro(uses=[redis]) registers a default Component for every kind the RedisProvider serves, and they all share its pool. Name a Component only to override one kind: Grelmicro(uses=[redis, Cache(postgres)]) keeps the rest on Redis.
  • Patterns are declared at module load. Lock("cart"), TTLCache(ttl=60), CircuitBreaker("svc") carry no backend reference. They resolve through the active app inside async with, and GrelmicroMiddleware extends that scope to request handlers. The same Lock works in production with Redis and in tests with MemoryLockAdapter, no rewiring.
  • Pay only for what you import. import grelmicro does not pull in redis, psycopg, or any other vendor SDK. First-party Providers live under grelmicro.providers.{vendor} and load only when you import them.

For multiple Redis instances, separate names, or test overrides, see the docs.

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.

Metadata

Release files for grelmicro 0.41.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for grelmicro 0.41.0
File Size Uploaded
grelmicro-0.41.0.tar.gz 2.7 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for grelmicro 0.41.0
File Interpreter ABI Platform
grelmicro-0.41.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.4 MB

Release files / grelmicro-0.41.0.tar.gz

Download URL grelmicro-0.41.0.tar.gz
Size 2.7 MB
Tags Source
SHA-256 checksum
How to use checksums
14c13566132c6676459bccfb6ffea7360e7e3abe5117d3bde5298d123fecff55
BLAKE2b-256 checksum
How to use checksums
943f47c3daf39c123736121217832424ec4470b3966b66182d7f2732d861d413
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release files / grelmicro-0.41.0-py3-none-any.whl

Download URL grelmicro-0.41.0-py3-none-any.whl
Size 708.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4501703176d9c78bd2e4205f9427c6b418f753b9b2f73b55bc0152dfa7003e52
BLAKE2b-256 checksum
How to use checksums
366893182306f5ff2d020d8f97094fef0f37229ec1d17441bb2eaaa527b009a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.41.1

2 release files

This release

0.41.0 This release

2 release files

0.40.0

2 release files

0.39.0

2 release files

0.38.1

2 release files

0.38.0

2 release files

0.37.4

2 release files

0.37.3

2 release files

0.37.2

2 release files

0.37.1

2 release files

0.37.0

2 release files

0.33.0

2 release files

0.32.9

2 release files

0.32.8

2 release files

0.32.7

2 release files

0.32.6

2 release files

0.32.5

2 release files

0.32.4

2 release files

0.32.3

2 release files

0.32.2

2 release files

0.32.1

2 release files

0.31.0

2 release files

0.30.1

2 release files

0.29.5

2 release files

0.29.4

2 release files

0.29.3

2 release files

0.29.2

2 release files

0.29.1

2 release files

0.28.0

2 release files

0.25.0

2 release files

0.24.0

2 release files

0.23.0

2 release files

0.22.0

2 release files

0.18.0

2 release files

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.14.3

2 release files

0.14.2

2 release files

0.14.1

2 release files

0.14.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page