fix: req changes redis

This commit is contained in:
Harshit28j 2026-03-07 13:34:26 +05:30
parent 8c1273fca9
commit 32877e0051

View file

@ -23,6 +23,8 @@ from litellm.litellm_core_utils.sensitive_data_masker import SensitiveDataMasker
from ._logging import verbose_logger
AZURE_REDIS_SCOPE = "https://redis.azure.com/.default"
def _get_redis_kwargs():
arg_spec = inspect.getfullargspec(redis.Redis)
@ -194,11 +196,11 @@ def _generate_azure_ad_redis_token(
"""
Generate Azure AD access token for Redis authentication.
Uses the azure-identity SDK to obtain a token scoped to Azure Cache for Redis.
Supports:
- Managed Identity (when running on Azure with AZURE_CLIENT_ID)
- Service Principal (AZURE_CLIENT_ID + AZURE_TENANT_ID + AZURE_CLIENT_SECRET)
- DefaultAzureCredential (automatic discovery)
Used by async paths (cluster, standard, connection pool) where a one-shot
token is needed. Creates a credential, fetches one token, and returns it.
For the sync path, prefer ``create_azure_ad_redis_connect_func`` which
keeps the credential alive across connections for automatic token refresh.
Args:
azure_client_id: Optional Azure client ID (overrides AZURE_CLIENT_ID env var)
@ -208,6 +210,26 @@ def _generate_azure_ad_redis_token(
Returns:
Access token string for Azure Redis authentication
"""
credential = _build_azure_credential(
azure_client_id=azure_client_id,
azure_tenant_id=azure_tenant_id,
azure_client_secret=azure_client_secret,
)
token = credential.get_token(AZURE_REDIS_SCOPE)
return token.token
def _build_azure_credential(
azure_client_id: Optional[str] = None,
azure_tenant_id: Optional[str] = None,
azure_client_secret: Optional[str] = None,
):
"""
Build a long-lived Azure credential object.
Azure SDK credentials cache tokens internally and handle expiry/refresh
transparently, so this should be called once and the result reused.
"""
try:
from azure.identity import (
ClientSecretCredential,
@ -220,26 +242,20 @@ def _generate_azure_ad_redis_token(
"Install it with: pip install azure-identity"
)
AZURE_REDIS_SCOPE = "https://redis.azure.com/.default"
# Determine credential type
_client_id = azure_client_id or os.environ.get("AZURE_CLIENT_ID")
_tenant_id = azure_tenant_id or os.environ.get("AZURE_TENANT_ID")
_client_secret = azure_client_secret or os.environ.get("AZURE_CLIENT_SECRET")
if _client_id and _tenant_id and _client_secret:
credential = ClientSecretCredential(
return ClientSecretCredential(
client_id=_client_id,
tenant_id=_tenant_id,
client_secret=_client_secret,
)
elif _client_id:
credential = ManagedIdentityCredential(client_id=_client_id)
return ManagedIdentityCredential(client_id=_client_id)
else:
credential = DefaultAzureCredential()
token = credential.get_token(AZURE_REDIS_SCOPE)
return token.token
return DefaultAzureCredential()
def create_azure_ad_redis_connect_func(
@ -250,8 +266,9 @@ def create_azure_ad_redis_connect_func(
"""
Creates a custom Redis connection function for Azure AD authentication.
Used for sync Redis clients. Generates a fresh Azure AD token on each
connection/reconnection, ensuring token refresh is handled automatically.
Used for sync Redis clients. The credential is created once (captured by the
closure) and reused across connections — the Azure SDK handles token caching
and silent renewal internally. Only ``get_token`` is called per connection.
Args:
azure_client_id: Optional Azure client ID
@ -261,6 +278,12 @@ def create_azure_ad_redis_connect_func(
Returns:
A connection function that can be used with Redis clients via `redis_connect_func`
"""
# Build credential once — it caches tokens and handles refresh internally
credential = _build_azure_credential(
azure_client_id=azure_client_id,
azure_tenant_id=azure_tenant_id,
azure_client_secret=azure_client_secret,
)
def ad_connect(self):
"""Initialize the connection and authenticate using Azure AD"""
@ -272,23 +295,22 @@ def create_azure_ad_redis_connect_func(
self._parser.on_connect(self)
# Get username from REDIS_USERNAME env var or default principal ID
access_token = credential.get_token(AZURE_REDIS_SCOPE).token
# Only include username when explicitly set — sending AUTH "" <token>
# is invalid for most ACL-configured Azure Redis instances.
username = os.environ.get("REDIS_USERNAME", "")
if username:
auth_args = (username, access_token)
else:
auth_args = (access_token,)
access_token = _generate_azure_ad_redis_token(
azure_client_id=azure_client_id,
azure_tenant_id=azure_tenant_id,
azure_client_secret=azure_client_secret,
)
# Azure Redis expects AUTH <username> <token> (Redis 6+ ACL style)
auth_args = (username, access_token)
self.send_command("AUTH", *auth_args, check_health=False)
try:
auth_response = self.read_response()
except AuthenticationWrongNumberOfArgsError:
# Fallback: try with just the token
# Fallback: try with just the token (Redis < 6 / no ACL)
self.send_command("AUTH", access_token, check_health=False)
auth_response = self.read_response()
@ -675,6 +697,52 @@ def get_redis_async_client( # noqa: PLR0915
# Check for Redis Sentinel
if "sentinel_nodes" in redis_kwargs and "service_name" in redis_kwargs:
return _init_async_redis_sentinel(redis_kwargs)
# Handle Azure AD / GCP IAM authentication for standard async Redis (non-cluster)
# The async client doesn't support redis_connect_func, so generate token upfront
# and set as password (same approach as async cluster path above).
redis_connect_func = redis_kwargs.pop("redis_connect_func", None)
if redis_connect_func and hasattr(redis_connect_func, "_azure_redis_ad_token"):
_az_client_id = getattr(redis_connect_func, "_azure_client_id", None)
_az_tenant_id = getattr(redis_connect_func, "_azure_tenant_id", None)
_az_client_secret = getattr(redis_connect_func, "_azure_client_secret", None)
verbose_logger.debug("Generating Azure AD token for async Redis client")
try:
access_token = _generate_azure_ad_redis_token(
azure_client_id=_az_client_id,
azure_tenant_id=_az_tenant_id,
azure_client_secret=_az_client_secret,
)
redis_kwargs["password"] = access_token
_username = os.environ.get("REDIS_USERNAME", "")
if _username:
redis_kwargs["username"] = _username
verbose_logger.debug(
"Successfully generated Azure AD token for async Redis client"
)
except Exception as e:
verbose_logger.error(f"Failed to generate Azure AD access token: {e}")
from redis.exceptions import AuthenticationError
raise AuthenticationError(
"Failed to generate Azure AD access token for Redis"
)
elif redis_connect_func and hasattr(redis_connect_func, "_gcp_service_account"):
gcp_service_account = redis_connect_func._gcp_service_account
verbose_logger.debug("Generating GCP IAM token for async Redis client")
try:
access_token = _generate_gcp_iam_access_token(gcp_service_account)
redis_kwargs["password"] = access_token
verbose_logger.debug(
"Successfully generated GCP IAM token for async Redis client"
)
except Exception as e:
verbose_logger.error(f"Failed to generate GCP IAM access token: {e}")
from redis.exceptions import AuthenticationError
raise AuthenticationError("Failed to generate GCP IAM access token")
_pretty_print_redis_config(redis_kwargs=redis_kwargs)
if connection_pool is not None:
@ -702,6 +770,41 @@ def get_redis_connection_pool(**env_overrides):
redis_kwargs["max_connections"],
)
return async_redis.BlockingConnectionPool.from_url(**pool_kwargs)
# Handle Azure AD / GCP IAM auth for connection pool — async pools don't
# support redis_connect_func, so resolve the token now and set as password.
redis_connect_func = redis_kwargs.pop("redis_connect_func", None)
if redis_connect_func and hasattr(redis_connect_func, "_azure_redis_ad_token"):
_az_client_id = getattr(redis_connect_func, "_azure_client_id", None)
_az_tenant_id = getattr(redis_connect_func, "_azure_tenant_id", None)
_az_client_secret = getattr(redis_connect_func, "_azure_client_secret", None)
try:
access_token = _generate_azure_ad_redis_token(
azure_client_id=_az_client_id,
azure_tenant_id=_az_tenant_id,
azure_client_secret=_az_client_secret,
)
redis_kwargs["password"] = access_token
except Exception as e:
verbose_logger.error(f"Failed to generate Azure AD token for pool: {e}")
from redis.exceptions import AuthenticationError
raise AuthenticationError(
"Failed to generate Azure AD access token for Redis connection pool"
)
elif redis_connect_func and hasattr(redis_connect_func, "_gcp_service_account"):
try:
access_token = _generate_gcp_iam_access_token(
redis_connect_func._gcp_service_account
)
redis_kwargs["password"] = access_token
except Exception as e:
verbose_logger.error(f"Failed to generate GCP IAM token for pool: {e}")
from redis.exceptions import AuthenticationError
raise AuthenticationError(
"Failed to generate GCP IAM access token for Redis connection pool"
)
connection_class = async_redis.Connection
if "ssl" in redis_kwargs:
connection_class = async_redis.SSLConnection