@@ -64,6 +64,7 @@ def __init__(
6464 self .gzip = gzip
6565 self .timeout = timeout
6666 self .feature_flags = None
67+ self .feature_flags_by_key = None
6768 self .group_type_mapping = None
6869 self .poll_interval = poll_interval
6970 self .poller = None
@@ -127,6 +128,14 @@ def identify(self, distinct_id=None, properties=None, context=None, timestamp=No
127128 return self ._enqueue (msg )
128129
129130 def get_feature_variants (self , distinct_id , groups = None , person_properties = None , group_properties = None ):
131+ resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties )
132+ return resp_data ["featureFlags" ]
133+
134+ def get_feature_payloads (self , distinct_id , groups = None , person_properties = None , group_properties = None ):
135+ resp_data = self .get_decide (distinct_id , groups , person_properties , group_properties )
136+ return resp_data ["featureFlagPayloads" ]
137+
138+ def get_decide (self , distinct_id , groups = None , person_properties = None , group_properties = None ):
130139 require ("distinct_id" , distinct_id , ID_TYPES )
131140
132141 if groups :
@@ -141,7 +150,7 @@ def get_feature_variants(self, distinct_id, groups=None, person_properties=None,
141150 "group_properties" : group_properties ,
142151 }
143152 resp_data = decide (self .api_key , self .host , timeout = 10 , ** request_data )
144- return resp_data [ "featureFlags" ]
153+ return resp_data
145154
146155 def capture (
147156 self ,
@@ -358,10 +367,18 @@ def shutdown(self):
358367
359368 def _load_feature_flags (self ):
360369 try :
370+
361371 response = get (
362- self .personal_api_key , f"/api/feature_flag/local_evaluation/?token={ self .api_key } " , self .host
372+ self .personal_api_key ,
373+ f"/api/feature_flag/local_evaluation/?token={ self .api_key } " ,
374+ self .host ,
375+ timeout = 10 ,
363376 )
377+
364378 self .feature_flags = response ["flags" ] or []
379+ self .feature_flags_by_key = {
380+ flag ["key" ]: flag for flag in self .feature_flags if flag .get ("key" ) is not None
381+ }
365382 self .group_type_mapping = response ["group_type_mapping" ] or {}
366383
367384 except APIError as e :
@@ -522,48 +539,117 @@ def get_feature_flag(
522539 self .distinct_ids_feature_flags_reported [distinct_id ].add (feature_flag_reported_key )
523540 return response
524541
542+ def get_feature_flag_payload (
543+ self ,
544+ key ,
545+ distinct_id ,
546+ * ,
547+ match_value = None ,
548+ groups = {},
549+ person_properties = {},
550+ group_properties = {},
551+ only_evaluate_locally = False ,
552+ send_feature_flag_events = True ,
553+ ):
554+ if match_value is None :
555+ match_value = self .get_feature_flag (
556+ key ,
557+ distinct_id ,
558+ groups = groups ,
559+ person_properties = person_properties ,
560+ group_properties = group_properties ,
561+ send_feature_flag_events = send_feature_flag_events ,
562+ only_evaluate_locally = True ,
563+ )
564+
565+ response = None
566+
567+ if match_value is not None :
568+ response = self ._compute_payload_locally (key , match_value )
569+
570+ if response is None and not only_evaluate_locally :
571+ decide_payloads = self .get_feature_payloads (distinct_id , groups , person_properties , group_properties )
572+ response = decide_payloads .get (str (key ).lower (), None )
573+
574+ return response
575+
576+ def _compute_payload_locally (self , key , match_value ):
577+ payload = None
578+
579+ if self .feature_flags_by_key is None :
580+ return payload
581+
582+ flag_definition = self .feature_flags_by_key .get (key ) or {}
583+ flag_filters = flag_definition .get ("filters" ) or {}
584+ flag_payloads = flag_filters .get ("payloads" ) or {}
585+ payload = flag_payloads .get (str (match_value ).lower (), None )
586+ return payload
587+
525588 def get_all_flags (
526589 self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = False
527590 ):
591+ flags = self .get_all_flags_and_payloads (
592+ distinct_id ,
593+ groups = groups ,
594+ person_properties = person_properties ,
595+ group_properties = group_properties ,
596+ only_evaluate_locally = only_evaluate_locally ,
597+ )
598+ return flags ["featureFlags" ]
599+
600+ def get_all_flags_and_payloads (
601+ self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = False
602+ ):
603+ flags , payloads , fallback_to_decide = self ._get_all_flags_and_payloads_locally (
604+ distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
605+ )
606+ response = {"featureFlags" : flags , "featureFlagPayloads" : payloads }
607+
608+ if fallback_to_decide and not only_evaluate_locally :
609+ try :
610+ flags_and_payloads = self .get_decide (
611+ distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
612+ )
613+ response = flags_and_payloads
614+ except Exception as e :
615+ self .log .exception (f"[FEATURE FLAGS] Unable to get feature flags and payloads: { e } " )
616+
617+ return response
618+
619+ def _get_all_flags_and_payloads_locally (self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}):
528620 require ("distinct_id" , distinct_id , ID_TYPES )
529621 require ("groups" , groups , dict )
530622
531623 if self .feature_flags == None and self .personal_api_key :
532624 self .load_feature_flags ()
533625
534- response = {}
626+ flags = {}
627+ payloads = {}
535628 fallback_to_decide = False
536-
537629 # If loading in previous line failed
538630 if self .feature_flags :
539631 for flag in self .feature_flags :
540632 try :
541- response [flag ["key" ]] = self ._compute_flag_locally (
633+ flags [flag ["key" ]] = self ._compute_flag_locally (
542634 flag ,
543635 distinct_id ,
544636 groups = groups ,
545637 person_properties = person_properties ,
546638 group_properties = group_properties ,
547639 )
640+ matched_payload = self ._compute_payload_locally (flag ["key" ], flags [flag ["key" ]])
641+ if matched_payload :
642+ payloads [flag ["key" ]] = matched_payload
548643 except InconclusiveMatchError as e :
549644 # No need to log this, since it's just telling us to fall back to `/decide`
550645 fallback_to_decide = True
551646 except Exception as e :
552- self .log .exception (f"[FEATURE FLAGS] Error while computing variant: { e } " )
647+ self .log .exception (f"[FEATURE FLAGS] Error while computing variant and payload : { e } " )
553648 fallback_to_decide = True
554649 else :
555650 fallback_to_decide = True
556651
557- if fallback_to_decide and not only_evaluate_locally :
558- try :
559- feature_flags = self .get_feature_variants (
560- distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
561- )
562- response = {** response , ** feature_flags }
563- except Exception as e :
564- self .log .exception (f"[FEATURE FLAGS] Unable to get feature variants: { e } " )
565-
566- return response
652+ return flags , payloads , fallback_to_decide
567653
568654
569655def require (name , field , data_type ):
0 commit comments