diff --git a/posthog/test/ai/test_prompts.py b/posthog/test/ai/test_prompts.py index 753405b5..c1d0a0fe 100644 --- a/posthog/test/ai/test_prompts.py +++ b/posthog/test/ai/test_prompts.py @@ -50,7 +50,7 @@ def create_mock_posthog( class TestPromptsGet(TestPrompts): - """Tests for the Prompts.get() method.""" + """Compatibility tests for the deprecated plain-string return value.""" @patch("posthog.ai.prompts._get_session") def test_successfully_fetch_a_prompt(self, mock_get_session): @@ -61,7 +61,7 @@ def test_successfully_fetch_a_prompt(self, mock_get_session): posthog = self.create_mock_posthog() prompts = Prompts(posthog) - result = prompts.get("test-prompt") + result = prompts.get("test-prompt", with_metadata=False) self.assertEqual(result, self.mock_prompt_response["prompt"]) mock_get.assert_called_once() @@ -89,7 +89,7 @@ def test_successfully_fetch_a_specific_prompt_version(self, mock_get_session): posthog = self.create_mock_posthog() prompts = Prompts(posthog) - result = prompts.get("test-prompt", version=1) + result = prompts.get("test-prompt", version=1, with_metadata=False) self.assertEqual(result, versioned_prompt_response["prompt"]) mock_get.assert_called_once() @@ -111,7 +111,7 @@ def test_return_cached_prompt_when_fresh(self, mock_time, mock_get_session): prompts = Prompts(posthog) # First call - fetches from API - result1 = prompts.get("test-prompt", cache_ttl_seconds=300) + result1 = prompts.get("test-prompt", cache_ttl_seconds=300, with_metadata=False) self.assertEqual(result1, self.mock_prompt_response["prompt"]) self.assertEqual(mock_get.call_count, 1) @@ -119,7 +119,7 @@ def test_return_cached_prompt_when_fresh(self, mock_time, mock_get_session): mock_time.return_value = 1060.0 # Second call - should use cache - result2 = prompts.get("test-prompt", cache_ttl_seconds=300) + result2 = prompts.get("test-prompt", cache_ttl_seconds=300, with_metadata=False) self.assertEqual(result2, self.mock_prompt_response["prompt"]) self.assertEqual(mock_get.call_count, 1) # No additional fetch @@ -146,14 +146,20 @@ def test_cache_latest_and_versioned_prompts_separately(self, mock_get_session): posthog = self.create_mock_posthog() prompts = Prompts(posthog) - self.assertEqual(prompts.get("test-prompt"), latest_prompt_response["prompt"]) self.assertEqual( - prompts.get("test-prompt", version=1), + prompts.get("test-prompt", with_metadata=False), + latest_prompt_response["prompt"], + ) + self.assertEqual( + prompts.get("test-prompt", version=1, with_metadata=False), versioned_prompt_response["prompt"], ) - self.assertEqual(prompts.get("test-prompt"), latest_prompt_response["prompt"]) self.assertEqual( - prompts.get("test-prompt", version=1), + prompts.get("test-prompt", with_metadata=False), + latest_prompt_response["prompt"], + ) + self.assertEqual( + prompts.get("test-prompt", version=1, with_metadata=False), versioned_prompt_response["prompt"], ) self.assertEqual(mock_get.call_count, 2) @@ -206,7 +212,9 @@ def test_version_and_label_together_raises(self): prompts = Prompts(self.create_mock_posthog()) with self.assertRaises(ValueError): - prompts.get("test-prompt", version=1, label="production") + prompts.get( + "test-prompt", version=1, label="production", with_metadata=False + ) @patch("posthog.ai.prompts._get_session") def test_cache_labeled_and_latest_prompts_separately(self, mock_get_session): @@ -233,14 +241,20 @@ def test_cache_labeled_and_latest_prompts_separately(self, mock_get_session): # A labeled fetch after a latest fetch must not be served from the # latest cache entry — that would silently return the wrong version. - self.assertEqual(prompts.get("test-prompt"), latest_prompt_response["prompt"]) self.assertEqual( - prompts.get("test-prompt", label="production"), + prompts.get("test-prompt", with_metadata=False), + latest_prompt_response["prompt"], + ) + self.assertEqual( + prompts.get("test-prompt", label="production", with_metadata=False), labeled_prompt_response["prompt"], ) - self.assertEqual(prompts.get("test-prompt"), latest_prompt_response["prompt"]) self.assertEqual( - prompts.get("test-prompt", label="production"), + prompts.get("test-prompt", with_metadata=False), + latest_prompt_response["prompt"], + ) + self.assertEqual( + prompts.get("test-prompt", label="production", with_metadata=False), labeled_prompt_response["prompt"], ) self.assertEqual(mock_get.call_count, 2) @@ -265,7 +279,7 @@ def test_refetch_when_cache_is_stale(self, mock_time, mock_get_session): prompts = Prompts(posthog) # First call - fetches from API - result1 = prompts.get("test-prompt", cache_ttl_seconds=60) + result1 = prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=False) self.assertEqual(result1, self.mock_prompt_response["prompt"]) self.assertEqual(mock_get.call_count, 1) @@ -273,7 +287,7 @@ def test_refetch_when_cache_is_stale(self, mock_time, mock_get_session): mock_time.return_value = 1061.0 # Second call - should refetch - result2 = prompts.get("test-prompt", cache_ttl_seconds=60) + result2 = prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=False) self.assertEqual(result2, updated_prompt_response["prompt"]) self.assertEqual(mock_get.call_count, 2) @@ -295,14 +309,14 @@ def test_use_stale_cache_on_fetch_failure_with_warning( prompts = Prompts(posthog) # First call - populates cache - result1 = prompts.get("test-prompt", cache_ttl_seconds=60) + result1 = prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=False) self.assertEqual(result1, self.mock_prompt_response["prompt"]) # Advance time past TTL mock_time.return_value = 1061.0 # Second call - should use stale cache - result2 = prompts.get("test-prompt", cache_ttl_seconds=60) + result2 = prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=False) self.assertEqual(result2, self.mock_prompt_response["prompt"]) # Check warning was logged @@ -323,7 +337,7 @@ def test_use_fallback_when_no_cache_and_fetch_fails_with_warning( prompts = Prompts(posthog) fallback = "Default system prompt." - result = prompts.get("test-prompt", fallback=fallback) + result = prompts.get("test-prompt", fallback=fallback, with_metadata=False) self.assertEqual(result, fallback) @@ -342,7 +356,7 @@ def test_throw_when_no_cache_no_fallback_and_fetch_fails(self, mock_get_session) prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertIn("Network error", str(context.exception)) @@ -368,7 +382,7 @@ def test_handle_404_response( prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("nonexistent-prompt", **get_kwargs) + prompts.get("nonexistent-prompt", with_metadata=False, **get_kwargs) self.assertIn(expected_message, str(context.exception)) @@ -382,7 +396,7 @@ def test_handle_403_response(self, mock_get_session): prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("restricted-prompt") + prompts.get("restricted-prompt", with_metadata=False) self.assertIn( 'Access denied for prompt "restricted-prompt"', str(context.exception) @@ -394,7 +408,7 @@ def test_throw_when_no_personal_api_key_configured(self): prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertIn( "personal_api_key is required to fetch prompts", str(context.exception) @@ -406,7 +420,7 @@ def test_throw_when_no_project_api_key_configured(self): prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertIn( "project_api_key is required to fetch prompts", str(context.exception) @@ -422,7 +436,7 @@ def test_throw_when_api_returns_invalid_response_format(self, mock_get_session): prompts = Prompts(posthog) with self.assertRaises(Exception) as context: - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertIn("Invalid response format", str(context.exception)) @@ -435,7 +449,7 @@ def test_use_custom_host_from_posthog_options(self, mock_get_session): posthog = self.create_mock_posthog(host="https://eu.posthog.com") prompts = Prompts(posthog) - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) call_args = mock_get.call_args self.assertTrue( @@ -457,21 +471,21 @@ def test_use_default_cache_ttl_5_minutes(self, mock_time, mock_get_session): prompts = Prompts(posthog) # First call - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 1) # Advance time by 4 minutes (within default 5-minute TTL) mock_time.return_value = 1000.0 + (4 * 60) # Second call - should use cache - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 1) # Advance time past 5-minute TTL mock_time.return_value = 1000.0 + (6 * 60) # Third call - should refetch - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 2) @patch("posthog.ai.prompts._get_session") @@ -488,14 +502,14 @@ def test_use_custom_default_cache_ttl_from_constructor( prompts = Prompts(posthog, default_cache_ttl_seconds=60) # First call - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 1) # Advance time past custom TTL mock_time.return_value = 1061.0 # Second call - should refetch - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 2) @patch("posthog.ai.prompts._get_session") @@ -507,7 +521,7 @@ def test_url_encode_prompt_names_with_special_characters(self, mock_get_session) posthog = self.create_mock_posthog() prompts = Prompts(posthog) - prompts.get("prompt with spaces/and/slashes") + prompts.get("prompt with spaces/and/slashes", with_metadata=False) call_args = mock_get.call_args self.assertEqual( @@ -525,7 +539,7 @@ def test_work_with_direct_options_no_posthog_client(self, mock_get_session): personal_api_key="phx_direct_key", project_api_key="phc_direct_key" ) - result = prompts.get("test-prompt") + result = prompts.get("test-prompt", with_metadata=False) self.assertEqual(result, self.mock_prompt_response["prompt"]) call_args = mock_get.call_args @@ -549,7 +563,7 @@ def test_use_custom_host_from_direct_options(self, mock_get_session): host="https://eu.posthog.com", ) - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) call_args = mock_get.call_args self.assertEqual( @@ -574,14 +588,14 @@ def test_use_custom_default_cache_ttl_from_direct_options( ) # First call - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 1) # Advance time past custom TTL mock_time.return_value = 1061.0 # Second call - should refetch - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=False) self.assertEqual(mock_get.call_count, 2) @@ -949,9 +963,11 @@ def test_capture_exception_called_on_fetch_failure_with_fallback( posthog = self.create_mock_posthog() prompts = Prompts(posthog, capture_errors=True) - result = prompts.get("test-prompt", fallback="fallback prompt", version=3) + result = prompts.get( + "test-prompt", fallback="fallback prompt", version=3, with_metadata=True + ) - self.assertEqual(result, "fallback prompt") + self.assertEqual(result.prompt, "fallback prompt") posthog.capture_exception.assert_called_once() captured_exc = posthog.capture_exception.call_args[0][0] self.assertIn("Network error", str(captured_exc)) @@ -979,14 +995,14 @@ def test_capture_exception_called_on_fetch_failure_with_stale_cache( prompts = Prompts(posthog, capture_errors=True) # First call populates cache - prompts.get("test-prompt", cache_ttl_seconds=60) + prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=True) # Expire cache mock_time.return_value = 1061.0 # Second call falls back to stale cache - result = prompts.get("test-prompt", cache_ttl_seconds=60) - self.assertEqual(result, self.mock_prompt_response["prompt"]) + result = prompts.get("test-prompt", cache_ttl_seconds=60, with_metadata=True) + self.assertEqual(result.prompt, self.mock_prompt_response["prompt"]) posthog.capture_exception.assert_called_once() @patch("posthog.ai.prompts._get_session") @@ -999,7 +1015,7 @@ def test_capture_exception_called_when_error_is_raised(self, mock_get_session): prompts = Prompts(posthog, capture_errors=True) with self.assertRaises(Exception): - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=True) posthog.capture_exception.assert_called_once() @@ -1012,7 +1028,7 @@ def test_no_capture_exception_when_capture_errors_is_false(self, mock_get_sessio posthog = self.create_mock_posthog() prompts = Prompts(posthog) - prompts.get("test-prompt", fallback="fallback prompt") + prompts.get("test-prompt", fallback="fallback prompt", with_metadata=True) posthog.capture_exception.assert_not_called() @@ -1028,9 +1044,11 @@ def test_no_capture_exception_without_client(self, mock_get_session): capture_errors=True, ) - result = prompts.get("test-prompt", fallback="fallback prompt") + result = prompts.get( + "test-prompt", fallback="fallback prompt", with_metadata=True + ) - self.assertEqual(result, "fallback prompt") + self.assertEqual(result.prompt, "fallback prompt") @patch("posthog.ai.prompts._get_session") def test_no_capture_exception_on_successful_fetch(self, mock_get_session): @@ -1041,7 +1059,7 @@ def test_no_capture_exception_on_successful_fetch(self, mock_get_session): posthog = self.create_mock_posthog() prompts = Prompts(posthog, capture_errors=True) - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=True) posthog.capture_exception.assert_not_called() @@ -1055,9 +1073,11 @@ def test_capture_exception_failure_does_not_affect_fallback(self, mock_get_sessi posthog.capture_exception.side_effect = Exception("capture failed") prompts = Prompts(posthog, capture_errors=True) - result = prompts.get("test-prompt", fallback="fallback prompt") + result = prompts.get( + "test-prompt", fallback="fallback prompt", with_metadata=True + ) - self.assertEqual(result, "fallback prompt") + self.assertEqual(result.prompt, "fallback prompt") class TestPromptsClearCache(TestPrompts): @@ -1080,8 +1100,8 @@ def _populate_versioned_cache(self, prompts, mock_get): MockResponse(json_data=versioned_prompt_response), ] - prompts.get("test-prompt") - prompts.get("test-prompt", version=1) + prompts.get("test-prompt", with_metadata=True) + prompts.get("test-prompt", version=1, with_metadata=True) return latest_prompt_response, versioned_prompt_response @@ -1111,19 +1131,19 @@ def test_clear_a_specific_prompt_from_cache(self, mock_get_session): prompts = Prompts(posthog) # Populate cache with two prompts - prompts.get("test-prompt") - prompts.get("other-prompt") + prompts.get("test-prompt", with_metadata=True) + prompts.get("other-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 2) # Clear only test-prompt prompts.clear_cache("test-prompt") # test-prompt should be refetched - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 3) # other-prompt should still be cached - prompts.get("other-prompt") + prompts.get("other-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 3) @patch("posthog.ai.prompts._get_session") @@ -1140,10 +1160,10 @@ def test_clear_a_specific_prompt_version_from_cache(self, mock_get_session): mock_get.side_effect = [MockResponse(json_data=versioned_prompt_response)] prompts.clear_cache("test-prompt", version=1) - prompts.get("test-prompt") + prompts.get("test-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 2) - prompts.get("test-prompt", version=1) + prompts.get("test-prompt", version=1, with_metadata=True) self.assertEqual(mock_get.call_count, 3) @patch("posthog.ai.prompts._get_session") @@ -1165,8 +1185,8 @@ def test_clear_a_prompt_name_clears_all_cached_versions(self, mock_get_session): ] prompts.clear_cache("test-prompt") - prompts.get("test-prompt") - prompts.get("test-prompt", version=1) + prompts.get("test-prompt", with_metadata=True) + prompts.get("test-prompt", version=1, with_metadata=True) self.assertEqual(mock_get.call_count, 4) @patch("posthog.ai.prompts._get_session") @@ -1186,16 +1206,16 @@ def test_clear_all_prompts_from_cache(self, mock_get_session): prompts = Prompts(posthog) # Populate cache with two prompts - prompts.get("test-prompt") - prompts.get("other-prompt") + prompts.get("test-prompt", with_metadata=True) + prompts.get("other-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 2) # Clear all cache prompts.clear_cache() # Both prompts should be refetched - prompts.get("test-prompt") - prompts.get("other-prompt") + prompts.get("test-prompt", with_metadata=True) + prompts.get("other-prompt", with_metadata=True) self.assertEqual(mock_get.call_count, 4) diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index 137fbeff..fd798f7b 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -9,6 +9,7 @@ from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags, use_span from parameterized import parameterized +import pytest from posthog.capture_compression import CaptureCompression from posthog.client import Client @@ -21,6 +22,18 @@ from posthog.contexts import tag +# Legacy single-flag behavior remains covered here; warning emission itself is +# asserted in test_evaluate_flags.py. +pytestmark = [ + pytest.mark.filterwarnings( + r"ignore:`(feature_enabled|get_feature_flag|get_feature_flag_payload)` is deprecated:DeprecationWarning" + ), + pytest.mark.filterwarnings( + r"ignore:`send_feature_flags` is deprecated:DeprecationWarning" + ), +] + + class TestClient(unittest.TestCase): @classmethod def setUpClass(cls): @@ -70,12 +83,13 @@ def test_trims_api_key_whitespace( mock_error.assert_not_called() def test_trims_host_and_personal_api_key_whitespace(self): - client = Client( - FAKE_TEST_API_KEY, - host=" \nhttps://eu.posthog.com/\t ", - personal_api_key=" \n\t ", - send=False, - ) + with self.assertWarnsRegex(DeprecationWarning, "personal_api_key"): + client = Client( + FAKE_TEST_API_KEY, + host=" \nhttps://eu.posthog.com/\t ", + personal_api_key=" \n\t ", + send=False, + ) self.assertEqual(client.raw_host, "https://eu.posthog.com/") self.assertEqual(client.host, "https://eu.i.posthog.com") @@ -201,7 +215,7 @@ def test_message_only_logs_do_not_duplicate_existing_posthog_prefix(self): @mock.patch("posthog.client.get") def test_disabled_client_does_not_load_feature_flags(self, patch_get): - client = Client("", personal_api_key="test", send=False) + client = Client("", secret_key="test", send=False) client.load_feature_flags() @@ -677,7 +691,7 @@ def test_basic_capture_with_feature_flags(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) msg_uuid = client.capture( @@ -800,7 +814,7 @@ def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) client.feature_flags = [multivariate_flag, basic_flag, false_flag] @@ -838,7 +852,7 @@ def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) client.feature_flags = [] @@ -865,7 +879,7 @@ def test_load_feature_flags_quota_limited(self, patch_get): } patch_get.side_effect = APIError(402, mock_response["detail"]) - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") with self.assertLogs("posthog", level="WARNING") as logs: client._load_feature_flags() @@ -879,7 +893,7 @@ def test_load_feature_flags_quota_limited(self, patch_get): def test_load_feature_flags_unauthorized(self, patch_get): patch_get.side_effect = APIError(401, "Unauthorized") - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") with self.assertLogs("posthog", level="ERROR") as logs: client._load_feature_flags() @@ -970,7 +984,7 @@ def test_dont_override_capture_with_local_flags(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) client.feature_flags = [multivariate_flag, basic_flag] @@ -1020,7 +1034,7 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_flags) client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) msg_uuid = client.capture( @@ -1079,7 +1093,7 @@ def test_feature_flags_request_max_retries_is_forwarded( client = Client( FAKE_TEST_API_KEY, feature_flags_request_max_retries=expected_max_retries, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, ) client.get_all_flags("distinct_id") @@ -1105,7 +1119,7 @@ def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly( FAKE_TEST_API_KEY, host="https://app.posthog.com", on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, disable_geoip=True, feature_flags_request_timeout_seconds=12, sync_mode=True, @@ -1164,7 +1178,7 @@ def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them( client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) msg_uuid = client.capture( @@ -1242,7 +1256,7 @@ def test_capture_with_send_feature_flags_false_and_local_evaluation_doesnt_send_ client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) client.feature_flags = [multivariate_flag, simple_flag] @@ -1324,7 +1338,7 @@ def test_capture_with_send_feature_flags_true_and_local_evaluation_uses_local_fl client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) client.feature_flags = [multivariate_flag, simple_flag] @@ -1370,7 +1384,7 @@ def test_capture_with_send_feature_flags_options_only_evaluate_locally_true( client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) @@ -1425,7 +1439,7 @@ def test_capture_with_send_feature_flags_options_only_evaluate_locally_false( client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) @@ -1471,7 +1485,7 @@ def test_capture_with_send_feature_flags_options_default_behavior( client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) @@ -1509,7 +1523,7 @@ def test_capture_exception_with_send_feature_flags_options(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) @@ -2416,7 +2430,7 @@ def raise_effect(): raise Exception("http exception") patch_get.return_value.raiseError.side_effect = raise_effect - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [{"key": "example"}] self.assertFalse(client.feature_enabled("example", "distinct_id")) @@ -2972,7 +2986,7 @@ def test_enable_local_evaluation_false_disables_poller( client = Client( FAKE_TEST_API_KEY, - personal_api_key="test-personal-key", + secret_key="test-personal-key", enable_local_evaluation=False, ) @@ -3008,7 +3022,7 @@ def test_enable_local_evaluation_true_starts_poller(self, patch_get, patch_polle client = Client( FAKE_TEST_API_KEY, - personal_api_key="test-personal-key", + secret_key="test-personal-key", enable_local_evaluation=True, ) @@ -3028,7 +3042,7 @@ def test_get_remote_config_payload_works_without_poller(self, patch_remote_confi client = Client( FAKE_TEST_API_KEY, - personal_api_key="test-personal-key", + secret_key="test-personal-key", enable_local_evaluation=False, ) @@ -3148,7 +3162,7 @@ def test_capture_with_send_feature_flags_flag_keys_filter(self, patch_flags): client = Client( FAKE_TEST_API_KEY, on_error=self.set_fail, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, sync_mode=True, ) @@ -3185,7 +3199,7 @@ def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_pos """Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string""" client = Client( FAKE_TEST_API_KEY, - personal_api_key="test_personal_api_key", + secret_key="test_personal_api_key", sync_mode=True, ) @@ -3236,7 +3250,7 @@ def test_get_all_flags_and_payloads_with_empty_string(self, patch_batch_post): """Test that get_all_flags_and_payloads includes flags with empty string payloads""" client = Client( FAKE_TEST_API_KEY, - personal_api_key="test_personal_api_key", + secret_key="test_personal_api_key", sync_mode=True, ) diff --git a/posthog/test/test_client_fork.py b/posthog/test/test_client_fork.py index eabae53b..294dcffb 100644 --- a/posthog/test/test_client_fork.py +++ b/posthog/test/test_client_fork.py @@ -59,7 +59,7 @@ def test_reinit_after_fork_keeps_memory_cache_instance(self): def test_reinit_after_fork_restarts_poller_when_enabled(self, mock_poller): client = Client( FAKE_TEST_API_KEY, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, send=False, enable_local_evaluation=True, poll_interval=123, @@ -82,7 +82,7 @@ def test_reinit_after_fork_restarts_poller_when_enabled(self, mock_poller): def test_reinit_after_fork_clears_poller_when_local_evaluation_disabled(self): client = Client( FAKE_TEST_API_KEY, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, send=False, enable_local_evaluation=False, ) @@ -281,7 +281,7 @@ def child_probe(): def test_register_at_fork_reinitializes_poller_and_sessions_in_child_process(self): client = Client( FAKE_TEST_API_KEY, - personal_api_key=FAKE_TEST_API_KEY, + secret_key=FAKE_TEST_API_KEY, send=False, enable_local_evaluation=True, poll_interval=100, diff --git a/posthog/test/test_feature_flag_called_minimization.py b/posthog/test/test_feature_flag_called_minimization.py index bf32d9d6..c32b4ea2 100644 --- a/posthog/test/test_feature_flag_called_minimization.py +++ b/posthog/test/test_feature_flag_called_minimization.py @@ -281,7 +281,7 @@ def _definitions_payload(self, has_experiment, gate): def test_gated_non_experiment_flag_sends_exactly_the_allowlist( self, patch_get, _patch_poller ): - client, captured = self._make_client(personal_api_key="personal-key") + client, captured = self._make_client(secret_key="personal-key") self._load_definitions( client, patch_get, @@ -313,7 +313,7 @@ def test_gated_non_experiment_flag_sends_exactly_the_allowlist( @mock.patch("posthog.client.Poller") @mock.patch("posthog.client.get") def test_gated_experiment_flag_sends_full_event(self, patch_get, _patch_poller): - client, captured = self._make_client(personal_api_key="personal-key") + client, captured = self._make_client(secret_key="personal-key") self._load_definitions( client, patch_get, @@ -331,7 +331,7 @@ def test_gated_experiment_flag_sends_full_event(self, patch_get, _patch_poller): @mock.patch("posthog.client.Poller") @mock.patch("posthog.client.get") def test_gate_absent_from_payload_sends_full_event(self, patch_get, _patch_poller): - client, captured = self._make_client(personal_api_key="personal-key") + client, captured = self._make_client(secret_key="personal-key") self._load_definitions( client, patch_get, @@ -348,7 +348,7 @@ def test_gate_absent_from_payload_sends_full_event(self, patch_get, _patch_polle @mock.patch("posthog.client.Poller") @mock.patch("posthog.client.get") def test_gate_survives_not_modified_polls(self, patch_get, _patch_poller): - client, captured = self._make_client(personal_api_key="personal-key") + client, captured = self._make_client(secret_key="personal-key") self._load_definitions( client, patch_get, diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 47eba37b..497069ef 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -7,6 +7,7 @@ from dateutil.relativedelta import relativedelta from freezegun import freeze_time from parameterized import parameterized +import pytest from posthog.client import Client import posthog.feature_flags @@ -20,6 +21,18 @@ from posthog.test.test_utils import FAKE_TEST_API_KEY +# This module preserves the legacy single-flag API's compatibility behavior; +# warning emission itself is asserted in test_evaluate_flags.py. +pytestmark = [ + pytest.mark.filterwarnings( + r"ignore:`(feature_enabled|get_feature_flag|get_feature_flag_payload)` is deprecated:DeprecationWarning" + ), + pytest.mark.filterwarnings( + r"ignore:send_feature_flag_events is deprecated in get_feature_flag_payload:DeprecationWarning" + ), +] + + class TestLocalEvaluation(unittest.TestCase): @classmethod def setUpClass(cls): @@ -487,7 +500,7 @@ def test_flag_with_complex_definition(self, patch_get, patch_flags): patch_flags.return_value = { "featureFlags": {"complex-flag": "flags-fallback-value"} } - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -617,7 +630,7 @@ def test_feature_flags_fallback_to_flags(self, patch_get, patch_flags): patch_flags.return_value = { "featureFlags": {"beta-feature": "alakazam", "beta-feature2": "alakazam2"} } - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -685,7 +698,7 @@ def test_feature_flags_dont_fallback_to_flags_when_only_local_evaluation_is_true patch_flags.return_value = { "featureFlags": {"beta-feature": "alakazam", "beta-feature2": "alakazam2"} } - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -767,7 +780,7 @@ def test_feature_flag_never_returns_undefined_during_regular_evaluation( self, patch_get, patch_flags ): patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -802,7 +815,7 @@ def test_feature_flag_return_none_when_flags_errors_out( self, patch_get, patch_flags ): patch_flags.side_effect = APIError(400, "Flags error") - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [] # beta-feature2 falls back to /flags, which on error returns None @@ -817,7 +830,7 @@ def test_experience_continuity_flag_not_evaluated_locally(self, patch_flags): patch_flags.return_value = { "featureFlags": {"beta-feature": "flags-fallback-value"} } - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -1345,7 +1358,7 @@ def test_compute_inactive_flags_locally(self, patch_flags, patch_capture): @mock.patch("posthog.client.flags") @mock.patch("posthog.client.get") def test_feature_flags_local_evaluation_None_values(self, patch_get, patch_flags): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { id: 1, @@ -1420,7 +1433,7 @@ def test_feature_flags_local_evaluation_None_values(self, patch_get, patch_flags @mock.patch("posthog.client.flags") @mock.patch("posthog.client.get") def test_feature_flags_local_evaluation_for_cohorts(self, patch_get, patch_flags): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 2, @@ -1508,7 +1521,7 @@ def test_feature_flags_local_evaluation_for_cohorts(self, patch_get, patch_flags def test_feature_flags_local_evaluation_for_negated_cohorts( self, patch_get, patch_flags ): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 2, @@ -1611,7 +1624,7 @@ def test_feature_flags_with_flag_dependencies( ): # Mock remote flags call to return empty for this flag (fallback returns None) patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -1670,7 +1683,7 @@ def test_feature_flags_with_flag_dependencies( @mock.patch("posthog.client.get") def test_flag_dependencies_simple_chain(self, patch_get, patch_flags): """Test basic flag dependency: flag-b depends on flag-a""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -1739,7 +1752,7 @@ def test_flag_dependencies_circular_dependency(self, patch_get, patch_flags): """Test circular dependency handling: flag-a depends on flag-b, flag-b depends on flag-a""" # Mock remote flags call to return empty for these flags (fallback returns None) patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -1801,7 +1814,7 @@ def test_flag_dependencies_missing_flag(self, patch_get, patch_flags): """Test handling of missing flag dependency""" # Mock remote flags call to return empty for this flag (fallback returns None) patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -1836,7 +1849,7 @@ def test_flag_dependencies_missing_flag(self, patch_get, patch_flags): @mock.patch("posthog.client.get") def test_flag_dependencies_complex_chain(self, patch_get, patch_flags): """Test complex dependency chain: flag-d -> flag-c -> [flag-a, flag-b]""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -1932,7 +1945,7 @@ def test_flag_dependencies_complex_chain(self, patch_get, patch_flags): @mock.patch("posthog.client.get") def test_flag_dependencies_mixed_conditions(self, patch_get, patch_flags): """Test flag dependency mixed with other property conditions""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2009,7 +2022,7 @@ def test_flag_dependencies_malformed_chain(self, patch_get, patch_flags): """Test handling of malformed dependency chains""" # Mock remote flags call to return empty for this flag (fallback returns None) patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2087,7 +2100,7 @@ def test_flag_dependencies_without_context_raises_inconclusive(self): @mock.patch("posthog.client.get") def test_flag_dependencies_evaluates_to_false(self, patch_get, patch_flags): """A `flag_evaluates_to: false` condition matches when the referenced flag is conclusively False, without falling back to /flags.""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2150,7 +2163,7 @@ def test_flag_dependencies_multi_level_evaluates_to_false( self, patch_get, patch_flags ): """Multi-level chain with a legitimately-False ancestor: A(false) -> B(true) -> D(true).""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2217,7 +2230,7 @@ def test_flag_dependencies_inactive_base_evaluates_to_false( self, patch_get, patch_flags ): """An inactive base flag caches False, so `flag_evaluates_to: false` matches.""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2260,7 +2273,7 @@ def test_flag_dependencies_inconclusive_base_falls_back( ): """An inconclusive base (missing person properties) is distinct from False and falls back to /flags.""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2319,7 +2332,7 @@ def test_flag_dependencies_null_value_no_match_locally( ): """A malformed `value: null` condition is a definitive local no-match (False), never falling back to /flags.""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2365,7 +2378,7 @@ def test_flag_dependencies_missing_key_no_match_locally( ): """A dependency condition with an empty/missing `key` is a definitive local no-match (False).""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2409,7 +2422,7 @@ def test_flag_dependencies_wrong_operator_no_match_locally( ): """A dependency condition with the wrong operator is a definitive local no-match (False).""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2453,7 +2466,7 @@ def test_flag_dependencies_malformed_condition_warns_once( ): """A malformed dependency condition warns on first eval, then stays quiet (dedup).""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2511,7 +2524,7 @@ def test_flag_dependencies_malformed_condition_falls_through_to_other_group( ): """A malformed dependency condition no-matches its group, but a second matching group still resolves the flag locally.""" patch_flags.return_value = {"featureFlags": {}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -2579,7 +2592,7 @@ def test_flag_dependencies_malformed_condition_falls_through_to_other_group( @mock.patch("posthog.client.get") def test_multi_level_multivariate_dependency_chain(self, patch_get, patch_flags): """Test multi-level multivariate dependency chain: dependent-flag -> intermediate-flag -> leaf-flag""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ # Leaf flag: multivariate with "control" and "test" variants using person property overrides { @@ -2846,7 +2859,7 @@ def test_production_style_multivariate_dependency_chain( self, patch_get, patch_flags ): """Test production-style multivariate dependency chain: multivariate-root-flag -> multivariate-intermediate-flag -> multivariate-leaf-flag""" - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ # Leaf flag: multivariate with fruit variants { @@ -3109,7 +3122,7 @@ def test_load_feature_flags(self, patch_get, patch_poll): }, etag='"abc123"', ) - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") with freeze_time("2020-01-01T12:01:00.0000Z"): client.load_feature_flags() self.assertEqual(len(client.feature_flags), 2) @@ -3136,7 +3149,7 @@ def test_load_feature_flags_sends_etag_on_subsequent_requests( }, etag='"initial-etag"', ) - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.load_feature_flags() # First call should have no etag @@ -3171,7 +3184,7 @@ def test_load_feature_flags_304_not_modified(self, patch_get, patch_poll): ) patch_get.side_effect = [initial_response, not_modified_response] - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.load_feature_flags() # Verify initial flags are loaded @@ -3212,7 +3225,7 @@ def test_load_feature_flags_etag_updated_on_new_response( ), ] - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.load_feature_flags() self.assertEqual(client._flags_etag, '"etag-v1"') @@ -3245,7 +3258,7 @@ def test_load_feature_flags_clears_etag_when_server_stops_sending( ), ] - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.load_feature_flags() self.assertEqual(client._flags_etag, '"etag-v1"') @@ -3257,7 +3270,7 @@ def test_load_feature_flags_clears_etag_when_server_stops_sending( @mock.patch("posthog.client.get") def test_load_feature_flags_wrong_key(self, patch_get, _patch_poll): patch_get.side_effect = APIError(401, "Unauthorized") - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) with self.assertLogs("posthog", level="ERROR") as logs: client.load_feature_flags() @@ -3367,7 +3380,7 @@ def test_feature_enabled_simple_with_project_api_key(self, patch_get): @mock.patch("posthog.client.flags") def test_feature_enabled_request_multi_variate(self, patch_flags): patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3412,7 +3425,7 @@ def test_feature_enabled_simple_without_rollout_percentage(self, patch_get): @mock.patch("posthog.client.flags") def test_get_feature_flag(self, patch_flags): patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3457,7 +3470,7 @@ def test_feature_enabled_doesnt_exist(self, patch_flags, patch_poll): @mock.patch("posthog.client.Poller") @mock.patch("posthog.client.flags") def test_personal_api_key_doesnt_exist(self, patch_flags, patch_poll): - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [] patch_flags.return_value = {"featureFlags": {"feature-flag": True}} @@ -3471,7 +3484,7 @@ def raise_effect(): raise Exception("http exception") patch_get.return_value.raiseError.side_effect = raise_effect - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [] self.assertFalse(client.feature_enabled("doesnt-exist", "distinct_id")) @@ -3479,7 +3492,7 @@ def raise_effect(): @mock.patch("posthog.client.flags") def test_get_feature_flag_with_variant_overrides(self, patch_flags): patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3542,7 +3555,7 @@ def test_get_feature_flag_with_variant_overrides(self, patch_flags): @mock.patch("posthog.client.flags") def test_flag_with_clashing_variant_overrides(self, patch_flags): patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3623,7 +3636,7 @@ def test_flag_with_clashing_variant_overrides(self, patch_flags): @mock.patch("posthog.client.flags") def test_flag_with_invalid_variant_overrides(self, patch_flags): patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3686,7 +3699,7 @@ def test_flag_with_invalid_variant_overrides(self, patch_flags): @mock.patch("posthog.client.flags") def test_conditions_evaluated_in_order(self, patch_flags): patch_flags.return_value = {"featureFlags": {"order-test": "server-variant"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key="test") + client = Client(FAKE_TEST_API_KEY, secret_key="test") client.feature_flags = [ { "id": 1, @@ -3907,7 +3920,7 @@ def test_fallback_to_api_when_flag_has_static_cohort_in_multi_condition( This prevents returning wrong variants when later conditions could match locally but the user is actually in the static cohort. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Mock the local flags response - cohort 999 is NOT in cohorts map (static cohort) client.feature_flags = [ @@ -3969,7 +3982,7 @@ def test_device_id_bucketing_uses_device_id_for_hash(self, patch_flags): When a flag has bucketing_identifier: "device_id", the device_id should be used for hashing instead of distinct_id. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # This flag uses device_id for bucketing client.feature_flags = [ @@ -4048,7 +4061,7 @@ def test_device_id_bucketing_same_device_different_users_same_result( When a flag uses device_id bucketing, different distinct_ids with the same device_id should get the same result. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4091,7 +4104,7 @@ def test_device_id_bucketing_fallback_when_device_id_missing(self, patch_flags): it should fallback to server evaluation. """ patch_flags.return_value = {"featureFlags": {"device-bucketed-flag": True}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4125,7 +4138,7 @@ def test_device_id_bucketing_returns_none_when_only_evaluate_locally_and_no_devi When only_evaluate_locally=True and device_id is required but missing, should return None instead of falling back to API. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4159,7 +4172,7 @@ def test_default_bucketing_identifier_uses_distinct_id(self, patch_flags): When bucketing_identifier is not set or is 'distinct_id', should use distinct_id for hashing (default behavior). """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Flag without bucketing_identifier (defaults to distinct_id) client.feature_flags = [ @@ -4192,7 +4205,7 @@ def test_device_id_bucketing_with_multivariate_flag(self, patch_flags): Multivariate flag variant selection should use device_id when bucketing_identifier is set to device_id. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4239,7 +4252,7 @@ def test_device_id_bucketing_from_context(self, patch_flags): """ from posthog.contexts import new_context, set_context_device_id - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4273,7 +4286,7 @@ def test_group_flags_ignore_bucketing_identifier(self, patch_flags): Group flags should continue to use the group identifier for hashing, regardless of the bucketing_identifier setting. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4312,7 +4325,7 @@ def test_group_flag_dependency_receives_device_id(self, patch_flags): device_id-bucketed flags can be evaluated locally. """ patch_flags.return_value = {"featureFlags": {"group-parent-flag": "from-api"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4373,7 +4386,7 @@ def test_group_flag_dependency_ignores_device_id_bucketing_identifier( the dependent group flag has bucketing_identifier set to device_id. """ patch_flags.return_value = {"featureFlags": {"parent-group-flag": "from-api"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4489,7 +4502,7 @@ def test_group_flag_dependency_ignores_device_id_bucketing_identifier( ) @mock.patch("posthog.client.flags") def test_mixed_targeting(self, _name, call_kwargs, expected, patch_flags): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [self.MIXED_FLAG] client.group_type_mapping = {"0": "company"} @@ -4499,7 +4512,7 @@ def test_mixed_targeting(self, _name, call_kwargs, expected, patch_flags): @mock.patch("posthog.client.flags") def test_mixed_targeting_only_group_conditions_no_groups_passed(self, patch_flags): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -4529,7 +4542,7 @@ def test_mixed_targeting_only_group_conditions_no_groups_passed(self, patch_flag @mock.patch("posthog.client.flags") def test_mixed_targeting_rollout_uses_correct_bucketing(self, patch_flags): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -4569,7 +4582,7 @@ def test_get_all_flags_with_device_id_bucketing(self, patch_flags): """ get_all_flags_and_payloads should properly handle flags with device_id bucketing. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -4609,7 +4622,7 @@ def test_get_all_flags_fallback_when_device_id_missing_for_some_flags( patch_flags.return_value = { "featureFlags": {"normal-flag": True, "device-flag": "from-api"} } - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -5573,7 +5586,7 @@ class TestCaptureCalls(unittest.TestCase): @mock.patch("posthog.client.flags") def test_capture_is_called(self, patch_flags, patch_capture): patch_flags.return_value = {"featureFlags": {"flags-flag": "flags-value"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -5800,7 +5813,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": {"flags-flag": "flags-value"}} - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -5858,9 +5871,7 @@ def test_get_feature_flag_payload_does_not_send_feature_flag_called_events( "featureFlags": {"person-flag": True}, "featureFlagPayloads": {"person-flag": 300}, } - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { @@ -5896,7 +5907,7 @@ def test_fallback_to_api_in_get_feature_flag_payload_when_flag_has_static_cohort Test that get_feature_flag_payload falls back to API when evaluating a flag with static cohorts, similar to get_feature_flag behavior. """ - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Mock the local flags response - cohort 999 is NOT in cohorts map (static cohort) client.feature_flags = [ @@ -5947,7 +5958,7 @@ def test_fallback_to_api_in_get_feature_flag_payload_when_flag_has_static_cohort def test_disable_geoip_get_flag_capture_call(self, patch_flags, patch_capture): 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 + FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY, disable_geoip=True ) client.feature_flags = [ { @@ -5989,7 +6000,7 @@ def test_disable_geoip_get_flag_capture_call(self, patch_flags, patch_capture): @mock.patch.object(Client, "capture") @mock.patch("posthog.client.flags") def test_capture_fires_per_group_context(self, patch_flags, patch_capture): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -6038,7 +6049,7 @@ def test_capture_fires_per_group_context(self, patch_flags, patch_capture): def test_capture_dedupes_repeated_calls_under_same_group_context( self, _name, first_groups, second_groups, patch_flags, patch_capture ): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -6066,7 +6077,7 @@ def test_capture_dedupes_repeated_calls_under_same_group_context( def test_capture_multiple_users_doesnt_out_of_memory( self, patch_flags, patch_capture ): - client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY) + client = Client(FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Set on the instance to avoid relying on module-constant patching behavior # across Python/runtime implementations. client.distinct_ids_feature_flags_reported.max_size = 100 @@ -6087,29 +6098,30 @@ def test_capture_multiple_users_doesnt_out_of_memory( } ] - for i in range(1000): - distinct_id = f"some-distinct-id{i}" - client.get_feature_flag( - "complex-flag", - distinct_id, - person_properties={"region": "USA", "name": "Aloha"}, - ) - patch_capture.assert_called_with( - "$feature_flag_called", - distinct_id=distinct_id, - properties={ - "$feature_flag": "complex-flag", - "$feature_flag_response": True, - "locally_evaluated": True, - "$feature/complex-flag": True, - }, - groups={}, - disable_geoip=None, - ) + with pytest.warns(DeprecationWarning, match="get_feature_flag.*deprecated"): + for i in range(1000): + distinct_id = f"some-distinct-id{i}" + client.get_feature_flag( + "complex-flag", + distinct_id, + person_properties={"region": "USA", "name": "Aloha"}, + ) + patch_capture.assert_called_with( + "$feature_flag_called", + distinct_id=distinct_id, + properties={ + "$feature_flag": "complex-flag", + "$feature_flag_response": True, + "locally_evaluated": True, + "$feature/complex-flag": True, + }, + groups={}, + disable_geoip=None, + ) - self.assertEqual( - len(client.distinct_ids_feature_flags_reported), i % 100 + 1 - ) + self.assertEqual( + len(client.distinct_ids_feature_flags_reported), i % 100 + 1 + ) class TestConsistency(unittest.TestCase): @@ -7152,15 +7164,18 @@ def test_simple_flag_consistency(self, patch_get): True, ] - for i in range(1000): - distinctID = f"distinct_id_{i}" + with pytest.warns(DeprecationWarning, match="feature_enabled.*deprecated"): + for i in range(1000): + distinctID = f"distinct_id_{i}" - feature_flag_match = self.client.feature_enabled("simple-flag", distinctID) + feature_flag_match = self.client.feature_enabled( + "simple-flag", distinctID + ) - if results[i]: - self.assertTrue(feature_flag_match) - else: - self.assertFalse(feature_flag_match) + if results[i]: + self.assertTrue(feature_flag_match) + else: + self.assertFalse(feature_flag_match) @mock.patch("posthog.client.get") def test_multivariate_flag_consistency(self, patch_get): @@ -8208,16 +8223,17 @@ def test_multivariate_flag_consistency(self, patch_get): "first-variant", ] - for i in range(1000): - distinctID = f"distinct_id_{i}" - feature_flag_match = self.client.get_feature_flag( - "multivariate-flag", distinctID - ) + with pytest.warns(DeprecationWarning, match="get_feature_flag.*deprecated"): + for i in range(1000): + distinctID = f"distinct_id_{i}" + feature_flag_match = self.client.get_feature_flag( + "multivariate-flag", distinctID + ) - if results[i]: - self.assertEqual(feature_flag_match, results[i]) - else: - self.assertFalse(feature_flag_match) + if results[i]: + self.assertEqual(feature_flag_match, results[i]) + else: + self.assertFalse(feature_flag_match) @mock.patch("posthog.client.flags") def test_feature_flag_case_sensitive(self, mock_flags): @@ -8225,9 +8241,7 @@ def test_feature_flag_case_sensitive(self, mock_flags): "featureFlags": {} } # Ensure /flags returns empty flags - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -8251,9 +8265,7 @@ def test_feature_flag_payload_case_sensitive(self, mock_flags): "featureFlagPayloads": {"Beta-Feature": {"some": "value"}}, } - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -8282,9 +8294,7 @@ def test_feature_flag_case_sensitive_consistency(self, mock_flags): "featureFlagPayloads": {"Beta-Feature": {"some": "value"}}, } - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) client.feature_flags = [ { "id": 1, @@ -8321,9 +8331,7 @@ def test_get_all_flags_with_flag_keys_to_evaluate(self, mock_flags): } } - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Call get_all_flags with flag_keys_to_evaluate result = client.get_all_flags( @@ -8355,9 +8363,7 @@ def test_get_all_flags_and_payloads_with_flag_keys_to_evaluate(self, mock_flags) }, } - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Call get_all_flags_and_payloads with flag_keys_to_evaluate result = client.get_all_flags_and_payloads( @@ -8384,9 +8390,7 @@ def test_get_all_flags_and_payloads_with_flag_keys_to_evaluate(self, mock_flags) def test_get_all_flags_locally_with_flag_keys_to_evaluate(self): """Test that local evaluation with flag_keys_to_evaluate only evaluates specified flags""" - client = Client( - project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY - ) + client = Client(project_api_key=FAKE_TEST_API_KEY, secret_key=FAKE_TEST_API_KEY) # Set up multiple flags client.feature_flags = [ diff --git a/posthog/test/test_flag_definition_cache.py b/posthog/test/test_flag_definition_cache.py index 8cee6618..149ee332 100644 --- a/posthog/test/test_flag_definition_cache.py +++ b/posthog/test/test_flag_definition_cache.py @@ -123,7 +123,7 @@ def _create_client_with_cache(self) -> Client: """Create a client with the mock cache provider.""" return Client( FAKE_TEST_API_KEY, - personal_api_key="test-personal-key", + secret_key="test-personal-key", flag_definition_cache_provider=self.cache_provider, sync_mode=True, enable_local_evaluation=False, # Disable poller for tests @@ -636,7 +636,7 @@ def test_works_without_cache_provider(self, mock_get): # Create client without cache provider client = Client( FAKE_TEST_API_KEY, - personal_api_key="test-personal-key", + secret_key="test-personal-key", sync_mode=True, enable_local_evaluation=False, )