-
Notifications
You must be signed in to change notification settings - Fork 554
Allow push_scope to act as a decorator #523
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
Conversation
NOTE: I can't figure out how to add tests for this functionality since there exist limited tests for decorators in the sentry codebase (thus far). |
Also, since there are not too many custom decorators in the codebase, I wasn't sure what naming convention to follow... |
Random thoughts:
Together they can be composed like this: @sentry_sdk.push_scope()
def foo():
sentry_sdk.set_extra("foo", "bar") |
I like that. So in api.py, along with a new overload of push_scope(), there would be set_tag() and set_extra() which would both utilize Hub.current from api.py's scope. What about user and level setters? The JS SDK has setUser and setLevel, so I imagine set_user() and set_level() methods would make the most sense? |
…) update Hub.push_scope() to allow it to act as both a contentmanager and a decorator, (api.py, utils.py) add new scopemethod api methods {set_tag, set_extra, set_user, set_level} that allow for updating of the current scope
…ator inherit from contextlib.ContextDecorator
…corator with _ScopeManager
…tDecorator compat issue, (sentry_sdk/api.py) update typing to match that of Hub.push_scope()
@untitaker, the current code fails on the tests.test_basics.test_scope_leaks_cleaned_up(), however I believe that that test is no longer valid. Now that the scope layering is occurring within ScopeManager.enter(), the following code sample will no longer produce a scope leak: with push_scope():
push_scope() Due to this, I am going to remove that test. |
…(tests/test_basics.py) remove unneeded test_scope_leaks_cleaned_up()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry for not thinking upfront about this, it's tricky to overload push_scope
without breaking some API.
I believe we could get the global methods done for now and defer the decision on whether to provide a decorator.
@@ -123,6 +123,11 @@ def level(self, value): | |||
"""When set this overrides the level.""" | |||
self._level = value | |||
|
|||
def set_level(self, value): | |||
# type: (Optional[str]) -> None | |||
"""When set this overrides the level.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is no longer a property this docstring should be rephrased. Also please change the level property docs to say "deprecated in favor of set_level
"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can change the docstring and docs.
old_stack = list(Hub.current._stack) | ||
|
||
with push_scope(): | ||
push_scope() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn, I didn't consider that this usecase no longer works. I'll have to think about this more. Right now I believe this is the less expected behavior, after all e.g. open()
also does its work "eagerly".
For context, this is used in third-party integrations in situations where a use of context manager is not possible (search on github for pop_scope_unsafe
). They could still manually call __enter__
and ``exit`, but it's a breaking API change in any case.
The scope cleanup described here has to continue working in any case because of concurrency issues in async envs that we need to mitigate against. For example, some async environment may share the hub across threads, which incurs data races left and right but push_scope and pop_scope_unsafe should crash as little as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely understand that scope cleanup needs to happen in async envs. However, if it is as critical as you say that push_scope() continues working the "eager" way it does now AND have it work as a context manager AND like this
@sentry_sdk.push_scope()
def foo():
sentry_sdk.set_tag("key", "value")
Then I haven't thought of how to make it work, it's not possible, or something else may have to give. From my outsider's perspective the easiest solution is to deprecate push_scope(callback) functionality + alter the Hub and ScopeManager classes so that the following examples would all work:
@sentry_sdk.push_scope
def foo():
sentry_sdk.set_tag("key", "value")
with sentry_sdk.push_scope():
print("pushed scope!")
scope = sentry_sdk.push_scope()
but... that feels like a very major change to the api.
@@ -263,6 +268,12 @@ def client(self): | |||
"""Returns the current client on the hub.""" | |||
return self._stack[-1][0] | |||
|
|||
@property | |||
def current_scope(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want this in I would like to call it scope
to be consistent with JS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I don't think this works because of a line below.
…(sentry_sdk/_compat.py) satisfy linters
@a-d-collins I have thought about this some more and I believe that we need to discuss the point of pushing a scope separately from the global setter functions. Could you split this PR up in two parts:
|
self._stack.append(new_layer) | ||
|
||
return _ScopeManager(self) | ||
return ScopeManager(self) | ||
|
||
scope = push_scope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@untitaker This property here seems to take the namespace of Hub.scope?
Currently the only way to temporarily modify sentry scope attributes (user, level, tag, extra) for a function's scope is to call the function within a use of the push_scope context manager. To streamline this operation, this pull request modifies the ScopeManager class to act as both a context manager and a decorator. In order to make scope attribute modifications possible, sentry_sdk/api.py now has the scope attribute setters: set_tag, set_extra, set_user, and set_level. These setters will apply scope attribute changes to the current scope/scope layer.