fix!: request Redis | RedisCluster in the Dishka provider and read per slot on a cluster - #33
Conversation
…r slot on a cluster AsyncRedisIdempotencyProvider asked for redis.asyncio.Redis while redis_client_kit.providers.AsyncRedisProvider provides the union alias AsyncRedisClient = Redis | RedisCluster. Dishka resolves by exact key, so a container holding both never built. The provider now requests the union, which is structurally the same key, so no shared alias or dependency is needed; the test group gains redis-client-kit so the wiring from the issue is tested against the real provider. The repository takes the union too, and get_many uses mget_nonatomic on a RedisCluster: a single MGET cannot span hash slots there, so the documented cluster compatibility was false for that one method. BREAKING CHANGE: AsyncRedisIdempotencyProvider requests redis.asyncio.Redis | RedisCluster, the key redis_client_kit.AsyncRedisClient names. A provider of your own annotated `-> Redis` no longer matches and the container fails at construction with GraphMissingFactoryError; annotate it `-> Redis | RedisCluster`. Closes #32
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Reporter here — checked the load-bearing claim and it holds. The design rests on >>> local_union = Redis | RedisCluster
>>> kit_alias = redis_client_kit.AsyncRedisClient
>>> local_union is kit_alias
False
>>> DependencyKey(local_union, None) == DependencyKey(kit_alias, None)
True
>>> hash(DependencyKey(local_union, None)) == hash(DependencyKey(kit_alias, None))
TrueSo no runtime dependency on redis-client-kit is needed, as the summary says. On the breaking change: it does not reach us. Nothing in our tree provides The |
Summary
AsyncRedisIdempotencyProviderasked forredis.asyncio.Redis;redis_client_kit.providers.AsyncRedisProviderprovidesAsyncRedisClient = Redis | RedisCluster. Dishka resolves by exact key, somake_async_container(AsyncRedisProvider(), AsyncRedisIdempotencyProvider(), ...)failed at construction withGraphMissingFactoryError, and the two org libraries that both talk to Redis could not be wired from the shipped pieces.The provider now requests
Redis | RedisCluster.types.UnionTypecompares structurally, andDependencyKey(Redis | RedisCluster)built here equals the one built fromredis_client_kit.AsyncRedisClient, so no shared alias and no runtime dependency on redis-client-kit is needed. The test group gainsredis-client-kit[providers,settings]so the wiring from the issue is tested against the realAsyncRedisProvider, not a look-alike.Accepting the union had to be honest, so I went through every command the repository issues.
get,save,replace,deleteare single-key;save_manyis apipeline(transaction=False)the asyncRedisClusteraccepts;delete_manyand the expired-key cleanup send multi-keyDEL, which redis-py's cluster client splits per slot itself. The one gap wasget_many: a singleMGETcannot span hash slots andRedisCluster._determine_slotraisesRedisClusterException("MGET - all keys must map to the same key slot")— and keys of one operation do land in different slots. On aRedisClusterthe repository now callsmget_nonatomic(oneMGETper slot, values in input order), so the documented cluster compatibility is true for every method.Breaking change
A provider of your own annotated
-> Redisno longer matches: the container fails at construction with the sameGraphMissingFactoryError, now namingRedis | RedisCluster. Change the annotation to-> Redis | RedisCluster. The docs example anddocs/agents.md(rule 25) say so.Rejected
client_type=constructor argument withRediskept as the default: no break, but the shipped pieces still would not compose by default, which is the complaint.Redisand shipping a checked narrowing: cluster is supportable onceget_manyreads per slot, so refusing it in the type would be the lie in the other direction.Nothing to mirror on the sync side: idempotency-kit is async only and redis-client-kit ships no sync provider. No change on the redis-client-kit side.
Verification
make check: ruff check, ruff format --check, mypy — all clean.uv run pytest -m unit: 171 passed (169 before, two new).uv run pytest -m integration(Docker): 7 passed.idempotency_kit/dishka/aio/redis.pyand.../repository.pyswapped for theirorigin/masterversions, the two new tests fail —GraphMissingFactoryError: Cannot find factory for (Redis, component='')for the wiring test,IdempotencyStorageError: Redis storage failure during mgetfor the cluster test, because the old code callsmget— and pass with them restored.AsyncRedisProvider(check_health_on_startup=False)+ the three shipped providers, redis-client-kit 0.2.0, no server) fails on master withCannot find factory for (Redis, component='')and printsbuilt: RedisAsyncIdempotencyRepository client: Redison this branch.Type of change
Checklist
make checkpasses locally (ruff+mypy)CHANGELOG.mdupdated under[Unreleased]— release-please writes it from the commitdocs/agents.mdincludedRelated issues
Closes #32