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.
  • 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, 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grelmicro-0.40.0.tar.gz (2.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

grelmicro-0.40.0-py3-none-any.whl (491.2 kB view details)

Uploaded Python 3

File details

Details for the file grelmicro-0.40.0.tar.gz.

File metadata

  • Download URL: grelmicro-0.40.0.tar.gz
  • Upload date:
  • Size: 2.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for grelmicro-0.40.0.tar.gz
Algorithm Hash digest
SHA256 ec6370f5a74bc80f3342cf6c72bdf7d3a203162bbb1c1fbf2f54f7fb4975b4bf
MD5 3a07c523ada6803d9764e0f167a31485
BLAKE2b-256 fb5ee379a407405ecd7c7ebccc36b2bc60a0a4203af179a11e1fb129e9bd5e48

See more details on using hashes here.

Provenance

The following attestation bundles were made for grelmicro-0.40.0.tar.gz:

Publisher: release.yml on grelinfo/grelmicro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file grelmicro-0.40.0-py3-none-any.whl.

File metadata

  • Download URL: grelmicro-0.40.0-py3-none-any.whl
  • Upload date:
  • Size: 491.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for grelmicro-0.40.0-py3-none-any.whl
Algorithm Hash digest
SHA256 51e4ec9b447f06c2c1a4edeb24ed0152580fba9018d307334db544c90440ccdb
MD5 0c36a54d57475fb01e8f47c8c98f1263
BLAKE2b-256 923c6af3189d9caf7aaf403a3890666b1831ee36e8fd6084b8d055fc3af46c85

See more details on using hashes here.

Provenance

The following attestation bundles were made for grelmicro-0.40.0-py3-none-any.whl:

Publisher: release.yml on grelinfo/grelmicro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.0.0b4

2 files

1.0.0b3

2 files

1.0.0b1

2 files

1.0.0a2

2 files

1.0.0a1

2 files

This release

0.40.0 This release

2 files

0.39.0

2 files

0.38.1

2 files

0.38.0

2 files

0.37.4

2 files

0.37.3

2 files

0.37.2

2 files

0.37.1

2 files

0.37.0

2 files

0.36.0

2 files

0.35.1

2 files

0.35.0

2 files

0.34.2

2 files

0.34.1

2 files

0.33.0

2 files

0.32.9

2 files

0.32.8

2 files

0.32.7

2 files

0.32.6

2 files

0.32.5

2 files

0.32.4

2 files

0.32.3

2 files

0.32.2

2 files

0.32.1

2 files

0.31.0

2 files

0.30.1

2 files

0.29.5

2 files

0.29.4

2 files

0.29.3

2 files

0.29.2

2 files

0.29.1

2 files

0.28.2

2 files

0.28.1

2 files

0.28.0

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.0

2 files

0.23.0

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.0

2 files

0.16.1

2 files

0.16.0

2 files

0.15.0

2 files

0.14.3

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.1

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page