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