Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion litellm/_redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,20 @@ def _init_arg_names(cls: type) -> frozenset[str]:

Keyword-only parameters are included, and the MRO is walked because redis-py splits a
connection's parameters between ``AbstractConnection`` and its concrete subclasses.

Each ``__init__`` is unwrapped before introspection: redis-py >= 7.4 decorates
``AbstractConnection.__init__`` with ``@deprecated_args``, whose wrapper is declared
``(self, *args, **kwargs)`` — introspecting the wrapper directly loses every real
parameter (``socket_timeout`` included), which silently emptied this allowlist and
dropped the socket timeouts from url-configured connections. ``inspect.unwrap``
follows the ``__wrapped__`` chain to the true signature and is a no-op on
undecorated ``__init__``s.
"""
return frozenset(
name
for klass in inspect.getmro(cls)
if klass is not object
for spec in (inspect.getfullargspec(klass.__init__),)
for spec in (inspect.getfullargspec(inspect.unwrap(klass.__init__)),)
for name in spec.args + spec.kwonlyargs
)

Expand Down
46 changes: 46 additions & 0 deletions tests/test_litellm/test_redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,3 +864,49 @@ def test_redis_uses_the_hiredis_response_parser():
client = get_redis_client(host="redis-host", port=6379)
connection = client.connection_pool.make_connection()
assert isinstance(connection._parser, _HiredisParser)


def test_init_arg_names_sees_through_decorated_inits():
"""redis-py >= 7.4 wraps AbstractConnection.__init__ with @deprecated_args, whose
wrapper is declared (self, *args, **kwargs). Introspecting the wrapper directly
yields no real parameters, which silently emptied the from_url allowlist and
dropped socket_timeout from url-configured connections. The MRO walk must follow
__wrapped__ to the true signature.
"""
import functools

from litellm._redis import _init_arg_names

def deprecating(fn):
@functools.wraps(fn)
def wrapper(self, *args, **kwargs):
return fn(self, *args, **kwargs)

return wrapper

class Base:
@deprecating
def __init__(self, socket_timeout=None, socket_connect_timeout=None):
pass

class Concrete(Base):
def __init__(self, host=None, **kwargs):
super().__init__(**kwargs)

names = _init_arg_names(Concrete)
assert "socket_timeout" in names
assert "socket_connect_timeout" in names
assert "host" in names


def test_url_allowlist_always_carries_socket_timeouts():
"""The load-bearing invariant behind test_url_config_* against the INSTALLED
redis-py, whatever its version: if a redis-py release changes how its __init__
signatures are declared (7.4 did, via @deprecated_args), this is the first
assertion that goes red.
"""
from litellm._redis import _get_redis_url_kwargs

allowed = _get_redis_url_kwargs()
assert "socket_timeout" in allowed
assert "socket_connect_timeout" in allowed
Loading