Skip to content

Commit

Permalink
feat: Added support for with-scope like behavior (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Sep 17, 2018
1 parent 020eb6f commit c99d54d
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 6 deletions.
14 changes: 14 additions & 0 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ def inner():
return inner()


@hubmethod
def push_scope(callback=None):
hub = Hub.current
if hub is not None:
return hub.push_scope(callback)
elif callback is None:

@contextmanager
def inner():
yield Scope()

return inner()


@hubmethod
def last_event_id():
hub = Hub.current
Expand Down
32 changes: 26 additions & 6 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ def __init__(self, hub, layer):
self._layer = layer

def __enter__(self):
return self
scope = self._layer[1]
if scope is None:
scope = Scope()
return scope

def __exit__(self, exc_type, exc_value, tb):
assert self._hub.pop_scope_unsafe() == self._layer, "popped wrong scope"
Expand Down Expand Up @@ -205,13 +208,20 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
while len(scope._breadcrumbs) >= client.options["max_breadcrumbs"]:
scope._breadcrumbs.popleft()

def push_scope(self):
def push_scope(self, callback=None):
"""Pushes a new layer on the scope stack. Returns a context manager
that should be used to pop the scope again."""
that should be used to pop the scope again. Alternatively a callback
can be provided that is executed in the context of the scope.
"""
client, scope = self._stack[-1]
new_layer = (client, copy.copy(scope))
self._stack.append(new_layer)
return _ScopeManager(self, new_layer)

if callback is not None:
if client is not None:
callback(scope)
else:
return _ScopeManager(self, new_layer)

def pop_scope_unsafe(self):
"""Pops a scope layer from the stack. Try to use the context manager
Expand All @@ -224,18 +234,28 @@ def configure_scope(self, callback=None):
"""Reconfigures the scope."""
client, scope = self._stack[-1]
if callback is not None:
if client is not None and scope is not None:
if client is not None:
callback(scope)
return

@contextmanager
def inner():
if client is not None and scope is not None:
if client is not None:
yield scope
else:
yield Scope()

return inner()

def scope(self, callback=None):
"""Pushes a new scope and yields it for configuration.
The scope is dropped at the end of the with statement. Alternatively
a callback can be provided similar to `configure_scope`.
"""
with self.push_scope():
client, scope = self._stack[-1]
return self.configure_scope(callback)


GLOBAL_HUB = Hub()
11 changes: 11 additions & 0 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Scope(object):
"""

__slots__ = (
"_level",
"_fingerprint",
"_transaction",
"_user",
Expand All @@ -28,6 +29,11 @@ def __init__(self):

self.clear()

@_attr_setter
def level(self, value):
"""When set this overrides the level."""
self._level = value

@_attr_setter
def fingerprint(self, value):
"""When set this overrides the default fingerprint."""
Expand Down Expand Up @@ -69,6 +75,7 @@ def remove_extra(self, key):

def clear(self):
"""Clears the entire scope."""
self._level = None
self._fingerprint = None
self._transaction = None
self._user = None
Expand Down Expand Up @@ -112,6 +119,9 @@ def apply_to_event(self, event, hint=None):
def _drop(event, cause, ty):
logger.info("%s (%s) dropped event (%s)", ty, cause, event)

if self._level is not None:
event["level"] = self._level

event.setdefault("breadcrumbs", []).extend(self._breadcrumbs)
if event.get("user") is None and self._user is not None:
event["user"] = self._user
Expand Down Expand Up @@ -151,6 +161,7 @@ def _drop(event, cause, ty):
def __copy__(self):
rv = object.__new__(self.__class__)

rv._level = self._level
rv._fingerprint = self._fingerprint
rv._transaction = self._transaction
rv._user = self._user
Expand Down
34 changes: 34 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sentry_sdk import (
push_scope,
configure_scope,
capture_exception,
add_breadcrumb,
Expand Down Expand Up @@ -105,3 +106,36 @@ def before_breadcrumb(crumb, hint):
assert_hint.clear()
add_breadcrumb(foo=42)
add_breadcrumb(crumb=dict(foo=42))


def test_push_scope(sentry_init, capture_events):
sentry_init()
events = capture_events()

with push_scope() as scope:
scope.level = "warning"
try:
1 / 0
except Exception as e:
capture_exception(e)

event, = events

assert event["level"] == "warning"
assert "exception" in event


def test_push_scope_null_client(sentry_init, capture_events):
sentry_init()
events = capture_events()

Hub.current.bind_client(None)

with push_scope() as scope:
scope.level = "warning"
try:
1 / 0
except Exception as e:
capture_exception(e)

assert len(events) == 0

0 comments on commit c99d54d

Please sign in to comment.