1111
1212import distro # For Linux OS detection
1313from dateutil .tz import tzutc
14+ import hashlib
1415from six import string_types
1516
1617from posthog .consumer import Consumer
1718from posthog .exception_capture import ExceptionCapture
1819from posthog .exception_utils import exc_info_from_error , exceptions_from_error_tuple , handle_in_app
1920from posthog .feature_flags import InconclusiveMatchError , match_feature_flag_properties
2021from posthog .poller import Poller
21- from posthog .request import DEFAULT_HOST , APIError , batch_post , decide , determine_server_host , get , remote_config
22+ from posthog .request import DEFAULT_HOST , APIError , batch_post , decide , determine_server_host , get , flags , remote_config
2223from posthog .types import (
23- DecideResponse ,
24+ FlagsResponse ,
2425 FeatureFlag ,
2526 FlagMetadata ,
2627 FlagsAndPayloads ,
2728 FlagValue ,
28- normalize_decide_response ,
29+ normalize_flags_response ,
2930 to_flags_and_payloads ,
3031 to_payloads ,
3132 to_values ,
4243ID_TYPES = (numbers .Number , string_types , UUID )
4344MAX_DICT_SIZE = 50_000
4445
46+ # TODO: Get rid of these when you're done rolling out `/flags` to all customers
47+ ROLLOUT_PERCENTAGE = 0.1
48+ INCLUDED_HASHES = set ({'c4c6803067869081a8c4686780f32de979ade862c6af9ff9ebe5b7161e18362f' }) # this is PostHog's API key
49+ # 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
50+ EXCLUDED_HASHES = set ({
51+ '5fbb169efa185c2a78d43574b01b56c66d7bb594b310f72702e1f167e4e283a9' ,
52+ '374be8e6556709787d472e276ebe3c46c0ab4b868ec99f4c96168a44df8307df' ,
53+ '6c8a2d5e9dbd4c71854aebca3026fe50045b05e19a16780dccea5439625ee1b4' ,
54+ 'c4c6803067869081a8c4686780f32de979ade862c6af9ff9ebe5b7161e18362f' ,
55+ '0f1fa079412bb39b5fce8d96af3539925ede61cbc561ffcd38e27c8e8ae64edb' ,
56+ 'e3bdce3350e62638ffbf79872c2fd69ef6cbbd35712d9faf735f874cf77ccbfc' ,
57+ 'f96fe01cdf22f1ec75bc7c897e9605e6431fb5d8f6a8bb9d0e8fce2b0a1384a6' ,
58+ '6859b51ac773ea98e146bae47e98759f97ec64c253b9c0524ab56793cc5b6c75' ,
59+ '06b28c04e490ce1c9c017396b8b8e16fce1176a8b5de131a99d9af4df1d0fbc9' ,
60+ 'd9c0afa45a34c9f3c1e615bfa77394b79ad7b434ea46856e3503445d5974d640' ,
61+ '320eb50509e2c58a50d80fac848ee0b86290c848a173a0402abdbb760b794595' ,
62+ '7380abb65605420dd6e61534c8eecaa6f14d25a6f90ec2edba811f7383123ded' ,
63+ '3182881fa027d1c8e4eea108df66dcb0387e375d1e4b551c3a3579fdb1e696d1' ,
64+ 'd685aeb7d02ec757c4cbe591050a168d34be2f5305d9071d9695ed773057ef16' ,
65+ '875ab92bec4da51cf229145565364e98347fafaa2316a4a8e20f5d852bc95aed' ,
66+ '4a0d726e4b56d6f6d0407faf5396847146084bbabd042ca0dedba2873d8f9236' ,
67+ 'a9dc6415c1ccd1874ed1cd303e3d5bf92ddb17ac2af968abed14a51dfb0c53be' ,
68+ '5f10a055c9e379869a159306b1d7242fec25584ce895f677f82a13133741c7f1' ,
69+ 'e3e7608bbda7c15bf82fd7e2945ca74052f8b99e2090962318b6ef983c0ddb16' ,
70+ '7f0cbd50e11b475f6c2ed50e620c473e4bfc8df1f4c5174b49ecee1fcec6853e' ,
71+ '03004fb2209e6e4186c4364c71e5abc9cf272caf83cf58fb538c42684fd42fb0' ,
72+ '8721e8bf608c5eb4d74eeaf26fe588b4e5414742e0494ca7e67a89e1a297332b' ,
73+ 'ac0d5c7daee8d2f89d5b3861fba0b9a0e560b0eb6944e974f37cdc52274f2d1f' ,
74+ '6581d65cf0c4c536122beb5d581ba2b128ed44b7528c07d4ec7837ea33d0cffe' ,
75+ 'd0c2d4e122ecd4520af7bac133b09fde357622f20aa5a0f7a9328d25c9e9f28e' ,
76+ 'd09de64bec03c750493b0771c9f2731204bc9a5f0479628848803e2ccded9aca' ,
77+ 'a9f483f0cdc028a5e05d03d7ab683738f09a940c0173d9e6b004fbe85738a1f5' ,
78+ '2ffb5817a9fc465b9bb37b9112393cc1a274185f7f18618192421b7511b98830' ,
79+ 'a6785a722fdb0f975a1a30302f8312709ae069358c901c609f4898a9ae14bdf2' ,
80+ '3d9ba35cab44358cf47c867f48c95f75b9ad54ca5407ed19576da55a085d3a8f' ,
81+ 'ff59d2907ecb66f4d4a1705435460124a390d8cf7762dc7860d4b4171f832976' ,
82+ 'aac9e8036d3e0efed49cd5fbea19ea8354c4e1dfc95a1585300c5178189e5bac' ,
83+ '1e7fa74813f733e35ea820f8272c6562b4b0c70429f1b549605cc9e8016f632e' ,
84+ '2cb74b224cb20b8e5a5a52f3fe5ca62672e5c77ce7f30223698bb4d4abff2293' ,
85+ '17a90589bfe29f40f826e2df4753c0bce17a05f4c04b9a0924304e7418aba9e8' ,
86+ '0925e4c5bc65ced02c65aa3afba5eaa98aed288d193f719a8fbaebafdeafc1ce' ,
87+ 'a0308973730b505f1d6af7cd2f39c69bd86ea2a35b9d27118910e1c58d9a6a1a' ,
88+ 'c780092461636d6d62179723f03cbfe4a7b5808a6b46de749d8b32c3384f1e74' ,
89+ '65d6083548c27387f9381ff2aa37581a41ba1d5e6162afdc18cf8130be528052' ,
90+ 'e2241631d1211e15688735ec6d9f56b4839e65d2095f278630c884bd49f00be8' ,
91+ 'f2d9e1c10371912c32e9eba18f348782345ff70d383ae8b38bc9e6b12c7841e7' ,
92+ '57411b20e1c406ac4339718287b3eaa83635291fb593c9a4068dd08ec1d03692' ,
93+ '06e91ecd6b2a9a02234951ab3a5a95aeb84ef34499a5001629aaa13d907ba1dc' ,
94+ '4d2f47e99000f6820307e525fcf972421335a86f39b6ada1c93d67410520af49' ,
95+ '538d3b1415c3feccbe68d59b5ad9ed35aa418fc64658ff603855494abf75f647' ,
96+ '68b11387ac9f805bdbea486b9d3e0724856180646f2b12617a81174d5c27833c' ,
97+ 'a74797287c3d29f92fc729c2a8b3f17638cb273388e12cb8ffd972bcfbcdfdb8' ,
98+ 'b53d2b6551ebd8d68321dbd2727a299b1d23ff15853be02fffb0c54f1f0e1349' ,
99+ 'abad9dc57c9cb9a244b89b11f0a9123baf924a6908443dd8527cf6b411bbb33a' ,
100+ 'd17b55c7d72052d76d76a039e1ceb613d443401d30eae91ac903a07d5ee0d2d2' ,
101+ '274a08018c6e4609dedc37e31aea589c527cd7b93242d305591c3f5313408ee8' ,
102+ '75ed9cca6d877ea218647d6021b89c5959156eed2ce4ccad29d4e497d9cd0119' ,
103+ '4862317bab4b4efc876a810b92a6841bcf6ba69ac7aa7ff792358862528e7fa8' ,
104+ 'f0498fff4318e52729573a8bf451d7b978c5242af51ec8b1699798090bc00d32' ,
105+ 'a6a3435402f66a94eefd07b16297f6b4a61e26992e8ed7742de2e49d7ea71104' ,
106+ '72d8ede07d3ef0fd8eb0cd7261d29f4f33b3554e06a726db151138a25a01b539' ,
107+ '937c4aae120326c861eb3ec23371e029d3cea21f5849e4d52d75e47e06473e5c' ,
108+ 'e0138f35502faac574232bbbaab7ad769e2dcd449b596e32454368cb3cc035f9' ,
109+ '084e32dc89830d7bb120492ed55cc543de0405c7ae3d0c16c8f64ab07c44506d' ,
110+ 'd59f0ce1670146019b2c77b56ff8faca6346adfcc93443712a613a89298e3fb9' ,
111+ 'b99bd54a29c2e9adc17527f9df539415a1c0a83293f72e3e0c8744c5677ea1a1' ,
112+ 'c252a61d3c19f58062ca9fe2b13dfe378bc11380705cec703d9d8d0a0e167995'
113+ })
114+
45115
46116def get_os_info ():
47117 """
@@ -96,6 +166,37 @@ def system_context() -> dict[str, Any]:
96166 "$os_version" : os_version ,
97167 }
98168
169+ def is_token_in_rollout (token : str , percentage : float = 0 , included_hashes : set [str ] = None , excluded_hashes : set [str ] = None ) -> bool :
170+ """
171+ Determines if a token should be included in a rollout based on:
172+ 1. If its hash matches any included_hashes provided
173+ 2. If its hash falls within the percentage rollout
174+
175+ Args:
176+ token: String to hash (usually API key)
177+ percentage: Float between 0 and 1 representing rollout percentage
178+ included_hashes: Optional set of specific SHA1 hashes to match against
179+ excluded_hashes: Optional set of specific SHA1 hashes to exclude from rollout
180+ Returns:
181+ bool: True if token should be included in rollout
182+ """
183+ # First generate SHA1 hash of token
184+ token_hash = hashlib .sha1 (token .encode ('utf-8' )).hexdigest ()
185+
186+ # Check if hash matches any included hashes
187+ if included_hashes and token_hash in included_hashes :
188+ return True
189+
190+ # Check if hash matches any excluded hashes
191+ if excluded_hashes and token_hash in excluded_hashes :
192+ return False
193+
194+ # Convert first 8 chars of hash to int and divide by max value to get number between 0-1
195+ hash_int = int (token_hash [:8 ], 16 )
196+ hash_float = hash_int / 0xffffffff
197+
198+ return hash_float < percentage
199+
99200
100201class Client (object ):
101202 """Create a new PostHog client."""
@@ -263,7 +364,7 @@ def get_feature_variants(
263364 """
264365 Get feature flag variants for a distinct_id by calling decide.
265366 """
266- resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
367+ resp_data = self .get_flags_decision (distinct_id , groups , person_properties , group_properties , disable_geoip )
267368 return to_values (resp_data ) or {}
268369
269370 def get_feature_payloads (
@@ -272,7 +373,7 @@ def get_feature_payloads(
272373 """
273374 Get feature flag payloads for a distinct_id by calling decide.
274375 """
275- resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
376+ resp_data = self .get_flags_decision (distinct_id , groups , person_properties , group_properties , disable_geoip )
276377 return to_payloads (resp_data ) or {}
277378
278379 def get_feature_flags_and_payloads (
@@ -281,12 +382,15 @@ def get_feature_flags_and_payloads(
281382 """
282383 Get feature flags and payloads for a distinct_id by calling decide.
283384 """
284- resp = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
385+ resp = self .get_flags_decision (distinct_id , groups , person_properties , group_properties , disable_geoip )
285386 return to_flags_and_payloads (resp )
286387
287- def get_decide (
388+ def get_flags_decision (
288389 self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None
289- ) -> DecideResponse :
390+ ) -> FlagsResponse :
391+ """
392+ Get feature flags decision, using either flags() or decide() API based on rollout.
393+ """
290394 require ("distinct_id" , distinct_id , ID_TYPES )
291395
292396 if disable_geoip is None :
@@ -304,9 +408,85 @@ def get_decide(
304408 "group_properties" : group_properties ,
305409 "disable_geoip" : disable_geoip ,
306410 }
307- resp_data = decide (self .api_key , self .host , timeout = self .feature_flags_request_timeout_seconds , ** request_data )
308411
309- return normalize_decide_response (resp_data )
412+ # Check if this API key should use flags() instead of decide()
413+ # You can adjust these values as needed for the rollout
414+ ROLLOUT_PERCENTAGE = 0.1
415+ INCLUDED_HASHES = set ({'c4c6803067869081a8c4686780f32de979ade862c6af9ff9ebe5b7161e18362f' }) # this is PostHog's API key
416+ # 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
417+ EXCLUDED_HASHES = set ({
418+ '5fbb169efa185c2a78d43574b01b56c66d7bb594b310f72702e1f167e4e283a9' ,
419+ '374be8e6556709787d472e276ebe3c46c0ab4b868ec99f4c96168a44df8307df' ,
420+ '6c8a2d5e9dbd4c71854aebca3026fe50045b05e19a16780dccea5439625ee1b4' ,
421+ 'c4c6803067869081a8c4686780f32de979ade862c6af9ff9ebe5b7161e18362f' ,
422+ '0f1fa079412bb39b5fce8d96af3539925ede61cbc561ffcd38e27c8e8ae64edb' ,
423+ 'e3bdce3350e62638ffbf79872c2fd69ef6cbbd35712d9faf735f874cf77ccbfc' ,
424+ 'f96fe01cdf22f1ec75bc7c897e9605e6431fb5d8f6a8bb9d0e8fce2b0a1384a6' ,
425+ '6859b51ac773ea98e146bae47e98759f97ec64c253b9c0524ab56793cc5b6c75' ,
426+ '06b28c04e490ce1c9c017396b8b8e16fce1176a8b5de131a99d9af4df1d0fbc9' ,
427+ 'd9c0afa45a34c9f3c1e615bfa77394b79ad7b434ea46856e3503445d5974d640' ,
428+ '320eb50509e2c58a50d80fac848ee0b86290c848a173a0402abdbb760b794595' ,
429+ '7380abb65605420dd6e61534c8eecaa6f14d25a6f90ec2edba811f7383123ded' ,
430+ '3182881fa027d1c8e4eea108df66dcb0387e375d1e4b551c3a3579fdb1e696d1' ,
431+ 'd685aeb7d02ec757c4cbe591050a168d34be2f5305d9071d9695ed773057ef16' ,
432+ '875ab92bec4da51cf229145565364e98347fafaa2316a4a8e20f5d852bc95aed' ,
433+ '4a0d726e4b56d6f6d0407faf5396847146084bbabd042ca0dedba2873d8f9236' ,
434+ 'a9dc6415c1ccd1874ed1cd303e3d5bf92ddb17ac2af968abed14a51dfb0c53be' ,
435+ '5f10a055c9e379869a159306b1d7242fec25584ce895f677f82a13133741c7f1' ,
436+ 'e3e7608bbda7c15bf82fd7e2945ca74052f8b99e2090962318b6ef983c0ddb16' ,
437+ '7f0cbd50e11b475f6c2ed50e620c473e4bfc8df1f4c5174b49ecee1fcec6853e' ,
438+ '03004fb2209e6e4186c4364c71e5abc9cf272caf83cf58fb538c42684fd42fb0' ,
439+ '8721e8bf608c5eb4d74eeaf26fe588b4e5414742e0494ca7e67a89e1a297332b' ,
440+ 'ac0d5c7daee8d2f89d5b3861fba0b9a0e560b0eb6944e974f37cdc52274f2d1f' ,
441+ '6581d65cf0c4c536122beb5d581ba2b128ed44b7528c07d4ec7837ea33d0cffe' ,
442+ 'd0c2d4e122ecd4520af7bac133b09fde357622f20aa5a0f7a9328d25c9e9f28e' ,
443+ 'd09de64bec03c750493b0771c9f2731204bc9a5f0479628848803e2ccded9aca' ,
444+ 'a9f483f0cdc028a5e05d03d7ab683738f09a940c0173d9e6b004fbe85738a1f5' ,
445+ '2ffb5817a9fc465b9bb37b9112393cc1a274185f7f18618192421b7511b98830' ,
446+ 'a6785a722fdb0f975a1a30302f8312709ae069358c901c609f4898a9ae14bdf2' ,
447+ '3d9ba35cab44358cf47c867f48c95f75b9ad54ca5407ed19576da55a085d3a8f' ,
448+ 'ff59d2907ecb66f4d4a1705435460124a390d8cf7762dc7860d4b4171f832976' ,
449+ 'aac9e8036d3e0efed49cd5fbea19ea8354c4e1dfc95a1585300c5178189e5bac' ,
450+ '1e7fa74813f733e35ea820f8272c6562b4b0c70429f1b549605cc9e8016f632e' ,
451+ '2cb74b224cb20b8e5a5a52f3fe5ca62672e5c77ce7f30223698bb4d4abff2293' ,
452+ '17a90589bfe29f40f826e2df4753c0bce17a05f4c04b9a0924304e7418aba9e8' ,
453+ '0925e4c5bc65ced02c65aa3afba5eaa98aed288d193f719a8fbaebafdeafc1ce' ,
454+ 'a0308973730b505f1d6af7cd2f39c69bd86ea2a35b9d27118910e1c58d9a6a1a' ,
455+ 'c780092461636d6d62179723f03cbfe4a7b5808a6b46de749d8b32c3384f1e74' ,
456+ '65d6083548c27387f9381ff2aa37581a41ba1d5e6162afdc18cf8130be528052' ,
457+ 'e2241631d1211e15688735ec6d9f56b4839e65d2095f278630c884bd49f00be8' ,
458+ 'f2d9e1c10371912c32e9eba18f348782345ff70d383ae8b38bc9e6b12c7841e7' ,
459+ '57411b20e1c406ac4339718287b3eaa83635291fb593c9a4068dd08ec1d03692' ,
460+ '06e91ecd6b2a9a02234951ab3a5a95aeb84ef34499a5001629aaa13d907ba1dc' ,
461+ '4d2f47e99000f6820307e525fcf972421335a86f39b6ada1c93d67410520af49' ,
462+ '538d3b1415c3feccbe68d59b5ad9ed35aa418fc64658ff603855494abf75f647' ,
463+ '68b11387ac9f805bdbea486b9d3e0724856180646f2b12617a81174d5c27833c' ,
464+ 'a74797287c3d29f92fc729c2a8b3f17638cb273388e12cb8ffd972bcfbcdfdb8' ,
465+ 'b53d2b6551ebd8d68321dbd2727a299b1d23ff15853be02fffb0c54f1f0e1349' ,
466+ 'abad9dc57c9cb9a244b89b11f0a9123baf924a6908443dd8527cf6b411bbb33a' ,
467+ 'd17b55c7d72052d76d76a039e1ceb613d443401d30eae91ac903a07d5ee0d2d2' ,
468+ '274a08018c6e4609dedc37e31aea589c527cd7b93242d305591c3f5313408ee8' ,
469+ '75ed9cca6d877ea218647d6021b89c5959156eed2ce4ccad29d4e497d9cd0119' ,
470+ '4862317bab4b4efc876a810b92a6841bcf6ba69ac7aa7ff792358862528e7fa8' ,
471+ 'f0498fff4318e52729573a8bf451d7b978c5242af51ec8b1699798090bc00d32' ,
472+ 'a6a3435402f66a94eefd07b16297f6b4a61e26992e8ed7742de2e49d7ea71104' ,
473+ '72d8ede07d3ef0fd8eb0cd7261d29f4f33b3554e06a726db151138a25a01b539' ,
474+ '937c4aae120326c861eb3ec23371e029d3cea21f5849e4d52d75e47e06473e5c' ,
475+ 'e0138f35502faac574232bbbaab7ad769e2dcd449b596e32454368cb3cc035f9' ,
476+ '084e32dc89830d7bb120492ed55cc543de0405c7ae3d0c16c8f64ab07c44506d' ,
477+ 'd59f0ce1670146019b2c77b56ff8faca6346adfcc93443712a613a89298e3fb9' ,
478+ 'b99bd54a29c2e9adc17527f9df539415a1c0a83293f72e3e0c8744c5677ea1a1' ,
479+ 'c252a61d3c19f58062ca9fe2b13dfe378bc11380705cec703d9d8d0a0e167995'
480+ })
481+
482+ use_flags = is_token_in_rollout (self .api_key , ROLLOUT_PERCENTAGE , included_hashes = INCLUDED_HASHES , excluded_hashes = EXCLUDED_HASHES )
483+
484+ if use_flags :
485+ resp_data = flags (self .api_key , self .host , timeout = self .feature_flags_request_timeout_seconds , ** request_data )
486+ else :
487+ resp_data = decide (self .api_key , self .host , timeout = self .feature_flags_request_timeout_seconds , ** request_data )
488+
489+ return normalize_flags_response (resp_data )
310490
311491 def capture (
312492 self ,
@@ -970,7 +1150,7 @@ def _get_feature_flag_details_from_decide(
9701150 """
9711151 Calls /decide and returns the flag details and request id
9721152 """
973- resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
1153+ resp_data = self .get_flags_decision (distinct_id , groups , person_properties , group_properties , disable_geoip )
9741154 request_id = resp_data .get ("requestId" )
9751155 flags = resp_data .get ("flags" )
9761156 flag_details = flags .get (key ) if flags else None
@@ -1101,7 +1281,7 @@ def get_all_flags_and_payloads(
11011281
11021282 if fallback_to_decide and not only_evaluate_locally :
11031283 try :
1104- decide_response = self .get_decide (
1284+ decide_response = self .get_flags_decision (
11051285 distinct_id ,
11061286 groups = groups ,
11071287 person_properties = person_properties ,
@@ -1181,4 +1361,4 @@ def stringify_id(val):
11811361 return None
11821362 if isinstance (val , string_types ):
11831363 return val
1184- return str (val )
1364+ return str (val )
0 commit comments