11import atexit
22import logging
33import numbers
4- from collections import defaultdict
54from datetime import datetime , timedelta
6- from tokenize import group
7- from uuid import UUID , uuid4
5+ from uuid import UUID
86
97from dateutil .tz import tzutc
108from six import string_types
2523ID_TYPES = (numbers .Number , string_types , UUID )
2624MAX_DICT_SIZE = 50_000
2725
26+ # Ensures that debug level messages are logged when debug mode is on.
27+ # Otherwise, defaults to WARNING level. See https://docs.python.org/3/howto/logging.html#what-happens-if-no-configuration-is-provided
28+ logging .basicConfig ()
29+
2830
2931class Client (object ):
3032 """Create a new PostHog client."""
@@ -73,13 +75,10 @@ def __init__(
7375
7476 # personal_api_key: This should be a generated Personal API Key, private
7577 self .personal_api_key = personal_api_key
76-
7778 if debug :
78- # Ensures that debug level messages are logged when debug mode is on.
79- # Otherwise, defaults to WARNING level. See https://docs.python.org/3/howto/logging.html#what-happens-if-no-configuration-is-provided
80- logging .basicConfig (level = logging .DEBUG )
79+ self .log .setLevel (logging .DEBUG )
8180 else :
82- logging . basicConfig ( level = logging .WARNING )
81+ self . log . setLevel ( logging .WARNING )
8382
8483 if sync_mode :
8584 self .consumers = None
@@ -375,7 +374,7 @@ def _load_feature_flags(self):
375374 "More information: https://posthog.com/docs/api/overview" ,
376375 )
377376 else :
378- raise APIError ( status = e . status , message = e . message )
377+ self . log . error ( f"[FEATURE FLAGS] Error loading feature flags: { e } " )
379378 except Exception as e :
380379 self .log .warning (
381380 "[FEATURE FLAGS] Fetching feature flags failed with following error. We will retry in %s seconds."
@@ -429,7 +428,18 @@ def _compute_flag_locally(self, feature_flag, distinct_id, *, groups={}, person_
429428 else :
430429 return match_feature_flag_properties (feature_flag , distinct_id , person_properties )
431430
432- def feature_enabled (self , key , distinct_id , default = False , * , groups = {}, person_properties = {}, group_properties = {}):
431+ def feature_enabled (
432+ self ,
433+ key ,
434+ distinct_id ,
435+ default = False ,
436+ * ,
437+ groups = {},
438+ person_properties = {},
439+ group_properties = {},
440+ only_evaluate_locally = False ,
441+ send_feature_flag_events = True ,
442+ ):
433443 return bool (
434444 self .get_feature_flag (
435445 key ,
@@ -438,11 +448,22 @@ def feature_enabled(self, key, distinct_id, default=False, *, groups={}, person_
438448 groups = groups ,
439449 person_properties = person_properties ,
440450 group_properties = group_properties ,
451+ only_evaluate_locally = only_evaluate_locally ,
452+ send_feature_flag_events = send_feature_flag_events ,
441453 )
442454 )
443455
444456 def get_feature_flag (
445- self , key , distinct_id , default = False , * , groups = {}, person_properties = {}, group_properties = {}
457+ self ,
458+ key ,
459+ distinct_id ,
460+ default = False ,
461+ * ,
462+ groups = {},
463+ person_properties = {},
464+ group_properties = {},
465+ only_evaluate_locally = False ,
466+ send_feature_flag_events = True ,
446467 ):
447468 require ("key" , key , string_types )
448469 require ("distinct_id" , distinct_id , ID_TYPES )
@@ -472,7 +493,8 @@ def get_feature_flag(
472493 self .log .exception (f"[FEATURE FLAGS] Error while computing variant locally: { e } " )
473494 continue
474495
475- if response is None :
496+ flag_was_locally_evaluated = response is not None
497+ if not flag_was_locally_evaluated and not only_evaluate_locally :
476498 try :
477499 feature_flags = self .get_feature_variants (
478500 distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
@@ -483,14 +505,27 @@ def get_feature_flag(
483505 self .log .exception (f"[FEATURE FLAGS] Unable to get flag remotely: { e } " )
484506 response = default
485507
486- if key not in self .distinct_ids_feature_flags_reported [distinct_id ]:
508+ feature_flag_reported_key = f"{ key } _{ str (response )} "
509+ if (
510+ feature_flag_reported_key not in self .distinct_ids_feature_flags_reported [distinct_id ]
511+ and send_feature_flag_events
512+ ):
487513 self .capture (
488- distinct_id , "$feature_flag_called" , {"$feature_flag" : key , "$feature_flag_response" : response }
514+ distinct_id ,
515+ "$feature_flag_called" ,
516+ {
517+ "$feature_flag" : key ,
518+ "$feature_flag_response" : response ,
519+ "locally_evaluated" : flag_was_locally_evaluated ,
520+ },
521+ groups = groups ,
489522 )
490- self .distinct_ids_feature_flags_reported [distinct_id ].add (key )
523+ self .distinct_ids_feature_flags_reported [distinct_id ].add (feature_flag_reported_key )
491524 return response
492525
493- def get_all_flags (self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}):
526+ def get_all_flags (
527+ self , distinct_id , * , groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = False
528+ ):
494529 require ("distinct_id" , distinct_id , ID_TYPES )
495530 require ("groups" , groups , dict )
496531
@@ -520,7 +555,7 @@ def get_all_flags(self, distinct_id, *, groups={}, person_properties={}, group_p
520555 else :
521556 fallback_to_decide = True
522557
523- if fallback_to_decide :
558+ if fallback_to_decide and not only_evaluate_locally :
524559 try :
525560 feature_flags = self .get_feature_variants (
526561 distinct_id , groups = groups , person_properties = person_properties , group_properties = group_properties
0 commit comments