Skip to content

Commit 8bdd3fa

Browse files
authored
fix: no-op clients without api key (#544)
1 parent f4af88a commit 8bdd3fa

5 files changed

Lines changed: 78 additions & 7 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: patch
3+
---
4+
5+
Treat clients with an empty project API key as disabled no-ops.

posthog/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,9 @@ def setup() -> Client:
886886
in_app_modules=in_app_modules,
887887
)
888888

889-
# always set incase user changes it
890-
default_client.disabled = disabled
889+
# Always set in case user changes it. Preserve Client's auto-disabled state
890+
# for API keys that become empty after trimming.
891+
default_client.disabled = disabled or not default_client.api_key
891892
default_client.debug = debug
892893
default_client._set_before_send(before_send)
893894

posthog/client.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def __init__(
220220
self.queue = queue.Queue(max_queue_size)
221221

222222
# api_key: This should be the Team API Key (token), public
223-
self.api_key = project_api_key.strip()
223+
self.api_key = (project_api_key or "").strip()
224224

225225
self.on_error = on_error
226226
self.debug = debug
@@ -245,7 +245,7 @@ def __init__(
245245
self.flag_definition_version = 0
246246
self._flags_etag: Optional[str] = None
247247
self._flag_definition_cache_provider = flag_definition_cache_provider
248-
self.disabled = disabled
248+
self.disabled = disabled or not self.api_key
249249
self.disable_geoip = disable_geoip
250250
self.historical_migration = historical_migration
251251
self.super_properties = super_properties
@@ -538,6 +538,9 @@ def get_flags_decision(
538538
Category:
539539
Feature flags
540540
"""
541+
if self.disabled:
542+
return normalize_flags_response({})
543+
541544
groups = groups or {}
542545
person_properties = person_properties or {}
543546
group_properties = group_properties or {}
@@ -1408,6 +1411,10 @@ def load_feature_flags(self):
14081411
Category:
14091412
Feature flags
14101413
"""
1414+
if self.disabled:
1415+
self.feature_flags = []
1416+
return
1417+
14111418
if not self.personal_api_key:
14121419
self.log.warning(
14131420
"[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags."

posthog/test/test_client.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,19 @@ def test_requires_api_key(self):
4343

4444
@parameterized.expand(
4545
[
46-
("valid_key", " \nphc_validkey\t ", "phc_validkey", False),
47-
("whitespace_only", " \n\t ", "", True),
46+
("valid_key", " \nphc_validkey\t ", "phc_validkey", False, False),
47+
("whitespace_only", " \n\t ", "", True, True),
48+
("empty_string", "", "", True, True),
4849
]
4950
)
5051
def test_trims_api_key_whitespace(
51-
self, _, raw_api_key, expected_api_key, expect_error_log
52+
self, _, raw_api_key, expected_api_key, expected_disabled, expect_error_log
5253
):
5354
with mock.patch.object(Client.log, "error") as mock_error:
5455
client = Client(raw_api_key, send=False)
5556

5657
self.assertEqual(client.api_key, expected_api_key)
58+
self.assertEqual(client.disabled, expected_disabled)
5759
if expect_error_log:
5860
mock_error.assert_called_once_with(
5961
"api_key is empty after trimming whitespace; check your project API key"
@@ -73,6 +75,37 @@ def test_trims_host_and_personal_api_key_whitespace(self):
7375
self.assertEqual(client.host, "https://eu.i.posthog.com")
7476
self.assertIsNone(client.personal_api_key)
7577

78+
def test_client_with_empty_api_key_is_noop(self):
79+
client = Client("", send=False)
80+
81+
self.assertIsNone(client.capture("event", distinct_id="distinct_id"))
82+
83+
@mock.patch("posthog.client.get")
84+
def test_disabled_client_does_not_load_feature_flags(self, patch_get):
85+
client = Client("", personal_api_key="test", send=False)
86+
87+
client.load_feature_flags()
88+
89+
patch_get.assert_not_called()
90+
self.assertEqual(client.feature_flags, [])
91+
self.assertIsNone(client.poller)
92+
93+
@mock.patch("posthog.client.flags")
94+
def test_disabled_client_does_not_get_flags_decision(self, patch_flags):
95+
client = Client("", send=False)
96+
97+
self.assertEqual(client.get_flags_decision("distinct_id")["flags"], {})
98+
self.assertEqual(client.get_feature_variants("distinct_id"), {})
99+
self.assertEqual(client.get_feature_payloads("distinct_id"), {})
100+
self.assertEqual(
101+
client.get_feature_flags_and_payloads("distinct_id"),
102+
{"featureFlags": {}, "featureFlagPayloads": {}},
103+
)
104+
self.assertIsNone(
105+
client.capture("event", distinct_id="distinct_id", send_feature_flags=True)
106+
)
107+
patch_flags.assert_not_called()
108+
76109
def test_empty_flush(self):
77110
self.client.flush()
78111

posthog/test/test_module.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ def test_flush(self):
3636
self.posthog.flush()
3737

3838

39+
class TestModuleLevelSetup(unittest.TestCase):
40+
def setUp(self):
41+
self._original_default_client = posthog.default_client
42+
self._original_api_key = posthog.api_key
43+
self._original_disabled = posthog.disabled
44+
self._original_send = posthog.send
45+
posthog.default_client = None
46+
posthog.api_key = " \n\t "
47+
posthog.disabled = False
48+
posthog.send = False
49+
50+
def tearDown(self):
51+
posthog.default_client = self._original_default_client
52+
posthog.api_key = self._original_api_key
53+
posthog.disabled = self._original_disabled
54+
posthog.send = self._original_send
55+
56+
def test_setup_preserves_client_disabled_when_trimmed_api_key_is_empty(self):
57+
posthog.setup()
58+
59+
self.assertIsNotNone(posthog.default_client)
60+
self.assertEqual(posthog.default_client.api_key, "")
61+
self.assertTrue(posthog.default_client.disabled)
62+
63+
3964
class TestModuleLevelWrappers(unittest.TestCase):
4065
"""Test that module-level wrapper functions in posthog/__init__.py
4166
correctly propagate all parameters to the Client methods."""

0 commit comments

Comments
 (0)