Skip to content

Commit e0d83ca

Browse files
committed
fix: Respect exception autocapture default for contexts
1 parent ee80810 commit e0d83ca

6 files changed

Lines changed: 129 additions & 20 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+
Respect exception autocapture defaults for new contexts.

‎posthog/__init__.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@
7676

7777
def new_context(
7878
fresh: bool = False,
79-
capture_exceptions: bool = True,
79+
capture_exceptions: Optional[bool] = None,
8080
client: Optional[Client] = None,
8181
):
8282
"""
8383
Create a new context scope that will be active for the duration of the with block.
8484
8585
Args:
8686
fresh: Whether to start with a fresh context (default: False)
87-
capture_exceptions: Whether to capture exceptions raised within the context (default: True)
87+
capture_exceptions: Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.
8888
client: Optional Posthog client instance to use for this context (default: None)
8989
9090
Examples:
@@ -103,13 +103,13 @@ def new_context(
103103
)
104104

105105

106-
def scoped(fresh=False, capture_exceptions=True):
106+
def scoped(fresh=False, capture_exceptions: Optional[bool] = None):
107107
"""
108108
Decorator that creates a new context for the function.
109109
110110
Args:
111111
fresh: Whether to start with a fresh context (default: False)
112-
capture_exceptions: Whether to capture and track exceptions with posthog error tracking (default: True)
112+
capture_exceptions: Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.
113113
114114
Examples:
115115
```python

‎posthog/client.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,13 +456,13 @@ def _set_before_send(self, before_send):
456456
else:
457457
self.before_send = None
458458

459-
def new_context(self, fresh=False, capture_exceptions=True):
459+
def new_context(self, fresh=False, capture_exceptions: Optional[bool] = None):
460460
"""
461461
Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).
462462
463463
Args:
464464
fresh: Whether to create a fresh context that doesn't inherit from parent.
465-
capture_exceptions: Whether to automatically capture exceptions in this context.
465+
capture_exceptions: Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.
466466
467467
Examples:
468468
```python

‎posthog/contexts.py‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,25 @@ def _get_current_context() -> Optional[ContextScope]:
112112
return _context_stack.get()
113113

114114

115+
def _default_capture_exceptions(client: Optional["Client"] = None) -> bool:
116+
if client is not None:
117+
return client.enable_exception_autocapture
118+
119+
import posthog
120+
121+
default_client = getattr(posthog, "default_client", None)
122+
if default_client is not None:
123+
client_default = getattr(default_client, "enable_exception_autocapture", None)
124+
if isinstance(client_default, bool):
125+
return client_default
126+
127+
return posthog.enable_exception_autocapture
128+
129+
115130
@contextmanager
116131
def new_context(
117132
fresh: bool = False,
118-
capture_exceptions: bool = True,
133+
capture_exceptions: Optional[bool] = None,
119134
client: Optional["Client"] = None,
120135
):
121136
"""
@@ -127,7 +142,8 @@ def new_context(
127142
fresh: Whether to start with a fresh context (default: False).
128143
If False, inherits tags, identity and session id's from parent context.
129144
If True, starts with no state
130-
capture_exceptions: Whether to capture exceptions raised within the context (default: True).
145+
capture_exceptions: Whether to capture exceptions raised within the context.
146+
If omitted, defaults to the relevant client's exception autocapture setting.
131147
If True, captures exceptions and tags them with the context tags before propagating them.
132148
If False, exceptions will propagate without being tagged or captured.
133149
client: Optional client instance to use for capturing exceptions (default: None).
@@ -162,7 +178,14 @@ def new_context(
162178
from posthog import capture_exception
163179

164180
current_context = _get_current_context()
165-
new_context = ContextScope(current_context, fresh, capture_exceptions, client)
181+
resolved_capture_exceptions = (
182+
capture_exceptions
183+
if capture_exceptions is not None
184+
else _default_capture_exceptions(client)
185+
)
186+
new_context = ContextScope(
187+
current_context, fresh, resolved_capture_exceptions, client
188+
)
166189
_context_stack.set(new_context)
167190

168191
try:
@@ -370,14 +393,14 @@ def get_code_variables_ignore_patterns_context() -> Optional[list]:
370393
F = TypeVar("F", bound=Callable[..., Any])
371394

372395

373-
def scoped(fresh: bool = False, capture_exceptions: bool = True):
396+
def scoped(fresh: bool = False, capture_exceptions: Optional[bool] = None):
374397
"""
375398
Decorator that creates a new context for the function. Simply wraps
376399
the function in a with posthog.new_context(): block.
377400
378401
Args:
379402
fresh: Whether to start with a fresh context (default: False)
380-
capture_exceptions: Whether to capture and track exceptions with posthog error tracking (default: True)
403+
capture_exceptions: Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.
381404
382405
Example:
383406
@posthog.scoped()

