Skip to content

Changelog

0.23.0 - 2026-05-17

Breaking

  • 💥 Rename the discriminator field from type to kind on every tagged union. Affects RateLimiterConfig (TokenBucketConfig, SlidingWindowConfig) and RetryBackoffConfig (ExponentialBackoff, ConstantBackoff, LinearBackoff, FibonacciBackoff, RandomBackoff). Serialized YAML and JSON configs must replace type: with kind: (for example GREL_RETRY_FOO_BACKOFF={"kind":"exponential",...}). Frees the Python type builtin from being shadowed on every config object. Issue #268.
  • 💥 Rename GCRAConfig to SlidingWindowConfig and RateLimiter.gcra(...) to RateLimiter.sliding_window(...). The discriminator value also moves from "gcra" to "sliding_window". Module grelmicro.resilience.algorithms.gcra becomes grelmicro.resilience.algorithms.sliding_window. Internal strategy classes (_RedisGCRA, _MemoryGCRA) keep their names since they describe the underlying algorithm. Issue #259.

Features

  • ✨ Add Log and Trace components. Register Log() and Trace() in Grelmicro(uses=[...]) to wire observability through the same verb as Sync, Cache, RateLimit, Breaker, and Tasks. Log() wraps grelmicro.log.configure(...) and snapshots stdlib root handlers on enter so sequential apps in tests do not stack handlers. Trace() owns an OTel TracerProvider: builds it from TracingConfig (env prefix GREL_TRACE_), installs it on enter, shuts it down and restores the prior global provider on exit. OTLP HTTP and gRPC exporters are lazy-imported. Issue #224.

Docs

  • 📝 Add Testing page documenting micro.override(...) and the conftest recipe. Issue #236.
  • 📝 Add Capability matrix page covering Pattern × Adapter pairs for 1.0.0. Issue #161.

0.22.0 - 2026-05-16

Features

  • ✨ Add Grelmicro app object and Component protocol. The user composes everything attached to the app into one container and opens it with async with micro:. Single Grelmicro.use(item) registration verb (and uses= constructor kwarg) accepts Component instances (registered with (kind, name) lookup, exposed on micro.<kind>), first-party backends (auto-wrapped into their canonical Component: RedisCacheAdapterCache, RedisSyncAdapterSync), and any other async context manager (lifecycled only, caller keeps the reference). Typed accessors micro.sync and micro.cache provide IDE completion. Grelmicro.components returns the registered Components in order for /healthz-style introspection. Issue #208, epic #201, unified in #219, Component rename and .components accessor in #233.
  • ✨ Add Sync component. Wraps a SyncBackend and exposes lock(...), task_lock(...), leader_election(...) factories. Use it via Grelmicro(uses=[redis, Sync(redis)]) (Provider-direct) or Sync(MemorySyncAdapter()) (Backend-direct). Reach it on micro.sync. Issue #210.
  • ✨ Add Cache component. Wraps a CacheBackend and exposes a ttl(...) factory that builds a TTLCache bound to the wrapped backend. Use it via Grelmicro(uses=[redis, Cache(redis)]) (Provider-direct) or Cache(MemoryCacheAdapter()) (Backend-direct). Reach it on micro.cache. Issue #212.
  • ✨ Add Component-direct Provider API. Sync, Cache, RateLimit, and Breaker accept a Provider or a Backend instance. When given a Provider, the Component calls provider.sync(), provider.cache(), provider.ratelimiter(), or provider.breaker() to build the canonical adapter. Add Provider base class in grelmicro.providers._base with the four factory methods. Add RedisProvider.ratelimiter() returning a RedisRateLimiterAdapter. The Adapter classes stay public as escape hatches for custom Providers, but the canonical user code uses Sync(redis) instead of Sync(RedisSyncAdapter(provider=redis)).
  • ✨ Add Grelmicro.current() classmethod for ambient lookup. Inside async with micro: it returns the active app for the current asyncio task.
  • ✨ Add Retry primitive with decorator, block, and class forms. Five backoff algorithms ship: ExponentialBackoff (default, with full jitter), ConstantBackoff, LinearBackoff, FibonacciBackoff, and RandomBackoff. when= is required and accepts a Match (or shorthand). Live reconfiguration via Reconfigurable[RetryConfig]. Three-paths configuration. Underlying exception is re-raised with a PEP 678 note on exhaustion. Issue #165.
  • ✨ Add Match and Outcome to grelmicro.resilience. Match is the resilience-wide outcome filter DSL: Match.exception(...), Match.result(...), Match.exception_message(...), Match.exception_cause(...), Match.predicate(...), Match.always(), Match.never() plus their not_* twins, composed with the | and & operators. Outcome[T] is the dataclass passed to custom predicates (exception, result, raised). Issue #242.
  • ✨ Add grelmicro.providers.redis.RedisProvider. First-class Redis connection holder shared across components: RedisProvider("redis://..."), RedisProvider(host="...", port=...), RedisProvider() (env-driven via REDIS_*), RedisProvider.from_config(RedisConfig(...)), and RedisProvider.from_client(client, own=False) for bring-your-own clients. Grelmicro dedupes implicit providers by (provider_class, env_prefix), so two adapters with the same prefix share one connection pool. Issue #226.
  • ✨ Add grelmicro.providers.postgres.PostgresProvider. First-class Postgres connection holder wrapping an asyncpg.Pool: PostgresProvider("postgresql://..."), PostgresProvider(host=..., database=..., user=..., password=...), PostgresProvider() (env-driven via POSTGRES_*), PostgresProvider.from_config(PostgresConfig(...)), and PostgresProvider.from_client(pool, own=False) for bring-your-own pools. Shares the same (provider_class, env_prefix) dedupe as RedisProvider. Issue #255.

