fix(redis): register transport tests and pass CI gates

This commit is contained in:
Devin AI 2026-10-30 14:52:23 +00:00
parent fe2f2f0e9d
commit facaebccb2
4 changed files with 41 additions and 8 deletions

View file

@ -669,6 +669,7 @@ jobs:
- shard: caching-local
test-path: >-
tests/unit/caching
tests/unit/redis
python-version: "3.12"
workers: 2
reruns: 0

View file

@ -1,5 +1,5 @@
import hashlib
from collections.abc import Awaitable, Callable, Sequence
from collections.abc import Awaitable, Callable, Iterator, Sequence
from dataclasses import dataclass
from datetime import timedelta
from typing import Final, Protocol, TypeVar
@ -128,6 +128,27 @@ def _int_reply(reply: object) -> int | RedisFailure:
def _eval_reply(reply: object) -> EvalReply | RedisFailure:
frames: Final[list[tuple[Iterator[object], list[EvalReply]]]] = [ # mutable-ok: linear-time traversal stack
(iter((reply,)), [])
]
while frames:
pending, values = frames[-1]
for item in pending:
if (array := _array(item)) is not None:
frames.append((iter(array), []))
break
if isinstance(scalar := _eval_scalar(item), RedisFailure):
return scalar
values.append(scalar)
else:
frames.pop()
if not frames:
return values[0]
frames[-1][1].append(tuple(values))
return _decode_failure(reply)
def _eval_scalar(reply: object) -> int | bytes | None | RedisFailure:
if isinstance(reply, RedisFailure) or reply is None or isinstance(reply, bytes):
return reply
if isinstance(reply, bool):
@ -136,9 +157,4 @@ def _eval_reply(reply: object) -> EvalReply | RedisFailure:
return reply
if isinstance(reply, str):
return reply.encode()
array: Final = _array(reply)
if array is None:
return _decode_failure(reply)
items: Final = tuple(_eval_reply(item) for item in array)
failure: Final = next((item for item in items if isinstance(item, RedisFailure)), None)
return failure if failure is not None else tuple(item for item in items if not isinstance(item, RedisFailure))
return _decode_failure(reply)

View file

@ -153,6 +153,22 @@ async def test_eval_namespaces_keys_and_returns_nested_replies(
assert await raw.get("ns:k") == b"w"
@pytest.mark.asyncio
@pytest.mark.parametrize("decode_responses", (False, True))
async def test_eval_preserves_empty_arrays_and_sibling_order(
server: fakeredis.FakeServer, decode_responses: bool
) -> None:
raw = fakeredis.FakeAsyncRedis(server=server, decode_responses=decode_responses)
transport = fake_redis_transport(raw, failure_threshold=1)
assert await transport.eval('return {{}, {1, {"hello", {}}}, {}, 0}', (), ()) == (
(),
(1, (b"hello", ())),
(),
0,
)
@pytest.mark.asyncio
async def test_eval_survives_a_script_flush(server: fakeredis.FakeServer, raw: fakeredis.FakeAsyncRedis) -> None:
transport = fake_redis_cache(server).transport()

View file

@ -25,7 +25,7 @@ _REPO: Final = Path(__file__).resolve().parents[2]
_REDIS_PRODUCER_ROOTS: Final = ("litellm", "enterprise")
# The cache implementations and facades: they emit the service events, their callers declare the family.
_CACHE_LAYER_DIRS: Final = ("litellm/caching", "litellm/_v2/cache")
_CACHE_LAYER_DIRS: Final = ("litellm/caching", "litellm/_v2/cache", "litellm/redis/transport")
# Helpers that act on a cache handed in by the declaring caller, or forward to the response-cache facade.
_CACHE_PARAMETER_HELPERS: Final = frozenset(
{