Skip to content

Commit 231d40a

Browse files
committed
inherit tags by default
1 parent 87302ec commit 231d40a

File tree

3 files changed

+56
-21
lines changed

3 files changed

+56
-21
lines changed

example.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106

107107
# You can add tags to a context, and these are automatically added to exceptions captured within that context.
108108
# You can enter a new context using a with statement. Any exceptions thrown in the context will be captured,
109-
# and tagged with the context tags
109+
# and tagged with the context tags. By default, it inherits tags from the parent context.
110110
with posthog.new_context():
111111
posthog.tag("user_id", 123)
112112
posthog.tag("transaction_id", "abc123")
@@ -115,12 +115,28 @@
115115
raise Exception("Order processing failed")
116116

117117

118-
# You can also use the `@posthog.scoped` decorator to enter a new context.
119-
@posthog.scoped
118+
# Use fresh=True to start with a clean context (no inherited tags)
119+
with posthog.new_context(fresh=True):
120+
posthog.tag("session_id", "xyz789")
121+
# Only session_id tag will be present, no inherited tags
122+
raise Exception("Session handling failed")
123+
124+
125+
# You can also use the `@posthog.scoped()` decorator to enter a new context.
126+
# By default, it inherits tags from the parent context
127+
@posthog.scoped()
120128
def process_order(order_id):
121129
posthog.tag("order_id", order_id)
122130
# Exception will be captured and tagged automatically
123131
raise Exception("Order processing failed")
124132

125133

134+
# Use fresh=True to start with a clean context (no inherited tags)
135+
@posthog.scoped(fresh=True)
136+
def process_payment(payment_id):
137+
posthog.tag("payment_id", payment_id)
138+
# Only payment_id tag will be present, no inherited tags
139+
raise Exception("Payment processing failed")
140+
141+
126142
posthog.shutdown()

posthog/scopes.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,36 @@ def _get_current_context() -> Dict[str, Any]:
1010

1111

1212
@contextmanager
13-
def new_context():
13+
def new_context(fresh=False):
1414
# TODO - we could extend this context idea to also apply to other event types eventually,
1515
# but right now it only applies to exceptions...
1616
"""
1717
Create a new context scope that will be active for the duration of the with block.
1818
Any tags set within this scope will be isolated to this context. Any exceptions raised
1919
within the context will be captured and tagged with the context tags.
2020
21-
Example:
21+
Args:
22+
fresh: Whether to start with a fresh context (default: False).
23+
If False, inherits tags from parent context.
24+
If True, starts with no tags.
25+
26+
Examples:
27+
# Inherit parent context tags
2228
with posthog.new_context():
2329
posthog.tag("user_id", "123")
24-
# The exception will be captured and tagged with the context tags
30+
raise ValueError("Something went wrong")
31+
32+
# Start with fresh context (no inherited tags)
33+
with posthog.new_context(fresh=True):
34+
posthog.tag("user_id", "123")
2535
raise ValueError("Something went wrong")
2636
2737
"""
2838
import posthog
2939

40+
current_tags = _get_current_context().copy()
3041
current_stack = _context_stack.get()
31-
new_stack = current_stack + [{}]
42+
new_stack = current_stack + [{}] if fresh else current_stack + [current_tags]
3243
token = _context_stack.set(new_stack)
3344

3445
try:
@@ -73,28 +84,32 @@ def clear_tags() -> None:
7384
F = TypeVar("F", bound=Callable[..., Any])
7485

7586

76-
def scoped(func: F) -> F:
87+
def scoped(fresh=False):
7788
"""
7889
Decorator that creates a new context for the function, wraps the function in a
7990
try/except block, and if an exception occurs, captures it with the current context
8091
tags before re-raising it.
8192
8293
Args:
83-
func: The function to wrap
94+
fresh: Whether to start with a fresh context (default: False)
8495
8596
Example:
86-
@posthog.scoped
97+
@posthog.scoped()
8798
def process_payment(payment_id):
8899
posthog.tag("payment_id", payment_id)
89100
posthog.tag("payment_method", "credit_card")
90101
# If this raises an exception, it will be captured with tags
91102
# and then re-raised
92103
"""
93-
from functools import wraps
94104

95-
@wraps(func)
96-
def wrapper(*args, **kwargs):
97-
with new_context():
98-
return func(*args, **kwargs)
105+
def decorator(func: F) -> F:
106+
from functools import wraps
107+
108+
@wraps(func)
109+
def wrapper(*args, **kwargs):
110+
with new_context(fresh=fresh):
111+
return func(*args, **kwargs)
112+
113+
return cast(F, wrapper)
99114

100-
return cast(F, wrapper)
115+
return decorator

posthog/test/test_scopes.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_new_context_isolation(self):
2828
# Set tag in outer context
2929
tag("outer", "value")
3030

31-
with new_context():
31+
with new_context(fresh=True):
3232
# Inner context should start empty
3333
self.assertEqual(get_tags(), {})
3434

@@ -39,6 +39,10 @@ def test_new_context_isolation(self):
3939
# Outer tag should not be visible
4040
self.assertNotIn("outer", get_tags())
4141

42+
with new_context(fresh=False):
43+
# Inner context should start empty
44+
self.assertEqual(get_tags(), {"outer": "value"})
45+
4246
# After exiting context, inner tag should be gone
4347
self.assertNotIn("inner", get_tags())
4448

@@ -48,10 +52,10 @@ def test_new_context_isolation(self):
4852
def test_nested_contexts(self):
4953
tag("level1", "value1")
5054

51-
with new_context():
55+
with new_context(fresh=True):
5256
tag("level2", "value2")
5357

54-
with new_context():
58+
with new_context(fresh=True):
5559
tag("level3", "value3")
5660
self.assertEqual(get_tags(), {"level3": "value3"})
5761

@@ -63,7 +67,7 @@ def test_nested_contexts(self):
6367

6468
@patch("posthog.capture_exception")
6569
def test_scoped_decorator_success(self, mock_capture):
66-
@scoped
70+
@scoped()
6771
def successful_function(x, y):
6872
tag("x", x)
6973
tag("y", y)
@@ -91,7 +95,7 @@ def check_context_on_capture(exception, **kwargs):
9195

9296
mock_capture.side_effect = check_context_on_capture
9397

94-
@scoped
98+
@scoped()
9599
def failing_function():
96100
tag("important_context", "value")
97101
raise test_exception

0 commit comments

Comments
 (0)