diff --git a/example.py b/example.py index 7311a3cd..19d1c868 100644 --- a/example.py +++ b/example.py @@ -59,8 +59,19 @@ # get payload print(posthog.get_feature_flag_payload("beta-feature", "distinct_id")) print(posthog.get_all_flags_and_payloads("distinct_id")) -exit() -# # Alias a previous distinct id with a new one + +# get feature flag result with all details (enabled, variant, payload, key, reason) +result = posthog.get_feature_flag_result("beta-feature", "distinct_id") +if result: + print(f"Flag key: {result.key}") + print(f"Flag enabled: {result.enabled}") + print(f"Variant: {result.variant}") + print(f"Payload: {result.payload}") + print(f"Reason: {result.reason}") + # get_value() returns the variant if it exists, otherwise the enabled value + print(f"Value (variant or enabled): {result.get_value()}") + +# Alias a previous distinct id with a new one posthog.alias("distinct_id", "new_distinct_id") diff --git a/posthog/__init__.py b/posthog/__init__.py index 0c81a4e0..87852a54 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -11,7 +11,7 @@ set_context_session as inner_set_context_session, identify_context as inner_identify_context, ) -from posthog.types import FeatureFlag, FlagsAndPayloads +from posthog.types import FeatureFlag, FlagsAndPayloads, FeatureFlagResult from posthog.version import VERSION __version__ = VERSION @@ -388,9 +388,9 @@ def capture_exception( def feature_enabled( key, # type: str distinct_id, # type: str - groups={}, # type: dict - person_properties={}, # type: dict - group_properties={}, # type: dict + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] only_evaluate_locally=False, # type: bool send_feature_flag_events=True, # type: bool disable_geoip=None, # type: Optional[bool] @@ -427,9 +427,9 @@ def feature_enabled( "feature_enabled", key=key, distinct_id=distinct_id, - groups=groups, - person_properties=person_properties, - group_properties=group_properties, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, disable_geoip=disable_geoip, @@ -439,9 +439,9 @@ def feature_enabled( def get_feature_flag( key, # type: str distinct_id, # type: str - groups={}, # type: dict - person_properties={}, # type: dict - group_properties={}, # type: dict + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] only_evaluate_locally=False, # type: bool send_feature_flag_events=True, # type: bool disable_geoip=None, # type: Optional[bool] @@ -477,9 +477,9 @@ def get_feature_flag( "get_feature_flag", key=key, distinct_id=distinct_id, - groups=groups, - person_properties=person_properties, - group_properties=group_properties, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, disable_geoip=disable_geoip, @@ -488,9 +488,9 @@ def get_feature_flag( def get_all_flags( distinct_id, # type: str - groups={}, # type: dict - person_properties={}, # type: dict - group_properties={}, # type: dict + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] only_evaluate_locally=False, # type: bool disable_geoip=None, # type: Optional[bool] ) -> Optional[dict[str, FeatureFlag]]: @@ -520,21 +520,64 @@ def get_all_flags( return _proxy( "get_all_flags", distinct_id=distinct_id, - groups=groups, - person_properties=person_properties, - group_properties=group_properties, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, only_evaluate_locally=only_evaluate_locally, disable_geoip=disable_geoip, ) +def get_feature_flag_result( + key, + distinct_id, + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] + only_evaluate_locally=False, + send_feature_flag_events=True, + disable_geoip=None, # type: Optional[bool] +): + # type: (...) -> Optional[FeatureFlagResult] + """ + Get a FeatureFlagResult object which contains the flag result and payload. + + This method evaluates a feature flag and returns a FeatureFlagResult object containing: + - enabled: Whether the flag is enabled + - variant: The variant value if the flag has variants + - payload: The payload associated with the flag (automatically deserialized from JSON) + - key: The flag key + - reason: Why the flag was enabled/disabled + + Example: + ```python + result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') + if result and result.enabled: + # Use the variant and payload + print(f"Variant: {result.variant}") + print(f"Payload: {result.payload}") + ``` + """ + return _proxy( + "get_feature_flag_result", + key=key, + distinct_id=distinct_id, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, + only_evaluate_locally=only_evaluate_locally, + send_feature_flag_events=send_feature_flag_events, + disable_geoip=disable_geoip, + ) + + def get_feature_flag_payload( key, distinct_id, match_value=None, - groups={}, - person_properties={}, - group_properties={}, + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, # type: Optional[bool] @@ -544,9 +587,9 @@ def get_feature_flag_payload( key=key, distinct_id=distinct_id, match_value=match_value, - groups=groups, - person_properties=person_properties, - group_properties=group_properties, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, disable_geoip=disable_geoip, @@ -575,18 +618,18 @@ def get_remote_config_payload( def get_all_flags_and_payloads( distinct_id, - groups={}, - person_properties={}, - group_properties={}, + groups=None, # type: Optional[dict] + person_properties=None, # type: Optional[dict] + group_properties=None, # type: Optional[dict] only_evaluate_locally=False, disable_geoip=None, # type: Optional[bool] ) -> FlagsAndPayloads: return _proxy( "get_all_flags_and_payloads", distinct_id=distinct_id, - groups=groups, - person_properties=person_properties, - group_properties=group_properties, + groups=groups or {}, + person_properties=person_properties or {}, + group_properties=group_properties or {}, only_evaluate_locally=only_evaluate_locally, disable_geoip=disable_geoip, ) diff --git a/posthog/client.py b/posthog/client.py index 54b135fa..143d4630 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -83,6 +83,7 @@ def get_identity_state(passed) -> tuple[str, bool]: def add_context_tags(properties): + properties = properties or {} current_context = _get_current_context() if current_context: context_tags = current_context.collect_tags() @@ -395,7 +396,7 @@ def get_feature_flags_and_payloads( def get_flags_decision( self, distinct_id: Optional[ID_TYPES] = None, - groups: Optional[dict] = {}, + groups: Optional[dict] = None, person_properties=None, group_properties=None, disable_geoip=None, @@ -418,6 +419,9 @@ def get_flags_decision( Category: Feature Flags """ + groups = groups or {} + person_properties = person_properties or {} + group_properties = group_properties or {} if distinct_id is None: distinct_id = get_context_distinct_id() @@ -505,6 +509,7 @@ def capture( properties = {**(properties or {}), **system_context()} properties = add_context_tags(properties) + assert properties is not None # Type hint for mypy (distinct_id, personless) = get_identity_state(distinct_id) @@ -520,7 +525,7 @@ def capture( } if groups: - msg["properties"]["$groups"] = groups + properties["$groups"] = groups extra_properties: dict[str, Any] = {} feature_variants: Optional[dict[str, Union[bool, str]]] = {} @@ -575,7 +580,8 @@ def capture( extra_properties["$active_feature_flags"] = active_feature_flags if extra_properties: - msg["properties"] = {**extra_properties, **msg["properties"]} + properties = {**extra_properties, **properties} + msg["properties"] = properties return self._enqueue(msg, disable_geoip) @@ -1153,11 +1159,15 @@ def _compute_flag_locally( feature_flag, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, warn_on_unknown_groups=True, ) -> FlagValue: + groups = groups or {} + person_properties = person_properties or {} + group_properties = group_properties or {} + if feature_flag.get("ensure_experience_continuity", False): raise InconclusiveMatchError("Flag has experience continuity enabled") @@ -1203,9 +1213,9 @@ def feature_enabled( key, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, @@ -1256,9 +1266,9 @@ def _get_feature_flag_result( distinct_id: ID_TYPES, *, override_match_value: Optional[FlagValue] = None, - groups: Dict[str, str] = {}, - person_properties={}, - group_properties={}, + groups: Optional[Dict[str, str]] = None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, @@ -1268,9 +1278,16 @@ def _get_feature_flag_result( person_properties, group_properties = ( self._add_local_person_and_group_properties( - distinct_id, groups, person_properties, group_properties + distinct_id, + groups or {}, + person_properties or {}, + group_properties or {}, ) ) + # Ensure non-None values for type checking + groups = groups or {} + person_properties = person_properties or {} + group_properties = group_properties or {} flag_result = None flag_details = None @@ -1354,9 +1371,9 @@ def get_feature_flag_result( key, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, @@ -1404,9 +1421,9 @@ def get_feature_flag( key, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, @@ -1492,9 +1509,9 @@ def get_feature_flag_payload( distinct_id, *, match_value: Optional[FlagValue] = None, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, send_feature_flag_events=True, disable_geoip=None, @@ -1662,9 +1679,9 @@ def get_all_flags( self, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, disable_geoip=None, ) -> Optional[dict[str, Union[bool, str]]]: @@ -1702,9 +1719,9 @@ def get_all_flags_and_payloads( self, distinct_id, *, - groups={}, - person_properties={}, - group_properties={}, + groups=None, + person_properties=None, + group_properties=None, only_evaluate_locally=False, disable_geoip=None, ) -> FlagsAndPayloads: @@ -1765,10 +1782,13 @@ def _get_all_flags_and_payloads_locally( distinct_id: ID_TYPES, *, groups: Dict[str, Union[str, int]], - person_properties={}, - group_properties={}, + person_properties=None, + group_properties=None, warn_on_unknown_groups=False, ) -> tuple[FlagsAndPayloads, bool]: + person_properties = person_properties or {} + group_properties = group_properties or {} + if self.feature_flags is None and self.personal_api_key: self.load_feature_flags() diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index 301ead14..c9507e05 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -647,8 +647,8 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_flags) timeout=3, distinct_id="distinct_id", groups={}, - person_properties=None, - group_properties=None, + person_properties={}, + group_properties={}, geoip_disable=True, ) @@ -711,8 +711,8 @@ def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly( timeout=12, distinct_id="distinct_id", groups={}, - person_properties=None, - group_properties=None, + person_properties={}, + group_properties={}, geoip_disable=False, )