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 
@@ -37,76 +42,71 @@ class RedisSettings(BaseSettings):
3742    REDIS_CLIENT_NAME : str  =  "stac-fastapi-app" 
3843    REDIS_HEALTH_CHECK_INTERVAL : int  =  30 
3944
40- 
41- # Select the Redis or Redis Sentinel configuration 
42- redis_settings :  BaseSettings  =  RedisSettings ()
45+ # Configure only one Redis configuration 
46+ sentinel_settings   =   RedisSentinelSettings () 
47+ standalone_settings  =  RedisSettings ()
4348
4449
45- async  def  connect_redis (settings :  Optional [ RedisSettings ]  =   None ) ->  aioredis .Redis :
46-     """Return a Redis connection.""" 
50+ async  def  connect_redis () ->  Optional [ aioredis .Redis ] :
51+     """Return a Redis connection Redis or Redis Sentinel .""" 
4752    global  redis_pool 
48-     settings  =  settings  or  redis_settings 
4953
50-     if  not  settings .REDIS_HOST  or  not  settings .REDIS_PORT :
51-         return  None 
52- 
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.""" 
73-     global  redis_pool 
54+     if  redis_pool  is  not None :
55+         return  redis_pool 
56+ 
57+     try :
58+         if  sentinel_settings .REDIS_SENTINEL_HOSTS :
59+             hosts  =  [
60+                 h .strip ()
61+                 for  h  in  sentinel_settings .REDIS_SENTINEL_HOSTS .split ("," )
62+                 if  h .strip ()
63+             ]
64+             ports  =  [
65+                 int (p .strip ())
66+                 for  p  in  sentinel_settings .REDIS_SENTINEL_PORTS .split ("," )
67+                 if  p .strip ()
68+             ]
7469
75-     settings  =  settings  or  redis_settings 
76- 
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 :
9170            sentinel  =  Sentinel (
9271                [(h , p ) for  h , p  in  zip (hosts , ports )],
93-                 decode_responses = settings .REDIS_DECODE_RESPONSES ,
72+                 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
9473            )
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 ,
103-             )
104-             redis_pool  =  master 
10574
106-         except  Exception :
75+             redis_pool  =  sentinel .master_for (
76+                 service_name = sentinel_settings .REDIS_SENTINEL_MASTER_NAME ,
77+                 db = sentinel_settings .REDIS_DB ,
78+                 decode_responses = sentinel_settings .REDIS_DECODE_RESPONSES ,
79+                 retry_on_timeout = sentinel_settings .REDIS_RETRY_TIMEOUT ,
80+                 client_name = sentinel_settings .REDIS_CLIENT_NAME ,
81+                 max_connections = sentinel_settings .REDIS_MAX_CONNECTIONS ,
82+                 health_check_interval = sentinel_settings .REDIS_HEALTH_CHECK_INTERVAL ,
83+             )
84+             logger .info ("Connected to Redis Sentinel" )
85+ 
86+         elif  standalone_settings .REDIS_HOST :
87+             pool  =  aioredis .ConnectionPool (
88+                 host = standalone_settings .REDIS_HOST ,
89+                 port = standalone_settings .REDIS_PORT ,
90+                 db = standalone_settings .REDIS_DB ,
91+                 max_connections = standalone_settings .REDIS_MAX_CONNECTIONS ,
92+                 decode_responses = standalone_settings .REDIS_DECODE_RESPONSES ,
93+                 retry_on_timeout = standalone_settings .REDIS_RETRY_TIMEOUT ,
94+                 health_check_interval = standalone_settings .REDIS_HEALTH_CHECK_INTERVAL ,
95+             )
96+             redis_pool  =  aioredis .Redis (
97+                 connection_pool = pool , client_name = standalone_settings .REDIS_CLIENT_NAME 
98+             )
99+             logger .info ("Connected to Redis" )
100+         else :
101+             logger .warning ("No Redis configuration found" )
107102            return  None 
108103
109-     return  redis_pool 
104+         return  redis_pool 
105+ 
106+     except  Exception  as  e :
107+         logger .error (f"Failed to connect to Redis: { e }  )
108+         redis_pool  =  None 
109+         return  None 
110110
111111
112112async  def  save_self_link (
@@ -122,3 +122,34 @@ async def get_prev_link(redis: aioredis.Redis, token: Optional[str]) -> Optional
122122    if  not  token :
123123        return  None 
124124    return  await  redis .get (f"nav:self:{ token }  )
125+ 
126+ 
127+ async  def  handle_pagination_links (
128+     current_url : str , token : str , next_token : str , links : list 
129+ ) ->  None :
130+     """Handle Redis pagination.""" 
131+     redis_enable  =  get_bool_env ("REDIS_ENABLE" , default = False )
132+     redis  =  None 
133+     if  redis_enable :
134+         try :
135+             redis  =  await  connect_redis ()
136+             logger .info ("Redis connection established successfully" )
137+         except  Exception  as  e :
138+             redis  =  None 
139+             logger .warning (f"Redis connection failed: { e }  )
140+ 
141+     if  redis_enable  and  redis :
142+         if  next_token :
143+             await  save_self_link (redis , next_token , current_url )
144+ 
145+         prev_link  =  await  get_prev_link (redis , token )
146+         if  prev_link :
147+             links .insert (
148+                 0 ,
149+                 {
150+                     "rel" : "prev" ,
151+                     "type" : "application/json" ,
152+                     "method" : "GET" ,
153+                     "href" : prev_link ,
154+                 },
155+             )
0 commit comments