Skip to content
Merged
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/disabled-no-api-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pypi/posthog: patch
---

Treat clients with an empty project API key as disabled no-ops.
5 changes: 3 additions & 2 deletions posthog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,9 @@ def setup() -> Client:
in_app_modules=in_app_modules,
)

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

Expand Down
11 changes: 9 additions & 2 deletions posthog/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def __init__(
self.queue = queue.Queue(max_queue_size)

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

self.on_error = on_error
self.debug = debug
Expand All @@ -245,7 +245,7 @@ def __init__(
self.flag_definition_version = 0
self._flags_etag: Optional[str] = None
self._flag_definition_cache_provider = flag_definition_cache_provider
self.disabled = disabled
self.disabled = disabled or not self.api_key
Comment thread
marandaneto marked this conversation as resolved.
self.disable_geoip = disable_geoip
self.historical_migration = historical_migration
self.super_properties = super_properties
Expand Down Expand Up @@ -538,6 +538,9 @@ def get_flags_decision(
Category:
Feature flags
"""
if self.disabled:
return normalize_flags_response({})

groups = groups or {}
person_properties = person_properties or {}
group_properties = group_properties or {}
Expand Down Expand Up @@ -1408,6 +1411,10 @@ def load_feature_flags(self):
Category:
Feature flags
"""
if self.disabled:
self.feature_flags = []
return

if not self.personal_api_key:
self.log.warning(
"[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags."
Expand Down
39 changes: 36 additions & 3 deletions posthog/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ def test_requires_api_key(self):

@parameterized.expand(
[
("valid_key", " \nphc_validkey\t ", "phc_validkey", False),
("whitespace_only", " \n\t ", "", True),
("valid_key", " \nphc_validkey\t ", "phc_validkey", False, False),
("whitespace_only", " \n\t ", "", True, True),
("empty_string", "", "", True, True),
]
)
def test_trims_api_key_whitespace(
self, _, raw_api_key, expected_api_key, expect_error_log
self, _, raw_api_key, expected_api_key, expected_disabled, expect_error_log
):
with mock.patch.object(Client.log, "error") as mock_error:
client = Client(raw_api_key, send=False)

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

def test_client_with_empty_api_key_is_noop(self):
client = Client("", send=False)

self.assertIsNone(client.capture("event", distinct_id="distinct_id"))

@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.load_feature_flags()

patch_get.assert_not_called()
self.assertEqual(client.feature_flags, [])
self.assertIsNone(client.poller)

@mock.patch("posthog.client.flags")
def test_disabled_client_does_not_get_flags_decision(self, patch_flags):
client = Client("", send=False)

self.assertEqual(client.get_flags_decision("distinct_id")["flags"], {})
self.assertEqual(client.get_feature_variants("distinct_id"), {})
self.assertEqual(client.get_feature_payloads("distinct_id"), {})
self.assertEqual(
client.get_feature_flags_and_payloads("distinct_id"),
{"featureFlags": {}, "featureFlagPayloads": {}},
)
self.assertIsNone(
client.capture("event", distinct_id="distinct_id", send_feature_flags=True)
)
patch_flags.assert_not_called()

def test_empty_flush(self):
self.client.flush()

Expand Down
25 changes: 25 additions & 0 deletions posthog/test/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,31 @@ def test_flush(self):
self.posthog.flush()


class TestModuleLevelSetup(unittest.TestCase):
def setUp(self):
self._original_default_client = posthog.default_client
self._original_api_key = posthog.api_key
self._original_disabled = posthog.disabled
self._original_send = posthog.send
posthog.default_client = None
posthog.api_key = " \n\t "
posthog.disabled = False
posthog.send = False

def tearDown(self):
posthog.default_client = self._original_default_client
posthog.api_key = self._original_api_key
posthog.disabled = self._original_disabled
posthog.send = self._original_send

def test_setup_preserves_client_disabled_when_trimmed_api_key_is_empty(self):
posthog.setup()

self.assertIsNotNone(posthog.default_client)
self.assertEqual(posthog.default_client.api_key, "")
self.assertTrue(posthog.default_client.disabled)


class TestModuleLevelWrappers(unittest.TestCase):
"""Test that module-level wrapper functions in posthog/__init__.py
correctly propagate all parameters to the Client methods."""
Expand Down
Loading