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.""" 
@@ -25,7 +30,7 @@ class RedisSentinelSettings(BaseSettings):
2530
2631
2732class  RedisSettings (BaseSettings ):
28-     """Configuration for connecting Redis Sentinel .""" 
33+     """Configuration for connecting Redis.""" 
2934
3035    REDIS_HOST : str  =  "" 
3136    REDIS_PORT : int  =  6379 
@@ -38,75 +43,71 @@ 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+ # Configure only one  Redis configuration 
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 Redis or Redis Sentinel.""" 
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+         if  sentinel_settings .REDIS_SENTINEL_HOSTS :
60+             hosts  =  [
61+                 h .strip ()
62+                 for  h  in  sentinel_settings .REDIS_SENTINEL_HOSTS .split ("," )
63+                 if  h .strip ()
64+             ]
65+             ports  =  [
66+                 int (p .strip ())
67+                 for  p  in  sentinel_settings .REDIS_SENTINEL_PORTS .split ("," )
68+                 if  p .strip ()
69+             ]
7670
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 :
9171            sentinel  =  Sentinel (
9272                [(h , p ) for  h , p  in  zip (hosts , ports )],
93-                 decode_responses = settings .REDIS_DECODE_RESPONSES ,
94-             )
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 ,
73+                 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
10374            )
104-             redis_pool  =  master 
10575
106-         except  Exception :
76+             redis_pool  =  sentinel .master_for (
77+                 service_name = sentinel_settings .REDIS_SENTINEL_MASTER_NAME ,
78+                 db = sentinel_settings .REDIS_DB ,
79+                 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
80+                 retry_on_timeout = sentinel_settings .REDIS_RETRY_TIMEOUT ,
81+                 client_name = sentinel_settings .REDIS_CLIENT_NAME ,
82+                 max_connections = sentinel_settings .REDIS_MAX_CONNECTIONS ,
83+                 health_check_interval = sentinel_settings .REDIS_HEALTH_CHECK_INTERVAL ,
84+             )
85+             logger .info ("Connected to Redis Sentinel" )
86+ 
87+         elif  standalone_settings .REDIS_HOST :
88+             pool  =  aioredis .ConnectionPool (
89+                 host = standalone_settings .REDIS_HOST ,
90+                 port = standalone_settings .REDIS_PORT ,
91+                 db = standalone_settings .REDIS_DB ,
92+                 max_connections = standalone_settings .REDIS_MAX_CONNECTIONS ,
93+                 decode_responses = standalone_settings .REDIS_DECODE_RESPONSES ,
94+                 retry_on_timeout = standalone_settings .REDIS_RETRY_TIMEOUT ,
95+                 health_check_interval = standalone_settings .REDIS_HEALTH_CHECK_INTERVAL ,
96+             )
97+             redis_pool  =  aioredis .Redis (
98+                 connection_pool = pool , client_name = standalone_settings .REDIS_CLIENT_NAME 
99+             )
100+             logger .info ("Connected to Redis" )
101+         else :
102+             logger .warning ("No Redis configuration found" )
107103            return  None 
108104
109-     return  redis_pool 
105+         return  redis_pool 
106+ 
107+     except  Exception  as  e :
108+         logger .error (f"Failed to connect to Redis: { e }  )
109+         redis_pool  =  None 
110+         return  None 
110111
111112
112113async  def  save_self_link (
@@ -122,3 +123,34 @@ async def get_prev_link(redis: aioredis.Redis, token: Optional[str]) -> Optional
122123    if  not  token :
123124        return  None 
124125    return  await  redis .get (f"nav:self:{ token }  )
126+ 
127+ 
128+ async  def  handle_pagination_links (
129+     current_url : str , token : str , next_token : str , links : list 
130+ ) ->  None :
131+     """Handle Redis pagination.""" 
132+     redis_enable  =  get_bool_env ("REDIS_ENABLE" , default = False )
133+     redis  =  None 
134+     if  redis_enable :
135+         try :
136+             redis  =  await  connect_redis ()
137+             logger .info ("Redis connection established successfully" )
138+         except  Exception  as  e :
139+             redis  =  None 
140+             logger .warning (f"Redis connection failed: { e }  )
141+ 
142+     if  redis_enable  and  redis :
143+         if  next_token :
144+             await  save_self_link (redis , next_token , current_url )
145+ 
146+         prev_link  =  await  get_prev_link (redis , token )
147+         if  prev_link :
148+             links .insert (
149+                 0 ,
150+                 {
151+                     "rel" : "prev" ,
152+                     "type" : "application/json" ,
153+                     "method" : "GET" ,
154+                     "href" : prev_link ,
155+                 },
156+             )
0 commit comments