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 , DecideResponse
21+ from posthog .request import (
22+ DEFAULT_HOST ,
23+ APIError ,
24+ batch_post ,
25+ decide ,
26+ determine_server_host ,
27+ get ,
28+ remote_config ,
29+ DecideResponse ,
30+ )
2231from posthog .utils import SizeLimitedDict , clean , guess_timezone , remove_trailing_slash
2332from posthog .version import VERSION
24- from posthog .types import FlagsAndPayloads , FlagValue , to_values , to_payloads , to_flags_and_payloads , normalize_decide_response
33+ from posthog .types import (
34+ FlagsAndPayloads ,
35+ FeatureFlag ,
36+ FlagValue ,
37+ FlagMetadata ,
38+ to_values ,
39+ to_payloads ,
40+ to_flags_and_payloads ,
41+ normalize_decide_response ,
42+ )
43+
2544try :
2645 import queue
2746except ImportError :
@@ -217,10 +236,10 @@ def feature_flags(self, flags):
217236 Set the local evaluation feature flags.
218237 """
219238 self ._feature_flags = flags or []
220- self .feature_flags_by_key = {
221- flag [ "key" ]: flag for flag in self . _feature_flags if flag . get ( "key" ) is not None
222- }
223- assert self . feature_flags_by_key is not None , "feature_flags_by_key should be initialized when feature_flags is set"
239+ self .feature_flags_by_key = {flag [ "key" ]: flag for flag in self . _feature_flags if flag . get ( "key" ) is not None }
240+ assert (
241+ self . feature_flags_by_key is not None
242+ ) , "feature_flags_by_key should be initialized when feature_flags is set"
224243
225244 def identify (self , distinct_id = None , properties = None , context = None , timestamp = None , uuid = None , disable_geoip = None ):
226245 if context is not None :
@@ -271,7 +290,9 @@ def get_feature_flags_and_payloads(
271290 resp = self .get_decide (distinct_id , groups , person_properties , group_properties , disable_geoip )
272291 return to_flags_and_payloads (resp )
273292
274- def get_decide (self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None ) -> DecideResponse :
293+ def get_decide (
294+ self , distinct_id , groups = None , person_properties = None , group_properties = None , disable_geoip = None
295+ ) -> DecideResponse :
275296 require ("distinct_id" , distinct_id , ID_TYPES )
276297
277298 if disable_geoip is None :
@@ -802,7 +823,7 @@ def get_feature_flag(
802823 ) -> FlagValue | None :
803824 """
804825 Get a feature flag value for a key by evaluating locally or remotely
805- depending on whether local evaluation is enabled and the flag can be
826+ depending on whether local evaluation is enabled and the flag can be
806827 locally evaluated.
807828
808829 This also captures the $feature_flag_called event unless send_feature_flag_events is False.
@@ -820,6 +841,9 @@ def get_feature_flag(
820841
821842 response = self ._locally_evaluate_flag (key , distinct_id , groups , person_properties , group_properties )
822843
844+ flag_details = None
845+ request_id = None
846+
823847 flag_was_locally_evaluated = response is not None
824848 if not flag_was_locally_evaluated and not only_evaluate_locally :
825849 try :
@@ -837,33 +861,37 @@ def get_feature_flag(
837861 except Exception as e :
838862 self .log .exception (f"[FEATURE FLAGS] Unable to get flag remotely: { e } " )
839863
840- feature_flag_reported_key = f"{ key } _{ str (response )} "
841- if (
842- feature_flag_reported_key not in self .distinct_ids_feature_flags_reported [distinct_id ]
843- and send_feature_flag_events # noqa: W503
844- ):
845- self .capture (
864+ if send_feature_flag_events :
865+ self ._capture_feature_flag_called (
846866 distinct_id ,
847- "$feature_flag_called" ,
848- {
849- "$feature_flag" : key ,
850- "$feature_flag_response" : response ,
851- "locally_evaluated" : flag_was_locally_evaluated ,
852- f"$feature/{ key } " : response ,
853- },
854- groups = groups ,
855- disable_geoip = disable_geoip ,
867+ key ,
868+ response ,
869+ None ,
870+ flag_was_locally_evaluated ,
871+ groups ,
872+ disable_geoip ,
873+ request_id ,
874+ flag_details
856875 )
857- self . distinct_ids_feature_flags_reported [ distinct_id ]. add ( feature_flag_reported_key )
876+
858877 return response
859878
860- def _locally_evaluate_flag (self , key : str , distinct_id : str , groups : dict [str , str ], person_properties : dict [str , str ], group_properties : dict [str , str ]) -> FlagValue | None :
879+ def _locally_evaluate_flag (
880+ self ,
881+ key : str ,
882+ distinct_id : str ,
883+ groups : dict [str , str ],
884+ person_properties : dict [str , str ],
885+ group_properties : dict [str , str ],
886+ ) -> FlagValue | None :
861887 if self .feature_flags is None and self .personal_api_key :
862888 self .load_feature_flags ()
863889 response = None
864890
865891 if self .feature_flags :
866- assert self .feature_flags_by_key is not None , "feature_flags_by_key should be initialized when feature_flags is set"
892+ assert (
893+ self .feature_flags_by_key is not None
894+ ), "feature_flags_by_key should be initialized when feature_flags is set"
867895 # Local evaluation
868896 flag = self .feature_flags_by_key .get (key )
869897 if flag :
@@ -906,6 +934,8 @@ def get_feature_flag_payload(
906934
907935 response = None
908936 payload = None
937+ flag_details = None
938+ request_id = None
909939
910940 if match_value is not None :
911941 payload = self ._compute_payload_locally (key , match_value )
@@ -919,36 +949,73 @@ def get_feature_flag_payload(
919949 featureFlags = responses_and_payloads ["featureFlags" ]
920950 if featureFlags is not None :
921951 response = featureFlags .get (key , None )
922-
952+
923953 featureFlagPayloads = responses_and_payloads ["featureFlagPayloads" ]
924954 if featureFlagPayloads is not None :
925955 payload = featureFlagPayloads .get (str (key ), None )
926956 except Exception as e :
927957 self .log .exception (f"[FEATURE FLAGS] Unable to get feature flags and payloads: { e } " )
928958
959+ if (send_feature_flag_events ):
960+ self ._capture_feature_flag_called (
961+ distinct_id ,
962+ key ,
963+ response ,
964+ payload ,
965+ flag_was_locally_evaluated ,
966+ groups ,
967+ disable_geoip ,
968+ request_id ,
969+ flag_details
970+ )
971+
972+ return payload
973+
974+ def _capture_feature_flag_called (
975+ self ,
976+ distinct_id : str ,
977+ key : str ,
978+ response : FlagValue ,
979+ payload : str | None ,
980+ flag_was_locally_evaluated : bool ,
981+ groups : dict [str , str ],
982+ disable_geoip : bool | None ,
983+ request_id : str | None ,
984+ flag_details : FeatureFlag | None ,
985+ ):
929986 feature_flag_reported_key = f"{ key } _{ str (response )} "
930987
931- if (
932- feature_flag_reported_key not in self .distinct_ids_feature_flags_reported [distinct_id ]
933- and send_feature_flag_events # noqa: W503
934- ):
988+ if feature_flag_reported_key not in self .distinct_ids_feature_flags_reported [distinct_id ]:
989+ properties = {
990+ "$feature_flag" : key ,
991+ "$feature_flag_response" : response ,
992+ "locally_evaluated" : flag_was_locally_evaluated ,
993+ f"$feature/{ key } " : response ,
994+ }
995+
996+ if payload :
997+ properties ["$feature_flag_payload" ] = payload
998+
999+ if request_id :
1000+ properties ["$feature_flag_request_id" ] = request_id
1001+ if isinstance (flag_details , FeatureFlag ):
1002+ if flag_details .reason and flag_details .reason .description :
1003+ properties ["$feature_flag_reason" ] = flag_details .reason .description
1004+ if isinstance (flag_details .metadata , FlagMetadata ):
1005+ if flag_details .metadata .version :
1006+ properties ["$feature_flag_version" ] = flag_details .metadata .version
1007+ if flag_details .metadata .id :
1008+ properties ["$feature_flag_id" ] = flag_details .metadata .id
1009+
9351010 self .capture (
9361011 distinct_id ,
9371012 "$feature_flag_called" ,
938- {
939- "$feature_flag" : key ,
940- "$feature_flag_response" : response ,
941- "$feature_flag_payload" : payload ,
942- "locally_evaluated" : flag_was_locally_evaluated ,
943- f"$feature/{ key } " : response ,
944- },
1013+ properties ,
9451014 groups = groups ,
9461015 disable_geoip = disable_geoip ,
9471016 )
9481017 self .distinct_ids_feature_flags_reported [distinct_id ].add (feature_flag_reported_key )
9491018
950- return payload
951-
9521019 def get_remote_config_payload (self , key : str ):
9531020 if self .disabled :
9541021 return None
@@ -1042,7 +1109,6 @@ def get_all_flags_and_payloads(
10421109
10431110 return response
10441111
1045-
10461112 def _get_all_flags_and_payloads_locally (
10471113 self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}, warn_on_unknown_groups = False
10481114 ) -> tuple [FlagsAndPayloads , bool ]:
0 commit comments