11"""Utilities for connecting to and managing Redis connections."""
22
3+ import logging
34from typing import Optional
45
56from pydantic_settings import BaseSettings
67from redis import asyncio as aioredis
78from redis .asyncio .sentinel import Sentinel
89
10+ from stac_fastapi .core .utilities import get_bool_env
11+
912redis_pool : Optional [aioredis .Redis ] = None
1013
14+ logger = logging .getLogger (__name__ )
15+
1116
1217class RedisSentinelSettings (BaseSettings ):
1318 """Configuration for connecting to Redis Sentinel."""
@@ -38,75 +43,76 @@ class RedisSettings(BaseSettings):
3843 REDIS_HEALTH_CHECK_INTERVAL : int = 30
3944
4045
41- # Select the Redis or Redis Sentinel configuration
42- redis_settings : BaseSettings = RedisSettings ()
43-
46+ # Create settings instances
47+ sentinel_settings = RedisSentinelSettings ()
48+ standalone_settings = RedisSettings ()
4449
45- async def connect_redis (settings : Optional [RedisSettings ] = None ) -> aioredis .Redis :
46- """Return a Redis connection."""
47- global redis_pool
48- settings = settings or redis_settings
49-
50- if not settings .REDIS_HOST or not settings .REDIS_PORT :
51- return None
5250
53- if redis_pool is None :
54- pool = aioredis .ConnectionPool (
55- host = settings .REDIS_HOST ,
56- port = settings .REDIS_PORT ,
57- db = settings .REDIS_DB ,
58- max_connections = settings .REDIS_MAX_CONNECTIONS ,
59- decode_responses = settings .REDIS_DECODE_RESPONSES ,
60- retry_on_timeout = settings .REDIS_RETRY_TIMEOUT ,
61- health_check_interval = settings .REDIS_HEALTH_CHECK_INTERVAL ,
62- )
63- redis_pool = aioredis .Redis (
64- connection_pool = pool , client_name = settings .REDIS_CLIENT_NAME
65- )
66- return redis_pool
67-
68-
69- async def connect_redis_sentinel (
70- settings : Optional [RedisSentinelSettings ] = None ,
71- ) -> Optional [aioredis .Redis ]:
72- """Return a Redis Sentinel connection."""
51+ async def connect_redis () -> Optional [aioredis .Redis ]:
52+ """Return a Redis connection, automatically detecting Sentinel or standalone."""
7353 global redis_pool
7454
75- settings = settings or redis_settings
55+ if redis_pool is not None :
56+ return redis_pool
57+
58+ try :
59+ # Check if Sentinel is configured
60+ if sentinel_settings .REDIS_SENTINEL_HOSTS :
61+ # Use Sentinel connection
62+ hosts = [
63+ h .strip ()
64+ for h in sentinel_settings .REDIS_SENTINEL_HOSTS .split ("," )
65+ if h .strip ()
66+ ]
67+ ports = [
68+ int (p .strip ())
69+ for p in sentinel_settings .REDIS_SENTINEL_PORTS .split ("," )
70+ if p .strip ()
71+ ]
7672
77- if (
78- not settings .REDIS_SENTINEL_HOSTS
79- or not settings .REDIS_SENTINEL_PORTS
80- or not settings .REDIS_SENTINEL_MASTER_NAME
81- ):
82- return None
83-
84- hosts = [h .strip () for h in settings .REDIS_SENTINEL_HOSTS .split ("," ) if h .strip ()]
85- ports = [
86- int (p .strip ()) for p in settings .REDIS_SENTINEL_PORTS .split ("," ) if p .strip ()
87- ]
88-
89- if redis_pool is None :
90- try :
9173 sentinel = Sentinel (
9274 [(h , p ) for h , p in zip (hosts , ports )],
93- decode_responses = settings .REDIS_DECODE_RESPONSES ,
75+ decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
9476 )
95- master = sentinel .master_for (
96- service_name = settings .REDIS_SENTINEL_MASTER_NAME ,
97- db = settings .REDIS_DB ,
98- decode_responses = settings .REDIS_DECODE_RESPONSES ,
99- retry_on_timeout = settings .REDIS_RETRY_TIMEOUT ,
100- client_name = settings .REDIS_CLIENT_NAME ,
101- max_connections = settings .REDIS_MAX_CONNECTIONS ,
102- health_check_interval = settings .REDIS_HEALTH_CHECK_INTERVAL ,
77+
78+ redis_pool = sentinel .master_for (
79+ service_name = sentinel_settings .REDIS_SENTINEL_MASTER_NAME ,
80+ db = sentinel_settings .REDIS_DB ,
81+ decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
82+ retry_on_timeout = sentinel_settings .REDIS_RETRY_TIMEOUT ,
83+ client_name = sentinel_settings .REDIS_CLIENT_NAME ,
84+ max_connections = sentinel_settings .REDIS_MAX_CONNECTIONS ,
85+ health_check_interval = sentinel_settings .REDIS_HEALTH_CHECK_INTERVAL ,
10386 )
104- redis_pool = master
10587
106- except Exception :
88+ logger .info ("Connected to Redis Sentinel" )
89+
90+ elif standalone_settings .REDIS_HOST :
91+ # Use standalone Redis connection
92+ pool = aioredis .ConnectionPool (
93+ host = standalone_settings .REDIS_HOST ,
94+ port = standalone_settings .REDIS_PORT ,
95+ db = standalone_settings .REDIS_DB ,
96+ max_connections = standalone_settings .REDIS_MAX_CONNECTIONS ,
97+ decode_responses = standalone_settings .REDIS_DECODE_RESPONSES ,
98+ retry_on_timeout = standalone_settings .REDIS_RETRY_TIMEOUT ,
99+ health_check_interval = standalone_settings .REDIS_HEALTH_CHECK_INTERVAL ,
100+ )
101+ redis_pool = aioredis .Redis (
102+ connection_pool = pool , client_name = standalone_settings .REDIS_CLIENT_NAME
103+ )
104+
105+ logger .info ("Connected to standalone Redis" )
106+ else :
107+ logger .warning ("No Redis configuration found" )
107108 return None
108109
109- return redis_pool
110+ return redis_pool
111+
112+ except Exception as e :
113+ logger .error (f"Failed to connect to Redis: { e } " )
114+ redis_pool = None
115+ return None
110116
111117
112118async def save_self_link (
@@ -122,3 +128,34 @@ async def get_prev_link(redis: aioredis.Redis, token: Optional[str]) -> Optional
122128 if not token :
123129 return None
124130 return await redis .get (f"nav:self:{ token } " )
131+
132+
133+ async def handle_pagination_links (
134+ current_url : str , token : str , next_token : str , links : list
135+ ) -> None :
136+ """Handle Redis pagination."""
137+ redis_enable = get_bool_env ("REDIS_ENABLE" , default = False )
138+ redis = None
139+ if redis_enable :
140+ try :
141+ redis = await connect_redis ()
142+ logger .info ("Redis connection established successfully" )
143+ except Exception as e :
144+ redis = None
145+ logger .warning (f"Redis connection failed, continuing without Redis: { e } " )
146+
147+ if redis_enable and redis :
148+ if next_token :
149+ await save_self_link (redis , next_token , current_url )
150+
151+ prev_link = await get_prev_link (redis , token )
152+ if prev_link :
153+ links .insert (
154+ 0 ,
155+ {
156+ "rel" : "prev" ,
157+ "type" : "application/json" ,
158+ "method" : "GET" ,
159+ "href" : prev_link ,
160+ },
161+ )
0 commit comments