Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .sampo/changesets/fearless-lady-kalma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pypi/posthog: minor
---

`feature_enabled()` now accepts a `default_value` parameter, returned when the flag has no value β€” not loaded, a failed `/flags` request, or no flag with that key. A flag that has a value, including `False` and variant strings, still always wins over the default. Existing calls that don't pass `default_value` are unaffected (it defaults to `None`, preserving the current three-state return).
7 changes: 6 additions & 1 deletion posthog/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3050,6 +3050,7 @@ def feature_enabled(
key: str,
distinct_id: ID_TYPES,
*,
default_value: Optional[bool] = None,
groups: Optional[Mapping[str, Union[str, int]]] = None,
person_properties: Optional[Dict[str, Any]] = None,
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
Expand All @@ -3064,6 +3065,10 @@ def feature_enabled(
Args:
key: The feature flag key.
distinct_id: The distinct ID of the user.
default_value: Returned when the flag has no value β€” not loaded, a failed
flags request, or no flag with that key. A flag that has a value,
including `False` and variant strings, always wins over this default.
Defaults to `None`, preserving the pre-existing three-state return.
groups: A dictionary of group information.
person_properties: A dictionary of person properties.
group_properties: A dictionary of group properties.
Expand Down Expand Up @@ -3108,7 +3113,7 @@ def feature_enabled(
response = flag_result.get_value() if flag_result else None

if response is None:
return None
return default_value
return bool(response)

def _get_stale_flag_fallback(
Expand Down
34 changes: 34 additions & 0 deletions posthog/test/test_feature_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3703,6 +3703,40 @@ def test_feature_enabled_doesnt_exist(self, patch_flags, patch_poll):
patch_flags.side_effect = APIError(401, "flags error")
self.assertIsNone(client.feature_enabled("doesnt-exist", "distinct_id"))

@mock.patch("posthog.client.Poller")
@mock.patch("posthog.client.flags")
def test_feature_enabled_doesnt_exist_returns_caller_default(
self, patch_flags, patch_poll
):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = []

patch_flags.return_value = {"featureFlags": {}}
self.assertTrue(
client.feature_enabled("doesnt-exist", "distinct_id", default_value=True)
)
self.assertFalse(
client.feature_enabled("doesnt-exist", "distinct_id", default_value=False)
)

patch_flags.side_effect = APIError(401, "flags error")
self.assertTrue(
client.feature_enabled("doesnt-exist", "distinct_id", default_value=True)
)

@mock.patch("posthog.client.Poller")
@mock.patch("posthog.client.flags")
def test_feature_enabled_present_value_wins_over_default(
self, patch_flags, patch_poll
):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = []

patch_flags.return_value = {"featureFlags": {"beta-feature": False}}
self.assertFalse(
client.feature_enabled("beta-feature", "distinct_id", default_value=True)
)

@mock.patch("posthog.client.Poller")
@mock.patch("posthog.client.flags")
def test_personal_api_key_doesnt_exist(self, patch_flags, patch_poll):
Expand Down
2 changes: 1 addition & 1 deletion references/public_api_snapshot.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ method posthog.client.Client.alias(previous_id: ID_TYPES, distinct_id: Optional[
method posthog.client.Client.capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]
method posthog.client.Client.capture_exception(exception: Optional[ExceptionArg], **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]
method posthog.client.Client.evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations
method posthog.client.Client.feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool]
method posthog.client.Client.feature_enabled(key: str, distinct_id: ID_TYPES, *, default_value: Optional[bool] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool]
method posthog.client.Client.feature_flag_definitions()
method posthog.client.Client.flush(timeout_seconds: Optional[float] = 10) -> None
method posthog.client.Client.get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]]
Expand Down