11import atexit
22import inspect
3- import json
43import logging
54import os
65import sys
5150)
5251from posthog .feature_flag_evaluations import (
5352 FeatureFlagEvaluations ,
54- _EvaluatedFlagRecord ,
5553 _FeatureFlagEvaluationsHost ,
54+ _feature_flag_called_properties ,
55+ _flag_details_metadata ,
56+ _local_evaluation_records ,
57+ _remote_evaluation_records ,
5658)
5759from posthog .feature_flags import (
5860 InconclusiveMatchError ,
8587 FeatureFlag ,
8688 FeatureFlagError ,
8789 FeatureFlagResult ,
88- FlagMetadata ,
90+ FlagMetadata as FlagMetadata ,
8991 FlagsAndPayloads ,
9092 FlagsResponse ,
9193 FlagValue ,
@@ -2530,31 +2532,19 @@ def _capture_feature_flag_called(
25302532 flag_details : Optional [FeatureFlag ],
25312533 feature_flag_error : Optional [str ] = None ,
25322534 ):
2533- properties : dict [str , Any ] = {
2534- "$feature_flag" : key ,
2535- "$feature_flag_response" : response ,
2536- "locally_evaluated" : flag_was_locally_evaluated ,
2537- f"$feature/{ key } " : response ,
2538- }
2539-
2540- if payload is not None :
2541- # if payload is not a string, json serialize it to a string
2542- properties ["$feature_flag_payload" ] = payload
2543-
2544- if request_id :
2545- properties ["$feature_flag_request_id" ] = request_id
2546- if evaluated_at :
2547- properties ["$feature_flag_evaluated_at" ] = evaluated_at
2548- if isinstance (flag_details , FeatureFlag ):
2549- if flag_details .reason and flag_details .reason .description :
2550- properties ["$feature_flag_reason" ] = flag_details .reason .description
2551- if isinstance (flag_details .metadata , FlagMetadata ):
2552- if flag_details .metadata .version :
2553- properties ["$feature_flag_version" ] = flag_details .metadata .version
2554- if flag_details .metadata .id :
2555- properties ["$feature_flag_id" ] = flag_details .metadata .id
2556- if feature_flag_error :
2557- properties ["$feature_flag_error" ] = feature_flag_error
2535+ flag_id , flag_version , flag_reason = _flag_details_metadata (flag_details )
2536+ properties = _feature_flag_called_properties (
2537+ key = key ,
2538+ response = response ,
2539+ locally_evaluated = flag_was_locally_evaluated ,
2540+ payload = payload ,
2541+ request_id = request_id ,
2542+ evaluated_at = evaluated_at ,
2543+ flag_id = flag_id ,
2544+ flag_version = flag_version ,
2545+ flag_reason = flag_reason ,
2546+ feature_flag_error = feature_flag_error ,
2547+ )
25582548
25592549 self ._capture_feature_flag_called_if_needed (
25602550 distinct_id = distinct_id ,
@@ -2875,12 +2865,10 @@ def evaluate_flags(
28752865 )
28762866 groups = groups or {}
28772867
2878- records : Dict [str , _EvaluatedFlagRecord ] = {}
28792868 request_id : Optional [str ] = None
28802869 evaluated_at : Optional [int ] = None
28812870 errors_while_computing = False
28822871 quota_limited = False
2883- locally_evaluated_keys : set [str ] = set ()
28842872
28852873 # Try local evaluation first when the poller has loaded definitions.
28862874 local_result , fallback_to_server = self ._get_all_flags_and_payloads_locally (
@@ -2891,24 +2879,9 @@ def evaluate_flags(
28912879 flag_keys_to_evaluate = flag_keys ,
28922880 )
28932881
2894- feature_flags_by_key : Dict [str , Any ] = self .feature_flags_by_key or {}
2895- local_flags = local_result .get ("featureFlags" ) or {}
2896- local_payloads = local_result .get ("featureFlagPayloads" ) or {}
2897- for key , value in local_flags .items ():
2898- flag_def = feature_flags_by_key .get (key ) or {}
2899- records [key ] = _EvaluatedFlagRecord (
2900- key = key ,
2901- enabled = value is not False ,
2902- variant = value if isinstance (value , str ) else None ,
2903- payload = local_payloads .get (key ),
2904- id = flag_def .get ("id" ),
2905- # The local-evaluation flag definition does not carry a version field;
2906- # only the remote ``/flags`` response does via ``metadata.version``.
2907- version = None ,
2908- reason = "Evaluated locally" ,
2909- locally_evaluated = True ,
2910- )
2911- locally_evaluated_keys .add (key )
2882+ records , locally_evaluated_keys = _local_evaluation_records (
2883+ local_result , self .feature_flags_by_key or {}
2884+ )
29122885
29132886 # Fall back to remote evaluation for any flags the poller couldn't resolve locally.
29142887 # Use the flags decision path directly so the resulting records carry id/version/reason
@@ -2924,52 +2897,13 @@ def evaluate_flags(
29242897 flag_keys_to_evaluate = flag_keys ,
29252898 device_id = device_id ,
29262899 )
2927- request_id = response .get ("requestId" )
2928- raw_evaluated_at = response .get ("evaluatedAt" )
2929- evaluated_at = (
2930- raw_evaluated_at if isinstance (raw_evaluated_at , int ) else None
2931- )
2932- errors_while_computing = bool (
2933- response .get ("errorsWhileComputingFlags" , False )
2934- )
2935- for key , detail in response .get ("flags" , {}).items ():
2936- if key in locally_evaluated_keys :
2937- continue
2938- payload : Optional [Any ] = None
2939- raw_payload = (
2940- detail .metadata .payload
2941- if isinstance (detail .metadata , FlagMetadata )
2942- else getattr (detail .metadata , "payload" , None )
2943- )
2944- if isinstance (raw_payload , str ) and raw_payload :
2945- try :
2946- payload = json .loads (raw_payload )
2947- except (json .JSONDecodeError , TypeError ):
2948- payload = raw_payload
2949- elif raw_payload is not None :
2950- payload = raw_payload
2951- records [key ] = _EvaluatedFlagRecord (
2952- key = key ,
2953- enabled = detail .enabled ,
2954- variant = detail .variant ,
2955- payload = payload ,
2956- id = (
2957- detail .metadata .id
2958- if isinstance (detail .metadata , FlagMetadata )
2959- else None
2960- ),
2961- version = (
2962- detail .metadata .version
2963- if isinstance (detail .metadata , FlagMetadata )
2964- else None
2965- ),
2966- reason = (
2967- detail .reason .description
2968- if detail .reason and detail .reason .description
2969- else None
2970- ),
2971- locally_evaluated = False ,
2972- )
2900+ (
2901+ remote_records ,
2902+ request_id ,
2903+ evaluated_at ,
2904+ errors_while_computing ,
2905+ ) = _remote_evaluation_records (response , locally_evaluated_keys )
2906+ records .update (remote_records )
29732907 except QuotaLimitError as e :
29742908 self .log .warning (f"[FEATURE FLAGS] Quota limit exceeded: { e } " )
29752909 quota_limited = True
0 commit comments