Skip to content

Commit 1daa8a8

Browse files
authored
chore(flags): roll everyone onto /flags (#246)
1 parent 5d58a53 commit 1daa8a8

File tree

4 files changed

+10
-209
lines changed

4 files changed

+10
-209
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 4.4.0 - 2025-06-09
2+
3+
- Use the new `/flags` endpoint for all feature flag evaluations (don't fall back to `/decide` at all)
4+
15
## 4.3.2 - 2025-06-06
26

37
Add context management:

posthog/client.py

Lines changed: 4 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import atexit
2-
import hashlib
32
import logging
43
import numbers
54
import os
@@ -27,7 +26,6 @@
2726
DEFAULT_HOST,
2827
APIError,
2928
batch_post,
30-
decide,
3129
determine_server_host,
3230
flags,
3331
get,
@@ -58,78 +56,6 @@
5856
ID_TYPES = (numbers.Number, string_types, UUID)
5957
MAX_DICT_SIZE = 50_000
6058

61-
# TODO: Get rid of these when you're done rolling out `/flags` to all customers
62-
ROLLOUT_PERCENTAGE = 1
63-
INCLUDED_HASHES = set(
64-
{"bc94e67150c97dbcbf52549d50a7b80814841dbf"}
65-
) # this is PostHog's API key
66-
# Explicitly excluding all the API tokens associated with the top 10 customers; we'll get to them soon, but don't want to rollout to them just yet
67-
EXCLUDED_HASHES = set(
68-
{
69-
"03005596796f9ee626e9596b8062972cb6a556a0",
70-
"05620a20b287e0d5cb1d4a0dd492797f36b952c5",
71-
"0f95b5ca12878693c01c6420e727904f1737caa7",
72-
"1212b6287a6e7e5ff6be5cb30ec563f35c2139d6",
73-
"171ec1bb2caf762e06b1fde2e36a38c4638691a8",
74-
"171faa9fc754b1aa42252a4eedb948b7c805d5cb",
75-
"178ddde3f628fb0030321387acf939e4e6946d35",
76-
"1790085d7e9aa136e8b73c180dd6a6060e2ef949",
77-
"1895a3349c2371559c886f19ef1bf60617a934e0",
78-
"1f01267d4f0295f88e8943bc963d816ee4abc84b",
79-
"213df54990a34e62e3570b430f7ee36ec0928743",
80-
"23d235537d988ab98ad259853eab02b07d828c2b",
81-
"27135f7ae8f936222a5fcfcdc75c139b27dd3254",
82-
"2817396d80fafc86c0816af8e73880f8b3e54320",
83-
"29d3235e63db42056858ef04c6a5488c2a459eaa",
84-
"2a76d9b5eb9307e540de9d516aa80f6cb5a0292f",
85-
"2a92965a1344ab8a1f7dac2507e858f579a88ac2",
86-
"2d5823818261512d616161de2bb8a161d48f1e35",
87-
"32942f6a879dbfa8011cc68288c098e4a76e6cc0",
88-
"3db6c17ab65827ceadf77d9a8462fabd94170ca6",
89-
"4975b24f9ced9b2c06b604ddc9612f663f9452d5",
90-
"497c7b017b13cd6cdbfe641c71f0dfb660a4c518",
91-
"49c79e1dbce4a7b9394d6c14bf0421e04cecb445",
92-
"4d63e1c5cd3a80972eac4e7526f03357ac538043",
93-
"4da0f42a6f8f116822411152e5cda3c65ed2561f",
94-
"4e494675ecd2b841784d6f29b658b38a0877a62e",
95-
"4e852d8422130cec991eca2d6416dbe321d0a689",
96-
"5120bfd92c9c6731074a89e4a82f49e947d34369",
97-
"512cd72f9aa7ab11dfd012cc2e19394a020bd9a8",
98-
"5b175d4064cc62f01118a2c6818c2c02fc8f27e1",
99-
"5ba4bba3979e97d2c84df2aba394ca29c6c43187",
100-
"639014946463614353ca640b268dc6592f62b652",
101-
"643b9be9d50104e2b4ba94bc56688adba69c80fe",
102-
"658f92992af9fc6a360143d72d93a36f63bbccb0",
103-
"673a59c99739dfcee35202e428dd020b94866d52",
104-
"67a9829b4997f5c6f3ab8173ad299f634adcfa53",
105-
"6d686043e914ae8275df65e1ad890bd32a3b6fdd",
106-
"6e4b5e1d649ad006d78f1f1617a9a0f35fc73078",
107-
"6f1fc3a8fa9df54d00cbc1ef9ad5f24640589fd0",
108-
"764e5fec2c7899cfee620fae8450fcc62cd72bf0",
109-
"80ea6d6ed9a5895633c7bee7aba4323eeacdc90e",
110-
"872e420156f583bc97351f3d83c02dae734a85df",
111-
"8a24844cbeae31e74b4372964cdea74e99d9c0e2",
112-
"975ae7330506d4583b000f96ad87abb41a0141ce",
113-
"9e3d71378b340def3080e0a3a785a1b964cf43ef",
114-
"9ede7b21365661331d024d92915de6e69749892b",
115-
"a1ed1b4216ef4cec542c6b3b676507770be24ddc",
116-
"a4f66a70a9647b3b89fc59f7642af8ffab073ba1",
117-
"a7adb80be9e90948ab6bb726cc6e8e52694aec74",
118-
"bca4b14ac8de49cccc02306c7bb6e5ae2acc0f72",
119-
"bde5fe49f61e13629c5498d7428a7f6215e482a6",
120-
"c54a7074c323aa7c5cb7b24bf826751b2a58f5d8",
121-
"c552d20da0c87fb4ebe2da97c7f95c05eef2bca1",
122-
"d7682f2d268f3064d433309af34f2935810989d2",
123-
"d794ac43d8be26bf99f369ea79501eb774fe1b16",
124-
"e0963e2552af77d46bb24d5b5806b5b456c64c5f",
125-
"e6f14b2100cb0598925958b097ace82486037a25",
126-
"e79ec399ad45f44a4295a5bb1322e2f14600ae39",
127-
"eecf29f73f9c31009e5737a6c5ec3f87ec5b8ea6",
128-
"f2c01f3cc770c7788257ee60910e2530f92eefc3",
129-
"f7bbc58f4122b1e2812c0f1962c584cb404a1ac3",
130-
}
131-
)
132-
13359

13460
def get_os_info():
13561
"""
@@ -185,43 +111,6 @@ def system_context() -> dict[str, Any]:
185111
}
186112

187113

188-
def is_token_in_rollout(
189-
token: str,
190-
percentage: float = 0,
191-
included_hashes: Optional[set[str]] = None,
192-
excluded_hashes: Optional[set[str]] = None,
193-
) -> bool:
194-
"""
195-
Determines if a token should be included in a rollout based on:
196-
1. If its hash matches any included_hashes provided
197-
2. If its hash falls within the percentage rollout
198-
199-
Args:
200-
token: String to hash (usually API key)
201-
percentage: Float between 0 and 1 representing rollout percentage
202-
included_hashes: Optional set of specific SHA1 hashes to match against
203-
excluded_hashes: Optional set of specific SHA1 hashes to exclude from rollout
204-
Returns:
205-
bool: True if token should be included in rollout
206-
"""
207-
# First generate SHA1 hash of token
208-
token_hash = hashlib.sha1(token.encode("utf-8")).hexdigest()
209-
210-
# Check if hash matches any included hashes
211-
if included_hashes and token_hash in included_hashes:
212-
return True
213-
214-
# Check if hash matches any excluded hashes
215-
if excluded_hashes and token_hash in excluded_hashes:
216-
return False
217-
218-
# Convert first 8 chars of hash to int and divide by max value to get number between 0-1
219-
hash_int = int(token_hash[:8], 16)
220-
hash_float = hash_int / 0xFFFFFFFF
221-
222-
return hash_float < percentage
223-
224-
225114
class Client(object):
226115
"""Create a new PostHog client."""
227116

@@ -475,28 +364,13 @@ def get_flags_decision(
475364
"geoip_disable": disable_geoip,
476365
}
477366

478-
use_flags = is_token_in_rollout(
367+
resp_data = flags(
479368
self.api_key,
480-
ROLLOUT_PERCENTAGE,
481-
included_hashes=INCLUDED_HASHES,
482-
excluded_hashes=EXCLUDED_HASHES,
369+
self.host,
370+
timeout=self.feature_flags_request_timeout_seconds,
371+
**request_data,
483372
)
484373

485-
if use_flags:
486-
resp_data = flags(
487-
self.api_key,
488-
self.host,
489-
timeout=self.feature_flags_request_timeout_seconds,
490-
**request_data,
491-
)
492-
else:
493-
resp_data = decide(
494-
self.api_key,
495-
self.host,
496-
timeout=self.feature_flags_request_timeout_seconds,
497-
**request_data,
498-
)
499-
500374
return normalize_flags_response(resp_data)
501375

502376
def capture(

posthog/test/test_client.py

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import six
99
from parameterized import parameterized
1010

11-
from posthog.client import EXCLUDED_HASHES, INCLUDED_HASHES, Client, is_token_in_rollout
11+
from posthog.client import Client
1212
from posthog.request import APIError
1313
from posthog.test.test_utils import FAKE_TEST_API_KEY
1414
from posthog.types import FeatureFlag, LegacyFlagMetadata
@@ -1462,80 +1462,3 @@ def test_get_decide_returns_normalized_decide_response(self, patch_flags):
14621462
"errorsWhileComputingFlags": False,
14631463
"requestId": "test-id",
14641464
}
1465-
1466-
@mock.patch("posthog.client.decide")
1467-
@mock.patch("posthog.client.flags")
1468-
def test_get_flags_decision_rollout(self, patch_flags, patch_decide):
1469-
# Set up mock responses
1470-
decide_response = {
1471-
"featureFlags": {"flag1": True},
1472-
"featureFlagPayloads": {},
1473-
"errorsWhileComputingFlags": False,
1474-
}
1475-
flags_response = {
1476-
"featureFlags": {"flag2": True},
1477-
"featureFlagPayloads": {},
1478-
"errorsWhileComputingFlags": False,
1479-
}
1480-
patch_decide.return_value = decide_response
1481-
patch_flags.return_value = flags_response
1482-
1483-
client = Client(FAKE_TEST_API_KEY)
1484-
1485-
# Test 100% rollout - should use flags
1486-
with mock.patch(
1487-
"posthog.client.is_token_in_rollout", return_value=True
1488-
) as mock_rollout:
1489-
client.get_flags_decision("distinct_id")
1490-
mock_rollout.assert_called_with(
1491-
FAKE_TEST_API_KEY,
1492-
1,
1493-
included_hashes=INCLUDED_HASHES,
1494-
excluded_hashes=EXCLUDED_HASHES,
1495-
)
1496-
patch_flags.assert_called_once()
1497-
patch_decide.assert_not_called()
1498-
1499-
def test_token_rollout_calculation(self):
1500-
# Test specific hash inclusion
1501-
token = "test_token"
1502-
token_hash = hashlib.sha1(token.encode("utf-8")).hexdigest()
1503-
included_hashes = {token_hash}
1504-
1505-
# Should be included due to specific hash, even with 0% rollout
1506-
self.assertTrue(
1507-
expr=is_token_in_rollout(
1508-
token, percentage=0.0, included_hashes=included_hashes
1509-
)
1510-
)
1511-
1512-
# Should not be included with 0% rollout and no specific hash
1513-
self.assertFalse(is_token_in_rollout(token, percentage=0.0))
1514-
1515-
# Should be included with 100% rollout regardless of specific hash
1516-
self.assertTrue(is_token_in_rollout(token, percentage=1.0))
1517-
self.assertTrue(
1518-
is_token_in_rollout(token, percentage=1.0, included_hashes=included_hashes)
1519-
)
1520-
1521-
# Test deterministic behavior - same token should always give same result
1522-
hash_float = int(token_hash[:8], 16) / 0xFFFFFFFF
1523-
percentage = hash_float + 0.1 # Just above the hash value
1524-
1525-
self.assertTrue(is_token_in_rollout(token, percentage))
1526-
self.assertFalse(
1527-
is_token_in_rollout(token, percentage - 0.2)
1528-
) # Just below the hash value
1529-
1530-
# Test that the token exclusion works correctly
1531-
self.assertFalse(
1532-
is_token_in_rollout(token, percentage=1.0, excluded_hashes={token_hash})
1533-
)
1534-
1535-
# Should work for other specific token hashes
1536-
# Include our API key
1537-
self.assertTrue(
1538-
is_token_in_rollout(
1539-
"sTMFPsFhdP1Ssg", percentage=0.1, included_hashes=INCLUDED_HASHES
1540-
)
1541-
)

posthog/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = "4.3.2"
1+
VERSION = "4.4.0"
22

33
if __name__ == "__main__":
44
print(VERSION, end="") # noqa: T201

0 commit comments

Comments
 (0)