22
33import json
44import logging
5+ from contextlib import asynccontextmanager
56from typing import List , Optional , Tuple
67
78from pydantic import field_validator
@@ -163,11 +164,24 @@ def validate_self_link_ttl_standalone(cls, v: int) -> int:
163164standalone_settings = RedisSettings ()
164165
165166
166- async def connect_redis () -> Optional [aioredis .Redis ]:
167- """Return a Redis connection Redis or Redis Sentinel."""
167+ @asynccontextmanager
168+ async def redis_connection ():
169+ """Create a Redis connection with proper error handling for tests."""
170+ redis = None
168171 try :
172+ # Check if Redis is configured at all
173+ if not (sentinel_settings .REDIS_SENTINEL_HOSTS or standalone_settings .REDIS_HOST ):
174+ logger .debug ("Redis not configured, skipping connection" )
175+ yield None
176+ return
177+
169178 if sentinel_settings .REDIS_SENTINEL_HOSTS :
170179 sentinel_nodes = sentinel_settings .get_sentinel_nodes ()
180+ if not sentinel_nodes :
181+ logger .warning ("No Redis Sentinel nodes configured" )
182+ yield None
183+ return
184+
171185 sentinel = Sentinel (
172186 sentinel_nodes ,
173187 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
@@ -185,83 +199,82 @@ async def connect_redis() -> Optional[aioredis.Redis]:
185199 logger .info ("Connected to Redis Sentinel" )
186200
187201 elif standalone_settings .REDIS_HOST :
188- pool = aioredis .ConnectionPool (
202+ redis = aioredis .Redis (
189203 host = standalone_settings .REDIS_HOST ,
190204 port = standalone_settings .REDIS_PORT ,
191205 db = standalone_settings .REDIS_DB ,
192- max_connections = standalone_settings .REDIS_MAX_CONNECTIONS ,
193206 decode_responses = standalone_settings .REDIS_DECODE_RESPONSES ,
194207 retry_on_timeout = standalone_settings .REDIS_RETRY_TIMEOUT ,
208+ client_name = standalone_settings .REDIS_CLIENT_NAME ,
195209 health_check_interval = standalone_settings .REDIS_HEALTH_CHECK_INTERVAL ,
210+ socket_connect_timeout = 5.0 , # Short timeout for tests
211+ socket_timeout = 5.0 ,
196212 )
197- redis = aioredis .Redis (
198- connection_pool = pool , client_name = standalone_settings .REDIS_CLIENT_NAME
199- )
200- logger .info ("Connected to Redis" )
201- else :
202- logger .warning ("No Redis configuration found" )
203- return None
204213
205- return redis
214+ await redis .ping ()
215+ logger .debug ("Connected to Redis" )
216+
217+ yield redis
206218
207- except aioredis .ConnectionError as e :
208- logger .error (f"Redis connection error: { e } " )
209- return None
210- except aioredis .AuthenticationError as e :
211- logger .error (f"Redis authentication error: { e } " )
212- return None
213- except aioredis .TimeoutError as e :
214- logger .error (f"Redis timeout error: { e } " )
215- return None
216219 except Exception as e :
217- logger .error (f"Failed to connect to Redis: { e } " )
218- return None
219-
220+ logger .debug (f"Redis connection failed (this is normal in tests): { e } " )
221+ yield None
222+ finally :
223+ if redis :
224+ await redis .close ()
225+ logger .debug ("Redis connection closed" )
220226
221227async def save_self_link (
222228 redis : aioredis .Redis , token : Optional [str ], self_href : str
223229) -> None :
224230 """Save the self link for the current token."""
225- if token :
231+ if redis and token :
226232 if sentinel_settings .REDIS_SENTINEL_HOSTS :
227233 ttl_seconds = sentinel_settings .REDIS_SELF_LINK_TTL
228234 elif standalone_settings .REDIS_HOST :
229235 ttl_seconds = standalone_settings .REDIS_SELF_LINK_TTL
230- await redis .setex (f"nav:self:{ token } " , ttl_seconds , self_href )
231-
236+ else :
237+ return
238+
239+ try :
240+ await redis .setex (f"nav:self:{ token } " , ttl_seconds , self_href )
241+ except Exception as e :
242+ logger .debug (f"Failed to save self link: { e } " )
232243
233244async def get_prev_link (redis : aioredis .Redis , token : Optional [str ]) -> Optional [str ]:
234245 """Get the previous page link for the current token (if exists)."""
235- if not token :
246+ if not redis or not token :
247+ return None
248+
249+ try :
250+ return await redis .get (f"nav:self:{ token } " )
251+ except Exception as e :
252+ logger .debug (f"Failed to get prev link: { e } " )
236253 return None
237- return await redis .get (f"nav:self:{ token } " )
238-
239254
240255async def redis_pagination_links (
241256 current_url : str , token : str , next_token : str , links : list
242257) -> None :
243258 """Handle Redis pagination."""
244- redis = await connect_redis ()
245- if not redis :
246- logger .warning ("Redis connection failed." )
247- return
248-
249- try :
250- if next_token :
251- await save_self_link (redis , next_token , current_url )
252-
253- prev_link = await get_prev_link (redis , token )
254- if prev_link :
255- links .insert (
256- 0 ,
257- {
258- "rel" : "prev" ,
259- "type" : "application/json" ,
260- "method" : "GET" ,
261- "href" : prev_link ,
262- },
263- )
264- except Exception as e :
265- logger .warning (f"Redis pagination operation failed: { e } " )
266- finally :
267- await redis .close ()
259+ async with redis_connection () as redis :
260+ if not redis :
261+ logger .warning ("Redis connection failed." )
262+ return
263+
264+ try :
265+ if next_token :
266+ await save_self_link (redis , next_token , current_url )
267+
268+ prev_link = await get_prev_link (redis , token )
269+ if prev_link :
270+ links .insert (
271+ 0 ,
272+ {
273+ "rel" : "prev" ,
274+ "type" : "application/json" ,
275+ "method" : "GET" ,
276+ "href" : prev_link ,
277+ },
278+ )
279+ except Exception as e :
280+ logger .warning (f"Redis pagination operation failed: { e } " )
0 commit comments