Changelog
0.23.0 - 2026-05-17
Breaking
- 💥 Rename the discriminator field from
typetokindon every tagged union. AffectsRateLimiterConfig(TokenBucketConfig,SlidingWindowConfig) andRetryBackoffConfig(ExponentialBackoff,ConstantBackoff,LinearBackoff,FibonacciBackoff,RandomBackoff). Serialized YAML and JSON configs must replacetype:withkind:(for exampleGREL_RETRY_FOO_BACKOFF={"kind":"exponential",...}). Frees the Pythontypebuiltin from being shadowed on every config object. Issue #268. - 💥 Rename
GCRAConfigtoSlidingWindowConfigandRateLimiter.gcra(...)toRateLimiter.sliding_window(...). The discriminator value also moves from"gcra"to"sliding_window". Modulegrelmicro.resilience.algorithms.gcrabecomesgrelmicro.resilience.algorithms.sliding_window. Internal strategy classes (_RedisGCRA,_MemoryGCRA) keep their names since they describe the underlying algorithm. Issue #259.
Features
- ✨ Add
LogandTracecomponents. RegisterLog()andTrace()inGrelmicro(uses=[...])to wire observability through the same verb asSync,Cache,RateLimit,Breaker, andTasks.Log()wrapsgrelmicro.log.configure(...)and snapshots stdlib root handlers on enter so sequential apps in tests do not stack handlers.Trace()owns an OTelTracerProvider: builds it fromTracingConfig(env prefixGREL_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
Grelmicroapp object andComponentprotocol. The user composes everything attached to the app into one container and opens it withasync with micro:. SingleGrelmicro.use(item)registration verb (anduses=constructor kwarg) acceptsComponentinstances (registered with(kind, name)lookup, exposed onmicro.<kind>), first-party backends (auto-wrapped into their canonical Component:RedisCacheAdapter→Cache,RedisSyncAdapter→Sync), and any other async context manager (lifecycled only, caller keeps the reference). Typed accessorsmicro.syncandmicro.cacheprovide IDE completion.Grelmicro.componentsreturns the registered Components in order for/healthz-style introspection. Issue #208, epic #201, unified in #219,Componentrename and.componentsaccessor in #233. - ✨ Add
Synccomponent. Wraps aSyncBackendand exposeslock(...),task_lock(...),leader_election(...)factories. Use it viaGrelmicro(uses=[redis, Sync(redis)])(Provider-direct) orSync(MemorySyncAdapter())(Backend-direct). Reach it onmicro.sync. Issue #210. - ✨ Add
Cachecomponent. Wraps aCacheBackendand exposes attl(...)factory that builds aTTLCachebound to the wrapped backend. Use it viaGrelmicro(uses=[redis, Cache(redis)])(Provider-direct) orCache(MemoryCacheAdapter())(Backend-direct). Reach it onmicro.cache. Issue #212. - ✨ Add Component-direct Provider API.
Sync,Cache,RateLimit, andBreakeraccept aProvideror aBackendinstance. When given a Provider, the Component callsprovider.sync(),provider.cache(),provider.ratelimiter(), orprovider.breaker()to build the canonical adapter. AddProviderbase class ingrelmicro.providers._basewith the four factory methods. AddRedisProvider.ratelimiter()returning aRedisRateLimiterAdapter. The Adapter classes stay public as escape hatches for custom Providers, but the canonical user code usesSync(redis)instead ofSync(RedisSyncAdapter(provider=redis)). - ✨ Add
Grelmicro.current()classmethod for ambient lookup. Insideasync with micro:it returns the active app for the current asyncio task. - ✨ Add
Retryprimitive with decorator, block, and class forms. Five backoff algorithms ship:ExponentialBackoff(default, with full jitter),ConstantBackoff,LinearBackoff,FibonacciBackoff, andRandomBackoff.when=is required and accepts aMatch(or shorthand). Live reconfiguration viaReconfigurable[RetryConfig]. Three-paths configuration. Underlying exception is re-raised with a PEP 678 note on exhaustion. Issue #165. - ✨ Add
MatchandOutcometogrelmicro.resilience.Matchis the resilience-wide outcome filter DSL:Match.exception(...),Match.result(...),Match.exception_message(...),Match.exception_cause(...),Match.predicate(...),Match.always(),Match.never()plus theirnot_*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 viaREDIS_*),RedisProvider.from_config(RedisConfig(...)), andRedisProvider.from_client(client, own=False)for bring-your-own clients.Grelmicrodedupes 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 anasyncpg.Pool:PostgresProvider("postgresql://..."),PostgresProvider(host=..., database=..., user=..., password=...),PostgresProvider()(env-driven viaPOSTGRES_*),PostgresProvider.from_config(PostgresConfig(...)), andPostgresProvider.from_client(pool, own=False)for bring-your-own pools. Shares the same(provider_class, env_prefix)dedupe asRedisProvider. Issue #255.
Breaking
- 💥 Patterns
RateLimiter,CircuitBreaker, and the FastAPI health router resolve through the activeGrelmicroapp. Add the two new ComponentsRateLimit(wrapsRateLimiterBackend, kind"ratelimiter") andBreaker(wrapsCircuitBreakerBackend, kind"circuitbreaker").Grelmicro.use(...)auto-wraps aRateLimiterBackendorCircuitBreakerBackendinstance into its canonical Component.HealthChecksbecomes a Component (kind = "health", defaultname = "default"). Pass it toGrelmicro(uses=[...])and the FastAPIhealth_router()resolves it viaGrelmicro.current(). Delete therate_limiter_backend_registry,circuit_breaker_backend_registry,health_checksregistries plusgrelmicro/_backends.py(BackendRegistry,BackendNotLoadedError,BackendAlreadyRegisteredError). Closes out #201. Issue #261. - 💥 Redis adapters now take
provider=orenv_prefix=, not a positionalurl=.RedisSyncAdapter,RedisCacheAdapter, andRedisRateLimiterAdapterlose theirurl=argument. Passprovider=RedisProvider(...)to share a pool, or rely onenv_prefix=(defaultREDIS_) to build one. Issue #226. - 💥
PostgresSyncAdapternow takesprovider=orenv_prefix=, not a positionalurl=. Passprovider=PostgresProvider(...)to share a pool, or rely onenv_prefix=(defaultPOSTGRES_) to build one. Issue #255. - 💥 Rename
TaskManagertoTasks. Class still extendsTaskRouter, mirroring FastAPI'sAPIRouter←FastAPIshape. Update imports tofrom grelmicro.task import Tasks. Issue #218. - 💥 Rename
HealthRegistrytoHealthChecks(andHealthRegistryConfigtoHealthChecksConfig). Update imports tofrom grelmicro.health import HealthChecks. Issue #201. - 💥 Rename concrete backends to
*Adapter.MemorySyncBackend,RedisSyncBackend,PostgresSyncBackend,SQLiteSyncBackend,KubernetesSyncBackend,MemoryCacheBackend,RedisCacheBackend,MemoryRateLimiterBackend,RedisRateLimiterBackend, andMemoryCircuitBreakerBackendbecome*Adapter. TheSyncBackend,CacheBackend,RateLimiterBackend, andCircuitBreakerBackendProtocols stay as-is. Issue #201. - 💥 Rename nested backoff classes to drop the redundant
Configsuffix.ExponentialBackoffConfig,ConstantBackoffConfig,LinearBackoffConfig,FibonacciBackoffConfig, andRandomBackoffConfigbecomeExponentialBackoff,ConstantBackoff,LinearBackoff,FibonacciBackoff, andRandomBackoff. TheRetryBackoffConfigdiscriminated-union alias and the JSON discriminator (type: "exponential") are unchanged. Issue #239. - 💥 Rename the
Moduleprotocol toComponentto avoid clashing with Python's own "module".ModuleAlreadyRegisteredErrorbecomesComponentAlreadyRegisteredErrorandModuleNotRegisteredErrorbecomesComponentNotRegisteredError. TheSyncandCacheclasses 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=becomesenv_load=on every component,GREL_CONFIG_FROM_ENVbecomesGREL_ENV_LOAD, and thegrelmicro._config.env_opt_in_enabled()helper becomesenv_load_default(). No deprecation shim. Issue #232. - 💥 Remove the module-level registry and lifespan API.
grelmicro.lifespan()is gone. Theregister/unregister/use/use_backend/use_registryhelpers acrossgrelmicro.{sync,cache,health,resilience}(plus the resilience circuit-breaker variants) are gone. Patterns (Lock,TaskLock,LeaderElection,TTLCache) now resolve their backend viaGrelmicro.current()at every call. Build aGrelmicro(uses=[...])and open it withasync with micro:. Thegrelmicro.sync._backendsandgrelmicro.cache._backendsmodules are removed (sync and cache resolve through the app). The internalrate_limiter_backend_registry,circuit_breaker_backend_registry, andhealth_checksregistries stay private until follow-up issues introduce their Component wrappers. Issue #207. - 💥
Retryoutcome filter is nowwhen=accepting aMatch. The oldon=parameter is gone. TheMatchDSL (Match.exception(...),Match.result(...),Match.exception_message(...),Match.exception_cause(...),Match.always(),Match.never(),Match.predicate(...)) plus theirnot_*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 returningNone. Env var renamedGREL_RETRY_{NAME}_ON→GREL_RETRY_{NAME}_WHEN. Issue #242.
Internal
- ⚡ Defer the
opentelemetryimport ingrelmicro.trace.import grelmicro.traceno longer loadsopentelemetry(was 16 modules). The package is resolved lazily on first call toinstrument,span, oradd_contextand 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 Streampython3.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
asynciodirectly. Issue #183. - 💥
CircuitBreakernow 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 throughcb.from_thread. - 💥 The sync adapters on
Lock,TaskLock,TTLCache, andCircuitBreakernow require the backend to be opened (async with backend:orgrelmicro.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, soexclude={"resilience"}still skips both registries.
Features
- ✨ Add
uvloopto thestandardextra (Linux and macOS). Activate withuvloop.run(main()).
Internal
- ✅ Migrate the test suite from
pytest.mark.anyiotopytest-asynciowithasyncio_mode = "auto". AnyIO is no longer a direct dependency of grelmicro (it may still arrive transitively, for example throughfast-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, andtests/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 incache/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-versiontopy312and 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)toLock,TaskLock, andLeaderElection. Swap timing fields without restarting. Theworkerfield cannot change. PR #159. - ✨ Add
CircuitBreaker.reconfigure(new_config). Swap thresholds andignore_exceptionswithout restarting. Runtime state andlast_errorare kept.log_levelis applied to the logger. PR #160. - ✨ Add
HealthRegistry.reconfigure(new_config). Swapcache_ttland the defaulttimeoutwithout 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=trueonce at startup to enable env reads across every component, or passread_env=Trueper 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_envkwarg default flips fromTruetoNoneon every component.Nonefollows the global flag.TrueandFalsekeep their meaning as explicit per-call overrides. - 💥 Remove obsolete deprecation shims that were marked for removal in 0.7.0. Replace
ResilienceExceptionwithResilienceError,SynchronizationwithSyncPrimitive, and thescheduled()decorator onTaskRouter/TaskManagerwithinterval(seconds=N, max_lock_seconds=N*5). Thetoken=kwarg onLockAcquireError,LockReleaseError, andLockNotOwnedErroris removed (drop it from your code). Thesync=parameter oninterval()no longer warns when used with non-Lockprimitives.
Internal
- ♻️ Add
grelmicro._config.env_opt_in_enabled()helper that exposes the truthyGREL_CONFIG_FROM_ENVcheck (1,true,yes,on, case-insensitive). Issue #142. - 📝 Document the "no field-mirroring" decision in
docs/architecture/config.mdwith the benchmark numbers frombenchmarks/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 keepself._configas the single source of truth instead of copying frozen fields onto the component. - 🔇 Silence the upstream
testcontainers@wait_container_is_readydeprecation banner via a scopedfilterwarningsentry inpyproject.toml. Replace the unawaitedlambda: sleep(math.inf)mock side-effect intests/sync/test_leaderelection.py::test_leadership_abandon_on_renew_deadline_reachedwith 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 autoinaddopts) and shrinking expiration sleeps intests/sync/test_backends.py. Fix--durationsreporting by removing the autousefreeze_timefixture:@freeze_time()decorator stays on the two tests that comparedatetime.now(), the two tests that previously calledfrozen_time.tick(...)switch tomonkeypatch.setattr(circuitbreaker, "monotonic", ...). Issue #125. - ⚡ Cache the dynamic
BaseSettingssubclass built bygrelmicro._config._build_settings_clswith@functools.lru_cache(maxsize=256). The env path ofresolve_confignow reuses the same_<Config>Settingssubclass across calls instead of rebuilding it every time, which makesLock("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_configtomerged_configin_build_settings_clsand document why the existing# type: ignorecomments 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. Theauto_registerkwarg is removed from every backend and fromHealthRegistry. PR #138. - 💥
BackendRegistry.setis renamedregisterandBackendRegistry.unregisteris added with an identity check.resetremains for test fixtures. PR #138. - 💥
async with backendopens the connection but no longer registers. Callregister(backend)(oruse_backend(backend)) to register, or open everything at once withgrelmicro.lifespan(). PR #139. - 💥
BackendRegistryis 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 andlifespan()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, andgrelmicro.health.use_registryfor 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,useongrelmicro.sync,grelmicro.cache,grelmicro.resilience(anduse_registry,useongrelmicro.health). PR #139. - ✨ Task-scoped overrides via
<module>.use(backend)or<module>.use(default=a, analytics=b). Stacks LIFO viacontextvarsfor 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
- 💥
CircuitBreakerconfig moves to a frozenCircuitBreakerConfig. Read it viacb.config. PR #132. - 💥 The mutable attributes
cb.error_threshold,cb.success_threshold,cb.reset_timeout,cb.half_open_capacity,cb.ignore_exceptions,cb.log_levelare removed. Construct a newCircuitBreakerto change config. PR #132. - 💥 Rename
grelmicro.loggingtogrelmicro.logandgrelmicro.tracingtogrelmicro.trace. Avoids shadowing stdlibloggingand aligns with the OpenTelemetry /ddtracetrace(singular) convention. Update imports:from grelmicro import log, trace. PR #135. - 💥
configure_logging()is renamedlog.configure(). Uselog.configure_with(config)for the declarative path. Both return the appliedLoggingConfig. PR #135. - 💥
LoggingSettings(theBaseSettingsshadow class) is removed.LoggingConfigis the canonical config class. Env reading happens insidelog.configure(). PR #135. - 💥
LoggingConfigfield names move to lowercase:LOG_BACKEND→backend,LOG_LEVEL→level,LOG_FORMAT→format,LOG_TIMEZONE→timezone,LOG_JSON_SERIALIZER→json_serializer,LOG_CALLER_ENABLED→caller_enabled,LOG_OTEL_ENABLED→otel_enabled. PR #135. - 💥 Env vars move from
LOG_*toGREL_LOG_*to align with the rest of the library. PR #135. - 💥
LoggingSettingsValidationErroris removed.pydantic.ValidationErrorpropagates fromlog.configure()like every other component. PR #135.
Features
- ✨ Add
CircuitBreakerConfigandCircuitBreaker.from_config(name, config). PR #132. - ✨
CircuitBreakerreadsGREL_CIRCUIT_BREAKER_<NAME>_*env vars and acceptsenv_prefix=/read_env=. PR #132. - ✨
ignore_exceptionsaccepts 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 everyLoggingConfigfield 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 appliedLoggingConfig. PR #135.
Internal
- ♻️ Add
grelmicro/_types.pyfor shared lightweight type aliases (LogLevel). PR #132. - ♻️ Add
grelmicro/_config.py::parse_csv_or_jsonshared 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
tyfrom 0.0.29 to 0.0.30. PR #111. - ⬆️ Pre-commit autoupdate. PR #114.
0.16.0 - 2026-04-29
Breaking
- 💥
LockConfig,TaskLockConfig,LeaderElectionConfig, andRateLimiterConfigno longer carry anamefield. Pass the name positionally:Lock("cart", LockConfig(lease_duration=30)). PR #123. - 💥 Rename
TokenBuckettoTokenBucketConfigandGCRAtoGCRAConfig.RateLimiterConfigbecomes the discriminated union of algorithm configs. PR #123. - 💥
RateLimitertakes the algorithm config positionally:RateLimiter("api", GCRAConfig(limit=100, window=60)). Thealgorithm=,limit=,window=kwargs are removed. PR #123. - 💥
fail_openmoves fromRateLimiter(...)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, ...)andRateLimiter.gcra(name, ...)factory classmethods. PR #123. - ✨ Add
env_prefix=andread_env=kwargs to every component that exposes the environmental path. PR #123. - ✨ Normalise instance names like
payments-eu,cart.v2, orweather/svcinto POSIX env var segments. PR #123.
Changed
- ♻️
Lock,TaskLock,LeaderElection, andRateLimiternow resolve the backend lazily on first use instead of at construction.BackendNotLoadedErrorsurfaces on the firstacquire/peek/resetcall rather than in__init__. Each component exposes a publicbackendproperty. 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.releaseclears local ownership only after the backend confirms the release. PR #122.
0.15.0 - 2026-04-29
Breaking
- 💥 Redesign the
healthmodule:@health.check("name")decorator, binaryok/errorstatus, empty probe bodies, per-check caching. PR #112. - 💥 Endpoint renames:
/health/live→/livez,/health/ready→/readyz. New/healthzreturns the full check JSON. PR #112. - 💥
HealthRegistry.check()renamed torun(). Thecheckname is now the decorator. PR #112. - 💥
HealthCheckerProtocol removed. Use plaindeforasync deffunctions. PR #112. - 💥
HealthReport.components: listbecomesHealthReport.checks: dict[name, ...]. PR #112. - 💥
HealthCheckTimeoutErrorand the three-stateHealthStatusremoved. PR #112.
Docs
- 📝 Restate the versioning policy: pre-1.0
MINORmay break,PATCHnever. Post-1.0 deprecations get twoMINORreleases.
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, andpoetrytabs. - 📝 Render PEP 727
Annotated[..., Doc(...)]parameter docs viagriffe-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_threadon Python 3.12. Fixes #105. - 🔧 Add keywords to
pyproject.tomlfor PyPI discovery.
0.14.0 - 2026-04-21
Features
- ✨ Add pluggable
RateLimiteralgorithms via thealgorithm=parameter:TokenBucketandGCRA. PR #102. - ✨ Add
MemoryTokenBucket, a standalone synchronous token-bucket primitive. PR #102. - ✨ Add
RateLimitFilter, alogging.Filterwith configurablekey_mode. PR #102. - ✨ Add
DuplicateFilter, alogging.Filterthat caps repeated records per key with optional TTL. PR #94. - ✨
HealthRegistrynow logs every unhealthy path atWARNING(ERRORfor unexpected exceptions). PR #92.
Deprecations
- 🗑️
RateLimiter(name, limit=..., window=...)is deprecated. UseRateLimiter(name, algorithm=GCRA(limit=..., window=...))instead. Will be removed in 0.15.0. PR #102.
Docs
- 📝 Add
CONTRIBUTING.mdwith repo conventions. PR #102. - 📝 Add a "Choosing an algorithm" guide for
TokenBucketvsGCRAin the Rate Limiter docs. PR #102. - 📝 Surface
THIRD_PARTY_NOTICES.mdin 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
pydanticto 2.13.0,opentelemetry-api/opentelemetry-sdkto 1.41.0,pytestto 9.0.3,ruffto 0.15.10,tyto 0.0.29,fastapito 0.135.3,uvicornto 0.44.0. PR #99. - ⬆️ Bump
pydantic-extra-typesfrom 2.11.1 to 2.11.2. PR #89. - ⬆️ Pre-commit
ruffautoupdate (v0.15.9 → v0.15.11). PR #91. - ⬆️ Bump
codecov/codecov-actionto v6. PR #96. - ⬆️ Bump
astral-sh/setup-uvto v8. PR #97. - ⬆️ Bump
dependabot/fetch-metadatato 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_openparameter toRateLimiter: return allowed result on backend errors instead of propagating exceptions. PR #90.
0.12.0 - 2026-04-07
Features
- ✨ Add
healthmodule 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.0to<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
callerinto separatelogger(logger name) andcaller(function:line) fields.calleris now opt-in viaGREL_LOG_CALLER_ENABLED(default:False), following common structured-logging conventions. Uvicorn formatter never includescaller. - 💥 Cache: replace
TTLCacheserializer/deserializercallable pair with a singleserializeraccepting aCacheSerializerprotocol object. UseJsonSerializer(),PydanticSerializer(Model), orPickleSerializer()instead.
Features
- ✨ Add
GREL_LOG_CALLER_ENABLEDsetting to opt in to caller info (function:line) in log records. Disabled by default for cleaner logs and better performance. - ✨ Add
loggerfield (logger name, e.g.,myapp.api) to all log records across all backends and formats. - ✨ Add
grelmicro.jsonmodule with fast JSON serialization usingorjsonwhen available, with automatic fallback to stdlibjson.
0.10.0 - 2026-04-02
Features
- ✨ Add
RateLimiterto theresiliencemodule: Redis-backed sliding-window rate limiting using the GCRA algorithm. IncludesRateLimitResultwith fields mapping to IETF rate limit headers, weighted requests viacostparameter, andRateLimitExceededError.
Removals
- 🗑️ Remove deprecated
UvicornJSONFormatterandUvicornAccessJSONFormatter. UseUvicornFormatterandUvicornAccessFormatterinstead (deprecated since 0.9.1).
CI
- ⚡ Migrate PyPI publishing from API token to OIDC trusted publishing.
0.9.1 - 2026-04-01
Deprecations
- 🗑️
UvicornJSONFormatterandUvicornAccessJSONFormatterare deprecated. UseUvicornFormatterandUvicornAccessFormatterinstead. The new formatters respectGREL_LOG_FORMATinstead of always producing JSON. Old names kept as aliases withDeprecationWarning.
0.9.0 - 2026-04-01
Breaking Changes
- 💥
GREL_LOG_FORMATdefault changed fromJSONtoAUTO. In production (non-TTY), behavior is identical (JSON output). In local dev (TTY), output switches to human-readableTEXTwith colors. SetGREL_LOG_FORMAT=JSONexplicitly to restore the previous default.
Features
- ✨ Add
AUTOlog format (new default): detects TTY and selectsTEXT(terminal) orJSON(piped/CI). - ✨ Add
LOGFMTlog format: key-value pairs following the logfmt convention, 30-40% smaller than JSON. - ✨ Add
PRETTYlog format: multi-line indented output with structured error rendering. - ✨ Enhanced
TEXTformat: now includes extra context fields askey=valuepairs and supports ANSI colors. - ✨ Add
NO_COLOR/FORCE_COLORenvironment 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 RedisSyncBackendinstead offrom 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 viadictConfig.
0.7.0 - 2026-03-31
Breaking Changes
- 💥 Logging JSON format redesigned to follow industry standards:
loggerrenamed tocallerthreadremovedctxremoved: extra fields are now flat at the top levelexceptionreplaced by structurederrorobject (type,message,stack)
Features
- ✨ Add
tracingmodule with@instrumentdecorator,span()context manager, andadd_context()for unified logging and OTel instrumentation.
Performance
- ⚡ Logging: Up to +23% throughput across all backends.
- ⚡ Use
OrderedDictfor O(1) LRU operations inTTLCache.
Refactors
- ♻️ Extract shared Redis config into
grelmicro/_redis.py. - ♻️ Make
TTLCachegeneric and addDocannotations. - ♻️ Extract context stack into
grelmicro/_context.pyto decouple logging from tracing. - ♻️ Filter private (
_-prefixed) attributes from stdlib JSON log output. - ♻️ Widen
@instrument(skip=...)type fromset[str]toAbstractSet[str].
Removals
- 🗑️
Synchronizationprotocol removed. UseSyncPrimitiveinstead (deprecated since 0.6.0). - 🗑️
ResilienceExceptionremoved. UseResilienceErrorinstead (deprecated since 0.6.0). - 🗑️ The
tokenparameter on lock errors removed (deprecated since 0.6.0). - 🗑️ The
syncparameter oninterval()removed (deprecated since 0.6.0). - 🗑️ The
scheduled()decorator removed (deprecated since 0.6.0).
0.6.0 - 2026-03-30
Deprecations
- 🗑️
Synchronizationprotocol renamed toSyncPrimitive. The old name still works but emits aDeprecationWarning. Will be removed in 0.7.0. - 🗑️
ResilienceExceptionrenamed toResilienceError. The old name still works but emits aDeprecationWarning. Will be removed in 0.7.0. - 🗑️ The
tokenparameter onLockAcquireError,LockReleaseError, andLockNotOwnedErroris deprecated. Tokens are no longer included in error messages for security. Will be removed in 0.7.0. - 🗑️ The
syncparameter oninterval()forTaskLockandLeaderElectionis deprecated. Usemax_lock_secondsandleaderparameters instead. Will be removed in 0.7.0. - 🗑️ The
scheduled()decorator is deprecated. Useinterval()withmax_lock_secondsorleaderinstead. Will be removed in 0.7.0.
Features
- ✨ Add in-memory TTL cache with LRU eviction, per-key stampede protection, and
@cacheddecorator. - ✨ Add
RedisCacheBackendfor 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
requeststo 2.33.0 (CVE fix in transitive dependency).
Refactors
- ♻️ Unify error hierarchy under
GrelmicroErrorbase 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
- 📝 Add cache module documentation with usage guide and API reference.
- 📝 Add Kubernetes Backend Architecture page.
- 📝 Add SQLite Backend Architecture page.
- 📝 Add backend comparison matrix to Synchronization guide.
- 📝 Rewrite README with project vision.
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_threadthread-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 viamax_lock_secondsandleaderparameters. PR #54.
Docs
- 📝 Add Synchronization Architecture page. PR #57.
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
TaskLockfor distributed task locking with auto-renewal. - ✨ Add
GREL_LOG_TIMEZONEsupport for configurable timezone in logging output. - ✨ Add OpenTelemetry trace context injection into log records.
- ✨ Add
structlogas alternative logging backend. - ✨ Add configurable JSON serializer (
json/orjson) for logging.
Docs
- 📝 Add logging benchmark and performance documentation.
Internal
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
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
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
Lockwith lease-based expiration. - ✨ Add
LeaderElectionfor single-leader task execution. - ✨ Add
IntervalTaskscheduler 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_LEVELandGREL_LOG_FORMATenvironment variables.
Docs
- 📝 Add MkDocs documentation site with Material theme.
Internal
- 👷 Add unified CI workflow with linting, testing, and coverage.