Skip to content

Commit 8db49a0

Browse files
committed
fix: Prevent core Client methods from raising exceptions
The goal is to ensure that our client doesn't cause a panic in an end-user application. This change updates capture/set/set_once/group_identify/alias to swallow and log any exceptions that occur. Note that this won't prevent errors from propagating via the `on_error` callback if an error occurs while processing the queue.
1 parent d76bfe6 commit 8db49a0

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

posthog/client.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import sys
55
from datetime import datetime, timedelta
6-
from typing import Any, Dict, Optional, Union
6+
from typing import Any, Callable, Dict, Optional, Union
77
from typing_extensions import Unpack
88
from uuid import uuid4
99

@@ -99,6 +99,31 @@ def add_context_tags(properties):
9999
return properties
100100

101101

102+
def no_throw(default_return=None):
103+
"""
104+
Decorator to prevent raising exceptions from public API methods.
105+
Note that this doesn't prevent errors from propagating via `on_error`.
106+
107+
Args:
108+
default_return: Value to return on exception (default: None)
109+
"""
110+
111+
def decorator(func):
112+
from functools import wraps
113+
114+
@wraps(func)
115+
def wrapper(self, *args, **kwargs):
116+
try:
117+
return func(self, *args, **kwargs)
118+
except Exception as e:
119+
self.log.exception(f"Error in {func.__name__}: {e}")
120+
return default_return
121+
122+
return wrapper
123+
124+
return decorator
125+
126+
102127
class Client(object):
103128
"""
104129
This is the SDK reference for the PostHog Python SDK.
@@ -481,6 +506,7 @@ def get_flags_decision(
481506

482507
return normalize_flags_response(resp_data)
483508

509+
@no_throw()
484510
def capture(
485511
self, event: str, **kwargs: Unpack[OptionalCaptureArgs]
486512
) -> Optional[str]:
@@ -657,6 +683,7 @@ def _parse_send_feature_flags(self, send_feature_flags) -> SendFeatureFlagsOptio
657683
f"Expected bool or dict."
658684
)
659685

686+
@no_throw()
660687
def set(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
661688
"""
662689
Set properties on a person profile.
@@ -716,6 +743,7 @@ def set(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
716743

717744
return self._enqueue(msg, disable_geoip)
718745

746+
@no_throw()
719747
def set_once(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
720748
"""
721749
Set properties on a person profile only if they haven't been set before.
@@ -759,6 +787,7 @@ def set_once(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
759787

760788
return self._enqueue(msg, disable_geoip)
761789

790+
@no_throw()
762791
def group_identify(
763792
self,
764793
group_type: str,
@@ -815,6 +844,7 @@ def group_identify(
815844

816845
return self._enqueue(msg, disable_geoip)
817846

847+
@no_throw()
818848
def alias(
819849
self,
820850
previous_id: str,

posthog/test/test_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,3 +2423,22 @@ def test_context_tags_added(self):
24232423
batch_data = mock_post.call_args[1]["batch"]
24242424
msg = batch_data[0]
24252425
self.assertEqual(msg["properties"]["$context_tags"], ["random_tag"])
2426+
2427+
@mock.patch("posthog.client.Client._enqueue", side_effect=Exception("Test error"))
2428+
def test_methods_handle_exceptions(self, mock_enqueue):
2429+
"""Test that all decorated methods handle exceptions gracefully."""
2430+
client = Client("test-key")
2431+
2432+
test_cases = [
2433+
("capture", ["test_event"], {}),
2434+
("set", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
2435+
("set_once", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
2436+
("group_identify", ["group-type", "group-key"], {}),
2437+
("alias", ["some-id", "new-id"], {}),
2438+
]
2439+
2440+
for method_name, args, kwargs in test_cases:
2441+
with self.subTest(method=method_name):
2442+
method = getattr(client, method_name)
2443+
result = method(*args, **kwargs)
2444+
self.assertEqual(result, None)

0 commit comments

Comments
 (0)