Breaking

  • 💥 Patterns RateLimiter, CircuitBreaker, and the FastAPI health router resolve through the active Grelmicro app. Add the two new Components RateLimit (wraps RateLimiterBackend, kind "ratelimiter") and Breaker (wraps CircuitBreakerBackend, kind "circuitbreaker"). Grelmicro.use(...) auto-wraps a RateLimiterBackend or CircuitBreakerBackend instance into its canonical Component. HealthChecks becomes a Component (kind = "health", default name = "default"). Pass it to Grelmicro(uses=[...]) and the FastAPI health_router() resolves it via Grelmicro.current(). Delete the rate_limiter_backend_registry, circuit_breaker_backend_registry, health_checks registries plus grelmicro/_backends.py (BackendRegistry, BackendNotLoadedError, BackendAlreadyRegisteredError). Closes out #201. Issue #261.
  • 💥 Redis adapters now take provider= or env_prefix=, not a positional url=. RedisSyncAdapter, RedisCacheAdapter, and RedisRateLimiterAdapter lose their url= argument. Pass provider=RedisProvider(...) to share a pool, or rely on env_prefix= (default REDIS_) to build one. Issue #226.
  • 💥 PostgresSyncAdapter now takes provider= or env_prefix=, not a positional url=. Pass provider=PostgresProvider(...) to share a pool, or rely on env_prefix= (default POSTGRES_) to build one. Issue #255.
  • 💥 Rename TaskManager to Tasks. Class still extends TaskRouter, mirroring FastAPI's APIRouterFastAPI shape. Update imports to from grelmicro.task import Tasks. Issue #218.
  • 💥 Rename HealthRegistry to HealthChecks (and HealthRegistryConfig to HealthChecksConfig). Update imports to from grelmicro.health import HealthChecks. Issue #201.
  • 💥 Rename concrete backends to *Adapter. MemorySyncBackend, RedisSyncBackend, PostgresSyncBackend, SQLiteSyncBackend, KubernetesSyncBackend, MemoryCacheBackend, RedisCacheBackend, MemoryRateLimiterBackend, RedisRateLimiterBackend, and MemoryCircuitBreakerBackend become *Adapter. The SyncBackend, CacheBackend, RateLimiterBackend, and CircuitBreakerBackend Protocols stay as-is. Issue #201.
  • 💥 Rename nested backoff classes to drop the redundant Config suffix. ExponentialBackoffConfig, ConstantBackoffConfig, LinearBackoffConfig, FibonacciBackoffConfig, and RandomBackoffConfig become ExponentialBackoff, ConstantBackoff, LinearBackoff, FibonacciBackoff, and RandomBackoff. The RetryBackoffConfig discriminated-union alias and the JSON discriminator (type: "exponential") are unchanged. Issue #239.
  • 💥 Rename the Module protocol to Component to avoid clashing with Python's own "module". ModuleAlreadyRegisteredError becomes ComponentAlreadyRegisteredError and ModuleNotRegisteredError becomes ComponentNotRegisteredError. The Sync and Cache classes keep their names. No deprecation shim. Issue #233.
  • 💥 Rename the env-loading flag to align the per-call kwarg and the global env var. read_env= becomes env_load= on every component, GREL_CONFIG_FROM_ENV becomes GREL_ENV_LOAD, and the grelmicro._config.env_opt_in_enabled() helper becomes env_load_default(). No deprecation shim. Issue #232.
  • 💥 Remove the module-level registry and lifespan API. grelmicro.lifespan() is gone. The register / unregister / use / use_backend / use_registry helpers across grelmicro.{sync,cache,health,resilience} (plus the resilience circuit-breaker variants) are gone. Patterns (Lock, TaskLock, LeaderElection, TTLCache) now resolve their backend via Grelmicro.current() at every call. Build a Grelmicro(uses=[...]) and open it with async with micro:. The grelmicro.sync._backends and grelmicro.cache._backends modules are removed (sync and cache resolve through the app). The internal rate_limiter_backend_registry, circuit_breaker_backend_registry, and health_checks registries stay private until follow-up issues introduce their Component wrappers. Issue #207.
  • 💥 Retry outcome filter is now when= accepting a Match. The old on= parameter is gone. The Match DSL (Match.exception(...), Match.result(...), Match.exception_message(...), Match.exception_cause(...), Match.always(), Match.never(), Match.predicate(...)) plus their not_* twins and the |/& operators cover the common retry-filter surface. Bare-class shorthand is still accepted (when=httpx.HTTPError). Result-based retry lands in the same change: when=Match.result(None) retries until the function stops returning None. Env var renamed GREL_RETRY_{NAME}_ONGREL_RETRY_{NAME}_WHEN. Issue #242.

