Skip to content

Commit e680a75

Browse files
authored
feat(scopes): Explicit scopes (getsentry#633)
1 parent 4112000 commit e680a75

File tree

4 files changed

+133
-7
lines changed

4 files changed

+133
-7
lines changed

sentry_sdk/api.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -67,34 +67,40 @@ def scopemethod(f):
6767
def capture_event(
6868
event, # type: Event
6969
hint=None, # type: Optional[Hint]
70+
scope=None, # type: Optional[Any]
71+
**scope_args # type: Dict[str, Any]
7072
):
7173
# type: (...) -> Optional[str]
7274
hub = Hub.current
7375
if hub is not None:
74-
return hub.capture_event(event, hint)
76+
return hub.capture_event(event, hint, scope=scope, **scope_args)
7577
return None
7678

7779

7880
@hubmethod
7981
def capture_message(
8082
message, # type: str
8183
level=None, # type: Optional[str]
84+
scope=None, # type: Optional[Any]
85+
**scope_args # type: Dict[str, Any]
8286
):
8387
# type: (...) -> Optional[str]
8488
hub = Hub.current
8589
if hub is not None:
86-
return hub.capture_message(message, level)
90+
return hub.capture_message(message, level, scope=scope, **scope_args)
8791
return None
8892

8993

9094
@hubmethod
9195
def capture_exception(
9296
error=None, # type: Optional[BaseException]
97+
scope=None, # type: Optional[Any]
98+
**scope_args # type: Dict[str, Any]
9399
):
94100
# type: (...) -> Optional[str]
95101
hub = Hub.current
96102
if hub is not None:
97-
return hub.capture_exception(error)
103+
return hub.capture_exception(error, scope=scope, **scope_args)
98104
return None
99105

100106

sentry_sdk/hub.py

+33-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import Any
2424
from typing import Optional
2525
from typing import Tuple
26+
from typing import Dict
2627
from typing import List
2728
from typing import Callable
2829
from typing import Generator
@@ -47,6 +48,24 @@ def overload(x):
4748
_local = ContextVar("sentry_current_hub")
4849

4950

51+
def _update_scope(base, scope_change, scope_kwargs):
52+
# type: (Scope, Optional[Any], Dict[str, Any]) -> Scope
53+
if scope_change and scope_kwargs:
54+
raise TypeError("cannot provide scope and kwargs")
55+
if scope_change is not None:
56+
final_scope = copy.copy(base)
57+
if callable(scope_change):
58+
scope_change(final_scope)
59+
else:
60+
final_scope.update_from_scope(scope_change)
61+
elif scope_kwargs:
62+
final_scope = copy.copy(base)
63+
final_scope.update_from_kwargs(scope_kwargs)
64+
else:
65+
final_scope = base
66+
return final_scope
67+
68+
5069
def _should_send_default_pii():
5170
# type: () -> bool
5271
client = Hub.current.client
@@ -285,11 +304,14 @@ def capture_event(
285304
self,
286305
event, # type: Event
287306
hint=None, # type: Optional[Hint]
307+
scope=None, # type: Optional[Any]
308+
**scope_args # type: Dict[str, Any]
288309
):
289310
# type: (...) -> Optional[str]
290311
"""Captures an event. Alias of :py:meth:`sentry_sdk.Client.capture_event`.
291312
"""
292-
client, scope = self._stack[-1]
313+
client, top_scope = self._stack[-1]
314+
scope = _update_scope(top_scope, scope, scope_args)
293315
if client is not None:
294316
rv = client.capture_event(event, hint, scope)
295317
if rv is not None:
@@ -301,6 +323,8 @@ def capture_message(
301323
self,
302324
message, # type: str
303325
level=None, # type: Optional[str]
326+
scope=None, # type: Optional[Any]
327+
**scope_args # type: Dict[str, Any]
304328
):
305329
# type: (...) -> Optional[str]
306330
"""Captures a message. The message is just a string. If no level
@@ -312,10 +336,15 @@ def capture_message(
312336
return None
313337
if level is None:
314338
level = "info"
315-
return self.capture_event({"message": message, "level": level})
339+
return self.capture_event(
340+
{"message": message, "level": level}, scope=scope, **scope_args
341+
)
316342

317343
def capture_exception(
318-
self, error=None # type: Optional[Union[BaseException, ExcInfo]]
344+
self,
345+
error=None, # type: Optional[Union[BaseException, ExcInfo]]
346+
scope=None, # type: Optional[Any]
347+
**scope_args # type: Dict[str, Any]
319348
):
320349
# type: (...) -> Optional[str]
321350
"""Captures an exception.
@@ -334,7 +363,7 @@ def capture_exception(
334363

335364
event, hint = event_from_exception(exc_info, client_options=client.options)
336365
try:
337-
return self.capture_event(event, hint=hint)
366+
return self.capture_event(event, hint=hint, scope=scope, **scope_args)
338367
except Exception:
339368
self._capture_internal_exception(sys.exc_info())
340369

sentry_sdk/scope.py

+44
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,50 @@ def _drop(event, cause, ty):
323323

324324
return event
325325

326+
def update_from_scope(self, scope):
327+
# type: (Scope) -> None
328+
if scope._level is not None:
329+
self._level = scope._level
330+
if scope._fingerprint is not None:
331+
self._fingerprint = scope._fingerprint
332+
if scope._transaction is not None:
333+
self._transaction = scope._transaction
334+
if scope._user is not None:
335+
self._user = scope._user
336+
if scope._tags:
337+
self._tags.update(scope._tags)
338+
if scope._contexts:
339+
self._contexts.update(scope._contexts)
340+
if scope._extras:
341+
self._extras.update(scope._extras)
342+
if scope._breadcrumbs:
343+
self._breadcrumbs.extend(scope._breadcrumbs)
344+
if scope._span:
345+
self._span = scope._span
346+
347+
def update_from_kwargs(
348+
self,
349+
user=None, # type: Optional[Any]
350+
level=None, # type: Optional[str]
351+
extras=None, # type: Optional[Dict[str, Any]]
352+
contexts=None, # type: Optional[Dict[str, Any]]
353+
tags=None, # type: Optional[Dict[str, str]]
354+
fingerprint=None, # type: Optional[List[str]]
355+
):
356+
# type: (...) -> None
357+
if level is not None:
358+
self._level = level
359+
if user is not None:
360+
self._user = user
361+
if extras is not None:
362+
self._extras.update(extras)
363+
if contexts is not None:
364+
self._contexts.update(contexts)
365+
if tags is not None:
366+
self._tags.update(tags)
367+
if fingerprint is not None:
368+
self._fingerprint = fingerprint
369+
326370
def __copy__(self):
327371
# type: () -> Scope
328372
rv = object.__new__(self.__class__) # type: Scope

tests/test_scope.py

+47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
from sentry_sdk import capture_exception
23
from sentry_sdk.scope import Scope
34

45

@@ -15,3 +16,49 @@ def test_copying():
1516
assert "bam" not in s2._tags
1617

1718
assert s1._fingerprint is s2._fingerprint
19+
20+
21+
def test_merging(sentry_init, capture_events):
22+
sentry_init()
23+
24+
s = Scope()
25+
s.set_user({"id": 42})
26+
27+
events = capture_events()
28+
29+
capture_exception(NameError(), scope=s)
30+
31+
(event,) = events
32+
assert event["user"] == {"id": 42}
33+
34+
35+
def test_common_args():
36+
s = Scope()
37+
s.update_from_kwargs(
38+
user={"id": 23},
39+
level="warning",
40+
extras={"k": "v"},
41+
contexts={"os": {"name": "Blafasel"}},
42+
tags={"x": "y"},
43+
fingerprint=["foo"],
44+
)
45+
46+
s2 = Scope()
47+
s2.set_extra("foo", "bar")
48+
s2.set_tag("a", "b")
49+
s2.set_context("device", {"a": "b"})
50+
s2.update_from_scope(s)
51+
52+
assert s._user == {"id": 23}
53+
assert s._level == "warning"
54+
assert s._extras == {"k": "v"}
55+
assert s._contexts == {"os": {"name": "Blafasel"}}
56+
assert s._tags == {"x": "y"}
57+
assert s._fingerprint == ["foo"]
58+
59+
assert s._user == s2._user
60+
assert s._level == s2._level
61+
assert s._fingerprint == s2._fingerprint
62+
assert s2._extras == {"k": "v", "foo": "bar"}
63+
assert s2._tags == {"a": "b", "x": "y"}
64+
assert s2._contexts == {"os": {"name": "Blafasel"}, "device": {"a": "b"}}

0 commit comments

Comments
 (0)