3131)
3232from posthog .types import (
3333 FeatureFlag ,
34+ FeatureFlagResult ,
3435 FlagMetadata ,
3536 FlagsAndPayloads ,
3637 FlagsResponse ,
@@ -939,25 +940,19 @@ def feature_enabled(
939940 return None
940941 return bool (response )
941942
942- def get_feature_flag (
943+ def _get_feature_flag_result (
943944 self ,
944945 key ,
945946 distinct_id ,
946947 * ,
948+ override_match_value : Optional [FlagValue ] = None ,
947949 groups = {},
948950 person_properties = {},
949951 group_properties = {},
950952 only_evaluate_locally = False ,
951953 send_feature_flag_events = True ,
952954 disable_geoip = None ,
953- ) -> Optional [FlagValue ]:
954- """
955- Get a feature flag value for a key by evaluating locally or remotely
956- depending on whether local evaluation is enabled and the flag can be
957- locally evaluated.
958-
959- This also captures the $feature_flag_called event unless send_feature_flag_events is False.
960- """
955+ ) -> Optional [FeatureFlagResult ]:
961956 require ("key" , key , string_types )
962957 require ("distinct_id" , distinct_id , ID_TYPES )
963958 require ("groups" , groups , dict )
@@ -969,36 +964,101 @@ def get_feature_flag(
969964 distinct_id , groups , person_properties , group_properties
970965 )
971966
972- response = self ._locally_evaluate_flag (key , distinct_id , groups , person_properties , group_properties )
973-
967+ flag_result = None
974968 flag_details = None
975969 request_id = None
976970
977- flag_was_locally_evaluated = response is not None
978- if not flag_was_locally_evaluated and not only_evaluate_locally :
971+ flag_value = self ._locally_evaluate_flag (key , distinct_id , groups , person_properties , group_properties )
972+ flag_was_locally_evaluated = flag_value is not None
973+
974+ if flag_was_locally_evaluated :
975+ lookup_match_value = override_match_value or flag_value
976+ payload = self ._compute_payload_locally (key , lookup_match_value ) if lookup_match_value else None
977+ flag_result = FeatureFlagResult .from_value_and_payload (key , lookup_match_value , payload )
978+ elif not only_evaluate_locally :
979979 try :
980980 flag_details , request_id = self ._get_feature_flag_details_from_decide (
981981 key , distinct_id , groups , person_properties , group_properties , disable_geoip
982982 )
983- response = flag_details . get_value () if flag_details else False
984- self .log .debug (f"Successfully computed flag remotely: #{ key } -> #{ response } " )
983+ flag_result = FeatureFlagResult . from_flag_details ( flag_details , override_match_value )
984+ self .log .debug (f"Successfully computed flag remotely: #{ key } -> #{ flag_result } " )
985985 except Exception as e :
986986 self .log .exception (f"[FEATURE FLAGS] Unable to get flag remotely: { e } " )
987987
988988 if send_feature_flag_events :
989989 self ._capture_feature_flag_called (
990990 distinct_id ,
991991 key ,
992- response or False ,
993- None ,
992+ flag_result . get_value () if flag_result else None ,
993+ flag_result . payload if flag_result else None ,
994994 flag_was_locally_evaluated ,
995995 groups ,
996996 disable_geoip ,
997997 request_id ,
998998 flag_details ,
999999 )
10001000
1001- return response
1001+ return flag_result
1002+
1003+ def get_feature_flag_result (
1004+ self ,
1005+ key ,
1006+ distinct_id ,
1007+ * ,
1008+ groups = {},
1009+ person_properties = {},
1010+ group_properties = {},
1011+ only_evaluate_locally = False ,
1012+ send_feature_flag_events = True ,
1013+ disable_geoip = None ,
1014+ ) -> Optional [FeatureFlagResult ]:
1015+ """
1016+ Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely
1017+ depending on whether local evaluation is enabled and the flag can be locally evaluated.
1018+
1019+ This also captures the $feature_flag_called event unless send_feature_flag_events is False.
1020+ """
1021+ return self ._get_feature_flag_result (
1022+ key ,
1023+ distinct_id ,
1024+ groups = groups ,
1025+ person_properties = person_properties ,
1026+ group_properties = group_properties ,
1027+ only_evaluate_locally = only_evaluate_locally ,
1028+ send_feature_flag_events = send_feature_flag_events ,
1029+ disable_geoip = disable_geoip ,
1030+ )
1031+
1032+ def get_feature_flag (
1033+ self ,
1034+ key ,
1035+ distinct_id ,
1036+ * ,
1037+ groups = {},
1038+ person_properties = {},
1039+ group_properties = {},
1040+ only_evaluate_locally = False ,
1041+ send_feature_flag_events = True ,
1042+ disable_geoip = None ,
1043+ ) -> Optional [FlagValue ]:
1044+ """
1045+ Get a feature flag value for a key by evaluating locally or remotely
1046+ depending on whether local evaluation is enabled and the flag can be
1047+ locally evaluated.
1048+
1049+ This also captures the $feature_flag_called event unless send_feature_flag_events is False.
1050+ """
1051+ feature_flag_result = self .get_feature_flag_result (
1052+ key ,
1053+ distinct_id ,
1054+ groups = groups ,
1055+ person_properties = person_properties ,
1056+ group_properties = group_properties ,
1057+ only_evaluate_locally = only_evaluate_locally ,
1058+ send_feature_flag_events = send_feature_flag_events ,
1059+ disable_geoip = disable_geoip ,
1060+ )
1061+ return feature_flag_result .get_value () if feature_flag_result else None
10021062
10031063 def _locally_evaluate_flag (
10041064 self ,
@@ -1039,56 +1099,26 @@ def get_feature_flag_payload(
10391099 key ,
10401100 distinct_id ,
10411101 * ,
1042- match_value = None ,
1102+ match_value : Optional [ FlagValue ] = None ,
10431103 groups = {},
10441104 person_properties = {},
10451105 group_properties = {},
10461106 only_evaluate_locally = False ,
10471107 send_feature_flag_events = True ,
10481108 disable_geoip = None ,
10491109 ):
1050- if self .disabled :
1051- return None
1052-
1053- if match_value is None :
1054- person_properties , group_properties = self ._add_local_person_and_group_properties (
1055- distinct_id , groups , person_properties , group_properties
1056- )
1057- match_value = self ._locally_evaluate_flag (key , distinct_id , groups , person_properties , group_properties )
1058-
1059- response = None
1060- payload = None
1061- flag_details = None
1062- request_id = None
1063-
1064- if match_value is not None :
1065- payload = self ._compute_payload_locally (key , match_value )
1066-
1067- flag_was_locally_evaluated = payload is not None
1068- if not flag_was_locally_evaluated and not only_evaluate_locally :
1069- try :
1070- flag_details , request_id = self ._get_feature_flag_details_from_decide (
1071- key , distinct_id , groups , person_properties , group_properties , disable_geoip
1072- )
1073- payload = flag_details .metadata .payload if flag_details else None
1074- response = flag_details .get_value () if flag_details else False
1075- except Exception as e :
1076- self .log .exception (f"[FEATURE FLAGS] Unable to get feature flags and payloads: { e } " )
1077-
1078- if send_feature_flag_events :
1079- self ._capture_feature_flag_called (
1080- distinct_id ,
1081- key ,
1082- response or False ,
1083- payload ,
1084- flag_was_locally_evaluated ,
1085- groups ,
1086- disable_geoip ,
1087- request_id ,
1088- flag_details ,
1089- )
1090-
1091- return payload
1110+ feature_flag_result = self ._get_feature_flag_result (
1111+ key ,
1112+ distinct_id ,
1113+ override_match_value = match_value ,
1114+ groups = groups ,
1115+ person_properties = person_properties ,
1116+ group_properties = group_properties ,
1117+ only_evaluate_locally = only_evaluate_locally ,
1118+ send_feature_flag_events = send_feature_flag_events ,
1119+ disable_geoip = disable_geoip ,
1120+ )
1121+ return feature_flag_result .payload if feature_flag_result else None
10921122
10931123 def _get_feature_flag_details_from_decide (
10941124 self ,
@@ -1112,15 +1142,15 @@ def _capture_feature_flag_called(
11121142 self ,
11131143 distinct_id : str ,
11141144 key : str ,
1115- response : FlagValue ,
1145+ response : Optional [ FlagValue ] ,
11161146 payload : Optional [str ],
11171147 flag_was_locally_evaluated : bool ,
11181148 groups : dict [str , str ],
11191149 disable_geoip : Optional [bool ],
11201150 request_id : Optional [str ],
11211151 flag_details : Optional [FeatureFlag ],
11221152 ):
1123- feature_flag_reported_key = f"{ key } _{ str (response )} "
1153+ feature_flag_reported_key = f"{ key } _{ '::null::' if response is None else str (response )} "
11241154
11251155 if feature_flag_reported_key not in self .distinct_ids_feature_flags_reported [distinct_id ]:
11261156 properties : dict [str , Any ] = {
@@ -1131,6 +1161,7 @@ def _capture_feature_flag_called(
11311161 }
11321162
11331163 if payload :
1164+ # if payload is not a string, json serialize it to a string
11341165 properties ["$feature_flag_payload" ] = payload
11351166
11361167 if request_id :
0 commit comments