Internal

  • ⚡ Defer the opentelemetry import in grelmicro.trace. import grelmicro.trace no longer loads opentelemetry (was 16 modules). The package is resolved lazily on first call to instrument, span, or add_context and cached. Issue #189.

0.21.0 - 2026-05-06

Breaking

  • 💥 Drop Python 3.11. The new floor is requires-python = ">=3.12". RHEL 9 (App Stream python3.12) and RHEL 10 (default) ship 3.12 and the UBI images are available, so enterprise users are covered. Issue #66.
  • 💥 Drop AnyIO. grelmicro now targets asyncio directly. Issue #183.
  • 💥 CircuitBreaker now takes a backend (CircuitBreakerBackend). The in-memory backend (MemoryCircuitBreakerBackend) is the default. A future Redis-backed implementation will share state across replicas (issue #188). The async API stays primary, sync code goes through cb.from_thread.
  • 💥 The sync adapters on Lock, TaskLock, TTLCache, and CircuitBreaker now require the backend to be opened (async with backend: or grelmicro.lifespan()). The backend captures the running loop and the sync adapter dispatches through it. Zero hot-path overhead.
  • 💥 Resilience registries are now namespaced. The rate limiter registry name moves from "resilience" to "resilience.ratelimiter" and the circuit breaker registry is "resilience.circuitbreaker". grelmicro.lifespan(exclude=...) now matches by dotted prefix, so exclude={"resilience"} still skips both registries.

Features

  • ✨ Add uvloop to the standard extra (Linux and macOS). Activate with uvloop.run(main()).

Internal

  • ✅ Migrate the test suite from pytest.mark.anyio to pytest-asyncio with asyncio_mode = "auto". AnyIO is no longer a direct dependency of grelmicro (it may still arrive transitively, for example through fast-depends).
  • ♻️ Adopt PEP 695 generic syntax (class Foo[T]:, def f[T](...), type X = ...) across _backends.py, _config.py, _types.py, health/_types.py, trace/_instrument.py, and tests/task/conftest.py. Two files keep the older form: the recursive aliases in _json.py (ty cannot expand recursive PEP 695 aliases) and the decorator factory in cache/cached.py (PEP 695 binds the inner decorator to the outer scope's type parameters, breaking per-decoration-site inference). Issue #65.
  • 🔨 Bump tool.ruff.target-version to py312 and the CI matrix to ["3.12","3.13","3.14"].

0.20.0 - 2026-05-03

Live reconfiguration is complete. Every stateful primitive now exposes reconfigure(new_config), so you can hot-reload from a ConfigMap or SIGHUP without restarting the process. See Live reconfiguration for the contract.

Features

  • ✨ Add RateLimiter.reconfigure(new_config). Swap algorithm config without rebuilding the limiter. PR #153.
  • ✨ Add reconfigure(new_config) to Lock, TaskLock, and LeaderElection. Swap timing fields without restarting. The worker field cannot change. PR #159.
  • ✨ Add CircuitBreaker.reconfigure(new_config). Swap thresholds and ignore_exceptions without restarting. Runtime state and last_error are kept. log_level is applied to the logger. PR #160.
  • ✨ Add HealthRegistry.reconfigure(new_config). Swap cache_ttl and the default timeout without restarting. Per-check timeouts stay as registered. PR #180.

Docs

  • 📝 Reframe README and docs landing as a microservice patterns toolkit. PR #155.
  • 📝 Replace "Production-ready" with "Railguarded": 100% pytest coverage, ty-checked, ruff-linted, Pydantic-validated. PR #181.

Internal

  • 🔨 Switch build backend to Hatch. PR #155.
  • 🎨 Supersample favicon PNGs with Lanczos downscaling for smoother anti-aliasing. PR #156.

0.19.0 - 2026-05-01

Cleans out the long-deprecated APIs (ResilienceException, Synchronization, scheduled(), the token= kwarg) ahead of the 1.0.0 design work, ships a 3.4× speedup on env-driven config construction, and brings the test suite under 20s for contributors.

Breaking

  • 💥 The Environmental config path is now opt-in. Set GREL_CONFIG_FROM_ENV=true once at startup to enable env reads across every component, or pass read_env=True per call. The per-call value (True/False) always wins over the global flag. This stops grelmicro from silently picking up ambient env vars in unit tests or scripts. Issue #142.
  • 💥 The read_env kwarg default flips from True to None on every component. None follows the global flag. True and False keep their meaning as explicit per-call overrides.
  • 💥 Remove obsolete deprecation shims that were marked for removal in 0.7.0. Replace ResilienceException with ResilienceError, Synchronization with SyncPrimitive, and the scheduled() decorator on TaskRouter / TaskManager with interval(seconds=N, max_lock_seconds=N*5). The token= kwarg on LockAcquireError, LockReleaseError, and LockNotOwnedError is removed (drop it from your code). The sync= parameter on interval() no longer warns when used with non-Lock primitives.

Internal

  • ♻️ Add grelmicro._config.env_opt_in_enabled() helper that exposes the truthy GREL_CONFIG_FROM_ENV check (1, true, yes, on, case-insensitive). Issue #142.
  • 📝 Document the "no field-mirroring" decision in docs/architecture/config.md with the benchmark numbers from benchmarks/config_attr_benchmark.py. Closes Issue #113 without code changes: hot-path config reads cost <1% of a real call (~2 ns out of ~250 ns), so we keep self._config as the single source of truth instead of copying frozen fields onto the component.
  • 🔇 Silence the upstream testcontainers @wait_container_is_ready deprecation banner via a scoped filterwarnings entry in pyproject.toml. Replace the unawaited lambda: sleep(math.inf) mock side-effect in tests/sync/test_leaderelection.py::test_leadership_abandon_on_renew_deadline_reached with an explicit async helper. The full suite now reports zero warnings.
  • ⚡ Speed up the test suite from ~73s to ~19s by adding pytest-xdist (-n auto in addopts) and shrinking expiration sleeps in tests/sync/test_backends.py. Fix --durations reporting by removing the autouse freeze_time fixture: @freeze_time() decorator stays on the two tests that compare datetime.now(), the two tests that previously called frozen_time.tick(...) switch to monkeypatch.setattr(circuitbreaker, "monotonic", ...). Issue #125.
  • ⚡ Cache the dynamic BaseSettings subclass built by grelmicro._config._build_settings_cls with @functools.lru_cache(maxsize=256). The env path of resolve_config now reuses the same _<Config>Settings subclass across calls instead of rebuilding it every time, which makes Lock("cart")-style construction ~3.4× faster (232 µs/op → 68 µs/op). The bound is a safety net for long-running processes that might derive prefixes from runtime inputs. Issue #119.
  • ♻️ Rename the local parent_config to merged_config in _build_settings_cls and document why the existing # type: ignore comments are needed. Issue #127.

0.18.0 - 2026-04-30

M2 milestone closed: backend wiring is now fully explicit. Construction is pure (no global writes), registration is named (<module>.register(backend, "name")), and grelmicro.lifespan(*ad_hoc, exclude=...) walks every registry that has been imported and opens its registered backends in one call. Task-scoped overrides via with <module>.use(...): swap backends per request or per test through contextvars.

Breaking

  • 💥 Backend constructors are now pure: __init__ performs no registry writes. The auto_register kwarg is removed from every backend and from HealthRegistry. PR #138.
  • 💥 BackendRegistry.set is renamed register and BackendRegistry.unregister is added with an identity check. reset remains for test fixtures. PR #138.
  • 💥 async with backend opens the connection but no longer registers. Call register(backend) (or use_backend(backend)) to register, or open everything at once with grelmicro.lifespan(). PR #139.
  • 💥 BackendRegistry is now multi-name: register(backend, name="default"), unregister(name, backend=None), get(name="default"). PR #139.
  • 💥 The sync registry name changed from "lock" to "sync" (used in error messages and lifespan() exclude keys); the rate limiter registry from "rate_limiter" to "resilience". PR #139.
  • 💥 Overwriting a registered name with a different instance now raises BackendAlreadyRegisteredError (was: warning + replace). Re-registering the same instance stays a no-op. PR #139.

Features

  • ✨ Add grelmicro.sync.use_backend, grelmicro.cache.use_backend, grelmicro.resilience.use_backend, and grelmicro.health.use_registry for explicit, idempotent process-lifetime registration. PR #138.
  • grelmicro.lifespan(*ad_hoc, exclude=...) opens every registered backend across every imported module in one call, with reverse-order shutdown. PR #139.
  • ✨ Per-module helpers register, unregister, use_backend, use on grelmicro.sync, grelmicro.cache, grelmicro.resilience (and use_registry, use on grelmicro.health). PR #139.
  • ✨ Task-scoped overrides via <module>.use(backend) or <module>.use(default=a, analytics=b). Stacks LIFO via contextvars for per-test and per-request substitution. PR #139.
  • ✨ Primitives accept backend= as either a backend instance or a registered name (Lock("audit", backend="analytics")). The registry is consulted on each call so <module>.use(...) overrides apply. PR #139.
  • ✨ Registries subscribe themselves on import: lifespan() walks only modules that are actually imported, so unused components have zero RAM cost and zero startup work. PR #139.
  • ✨ Lookup falls back to the sole registered entry when no "default" is named, so the single-backend case stays one-call. PR #139.

0.17.0 - 2026-04-29

Breaking

  • 💥 CircuitBreaker config moves to a frozen CircuitBreakerConfig. Read it via cb.config. PR #132.
  • 💥 The mutable attributes cb.error_threshold, cb.success_threshold, cb.reset_timeout, cb.half_open_capacity, cb.ignore_exceptions, cb.log_level are removed. Construct a new CircuitBreaker to change config. PR #132.
  • 💥 Rename grelmicro.logging to grelmicro.log and grelmicro.tracing to grelmicro.trace. Avoids shadowing stdlib logging and aligns with the OpenTelemetry / ddtrace trace (singular) convention. Update imports: from grelmicro import log, trace. PR #135.
  • 💥 configure_logging() is renamed log.configure(). Use log.configure_with(config) for the declarative path. Both return the applied LoggingConfig. PR #135.
  • 💥 LoggingSettings (the BaseSettings shadow class) is removed. LoggingConfig is the canonical config class. Env reading happens inside log.configure(). PR #135.
  • 💥 LoggingConfig field names move to lowercase: LOG_BACKENDbackend, LOG_LEVELlevel, LOG_FORMATformat, LOG_TIMEZONEtimezone, LOG_JSON_SERIALIZERjson_serializer, LOG_CALLER_ENABLEDcaller_enabled, LOG_OTEL_ENABLEDotel_enabled. PR #135.
  • 💥 Env vars move from LOG_* to GREL_LOG_* to align with the rest of the library. PR #135.
  • 💥 LoggingSettingsValidationError is removed. pydantic.ValidationError propagates from log.configure() like every other component. PR #135.

Features

  • ✨ Add CircuitBreakerConfig and CircuitBreaker.from_config(name, config). PR #132.
  • CircuitBreaker reads GREL_CIRCUIT_BREAKER_<NAME>_* env vars and accepts env_prefix= / read_env=. PR #132.
  • ignore_exceptions accepts fully-qualified import strings ("builtins.ValueError") so YAML and env loaders can specify exception types. PR #132.
  • ✨ Env vars for tuple/list fields accept comma-separated values in addition to JSON arrays. PR #132.
  • log.configure(**kwargs) accepts every LoggingConfig field as a kwarg, mirroring the three-paths contract used by other components. PR #135.
  • log.configure_with(config) is the declarative entry point. Returns the applied LoggingConfig. PR #135.

Internal

  • ♻️ Add grelmicro/_types.py for shared lightweight type aliases (LogLevel). PR #132.
  • ♻️ Add grelmicro/_config.py::parse_csv_or_json shared utility for env var list parsing. PR #132.

Docs

  • 🎨 Switch logo typeface from Funnel Sans to Funnel Display.

0.16.1 - 2026-04-29

Internal

  • ✅ "No registry call at construction" tests now patch the registry source instead of the per-module import alias, so a future refactor that bypasses the local alias can no longer silently pass the check. PR #130.
  • ⬆️ Bump ty from 0.0.29 to 0.0.30. PR #111.
  • ⬆️ Pre-commit autoupdate. PR #114.

0.16.0 - 2026-04-29

Breaking

  • 💥 LockConfig, TaskLockConfig, LeaderElectionConfig, and RateLimiterConfig no longer carry a name field. Pass the name positionally: Lock("cart", LockConfig(lease_duration=30)). PR #123.
  • 💥 Rename TokenBucket to TokenBucketConfig and GCRA to GCRAConfig. RateLimiterConfig becomes the discriminated union of algorithm configs. PR #123.
  • 💥 RateLimiter takes the algorithm config positionally: RateLimiter("api", GCRAConfig(limit=100, window=60)). The algorithm=, limit=, window= kwargs are removed. PR #123.
  • 💥 fail_open moves from RateLimiter(...) to the algorithm config. PR #123.

Features

  • ✨ Add Component.from_config(name, config) to every primitive (Lock, TaskLock, LeaderElection, RateLimiter, HealthRegistry, RateLimitFilter, DuplicateFilter). PR #123.
  • ✨ Read environment variables under GREL_<COMPONENT>_<NAME>_* for every component that supports the environmental path. PR #123.
  • ✨ Add RateLimiter.token_bucket(name, ...) and RateLimiter.gcra(name, ...) factory classmethods. PR #123.
  • ✨ Add env_prefix= and read_env= kwargs to every component that exposes the environmental path. PR #123.
  • ✨ Normalise instance names like payments-eu, cart.v2, or weather/svc into POSIX env var segments. PR #123.

Changed

  • ♻️ Lock, TaskLock, LeaderElection, and RateLimiter now resolve the backend lazily on first use instead of at construction. BackendNotLoadedError surfaces on the first acquire/peek/reset call rather than in __init__. Each component exposes a public backend property. PR #128.

Fixed

  • 🐛 Auto-registered backends now identity-check before clearing the registry on __aexit__, so a replacement instance is left alone. PR #122.
  • 🐛 Lock.release clears local ownership only after the backend confirms the release. PR #122.

0.15.0 - 2026-04-29

Breaking

  • 💥 Redesign the health module: @health.check("name") decorator, binary ok/error status, empty probe bodies, per-check caching. PR #112.
  • 💥 Endpoint renames: /health/live/livez, /health/ready/readyz. New /healthz returns the full check JSON. PR #112.
  • 💥 HealthRegistry.check() renamed to run(). The check name is now the decorator. PR #112.
  • 💥 HealthChecker Protocol removed. Use plain def or async def functions. PR #112.
  • 💥 HealthReport.components: list becomes HealthReport.checks: dict[name, ...]. PR #112.
  • 💥 HealthCheckTimeoutError and the three-state HealthStatus removed. PR #112.

Docs

  • 📝 Restate the versioning policy: pre-1.0 MINOR may break, PATCH never. Post-1.0 deprecations get two MINOR releases.

0.14.3 - 2026-04-22

Docs

  • 🐛 Fix the wordmark duplicating on PyPI and other renderers that don't understand GitHub's theme-only URL fragments. PR #109.

0.14.2 - 2026-04-22

Docs

  • 🐛 Fix the landing-page wordmark disappearing when the docs site is toggled into dark mode. PR #108.
  • 📝 Centre the badges row under the tagline. PR #108.

0.14.1 - 2026-04-22

Docs

  • 🎨 Ship the grelmicro brand identity: wordmark, favicon, and social-preview card. PR #106.
  • 🎨 Refresh the docs theme with the brand palette. PR #106.
  • 📝 Rewrite the "Why grelmicro" pillars. PR #106.
  • 📝 Split the resilience docs into per-pattern pages.
  • 📝 Add an Installation guide with pip, uv, and poetry tabs.
  • 📝 Render PEP 727 Annotated[..., Doc(...)] parameter docs via griffe-typingdoc.
  • 📝 Plain-English pass on docs and docstrings for non-native readers.
  • 📝 Add a Mermaid state diagram to the Circuit Breaker page.
  • 📝 Document every __all__ symbol in the API reference.
  • 📝 Add a plain-English style guide to CONTRIBUTING.md.

Internal

  • 🐛 De-flake test_lock_reentrant_from_thread on Python 3.12. Fixes #105.
  • 🔧 Add keywords to pyproject.toml for PyPI discovery.

0.14.0 - 2026-04-21

Features

  • ✨ Add pluggable RateLimiter algorithms via the algorithm= parameter: TokenBucket and GCRA. PR #102.
  • ✨ Add MemoryTokenBucket, a standalone synchronous token-bucket primitive. PR #102.
  • ✨ Add RateLimitFilter, a logging.Filter with configurable key_mode. PR #102.
  • ✨ Add DuplicateFilter, a logging.Filter that caps repeated records per key with optional TTL. PR #94.
  • HealthRegistry now logs every unhealthy path at WARNING (ERROR for unexpected exceptions). PR #92.

Deprecations

  • 🗑️ RateLimiter(name, limit=..., window=...) is deprecated. Use RateLimiter(name, algorithm=GCRA(limit=..., window=...)) instead. Will be removed in 0.15.0. PR #102.

Docs

  • 📝 Add CONTRIBUTING.md with repo conventions. PR #102.
  • 📝 Add a "Choosing an algorithm" guide for TokenBucket vs GCRA in the Rate Limiter docs. PR #102.
  • 📝 Surface THIRD_PARTY_NOTICES.md in the docs site. PR #102.

Security

  • 🔒️ Harden CI supply chain: pin all Actions to SHAs, close run: injection vectors, add zizmor workflow-lint, restrict Dependabot auto-merge to uv patch/minor updates. PRs #95, #100, #101.

Internal

  • ⬆️ Bump pydantic to 2.13.0, opentelemetry-api / opentelemetry-sdk to 1.41.0, pytest to 9.0.3, ruff to 0.15.10, ty to 0.0.29, fastapi to 0.135.3, uvicorn to 0.44.0. PR #99.
  • ⬆️ Bump pydantic-extra-types from 2.11.1 to 2.11.2. PR #89.
  • ⬆️ Pre-commit ruff autoupdate (v0.15.9 → v0.15.11). PR #91.
  • ⬆️ Bump codecov/codecov-action to v6. PR #96.
  • ⬆️ Bump astral-sh/setup-uv to v8. PR #97.
  • ⬆️ Bump dependabot/fetch-metadata to v3. PR #98.

0.13.0 - 2026-04-08

Features

  • ✨ Add RateLimiter.peek(key): check rate limit state without consuming tokens. PR #90.
  • ✨ Add RateLimiter.reset(key): delete rate limit state for a key, restoring full quota. PR #90.
  • ✨ Add fail_open parameter to RateLimiter: return allowed result on backend errors instead of propagating exceptions. PR #90.

0.12.0 - 2026-04-07

Features

  • ✨ Add health module with health check registry, concurrent checker execution, and FastAPI integration for liveness/readiness probes. PR #84.

Internal

  • ⬆️ Bump orjson from 3.11.7 to 3.11.8. PR #72.
  • ⬆️ Bump ty from 0.0.26 to 0.0.27. PR #74.
  • ⬆️ Update uv-build requirement from <0.10.0 to <0.12.0. PR #75.
  • 👷 Add build provenance attestations and wheel verification to release pipeline.
  • ♻️ Pre-release cleanup: add health/json to overview, fix style inconsistencies, remove stale branches.

0.11.0 - 2026-04-03

Breaking Changes

  • 💥 Logging: split caller into separate logger (logger name) and caller (function:line) fields. caller is now opt-in via GREL_LOG_CALLER_ENABLED (default: False), following common structured-logging conventions. Uvicorn formatter never includes caller.
  • 💥 Cache: replace TTLCache serializer/deserializer callable pair with a single serializer accepting a CacheSerializer protocol object. Use JsonSerializer(), PydanticSerializer(Model), or PickleSerializer() instead.

Features

  • ✨ Add GREL_LOG_CALLER_ENABLED setting to opt in to caller info (function:line) in log records. Disabled by default for cleaner logs and better performance.
  • ✨ Add logger field (logger name, e.g., myapp.api) to all log records across all backends and formats.
  • ✨ Add grelmicro.json module with fast JSON serialization using orjson when available, with automatic fallback to stdlib json.

0.10.0 - 2026-04-02

Features

  • ✨ Add RateLimiter to the resilience module: Redis-backed sliding-window rate limiting using the GCRA algorithm. Includes RateLimitResult with fields mapping to IETF rate limit headers, weighted requests via cost parameter, and RateLimitExceededError.

Removals

  • 🗑️ Remove deprecated UvicornJSONFormatter and UvicornAccessJSONFormatter. Use UvicornFormatter and UvicornAccessFormatter instead (deprecated since 0.9.1).

CI

  • ⚡ Migrate PyPI publishing from API token to OIDC trusted publishing.

0.9.1 - 2026-04-01

Deprecations

  • 🗑️ UvicornJSONFormatter and UvicornAccessJSONFormatter are deprecated. Use UvicornFormatter and UvicornAccessFormatter instead. The new formatters respect GREL_LOG_FORMAT instead of always producing JSON. Old names kept as aliases with DeprecationWarning.

0.9.0 - 2026-04-01

Breaking Changes

  • 💥 GREL_LOG_FORMAT default changed from JSON to AUTO. In production (non-TTY), behavior is identical (JSON output). In local dev (TTY), output switches to human-readable TEXT with colors. Set GREL_LOG_FORMAT=JSON explicitly to restore the previous default.

Features

  • ✨ Add AUTO log format (new default): detects TTY and selects TEXT (terminal) or JSON (piped/CI).
  • ✨ Add LOGFMT log format: key-value pairs following the logfmt convention, 30-40% smaller than JSON.
  • ✨ Add PRETTY log format: multi-line indented output with structured error rendering.
  • ✨ Enhanced TEXT format: now includes extra context fields as key=value pairs and supports ANSI colors.
  • ✨ Add NO_COLOR / FORCE_COLOR environment variable support following no-color.org standard.

0.8.0 - 2026-04-01

Breaking Changes

  • 💥 Backend imports moved to submodules. Use from grelmicro.sync.redis import RedisSyncBackend instead of from grelmicro.sync import RedisSyncBackend. Same for all sync, cache, and logging backends. See Import Strategy.

Features

  • ✨ Add Uvicorn JSON formatters (UvicornJSONFormatter, UvicornAccessJSONFormatter) for structured logging via dictConfig.

0.7.0 - 2026-03-31

Breaking Changes

  • 💥 Logging JSON format redesigned to follow industry standards:
    • logger renamed to caller
    • thread removed
    • ctx removed: extra fields are now flat at the top level
    • exception replaced by structured error object (type, message, stack)

Features

  • ✨ Add tracing module with @instrument decorator, span() context manager, and add_context() for unified logging and OTel instrumentation.

Performance

  • Logging: Up to +23% throughput across all backends.
  • ⚡ Use OrderedDict for O(1) LRU operations in TTLCache.

Refactors

  • ♻️ Extract shared Redis config into grelmicro/_redis.py.
  • ♻️ Make TTLCache generic and add Doc annotations.
  • ♻️ Extract context stack into grelmicro/_context.py to decouple logging from tracing.
  • ♻️ Filter private (_-prefixed) attributes from stdlib JSON log output.
  • ♻️ Widen @instrument(skip=...) type from set[str] to AbstractSet[str].

Removals

  • 🗑️ Synchronization protocol removed. Use SyncPrimitive instead (deprecated since 0.6.0).
  • 🗑️ ResilienceException removed. Use ResilienceError instead (deprecated since 0.6.0).
  • 🗑️ The token parameter on lock errors removed (deprecated since 0.6.0).
  • 🗑️ The sync parameter on interval() removed (deprecated since 0.6.0).
  • 🗑️ The scheduled() decorator removed (deprecated since 0.6.0).

0.6.0 - 2026-03-30

Deprecations

  • 🗑️ Synchronization protocol renamed to SyncPrimitive. The old name still works but emits a DeprecationWarning. Will be removed in 0.7.0.
  • 🗑️ ResilienceException renamed to ResilienceError. The old name still works but emits a DeprecationWarning. Will be removed in 0.7.0.
  • 🗑️ The token parameter on LockAcquireError, LockReleaseError, and LockNotOwnedError is deprecated. Tokens are no longer included in error messages for security. Will be removed in 0.7.0.
  • 🗑️ The sync parameter on interval() for TaskLock and LeaderElection is deprecated. Use max_lock_seconds and leader parameters instead. Will be removed in 0.7.0.
  • 🗑️ The scheduled() decorator is deprecated. Use interval() with max_lock_seconds or leader instead. Will be removed in 0.7.0.

Features

  • ✨ Add in-memory TTL cache with LRU eviction, per-key stampede protection, and @cached decorator.
  • ✨ Add RedisCacheBackend for distributed cache storage.
  • ✨ Add cache statistics via CacheInfo (hits, misses, evictions, stampedes).
  • ✨ Add Kubernetes sync backend using Lease resources (pip install grelmicro[kubernetes]).
  • ✨ Add SQLite sync backend for home lab and local testing (pip install grelmicro[sqlite]).

Security

  • 🔒️ Remove token values from lock error messages to prevent leaking in logs.
  • 🔒️ Upgrade requests to 2.33.0 (CVE fix in transitive dependency).

Refactors

  • ♻️ Unify error hierarchy under GrelmicroError base class. All module errors (SyncError, ResilienceError, LoggingError, TaskError, CacheError) now share a common base.
  • ♻️ Use server-side timestamps and native Lease fields in sync backends.
  • ♻️ Simplify token generation from UUID-based to string concatenation.
  • ♻️ Harden TaskLock token nonce and error handling.

Internal

  • ✅ Achieve 100% library code coverage.
  • 💚 Fix flaky integration test timeout in CI.
  • ⬆️ Bump dependencies and fix ty v0.0.26 type errors.

Docs

0.5.0 - 2026-03-17

Breaking Changes

  • 💥 Add namespace prefix to sync primitive backend keys (lock:, tasklock:, leader:). See Migration Guide below.

Features

  • ✨ Add TaskLock.from_thread thread-safe adapter. PR #57.
  • ✨ Add specific lock error classes (LockAcquireError, LockReleaseError, LockLockedCheckError, LockOwnedCheckError, LockReentrantError). PR #57.

Refactors

  • ♻️ Consolidate distributed lock and leader gating into the interval() decorator via max_lock_seconds and leader parameters. PR #54.

Docs

Internal

  • ⬆️ Bump redis, fastapi, pydantic, and pydantic-settings. PR #55.
  • ⬆️ Update pre-commit hooks. PR #50.

Migration Guide

Namespace-Prefixed Backend Keys

Prior versions used the name parameter directly as the backend key. Now each primitive adds a type-specific prefix:

Primitive Name Backend Key
Lock("my-resource") my-resource lock:my-resource
TaskLock("cleanup") cleanup tasklock:cleanup
LeaderElection("main") main leader:main

Existing locks stored in Redis or PostgreSQL will no longer match after upgrading. A running instance on the old version and one on the new version will not see each other's locks.

Upgrade all running instances together so they use the same key format. Old keys expire automatically via their lease duration (Redis PEXPIRE / PostgreSQL expire_at).

0.4.1 - 2026-03-13

Docs

  • 📝 Add Task Lock to synchronization primitives guide.

Internal

  • ⬆️ Bump actions/checkout to v6 and astral-sh/setup-uv to v7.

0.4.0 - 2026-03-13

Features

  • ✨ Add TaskLock for distributed task locking with auto-renewal.
  • ✨ Add GREL_LOG_TIMEZONE support for configurable timezone in logging output.
  • ✨ Add OpenTelemetry trace context injection into log records.
  • ✨ Add structlog as alternative logging backend.
  • ✨ Add configurable JSON serializer (json / orjson) for logging.

Docs

  • 📝 Add logging benchmark and performance documentation.

Internal

  • ⬆️ Bump orjson from 3.11.5 to 3.11.6. PR #51.
  • ⬆️ Bump freezegun from 1.5.2 to 1.5.5. PR #33.

0.3.2 - 2026-01-27

Internal

  • 👷 Migrate from mypy to Astral ty for type checking. PR #45.
  • 🔧 Add Python 3.14 support. PR #47.
  • 🔧 Switch build system to uv_build. PR #49.
  • 💚 Simplify CI and release workflow. PR #24.

0.3.1 - 2025-06-05

Docs

  • 📝 Add resilience patterns section and update links in README and index.

Internal

  • 💚 Fix release pipeline and GitHub Pages deployment permissions.

0.3.0 - 2025-06-05

Features

  • ✨ Add Circuit Breaker resilience pattern. PR #18.

Docs

  • 📝 Refactor code examples to use snippets.

Internal

  • 👷 Add Dependabot configuration for weekly updates.
  • 🔒️ Fix workflow permission issues. PR #21, #22.

0.2.3 - 2024-12-04

Features

  • ✨ Add Redis key prefix support to avoid conflicts in shared instances.
  • ✨ Add Redis and PostgreSQL settings management from environment variables.

0.2.2 - 2024-11-28

Features

  • ✨ Add PostgreSQL backend configuration from environment variables.

Internal

  • 🐛 Fix release workflow. PR #7, #9.

0.2.1 - 2024-11-26

Internal

  • 💚 Set up release workflow with version tagging.

0.2.0 - 2024-11-26

First public release.

Features

  • ✨ Add distributed Lock with lease-based expiration.
  • ✨ Add LeaderElection for single-leader task execution.
  • ✨ Add IntervalTask scheduler for periodic tasks with synchronization support.
  • ✨ Add Redis, PostgreSQL, and in-memory synchronization backends.
  • ✨ Add logging module with JSON and TEXT formatting via GREL_LOG_LEVEL and GREL_LOG_FORMAT environment variables.

Docs

  • 📝 Add MkDocs documentation site with Material theme.

Internal

  • 👷 Add unified CI workflow with linting, testing, and coverage.