Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scopes): Explicit scopes #633

Merged
merged 2 commits into from Mar 11, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions sentry_sdk/api.py
Expand Up @@ -67,34 +67,40 @@ def scopemethod(f):
def capture_event(
event, # type: Event
hint=None, # type: Optional[Hint]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
hub = Hub.current
if hub is not None:
return hub.capture_event(event, hint)
return hub.capture_event(event, hint, scope=scope, **scope_args)
return None


@hubmethod
def capture_message(
message, # type: str
level=None, # type: Optional[str]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
hub = Hub.current
if hub is not None:
return hub.capture_message(message, level)
return hub.capture_message(message, level, scope=scope, **scope_args)
return None


@hubmethod
def capture_exception(
error=None, # type: Optional[BaseException]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
hub = Hub.current
if hub is not None:
return hub.capture_exception(error)
return hub.capture_exception(error, scope=scope, **scope_args)
return None


Expand Down
37 changes: 33 additions & 4 deletions sentry_sdk/hub.py
Expand Up @@ -23,6 +23,7 @@
from typing import Any
from typing import Optional
from typing import Tuple
from typing import Dict
from typing import List
from typing import Callable
from typing import Generator
Expand All @@ -47,6 +48,24 @@ def overload(x):
_local = ContextVar("sentry_current_hub")


def _update_scope(base, scope_change, scope_kwargs):
# type: (Scope, Optional[Any], Dict[str, Any]) -> Scope
if scope_change and scope_kwargs:
raise TypeError("cannot provide scope and kwargs")
if scope_change is not None:
final_scope = copy.copy(base)
if callable(scope_change):
scope_change(final_scope)
else:
final_scope.update_from_scope(scope_change)
elif scope_kwargs:
final_scope = copy.copy(base)
final_scope.update_from_kwargs(scope_kwargs)
else:
final_scope = base
return final_scope


def _should_send_default_pii():
# type: () -> bool
client = Hub.current.client
Expand Down Expand Up @@ -285,11 +304,14 @@ def capture_event(
self,
event, # type: Event
hint=None, # type: Optional[Hint]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
"""Captures an event. Alias of :py:meth:`sentry_sdk.Client.capture_event`.
"""
client, scope = self._stack[-1]
client, top_scope = self._stack[-1]
scope = _update_scope(top_scope, scope, scope_args)
if client is not None:
rv = client.capture_event(event, hint, scope)
if rv is not None:
Expand All @@ -301,6 +323,8 @@ def capture_message(
self,
message, # type: str
level=None, # type: Optional[str]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
"""Captures a message. The message is just a string. If no level
Expand All @@ -312,10 +336,15 @@ def capture_message(
return None
if level is None:
level = "info"
return self.capture_event({"message": message, "level": level})
return self.capture_event(
{"message": message, "level": level}, scope=scope, **scope_args
)

def capture_exception(
self, error=None # type: Optional[Union[BaseException, ExcInfo]]
self,
error=None, # type: Optional[Union[BaseException, ExcInfo]]
scope=None, # type: Optional[Any]
**scope_args # type: Dict[str, Any]
):
# type: (...) -> Optional[str]
"""Captures an exception.
Expand All @@ -334,7 +363,7 @@ def capture_exception(

event, hint = event_from_exception(exc_info, client_options=client.options)
try:
return self.capture_event(event, hint=hint)
return self.capture_event(event, hint=hint, scope=scope, **scope_args)
except Exception:
self._capture_internal_exception(sys.exc_info())

Expand Down
44 changes: 44 additions & 0 deletions sentry_sdk/scope.py
Expand Up @@ -323,6 +323,50 @@ def _drop(event, cause, ty):

return event

def update_from_scope(self, scope):
# type: (Scope) -> None
if scope._level is not None:
self._level = scope._level
if scope._fingerprint is not None:
self._fingerprint = scope._fingerprint
if scope._transaction is not None:
self._transaction = scope._transaction
if scope._user is not None:
self._user = scope._user
if scope._tags:
self._tags.update(scope._tags)
if scope._contexts:
self._contexts.update(scope._contexts)
if scope._extras:
self._extras.update(scope._extras)
if scope._breadcrumbs:
self._breadcrumbs.extend(scope._breadcrumbs)
if scope._span:
self._span = scope._span

def update_from_kwargs(
self,
user=None, # type: Optional[Any]
level=None, # type: Optional[str]
extras=None, # type: Optional[Dict[str, Any]]
contexts=None, # type: Optional[Dict[str, Any]]
tags=None, # type: Optional[Dict[str, str]]
fingerprint=None, # type: Optional[List[str]]
):
# type: (...) -> None
if level is not None:
self._level = level
if user is not None:
self._user = user
if extras is not None:
self._extras.update(extras)
if contexts is not None:
self._contexts.update(contexts)
if tags is not None:
self._tags.update(tags)
if fingerprint is not None:
self._fingerprint = fingerprint

def __copy__(self):
# type: () -> Scope
rv = object.__new__(self.__class__) # type: Scope
Expand Down
47 changes: 47 additions & 0 deletions tests/test_scope.py
@@ -1,4 +1,5 @@
import copy
from sentry_sdk import capture_exception
from sentry_sdk.scope import Scope


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

assert s1._fingerprint is s2._fingerprint


def test_merging(sentry_init, capture_events):
sentry_init()

s = Scope()
s.set_user({"id": 42})

events = capture_events()

capture_exception(NameError(), scope=s)

(event,) = events
assert event["user"] == {"id": 42}


def test_common_args():
s = Scope()
s.update_from_kwargs(
user={"id": 23},
level="warning",
extras={"k": "v"},
contexts={"os": {"name": "Blafasel"}},
tags={"x": "y"},
fingerprint=["foo"],
)

s2 = Scope()
s2.set_extra("foo", "bar")
s2.set_tag("a", "b")
s2.set_context("device", {"a": "b"})
s2.update_from_scope(s)

assert s._user == {"id": 23}
assert s._level == "warning"
assert s._extras == {"k": "v"}
assert s._contexts == {"os": {"name": "Blafasel"}}
assert s._tags == {"x": "y"}
assert s._fingerprint == ["foo"]

assert s._user == s2._user
assert s._level == s2._level
assert s._fingerprint == s2._fingerprint
assert s2._extras == {"k": "v", "foo": "bar"}
assert s2._tags == {"a": "b", "x": "y"}
assert s2._contexts == {"os": {"name": "Blafasel"}, "device": {"a": "b"}}