-
Notifications
You must be signed in to change notification settings - Fork 656
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
Adding attach/detach methods as per spec #429
Changes from 4 commits
ba7d8e9
e3567c4
6c65d62
674658a
9d94509
c0ede6c
e6988ff
71256f3
a72befe
2e60e84
ea4e732
34c5990
abadcac
fd954c7
10cd989
d288715
2b4e958
ae5d8f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,14 +23,20 @@ class ThreadLocalRuntimeContext(RuntimeContext): | |
implementation is available for usage with Python 3.4. | ||
""" | ||
|
||
class _Token: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this class really necessary? I think we can just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree philosophically, although there's probably something in the spec that states that the token should not be the context. Although I don't see anything stating that now: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context.md#attach-context There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reasoning for returning a token rather than a context was discussed in this pull request: open-telemetry/opentelemetry-specification#424 (comment)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If so, then at least the underscore should be in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the class name |
||
def __init__(self, context: Context) -> None: | ||
self.context = context | ||
|
||
_CONTEXT_KEY = "current_context" | ||
|
||
def __init__(self) -> None: | ||
self._current_context = threading.local() | ||
|
||
def set_current(self, context: Context) -> None: | ||
def set_current(self, context: Context) -> object: | ||
"""See `opentelemetry.context.RuntimeContext.set_current`.""" | ||
current = self.get_current() | ||
setattr(self._current_context, self._CONTEXT_KEY, context) | ||
return self._Token(current) | ||
|
||
def get_current(self) -> Context: | ||
"""See `opentelemetry.context.RuntimeContext.get_current`.""" | ||
|
@@ -43,5 +49,11 @@ def get_current(self) -> Context: | |
) # type: Context | ||
return context | ||
|
||
def reset(self, token: object) -> None: | ||
"""See `opentelemetry.context.RuntimeContext.reset`.""" | ||
if not isinstance(token, self._Token): | ||
raise ValueError("invalid token") | ||
setattr(self._current_context, self._CONTEXT_KEY, token.context) | ||
|
||
|
||
__all__ = ["ThreadLocalRuntimeContext"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
# limitations under the License. | ||
|
||
import unittest | ||
from logging import ERROR | ||
from unittest.mock import patch | ||
|
||
from opentelemetry import context | ||
|
@@ -27,18 +28,19 @@ | |
|
||
|
||
def do_work() -> None: | ||
context.set_current(context.set_value("say", "bar")) | ||
context.attach(context.set_value("say", "bar")) | ||
|
||
|
||
class TestContextVarsContext(unittest.TestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we create a base class of the context tests, which we can extend and set the context constructor? seems like a great way to ensure standard behavior across context implementations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
def setUp(self): | ||
self.previous_context = context.get_current() | ||
|
||
def tearDown(self): | ||
context.set_current(self.previous_context) | ||
context.attach(self.previous_context) | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() # type: ignore | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ContextVarsRuntimeContext(), | ||
) | ||
def test_context(self): | ||
self.assertIsNone(context.get_value("say")) | ||
|
@@ -56,7 +58,8 @@ def test_context(self): | |
self.assertEqual(context.get_value("say", context=third), "bar") | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() # type: ignore | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ContextVarsRuntimeContext(), | ||
) | ||
def test_set_value(self): | ||
first = context.set_value("a", "yyy") | ||
|
@@ -66,3 +69,19 @@ def test_set_value(self): | |
self.assertEqual("zzz", context.get_value("a", context=second)) | ||
self.assertEqual("---", context.get_value("a", context=third)) | ||
self.assertEqual(None, context.get_value("a")) | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ContextVarsRuntimeContext(), | ||
) | ||
def test_set_current(self): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
context.attach(context.set_value("a", "yyy")) | ||
|
||
token = context.attach(context.set_value("a", "zzz")) | ||
self.assertEqual("zzz", context.get_value("a")) | ||
|
||
context.detach(token) | ||
self.assertEqual("yyy", context.get_value("a")) | ||
|
||
with self.assertLogs(level=ERROR): | ||
context.detach("some garbage") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,25 +13,27 @@ | |
# limitations under the License. | ||
|
||
import unittest | ||
from logging import ERROR | ||
from unittest.mock import patch | ||
|
||
from opentelemetry import context | ||
from opentelemetry.context.threadlocal_context import ThreadLocalRuntimeContext | ||
|
||
|
||
def do_work() -> None: | ||
context.set_current(context.set_value("say", "bar")) | ||
context.attach(context.set_value("say", "bar")) | ||
|
||
|
||
class TestThreadLocalContext(unittest.TestCase): | ||
def setUp(self): | ||
self.previous_context = context.get_current() | ||
|
||
def tearDown(self): | ||
context.set_current(self.previous_context) | ||
context.attach(self.previous_context) | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ThreadLocalRuntimeContext(), | ||
) | ||
def test_context(self): | ||
self.assertIsNone(context.get_value("say")) | ||
|
@@ -49,7 +51,8 @@ def test_context(self): | |
self.assertEqual(context.get_value("say", context=third), "bar") | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ThreadLocalRuntimeContext(), | ||
) | ||
def test_set_value(self): | ||
first = context.set_value("a", "yyy") | ||
|
@@ -59,3 +62,19 @@ def test_set_value(self): | |
self.assertEqual("zzz", context.get_value("a", context=second)) | ||
self.assertEqual("---", context.get_value("a", context=third)) | ||
self.assertEqual(None, context.get_value("a")) | ||
|
||
@patch( | ||
"opentelemetry.context._RUNTIME_CONTEXT", # type: ignore | ||
ThreadLocalRuntimeContext(), | ||
) | ||
def test_set_current(self): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
context.attach(context.set_value("a", "yyy")) | ||
|
||
token = context.attach(context.set_value("a", "zzz")) | ||
self.assertEqual("zzz", context.get_value("a")) | ||
|
||
context.detach(token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be worth checking that we can detach contexts out of order too, maybe in a separate In [2]: from opentelemetry import context
In [3]: t1 = context.attach(context.set_value('c', 1)); context.get_current()
Out[3]: {'c': 1}
In [4]: t2 = context.attach(context.set_value('c', 2)); context.get_current()
Out[4]: {'c': 2}
In [5]: context.detach(t1); context.get_current()
Out[5]: {}
In [6]: context.detach(t2); context.get_current()
Out[6]: {'c': 1} I was surprised to see contextvars works this way. One difference between contextvars and threadlocals is that contextvars will raise if you try to reset using the same token twice. I don't know whether it's worth enforcing this behavoir for threadlocals though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. test added. Didn't know about the reset behaviour either, good thinng to know! |
||
self.assertEqual("yyy", context.get_value("a")) | ||
|
||
with self.assertLogs(level=ERROR): | ||
context.detach("some garbage") |
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.
Did you try making the token type a
TypeVar
? Just curious, I don't know that it's worth the extra typing boilerplate to do so.