diff --git a/posthog/client.py b/posthog/client.py index 3ac2446d..c82a03fc 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -390,7 +390,7 @@ def get_feature_variants( device_id: Optional[str] = None, ) -> dict[str, Union[bool, str]]: """ - Get feature flag variants for a user by calling decide. + Get feature flag variants for a user. Args: distinct_id: The distinct ID of the user. @@ -427,7 +427,7 @@ def get_feature_payloads( device_id: Optional[str] = None, ) -> dict[str, str]: """ - Get feature flag payloads for a user by calling decide. + Get feature flag payloads for a user. Args: distinct_id: The distinct ID of the user. @@ -469,7 +469,7 @@ def get_feature_flags_and_payloads( device_id: Optional[str] = None, ) -> FlagsAndPayloads: """ - Get feature flags and payloads for a user by calling decide. + Get feature flags and payloads for a user. Args: distinct_id: The distinct ID of the user. diff --git a/posthog/request.py b/posthog/request.py index 84632ef9..3d335d17 100644 --- a/posthog/request.py +++ b/posthog/request.py @@ -73,7 +73,7 @@ def init_poolmanager(self, *args, **kwargs): def _build_session(socket_options: Optional[SocketOptions] = None) -> requests.Session: - """Build a session for general requests (batch, decide, etc.).""" + """Build a session for general requests (batch, remote config, etc.).""" adapter = HTTPAdapterWithSocketOptions( max_retries=Retry( total=2, @@ -221,7 +221,7 @@ def _process_response( if res.status_code == 200: log.debug(success_message) response = res.json() if return_json else res - # Handle quota limited decide responses by raising a specific error + # Handle quota-limited feature flag responses by raising a specific error # NB: other services also put entries into the quotaLimited key, but right now we only care about feature flags # since most of the other services handle quota limiting in other places in the application. if ( @@ -262,18 +262,6 @@ def _process_response( raise APIError(res.status_code, res.text, retry_after=retry_after) -def decide( - api_key: str, - host: Optional[str] = None, - gzip: bool = False, - timeout: int = 15, - **kwargs, -) -> Any: - """Post the `kwargs to the decide API endpoint""" - res = post(api_key, host, "/decide/?v=4", gzip, timeout, **kwargs) - return _process_response(res, success_message="Feature flags decided successfully") - - def flags( api_key: str, host: Optional[str] = None, diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index e137c1d2..aab452e2 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -219,7 +219,7 @@ def test_flag_group_properties(self, patch_get, patch_flags): # Now group type mappings are gone, so fall back to /flags/ patch_flags.return_value = { - "featureFlags": {"group-flag": "decide-fallback-value"} + "featureFlags": {"group-flag": "flags-fallback-value"} } self.client.group_type_mapping = {} @@ -230,7 +230,7 @@ def test_flag_group_properties(self, patch_get, patch_flags): groups={"company": "amazon"}, group_properties={"company": {"name": "Project Name 1"}}, ), - "decide-fallback-value", + "flags-fallback-value", ) self.assertEqual(patch_flags.call_count, 1) @@ -260,7 +260,7 @@ def test_group_flag_is_inconclusive_when_group_properties_missing(self): @mock.patch("posthog.client.get") def test_flag_with_complex_definition(self, patch_get, patch_flags): patch_flags.return_value = { - "featureFlags": {"complex-flag": "decide-fallback-value"} + "featureFlags": {"complex-flag": "flags-fallback-value"} } client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) client.feature_flags = [ @@ -341,7 +341,7 @@ def test_flag_with_complex_definition(self, patch_get, patch_flags): "some-distinct-id_outside_rollout?", person_properties={"region": "USA", "email": "a@b.com"}, ), - "decide-fallback-value", + "flags-fallback-value", ) self.assertEqual(patch_flags.call_count, 1) @@ -354,7 +354,7 @@ def test_flag_with_complex_definition(self, patch_get, patch_flags): "some-distinct-id", person_properties={"doesnt_matter": "1"}, ), - "decide-fallback-value", + "flags-fallback-value", ) self.assertEqual(patch_flags.call_count, 1) @@ -365,7 +365,7 @@ def test_flag_with_complex_definition(self, patch_get, patch_flags): client.get_feature_flag( "complex-flag", "some-distinct-id", person_properties={"region": "USA"} ), - "decide-fallback-value", + "flags-fallback-value", ) self.assertEqual(patch_flags.call_count, 1) @@ -438,13 +438,13 @@ def test_feature_flags_fallback_to_flags(self, patch_get, patch_flags): }, ] - # beta-feature fallbacks to decide because property type is unknown + # beta-feature falls back to /flags because property type is unknown feature_flag_match = client.get_feature_flag("beta-feature", "some-distinct-id") self.assertEqual(feature_flag_match, "alakazam") self.assertEqual(patch_flags.call_count, 1) - # beta-feature2 fallbacks to decide because region property not given with call + # beta-feature2 falls back to /flags because region property was not given with the call feature_flag_match = client.get_feature_flag( "beta-feature2", "some-distinct-id" ) @@ -506,7 +506,7 @@ def test_feature_flags_dont_fallback_to_flags_when_only_local_evaluation_is_true }, ] - # beta-feature should fallback to decide because property type is unknown, + # beta-feature would fall back to /flags because the property type is unknown, # but doesn't because only_evaluate_locally is true feature_flag_match = client.get_feature_flag( "beta-feature", "some-distinct-id", only_evaluate_locally=True @@ -522,7 +522,7 @@ def test_feature_flags_dont_fallback_to_flags_when_only_local_evaluation_is_true self.assertEqual(feature_flag_match, None) self.assertEqual(patch_flags.call_count, 0) - # beta-feature2 should fallback to decide because region property not given with call + # beta-feature2 would fall back to /flags because the region property was not given with the call, # but doesn't because only_evaluate_locally is true feature_flag_match = client.get_feature_flag( "beta-feature2", "some-distinct-id", only_evaluate_locally=True @@ -564,7 +564,7 @@ def test_feature_flag_never_returns_undefined_during_regular_evaluation( self.assertFalse(client.get_feature_flag("beta-feature", "some-distinct-id")) self.assertFalse(client.feature_enabled("beta-feature", "some-distinct-id")) - # beta-feature2 falls back to decide, and whatever decide returns is the value + # beta-feature2 falls back to /flags, and whatever /flags returns is the value self.assertFalse(client.get_feature_flag("beta-feature2", "some-distinct-id")) self.assertEqual(patch_flags.call_count, 1) @@ -573,14 +573,14 @@ def test_feature_flag_never_returns_undefined_during_regular_evaluation( @mock.patch("posthog.client.flags") @mock.patch("posthog.client.get") - def test_feature_flag_return_none_when_decide_errors_out( + def test_feature_flag_return_none_when_flags_errors_out( self, patch_get, patch_flags ): - patch_flags.side_effect = APIError(400, "Decide error") + patch_flags.side_effect = APIError(400, "Flags error") client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) client.feature_flags = [] - # beta-feature2 falls back to decide, which on error returns None + # beta-feature2 falls back to /flags, which on error returns None self.assertIsNone(client.get_feature_flag("beta-feature2", "some-distinct-id")) self.assertEqual(patch_flags.call_count, 1) @@ -590,7 +590,7 @@ def test_feature_flag_return_none_when_decide_errors_out( @mock.patch("posthog.client.flags") def test_experience_continuity_flag_not_evaluated_locally(self, patch_flags): patch_flags.return_value = { - "featureFlags": {"beta-feature": "decide-fallback-value"} + "featureFlags": {"beta-feature": "flags-fallback-value"} } client = Client(FAKE_TEST_API_KEY, personal_api_key="test") client.feature_flags = [ @@ -611,10 +611,10 @@ def test_experience_continuity_flag_not_evaluated_locally(self, patch_flags): "ensure_experience_continuity": True, } ] - # decide called always because experience_continuity is set + # /flags is always called when experience_continuity is set self.assertEqual( client.get_feature_flag("beta-feature", "distinct_id"), - "decide-fallback-value", + "flags-fallback-value", ) self.assertEqual(patch_flags.call_count, 1) @@ -627,7 +627,7 @@ def test_get_all_flags_with_fallback(self, patch_flags, patch_capture): "beta-feature2": "variant-2", "disabled-feature": False, } - } # decide should return the same flags + } # /flags should return the same flags client = self.client client.feature_flags = [ { @@ -838,7 +838,7 @@ def test_get_all_flags_with_no_fallback(self, patch_flags, patch_capture): client.get_all_flags("distinct_id"), {"beta-feature": True, "disabled-feature": False}, ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) self.assertEqual(patch_capture.call_count, 0) @@ -891,7 +891,7 @@ def test_get_all_flags_and_payloads_with_no_fallback( client.get_all_flags_and_payloads("distinct_id")["featureFlagPayloads"], {"beta-feature": "new"}, ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) self.assertEqual(patch_capture.call_count, 0) @@ -1073,7 +1073,7 @@ def test_compute_inactive_flags_locally(self, patch_flags, patch_capture): client.get_all_flags("distinct_id"), {"beta-feature": True, "disabled-feature": False}, ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) self.assertEqual(patch_capture.call_count, 0) @@ -1113,7 +1113,7 @@ def test_compute_inactive_flags_locally(self, patch_flags, patch_capture): client.get_all_flags("distinct_id"), {"beta-feature": False, "disabled-feature": True}, ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) self.assertEqual(patch_capture.call_count, 0) @@ -1363,7 +1363,7 @@ def test_feature_flags_local_evaluation_for_negated_cohorts( "some-distinct-id", person_properties={"region": "USA", "other": "thing"}, ) - # since 'other' is negated, we return False. Since 'nation' is not present, we can't tell whether the flag should be true or false, so go to decide + # since 'other' is negated, we return False. Since 'nation' is not present, we can't tell whether the flag should be true or false, so fall back to /flags self.assertEqual(patch_flags.call_count, 1) self.assertEqual(patch_get.call_count, 0) @@ -2669,7 +2669,7 @@ def test_feature_enabled_request_multi_variate(self, patch_flags): } ] self.assertTrue(client.feature_enabled("beta-feature", "distinct_id")) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) @mock.patch("posthog.client.get") @@ -2722,7 +2722,7 @@ def test_get_feature_flag(self, patch_flags): self.assertEqual( client.get_feature_flag("beta-feature", "distinct_id"), "variant-1" ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) @mock.patch("posthog.client.Poller") @@ -2734,7 +2734,7 @@ def test_feature_enabled_doesnt_exist(self, patch_flags, patch_poll): patch_flags.return_value = {"featureFlags": {}} self.assertFalse(client.feature_enabled("doesnt-exist", "distinct_id")) - patch_flags.side_effect = APIError(401, "decide error") + patch_flags.side_effect = APIError(401, "flags error") self.assertIsNone(client.feature_enabled("doesnt-exist", "distinct_id")) @mock.patch("posthog.client.Poller") @@ -2819,7 +2819,7 @@ def test_get_feature_flag_with_variant_overrides(self, patch_flags): self.assertEqual( client.get_feature_flag("beta-feature", "example_id"), "first-variant" ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) @mock.patch("posthog.client.flags") @@ -2900,7 +2900,7 @@ def test_flag_with_clashing_variant_overrides(self, patch_flags): ), "second-variant", ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) @mock.patch("posthog.client.flags") @@ -2963,7 +2963,7 @@ def test_flag_with_invalid_variant_overrides(self, patch_flags): self.assertEqual( client.get_feature_flag("beta-feature", "example_id"), "second-variant" ) - # decide not called because this can be evaluated locally + # /flags is not called because this can be evaluated locally self.assertEqual(patch_flags.call_count, 0) @mock.patch("posthog.client.flags") @@ -4688,7 +4688,7 @@ class TestCaptureCalls(unittest.TestCase): @mock.patch.object(Client, "capture") @mock.patch("posthog.client.flags") def test_capture_is_called(self, patch_flags, patch_capture): - patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}} + patch_flags.return_value = {"featureFlags": {"flags-flag": "flags-value"}} client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4775,15 +4775,15 @@ def test_capture_is_called(self, patch_flags, patch_capture): self.assertEqual(patch_capture.call_count, 0) patch_capture.reset_mock() - # called for different flag, falls back to decide, should call capture again + # called for different flag, falls back to /flags, should call capture again self.assertEqual( client.get_feature_flag( - "decide-flag", + "flags-flag", "some-distinct-id2", person_properties={"region": "USA", "name": "Aloha"}, groups={"organization": "org1"}, ), - "decide-value", + "flags-value", ) self.assertEqual(patch_flags.call_count, 1) self.assertEqual(patch_capture.call_count, 1) @@ -4791,10 +4791,10 @@ def test_capture_is_called(self, patch_flags, patch_capture): "$feature_flag_called", distinct_id="some-distinct-id2", properties={ - "$feature_flag": "decide-flag", - "$feature_flag_response": "decide-value", + "$feature_flag": "flags-flag", + "$feature_flag_response": "flags-value", "locally_evaluated": False, - "$feature/decide-flag": "decide-value", + "$feature/flags-flag": "flags-value", }, groups={"organization": "org1"}, disable_geoip=None, @@ -4805,10 +4805,10 @@ def test_capture_is_called(self, patch_flags, patch_capture): def test_capture_is_called_with_flag_details(self, patch_flags, patch_capture): patch_flags.return_value = { "flags": { - "decide-flag": { - "key": "decide-flag", + "flags-flag": { + "key": "flags-flag", "enabled": True, - "variant": "decide-variant", + "variant": "flags-variant", "reason": { "description": "Matched condition set 1", }, @@ -4838,17 +4838,17 @@ def test_capture_is_called_with_flag_details(self, patch_flags, patch_capture): client = Client(FAKE_TEST_API_KEY) self.assertEqual( - client.get_feature_flag("decide-flag", "some-distinct-id"), "decide-variant" + client.get_feature_flag("flags-flag", "some-distinct-id"), "flags-variant" ) self.assertEqual(patch_capture.call_count, 1) patch_capture.assert_called_with( "$feature_flag_called", distinct_id="some-distinct-id", properties={ - "$feature_flag": "decide-flag", - "$feature_flag_response": "decide-variant", + "$feature_flag": "flags-flag", + "$feature_flag_response": "flags-variant", "locally_evaluated": False, - "$feature/decide-flag": "decide-variant", + "$feature/flags-flag": "flags-variant", "$feature_flag_reason": "Matched condition set 1", "$feature_flag_id": 23, "$feature_flag_version": 42, @@ -4866,8 +4866,8 @@ def test_capture_is_called_with_flag_details_and_payload( ): patch_flags.return_value = { "flags": { - "decide-flag-with-payload": { - "key": "decide-flag-with-payload", + "flags-flag-with-payload": { + "key": "flags-flag-with-payload", "enabled": True, "variant": None, "reason": { @@ -4888,7 +4888,7 @@ def test_capture_is_called_with_flag_details_and_payload( self.assertEqual( client.get_feature_flag_payload( - "decide-flag-with-payload", + "flags-flag-with-payload", "some-distinct-id", send_feature_flag_events=True, ), @@ -4899,10 +4899,10 @@ def test_capture_is_called_with_flag_details_and_payload( "$feature_flag_called", distinct_id="some-distinct-id", properties={ - "$feature_flag": "decide-flag-with-payload", + "$feature_flag": "flags-flag-with-payload", "$feature_flag_response": True, "locally_evaluated": False, - "$feature/decide-flag-with-payload": True, + "$feature/flags-flag-with-payload": True, "$feature_flag_reason": "Matched condition set 1", "$feature_flag_id": 23, "$feature_flag_version": 42, @@ -4915,7 +4915,7 @@ def test_capture_is_called_with_flag_details_and_payload( @mock.patch("posthog.client.flags") def test_capture_is_called_but_does_not_add_all_flags(self, patch_flags): - patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}} + patch_flags.return_value = {"featureFlags": {"flags-flag": "flags-value"}} client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -5061,7 +5061,7 @@ def test_fallback_to_api_in_get_feature_flag_payload_when_flag_has_static_cohort @mock.patch.object(Client, "capture") @mock.patch("posthog.client.flags") def test_disable_geoip_get_flag_capture_call(self, patch_flags, patch_capture): - patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}} + patch_flags.return_value = {"featureFlags": {"flags-flag": "flags-value"}} client = Client( FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY, disable_geoip=True ) @@ -7258,10 +7258,10 @@ def test_multivariate_flag_consistency(self, patch_get): self.assertFalse(feature_flag_match) @mock.patch("posthog.client.flags") - def test_feature_flag_case_sensitive(self, mock_decide): - mock_decide.return_value = { + def test_feature_flag_case_sensitive(self, mock_flags): + mock_flags.return_value = { "featureFlags": {} - } # Ensure decide returns empty flags + } # Ensure /flags returns empty flags client = Client( project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY @@ -7283,8 +7283,8 @@ def test_feature_flag_case_sensitive(self, mock_decide): self.assertFalse(client.feature_enabled("BETA-FEATURE", "user1")) @mock.patch("posthog.client.flags") - def test_feature_flag_payload_case_sensitive(self, mock_decide): - mock_decide.return_value = { + def test_feature_flag_payload_case_sensitive(self, mock_flags): + mock_flags.return_value = { "featureFlags": {"Beta-Feature": True}, "featureFlagPayloads": {"Beta-Feature": {"some": "value"}}, } @@ -7314,8 +7314,8 @@ def test_feature_flag_payload_case_sensitive(self, mock_decide): self.assertIsNone(client.get_feature_flag_payload("BETA-FEATURE", "user1")) @mock.patch("posthog.client.flags") - def test_feature_flag_case_sensitive_consistency(self, mock_decide): - mock_decide.return_value = { + def test_feature_flag_case_sensitive_consistency(self, mock_flags): + mock_flags.return_value = { "featureFlags": {"Beta-Feature": True}, "featureFlagPayloads": {"Beta-Feature": {"some": "value"}}, } diff --git a/posthog/test/test_request.py b/posthog/test/test_request.py index f1671973..39ca82bf 100644 --- a/posthog/test/test_request.py +++ b/posthog/test/test_request.py @@ -15,7 +15,6 @@ QuotaLimitError, _mask_tokens_in_url, batch_post, - decide, determine_server_host, disable_connection_reuse, enable_keep_alive, @@ -116,7 +115,7 @@ def test_should_timeout(self): timeout=0.0001, ) - def test_quota_limited_response(self): + def test_quota_limited_flags_response(self): mock_response = requests.Response() mock_response.status_code = 200 mock_response._content = json.dumps( @@ -128,14 +127,16 @@ def test_quota_limited_response(self): } ).encode("utf-8") - with mock.patch("posthog.request._session.post", return_value=mock_response): + with mock.patch( + "posthog.request._flags_session.post", return_value=mock_response + ): with self.assertRaises(QuotaLimitError) as cm: - decide("fake_key", "fake_host") + flags("fake_key", "fake_host") self.assertEqual(cm.exception.status, 200) self.assertEqual(cm.exception.message, "Feature flags quota limited") - def test_normal_decide_response(self): + def test_normal_flags_response(self): mock_response = requests.Response() mock_response.status_code = 200 mock_response._content = json.dumps( @@ -146,8 +147,10 @@ def test_normal_decide_response(self): } ).encode("utf-8") - with mock.patch("posthog.request._session.post", return_value=mock_response): - response = decide("fake_key", "fake_host") + with mock.patch( + "posthog.request._flags_session.post", return_value=mock_response + ): + response = flags("fake_key", "fake_host") self.assertEqual(response["featureFlags"], {"flag1": True}) diff --git a/posthog/types.py b/posthog/types.py index 03104278..3b6a392d 100644 --- a/posthog/types.py +++ b/posthog/types.py @@ -234,10 +234,10 @@ def from_flag_details( def normalize_flags_response(resp: Any) -> FlagsResponse: """ - Normalize the response from the decide or flags API endpoint into a FlagsResponse. + Normalize the response from the flags API endpoint into a FlagsResponse. Args: - resp: A v3 or v4 response from the decide (or a v1 or v2 response from the flags) API endpoint. + resp: A v1 or v2 response from the flags API endpoint. Returns: A FlagsResponse containing feature flags and their details.