1818from posthog .exception_utils import exc_info_from_error , exceptions_from_error_tuple , handle_in_app
1919from posthog .feature_flags import InconclusiveMatchError , match_feature_flag_properties
2020from posthog .poller import Poller
21- from posthog .request import DEFAULT_HOST , APIError , batch_post , decide , determine_server_host , get , remote_config , normalize_decide_response , DecideResponse
21+ from posthog .request import DEFAULT_HOST , APIError , batch_post , decide , determine_server_host , get , remote_config , DecideResponse
2222from posthog .utils import SizeLimitedDict , clean , guess_timezone , remove_trailing_slash
2323from posthog .version import VERSION
24-
24+ from posthog . types import FlagsAndPayloads , FlagValue , to_values , to_payloads , to_flags_and_payloads , normalize_decide_response
2525try :
2626 import queue
2727except ImportError :
@@ -239,26 +239,23 @@ def identify(self, distinct_id=None, properties=None, context=None, timestamp=No
239239
240240 def get_feature_variants (
241241 self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None
242- ):
242+ ) -> dict [ str , str | bool ] :
243243 resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
244- return self . to_variants (resp_data )
244+ return to_values (resp_data ) or {}
245245
246246 def get_feature_payloads (
247247 self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None
248- ):
248+ ) -> dict [ str , str ] :
249249 resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
250- return self . to_payloads (resp_data )
250+ return to_payloads (resp_data ) or {}
251251
252252 def get_feature_flags_and_payloads (
253253 self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None
254- ):
255- resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
256- return {
257- "featureFlags" : self .to_variants (resp_data ),
258- "featureFlagPayloads" : self .to_payloads (resp_data ),
259- }
254+ ) -> FlagsAndPayloads :
255+ resp = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
256+ return to_flags_and_payloads (resp )
260257
261- def get_decide (self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None ):
258+ def get_decide (self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None ) -> DecideResponse :
262259 require ("distinct_id" , distinct_id , ID_TYPES )
263260
264261 if disable_geoip is None :
@@ -317,8 +314,8 @@ def capture(
317314 require ("groups" , groups , dict )
318315 msg ["properties" ]["$groups" ] = groups
319316
320- extra_properties = {}
321- feature_variants = {}
317+ extra_properties : dict [ str , Any ] = {}
318+ feature_variants : dict [ str , bool | str ] | None = {}
322319 if send_feature_flags :
323320 try :
324321 feature_variants = self .get_feature_variants (distinct_id , groups , disable_geoip = disable_geoip )
@@ -331,10 +328,10 @@ def capture(
331328 distinct_id , groups = (groups or {}), disable_geoip = disable_geoip , only_evaluate_locally = True
332329 )
333330
334- for feature , variant in feature_variants .items ():
331+ for feature , variant in ( feature_variants or {}) .items ():
335332 extra_properties [f"$feature/{ feature } " ] = variant
336333
337- active_feature_flags = [key for (key , value ) in feature_variants .items () if value is not False ]
334+ active_feature_flags = [key for (key , value ) in ( feature_variants or {}) .items () if value is not False ]
338335 if active_feature_flags :
339336 extra_properties ["$active_feature_flags" ] = active_feature_flags
340337
@@ -711,7 +708,7 @@ def _compute_flag_locally(
711708 person_properties = {},
712709 group_properties = {},
713710 warn_on_unknown_groups = True ,
714- ):
711+ ) -> FlagValue :
715712 if feature_flag .get ("ensure_experience_continuity" , False ):
716713 raise InconclusiveMatchError ("Flag has experience continuity enabled" )
717714
@@ -901,8 +898,13 @@ def get_feature_flag_payload(
901898 responses_and_payloads = self .get_feature_flags_and_payloads (
902899 distinct_id , groups , person_properties , group_properties , disable_geoip
903900 )
904- response = responses_and_payloads ["featureFlags" ].get (key , None )
905- payload = responses_and_payloads ["featureFlagPayloads" ].get (str (key ), None )
901+ featureFlags = responses_and_payloads ["featureFlags" ]
902+ if featureFlags is not None :
903+ response = featureFlags .get (key , None )
904+
905+ featureFlagPayloads = responses_and_payloads ["featureFlagPayloads" ]
906+ if featureFlagPayloads is not None :
907+ payload = featureFlagPayloads .get (str (key ), None )
906908 except Exception as e :
907909 self .log .exception (f"[FEATURE FLAGS] Unable to get feature flags and payloads: { e } " )
908910
@@ -949,7 +951,7 @@ def get_remote_config_payload(self, key: str):
949951 except Exception as e :
950952 self .log .exception (f"[FEATURE FLAGS] Unable to get decrypted feature flag payload: { e } " )
951953
952- def _compute_payload_locally (self , key , match_value ) :
954+ def _compute_payload_locally (self , key : str , match_value : FlagValue ) -> str | None :
953955 payload = None
954956
955957 if self .feature_flags_by_key is None :
@@ -974,7 +976,7 @@ def get_all_flags(
974976 group_properties = {},
975977 only_evaluate_locally = False ,
976978 disable_geoip = None ,
977- ) -> dict [str , bool | str ]:
979+ ) -> dict [str , bool | str ] | None :
978980 response = self .get_all_flags_and_payloads (
979981 distinct_id ,
980982 groups = groups ,
@@ -984,7 +986,7 @@ def get_all_flags(
984986 disable_geoip = disable_geoip ,
985987 )
986988
987- return self . to_variants ( response )
989+ return response [ "featureFlags" ]
988990
989991 def get_all_flags_and_payloads (
990992 self ,
@@ -995,46 +997,45 @@ def get_all_flags_and_payloads(
995997 group_properties = {},
996998 only_evaluate_locally = False ,
997999 disable_geoip = None ,
998- ) -> DecideResponse :
1000+ ) -> FlagsAndPayloads :
9991001 if self .disabled :
10001002 return {"featureFlags" : None , "featureFlagPayloads" : None }
10011003
10021004 person_properties , group_properties = self ._add_local_person_and_group_properties (
10031005 distinct_id , groups , person_properties , group_properties
10041006 )
10051007
1006- flags , payloads , fallback_to_decide = self ._get_all_flags_and_payloads_locally (
1008+ response , fallback_to_decide = self ._get_all_flags_and_payloads_locally (
10071009 distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
10081010 )
1009-
1010- response = normalize_decide_response ({"featureFlags" : flags , "featureFlagPayloads" : payloads })
10111011
10121012 if fallback_to_decide and not only_evaluate_locally :
10131013 try :
1014- flags_and_payloads = self .get_decide (
1014+ decide_response = self .get_decide (
10151015 distinct_id ,
10161016 groups = groups ,
10171017 person_properties = person_properties ,
10181018 group_properties = group_properties ,
10191019 disable_geoip = disable_geoip ,
10201020 )
1021- response = flags_and_payloads
1021+ return to_flags_and_payloads ( decide_response )
10221022 except Exception as e :
10231023 self .log .exception (f"[FEATURE FLAGS] Unable to get feature flags and payloads: { e } " )
10241024
10251025 return response
10261026
1027+
10271028 def _get_all_flags_and_payloads_locally (
10281029 self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}, warn_on_unknown_groups = False
1029- ):
1030+ ) -> tuple [ FlagsAndPayloads , bool ] :
10301031 require ("distinct_id" , distinct_id , ID_TYPES )
10311032 require ("groups" , groups , dict )
10321033
10331034 if self .feature_flags is None and self .personal_api_key :
10341035 self .load_feature_flags ()
10351036
1036- flags = {}
1037- payloads = {}
1037+ flags : dict [ str , FlagValue ] = {}
1038+ payloads : dict [ str , str ] = {}
10381039 fallback_to_decide = False
10391040 # If loading in previous line failed
10401041 if self .feature_flags :
@@ -1060,7 +1061,7 @@ def _get_all_flags_and_payloads_locally(
10601061 else :
10611062 fallback_to_decide = True
10621063
1063- return flags , payloads , fallback_to_decide
1064+ return { "featureFlags" : flags , "featureFlagPayloads" : payloads } , fallback_to_decide
10641065
10651066 def feature_flag_definitions (self ):
10661067 return self .feature_flags
@@ -1078,18 +1079,6 @@ def _add_local_person_and_group_properties(self, distinct_id, groups, person_pro
10781079
10791080 return all_person_properties , all_group_properties
10801081
1081- def to_variants (self , response : any ) -> dict [str , bool | str ]:
1082- if "flags" not in response :
1083- return None
1084-
1085- return {key : value .get_value () for key , value in response .get ("flags" , {}).items ()}
1086-
1087- def to_payloads (self , response : any ) -> dict [str , str ]:
1088- if "flags" not in response :
1089- return None
1090-
1091- return {key : value .metadata .payload for key , value in response .get ("flags" , {}).items () if value .enabled }
1092-
10931082
10941083def require (name , field , data_type ):
10951084 """Require that the named `field` has the right `data_type`"""
0 commit comments