Skip to content

Commit fb3dd2b

Browse files
committed
Refactor capture $feature_flag_called
1 parent b8ecbd7 commit fb3dd2b

File tree

1 file changed

+107
-41
lines changed

1 file changed

+107
-41
lines changed

posthog/client.py

Lines changed: 107 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,29 @@
1818
from posthog.exception_utils import exc_info_from_error, exceptions_from_error_tuple, handle_in_app
1919
from posthog.feature_flags import InconclusiveMatchError, match_feature_flag_properties
2020
from 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+
)
2231
from posthog.utils import SizeLimitedDict, clean, guess_timezone, remove_trailing_slash
2332
from 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+
2544
try:
2645
import queue
2746
except 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

Comments
 (0)