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,26 @@ 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 (
174+ sentinel_settings .REDIS_SENTINEL_HOSTS or standalone_settings .REDIS_HOST
175+ ):
176+ logger .debug ("Redis not configured, skipping connection" )
177+ yield None
178+ return
179+
169180 if sentinel_settings .REDIS_SENTINEL_HOSTS :
170181 sentinel_nodes = sentinel_settings .get_sentinel_nodes ()
182+ if not sentinel_nodes :
183+ logger .warning ("No Redis Sentinel nodes configured" )
184+ yield None
185+ return
186+
171187 sentinel = Sentinel (
172188 sentinel_nodes ,
173189 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
@@ -185,83 +201,85 @@ async def connect_redis() -> Optional[aioredis.Redis]:
185201 logger .info ("Connected to Redis Sentinel" )
186202
187203 elif standalone_settings .REDIS_HOST :
188- pool = aioredis .ConnectionPool (
204+ redis = aioredis .Redis (
189205 host = standalone_settings .REDIS_HOST ,
190206 port = standalone_settings .REDIS_PORT ,
191207 db = standalone_settings .REDIS_DB ,
192- max_connections = standalone_settings .REDIS_MAX_CONNECTIONS ,
193208 decode_responses = standalone_settings .REDIS_DECODE_RESPONSES ,
194209 retry_on_timeout = standalone_settings .REDIS_RETRY_TIMEOUT ,
210+ client_name = standalone_settings .REDIS_CLIENT_NAME ,
195211 health_check_interval = standalone_settings .REDIS_HEALTH_CHECK_INTERVAL ,
212+ socket_connect_timeout = 5.0 , # Short timeout for tests
213+ socket_timeout = 5.0 ,
196214 )
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
204215
205- return redis
216+ await redis .ping ()
217+ logger .debug ("Connected to Redis" )
218+
219+ yield redis
206220
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
216221 except Exception as e :
217- logger .error (f"Failed to connect to Redis: { e } " )
218- return None
222+ logger .debug (f"Redis connection failed (this is normal in tests): { e } " )
223+ yield None
224+ finally :
225+ if redis :
226+ await redis .close ()
227+ logger .debug ("Redis connection closed" )
219228
220229
221230async def save_self_link (
222231 redis : aioredis .Redis , token : Optional [str ], self_href : str
223232) -> None :
224233 """Save the self link for the current token."""
225- if token :
234+ if redis and token :
226235 if sentinel_settings .REDIS_SENTINEL_HOSTS :
227236 ttl_seconds = sentinel_settings .REDIS_SELF_LINK_TTL
228237 elif standalone_settings .REDIS_HOST :
229238 ttl_seconds = standalone_settings .REDIS_SELF_LINK_TTL
230- await redis .setex (f"nav:self:{ token } " , ttl_seconds , self_href )
239+ else :
240+ return
241+
242+ try :
243+ await redis .setex (f"nav:self:{ token } " , ttl_seconds , self_href )
244+ except Exception as e :
245+ logger .debug (f"Failed to save self link: { e } " )
231246
232247
233248async def get_prev_link (redis : aioredis .Redis , token : Optional [str ]) -> Optional [str ]:
234249 """Get the previous page link for the current token (if exists)."""
235- if not token :
250+ if not redis or not token :
251+ return None
252+
253+ try :
254+ return await redis .get (f"nav:self:{ token } " )
255+ except Exception as e :
256+ logger .debug (f"Failed to get prev link: { e } " )
236257 return None
237- return await redis .get (f"nav:self:{ token } " )
238258
239259
240260async def redis_pagination_links (
241261 current_url : str , token : str , next_token : str , links : list
242262) -> None :
243263 """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 ()
264+ async with redis_connection () as redis :
265+ if not redis :
266+ logger .warning ("Redis connection failed." )
267+ return
268+
269+ try :
270+ if next_token :
271+ await save_self_link (redis , next_token , current_url )
272+
273+ prev_link = await get_prev_link (redis , token )
274+ if prev_link :
275+ links .insert (
276+ 0 ,
277+ {
278+ "rel" : "prev" ,
279+ "type" : "application/json" ,
280+ "method" : "GET" ,
281+ "href" : prev_link ,
282+ },
283+ )
284+ except Exception as e :
285+ logger .warning (f"Redis pagination operation failed: { e } " )
0 commit comments