Skip to content

Commit 7539746

Browse files
committed
more PR feedback
1 parent 2aff4ca commit 7539746

File tree

2 files changed

+90
-74
lines changed

2 files changed

+90
-74
lines changed

posthog/client.py

Lines changed: 86 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -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

posthog/utils.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import json
22
import logging
33
import numbers
4-
import pickle
54
import re
65
import time
76
from collections import defaultdict
@@ -293,8 +292,8 @@ def _serialize_entry(self, flag_result, flag_definition_version, timestamp=None)
293292
if timestamp is None:
294293
timestamp = time.time()
295294

296-
# Use pickle for FeatureFlagResult to preserve all object data
297-
serialized_result = pickle.dumps(flag_result).hex()
295+
# Use clean to make flag_result JSON-serializable for cross-platform compatibility
296+
serialized_result = clean(flag_result)
298297

299298
entry = {
300299
"flag_result": serialized_result,
@@ -306,14 +305,13 @@ def _serialize_entry(self, flag_result, flag_definition_version, timestamp=None)
306305
def _deserialize_entry(self, data):
307306
try:
308307
entry = json.loads(data)
309-
# Deserialize the flag result from hex-encoded pickle
310-
flag_result = pickle.loads(bytes.fromhex(entry["flag_result"]))
308+
flag_result = entry["flag_result"]
311309
return FlagCacheEntry(
312310
flag_result=flag_result,
313311
flag_definition_version=entry["flag_version"],
314312
timestamp=entry["timestamp"],
315313
)
316-
except (json.JSONDecodeError, pickle.PickleError, KeyError, ValueError):
314+
except (json.JSONDecodeError, KeyError, ValueError):
317315
# If deserialization fails, treat as cache miss
318316
return None
319317

0 commit comments

Comments
 (0)