‎posthog/test/test_contexts.py‎

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
22
import unittest
3-
from unittest.mock import patch
3+
from unittest.mock import MagicMock, patch
44

5+
import posthog
6+
from posthog.client import Client
57
from posthog.contexts import (
68
get_tags,
79
new_context,
@@ -101,7 +103,7 @@ def check_context_on_capture(exception, **kwargs):
101103

102104
if is_async:
103105

104-
@scoped()
106+
@scoped(capture_exceptions=True)
105107
async def failing_function():
106108
tag("important_context", "value")
107109
raise test_exception
@@ -111,7 +113,7 @@ def run():
111113

112114
else:
113115

114-
@scoped()
116+
@scoped(capture_exceptions=True)
115117
def failing_function():
116118
tag("important_context", "value")
117119
raise test_exception
@@ -146,7 +148,7 @@ def check_context_on_capture(exception, **kwargs):
146148
tag("outer_context", "outer_value")
147149

148150
try:
149-
with new_context():
151+
with new_context(capture_exceptions=True):
150152
tag("inner_context", "inner_value")
151153
raise test_exception
152154
except RuntimeError:
@@ -158,6 +160,85 @@ def check_context_on_capture(exception, **kwargs):
158160
# Verify capture_exception was called
159161
mock_capture.assert_called_once_with(test_exception)
160162

163+
@patch("posthog.capture_exception")
164+
def test_new_context_defaults_to_global_exception_autocapture_disabled(
165+
self, mock_capture
166+
):
167+
original_default_client = posthog.default_client
168+
original_enable_exception_autocapture = posthog.enable_exception_autocapture
169+
posthog.default_client = None
170+
posthog.enable_exception_autocapture = False
171+
test_exception = RuntimeError("Context exception")
172+
173+
try:
174+
with self.assertRaises(RuntimeError):
175+
with posthog.new_context():
176+
raise test_exception
177+
finally:
178+
posthog.default_client = original_default_client
179+
posthog.enable_exception_autocapture = original_enable_exception_autocapture
180+
181+
mock_capture.assert_not_called()
182+
183+
def test_new_context_defaults_to_custom_client_exception_autocapture_disabled(self):
184+
client = Client(
185+
"phc_test",
186+
sync_mode=True,
187+
disabled=True,
188+
enable_exception_autocapture=False,
189+
)
190+
client.capture_exception = MagicMock()
191+
test_exception = RuntimeError("Context exception")
192+
193+
try:
194+
with self.assertRaises(RuntimeError):
195+
with client.new_context():
196+
raise test_exception
197+
finally:
198+
client.shutdown()
199+
200+
client.capture_exception.assert_not_called()
201+
202+
@patch("posthog.capture_exception")
203+
def test_new_context_explicit_true_captures_when_global_autocapture_disabled(
204+
self, mock_capture
205+
):
206+
original_default_client = posthog.default_client
207+
original_enable_exception_autocapture = posthog.enable_exception_autocapture
208+
posthog.default_client = None
209+
posthog.enable_exception_autocapture = False
210+
test_exception = RuntimeError("Context exception")
211+
212+
try:
213+
with self.assertRaises(RuntimeError):
214+
with posthog.new_context(capture_exceptions=True):
215+
raise test_exception
216+
finally:
217+
posthog.default_client = original_default_client
218+
posthog.enable_exception_autocapture = original_enable_exception_autocapture
219+
220+
mock_capture.assert_called_once_with(test_exception)
221+
222+
@patch("posthog.capture_exception")
223+
def test_new_context_explicit_false_skips_capture_when_global_autocapture_enabled(
224+
self, mock_capture
225+
):
226+
original_default_client = posthog.default_client
227+
original_enable_exception_autocapture = posthog.enable_exception_autocapture
228+
posthog.default_client = None
229+
posthog.enable_exception_autocapture = True
230+
test_exception = RuntimeError("Context exception")
231+
232+
try:
233+
with self.assertRaises(RuntimeError):
234+
with posthog.new_context(capture_exceptions=False):
235+
raise test_exception
236+
finally:
237+
posthog.default_client = original_default_client
238+
posthog.enable_exception_autocapture = original_enable_exception_autocapture
239+
240+
mock_capture.assert_not_called()
241+
161242
def test_identify_context(self):
162243
with new_context(fresh=True):
163244
# Initially no distinct ID

‎references/public_api_snapshot.txt‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -870,8 +870,8 @@ function posthog.contexts.get_context_distinct_id() -> Optional[str]
870870
function posthog.contexts.get_context_session_id() -> Optional[str]
871871
function posthog.contexts.get_tags() -> Dict[str, Any]
872872
function posthog.contexts.identify_context(distinct_id: str) -> None
873-
function posthog.contexts.new_context(fresh: bool = False, capture_exceptions: bool = True, client: Optional[Client] = None)
874-
function posthog.contexts.scoped(fresh: bool = False, capture_exceptions: bool = True)
873+
function posthog.contexts.new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None)
874+
function posthog.contexts.scoped(fresh: bool = False, capture_exceptions: Optional[bool] = None)
875875
function posthog.contexts.set_capture_exception_code_variables_context(enabled: bool) -> None
876876
function posthog.contexts.set_code_variables_ignore_patterns_context(ignore_patterns: list) -> None
877877
function posthog.contexts.set_code_variables_mask_patterns_context(mask_patterns: list) -> None
@@ -939,7 +939,7 @@ function posthog.identify_context(distinct_id: str)
939939
function posthog.integrations.django.markcoroutinefunction(func)
940940
function posthog.join() -> None
941941
function posthog.load_feature_flags()
942-
function posthog.new_context(fresh: bool = False, capture_exceptions: bool = True, client: Optional[Client] = None)
942+
function posthog.new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None)
943943
function posthog.request.batch_post(api_key: str, host: Optional[str] = None, gzip: bool = False, timeout: int = 15, path: str = EVENTS_ENDPOINT, **kwargs) -> requests.Response
944944
function posthog.request.determine_server_host(host: Optional[str]) -> str
945945
function posthog.request.disable_connection_reuse() -> None
@@ -952,7 +952,7 @@ function posthog.request.post(api_key: str, host: Optional[str] = None, path: Op
952952
function posthog.request.remote_config(personal_api_key: str, project_api_key: str, host: Optional[str] = None, key: str = '', timeout: int = 15) -> Any
953953
function posthog.request.reset_sessions() -> None
954954
function posthog.request.set_socket_options(socket_options: Optional[SocketOptions]) -> None
955-
function posthog.scoped(fresh=False, capture_exceptions=True)
955+
function posthog.scoped(fresh=False, capture_exceptions: Optional[bool] = None)
956956
function posthog.set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]
957957
function posthog.set_capture_exception_code_variables_context(enabled: bool)
958958
function posthog.set_code_variables_ignore_patterns_context(ignore_patterns: list)
@@ -1062,7 +1062,7 @@ method posthog.client.Client.get_remote_config_payload(key: str)
10621062
method posthog.client.Client.group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str]
10631063
method posthog.client.Client.join() -> None
10641064
method posthog.client.Client.load_feature_flags()
1065-
method posthog.client.Client.new_context(fresh=False, capture_exceptions=True)
1065+
method posthog.client.Client.new_context(fresh=False, capture_exceptions: Optional[bool] = None)
10661066
method posthog.client.Client.set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]
10671067
method posthog.client.Client.set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]
10681068
method posthog.client.Client.shutdown() -> None

0 commit comments

Comments
 (0)