@@ -103,25 +103,22 @@ class Client(object):
103103 Basic usage:
104104 >>> client = Client("your-api-key")
105105
106- With feature flag fallback cache (memory):
107- >>> client = Client("your-api-key", enable_flag_fallback_cache=True)
106+ With memory-based feature flag fallback cache:
107+ >>> client = Client(
108+ ... "your-api-key",
109+ ... flag_fallback_cache_url="memory://local/?ttl=300&size=10000"
110+ ... )
108111
109112 With Redis fallback cache for high-scale applications:
110113 >>> client = Client(
111114 ... "your-api-key",
112- ... enable_flag_fallback_cache=True,
113- ... flag_fallback_cache_backend="redis",
114- ... flag_fallback_cache_redis_url="redis://localhost:6379/0"
115+ ... flag_fallback_cache_url="redis://localhost:6379/0/?ttl=300"
115116 ... )
116117
117- With existing Redis client:
118- >>> import redis
119- >>> redis_client = redis.Redis(host='localhost', port=6379, db=0)
118+ With Redis authentication:
120119 >>> client = Client(
121120 ... "your-api-key",
122- ... enable_flag_fallback_cache=True,
123- ... flag_fallback_cache_backend="redis",
124- ... flag_fallback_cache_redis_client=redis_client
121+ ... flag_fallback_cache_url="redis://username:password@localhost:6379/0/?ttl=300"
125122 ... )
126123 """
127124
@@ -154,12 +151,7 @@ def __init__(
154151 project_root = None ,
155152 privacy_mode = False ,
156153 before_send = None ,
157- enable_flag_fallback_cache = False ,
158- flag_fallback_cache_size = 10000 ,
159- flag_fallback_cache_ttl = 300 ,
160- flag_fallback_cache_backend = "memory" ,
161- flag_fallback_cache_redis_url = None ,
162- flag_fallback_cache_redis_client = None ,
154+ flag_fallback_cache_url = None ,
163155 ):
164156 self .queue = queue .Queue (max_queue_size )
165157
@@ -185,14 +177,7 @@ def __init__(
185177 )
186178 self .poller = None
187179 self .distinct_ids_feature_flags_reported = SizeLimitedDict (MAX_DICT_SIZE , set )
188- self .flag_cache = self ._initialize_flag_cache (
189- enable_flag_fallback_cache ,
190- flag_fallback_cache_backend ,
191- flag_fallback_cache_size ,
192- flag_fallback_cache_ttl ,
193- flag_fallback_cache_redis_url ,
194- flag_fallback_cache_redis_client ,
195- )
180+ self .flag_cache = self ._initialize_flag_cache (flag_fallback_cache_url )
196181 self .flag_definition_version = 0
197182 self .disabled = disabled
198183 self .disable_geoip = disable_geoip
@@ -1360,27 +1345,31 @@ def _get_all_flags_and_payloads_locally(
13601345 "featureFlagPayloads" : payloads ,
13611346 }, fallback_to_decide
13621347
1363- def _initialize_flag_cache (
1364- self ,
1365- enable_flag_fallback_cache ,
1366- backend ,
1367- cache_size ,
1368- cache_ttl ,
1369- redis_url ,
1370- redis_client ,
1371- ):
1348+ def _initialize_flag_cache (self , cache_url ):
13721349 """Initialize feature flag cache for graceful degradation during service outages.
13731350
13741351 When enabled, the cache stores flag evaluation results and serves them as fallback
13751352 when the PostHog API is unavailable. This ensures your application continues to
13761353 receive flag values even during outages.
13771354
1378- Example Redis usage:
1355+ Args:
1356+ cache_url: Cache configuration URL. Examples:
1357+ - None: Disable caching
1358+ - "memory://local/?ttl=300&size=10000": Memory cache with TTL and size
1359+ - "redis://localhost:6379/0/?ttl=300": Redis cache with TTL
1360+ - "redis://username:password@host:port/?ttl=300": Redis with auth
1361+
1362+ Example usage:
1363+ # Memory cache
1364+ client = Client(
1365+ "your-api-key",
1366+ flag_fallback_cache_url="memory://local/?ttl=300&size=10000"
1367+ )
1368+
1369+ # Redis cache
13791370 client = Client(
13801371 "your-api-key",
1381- enable_flag_fallback_cache=True,
1382- flag_fallback_cache_backend="redis",
1383- flag_fallback_cache_redis_url="redis://localhost:6379/0"
1372+ flag_fallback_cache_url="redis://localhost:6379/0/?ttl=300"
13841373 )
13851374
13861375 # Normal evaluation - cache is populated
@@ -1389,45 +1378,74 @@ def _initialize_flag_cache(
13891378 # During API outage - returns cached value instead of None
13901379 flag_value = client.get_feature_flag("my-flag", "user123") # Uses cache
13911380 """
1392- if not enable_flag_fallback_cache :
1381+ if not cache_url :
13931382 return None
13941383
1395- if backend == "redis" :
1396- try :
1397- # Try to import redis
1398- import redis
1384+ try :
1385+ from urllib . parse import urlparse , parse_qs
1386+ except ImportError :
1387+ from urlparse import urlparse , parse_qs
13991388
1400- # Use provided client or create from URL
1401- if redis_client :
1402- client = redis_client
1403- elif redis_url :
1404- client = redis .from_url (redis_url )
1405- else :
1406- raise ValueError (
1407- "Redis backend requires either flag_cache_redis_url or flag_cache_redis_client"
1389+ try :
1390+ parsed = urlparse (cache_url )
1391+ scheme = parsed .scheme .lower ()
1392+
1393+ # Parse query parameters
1394+ query_params = parse_qs (parsed .query )
1395+
1396+ # Extract common parameters with defaults
1397+ ttl = int (query_params .get ("ttl" , [300 ])[0 ])
1398+
1399+ if scheme == "memory" :
1400+ size = int (query_params .get ("size" , [10000 ])[0 ])
1401+ return FlagCache (size , ttl )
1402+
1403+ elif scheme == "redis" :
1404+ try :
1405+ # Try to import redis
1406+ import redis
1407+
1408+ # Reconstruct Redis URL without query parameters
1409+ redis_url = f"{ parsed .scheme } ://"
1410+ if parsed .username or parsed .password :
1411+ redis_url += f"{ parsed .username or '' } :{ parsed .password or '' } @"
1412+ redis_url += (
1413+ f"{ parsed .hostname or 'localhost' } :{ parsed .port or 6379 } "
14081414 )
1415+ if parsed .path :
1416+ redis_url += parsed .path
14091417
1410- # Test connection
1411- client .ping ()
1418+ client = redis .from_url (redis_url )
14121419
1413- return RedisFlagCache (client , default_ttl = cache_ttl )
1420+ # Test connection
1421+ client .ping ()
14141422
1415- except ImportError :
1416- self .log .warning (
1417- "[FEATURE FLAGS] Redis not available, falling back to memory cache"
1418- )
1419- return FlagCache (cache_size , cache_ttl )
1420- except Exception as e :
1421- self .log .warning (
1422- f"[FEATURE FLAGS] Redis connection failed: { e } , falling back to memory cache"
1423- )
1424- return FlagCache (cache_size , cache_ttl )
1423+ return RedisFlagCache (client , default_ttl = ttl )
14251424
1426- elif backend == "memory" :
1427- return FlagCache (cache_size , cache_ttl )
1425+ except ImportError :
1426+ self .log .warning (
1427+ "[FEATURE FLAGS] Redis not available, falling back to memory cache"
1428+ )
1429+ # Fallback to memory cache with same TTL
1430+ size = int (query_params .get ("size" , [10000 ])[0 ])
1431+ return FlagCache (size , ttl )
1432+ except Exception as e :
1433+ self .log .warning (
1434+ f"[FEATURE FLAGS] Redis connection failed: { e } , falling back to memory cache"
1435+ )
1436+ # Fallback to memory cache with same TTL
1437+ size = int (query_params .get ("size" , [10000 ])[0 ])
1438+ return FlagCache (size , ttl )
1439+ else :
1440+ raise ValueError (
1441+ f"Unknown cache URL scheme: { scheme } . Supported schemes: memory, redis"
1442+ )
14281443
1429- else :
1430- raise ValueError (f"Unknown flag cache backend: { backend } " )
1444+ except Exception as e :
1445+ self .log .warning (
1446+ f"[FEATURE FLAGS] Failed to parse cache URL '{ cache_url } ': { e } "
1447+ )
1448+ return None
14311449
14321450 def feature_flag_definitions (self ):
14331451 return self .feature_flags
0 commit comments