From ba7d8e96bd61b1fe24d361cb38d21b8a7c5da6c2 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 14 Feb 2020 10:50:41 -0800 Subject: [PATCH 01/17] Adding attach/detach methods as per spec This change updates the Context API with the following: - removes the `remove_value` method - removes the `set_current` method - adds `attach` and `detach` methods Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 50 ++++++++----------- .../src/opentelemetry/context/context.py | 13 ++++- .../context/contextvars_context.py | 12 +++-- .../context/threadlocal_context.py | 12 ++++- .../distributedcontext/__init__.py | 4 +- .../tests/context/test_context.py | 11 ++-- .../tests/context/test_contextvars_context.py | 27 ++++++++-- .../tests/context/test_threadlocal_context.py | 27 ++++++++-- .../src/opentelemetry/sdk/trace/__init__.py | 5 +- .../sdk/trace/export/__init__.py | 12 ++--- .../tests/context/test_asyncio.py | 5 +- 11 files changed, 115 insertions(+), 63 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 1d1b53e7cb2..69afc58c15c 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -57,24 +57,6 @@ def set_value( return Context(new_values) -def remove_value( - key: str, context: typing.Optional[Context] = None -) -> Context: - """To remove a value, this method returns a new context with the key - cleared. Note that the removed value still remains present in the old - context. - - Args: - key: The key of the entry to remove - context: The context to copy, if None, the current context is used - """ - if context is None: - context = get_current() - new_values = context.copy() - new_values.pop(key, None) - return Context(new_values) - - def get_current() -> Context: """To access the context associated with program execution, the RuntimeContext API provides a function which takes no arguments @@ -104,16 +86,29 @@ def get_current() -> Context: return _RUNTIME_CONTEXT.get_current() # type:ignore -def set_current(context: Context) -> Context: - """To associate a context with program execution, the Context - API provides a function which takes a Context. +def attach(context: Context) -> object: + """Associates a Context with the caller's current execution unit. Returns + a token that can be used to restore the previous Context. + + Args: + context: The Context to set as current. + """ + get_current() + + return _RUNTIME_CONTEXT.set_current(context) # type:ignore + + +def detach(token: object) -> None: + """Resets the Context associated with the caller's current execution unit + to the value it had before attaching a specified Context. Args: - context: The context to use as current. + token: The Token that was returned by a previous call to attach a Context. """ - old_context = get_current() - _RUNTIME_CONTEXT.set_current(context) # type:ignore - return old_context + try: + _RUNTIME_CONTEXT.reset(token) # type: ignore + except ValueError: + logger.error("Failed to detach context") def with_current_context( @@ -127,10 +122,9 @@ def call_with_current_context( *args: "object", **kwargs: "object" ) -> "object": try: - backup = get_current() - set_current(caller_context) + token = attach(caller_context) return func(*args, **kwargs) finally: - set_current(backup) + detach(token) return call_with_current_context diff --git a/opentelemetry-api/src/opentelemetry/context/context.py b/opentelemetry-api/src/opentelemetry/context/context.py index 148312a884c..3e94c126f15 100644 --- a/opentelemetry-api/src/opentelemetry/context/context.py +++ b/opentelemetry-api/src/opentelemetry/context/context.py @@ -29,8 +29,9 @@ class RuntimeContext(ABC): """ @abstractmethod - def set_current(self, context: Context) -> None: - """ Sets the current `Context` object. + def set_current(self, context: Context) -> object: + """ Sets the current `Context` object. Returns a + token that can be used to reset to the previous `Context`. Args: context: The Context to set. @@ -40,5 +41,13 @@ def set_current(self, context: Context) -> None: def get_current(self) -> Context: """ Returns the current `Context` object. """ + @abstractmethod + def reset(self, token: object) -> None: + """ Resets Context to a previous value + + Args: + token: A reference to a previous Context. + """ + __all__ = ["Context", "RuntimeContext"] diff --git a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py index 1fd202275a3..16cf08892ac 100644 --- a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py +++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from contextvars import ContextVar +from contextvars import ContextVar, Token from sys import version_info from opentelemetry.context.context import Context, RuntimeContext @@ -35,13 +35,19 @@ def __init__(self) -> None: self._CONTEXT_KEY, default=Context() ) - def set_current(self, context: Context) -> None: + def set_current(self, context: Context) -> object: """See `opentelemetry.context.RuntimeContext.set_current`.""" - self._current_context.set(context) + return self._current_context.set(context) def get_current(self) -> Context: """See `opentelemetry.context.RuntimeContext.get_current`.""" return self._current_context.get() + def reset(self, token: object) -> None: + """See `opentelemetry.context.RuntimeContext.reset`.""" + if not isinstance(token, Token): + raise ValueError("invalid token") + self._current_context.reset(token) + __all__ = ["ContextVarsRuntimeContext"] diff --git a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py index 899ab863262..af2a6ed4952 100644 --- a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py +++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py @@ -28,9 +28,12 @@ class ThreadLocalRuntimeContext(RuntimeContext): 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, str(id(current)), current) setattr(self._current_context, self._CONTEXT_KEY, context) + return str(id(current)) def get_current(self) -> Context: """See `opentelemetry.context.RuntimeContext.get_current`.""" @@ -43,5 +46,12 @@ def get_current(self) -> Context: ) # type: Context return context + def reset(self, token: object) -> None: + """See `opentelemetry.context.RuntimeContext.reset`.""" + if not hasattr(self._current_context, str(token)): + raise ValueError("invalid token") + context = getattr(self._current_context, str(token)) # type: Context + setattr(self._current_context, self._CONTEXT_KEY, context) + __all__ = ["ThreadLocalRuntimeContext"] diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index a89d9825502..dbc7b7e79bd 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -17,7 +17,7 @@ import typing from contextlib import contextmanager -from opentelemetry.context import get_value, set_current, set_value +from opentelemetry.context import attach, get_value, set_value from opentelemetry.context.context import Context PRINTABLE = frozenset( @@ -142,4 +142,4 @@ def distributed_context_from_context( def with_distributed_context( dctx: DistributedContext, context: typing.Optional[Context] = None ) -> None: - set_current(set_value(_DISTRIBUTED_CONTEXT_KEY, dctx, context=context)) + attach(set_value(_DISTRIBUTED_CONTEXT_KEY, dctx, context=context)) diff --git a/opentelemetry-api/tests/context/test_context.py b/opentelemetry-api/tests/context/test_context.py index 2536e5149be..8942a333ed6 100644 --- a/opentelemetry-api/tests/context/test_context.py +++ b/opentelemetry-api/tests/context/test_context.py @@ -19,12 +19,12 @@ def do_work() -> None: - context.set_current(context.set_value("say", "bar")) + context.attach(context.set_value("say", "bar")) class TestContext(unittest.TestCase): def setUp(self): - context.set_current(Context()) + context.attach(Context()) def test_context(self): self.assertIsNone(context.get_value("say")) @@ -55,11 +55,10 @@ def test_context_is_immutable(self): context.get_current()["test"] = "cant-change-immutable" def test_set_current(self): - context.set_current(context.set_value("a", "yyy")) + context.attach(context.set_value("a", "yyy")) - old_context = context.set_current(context.set_value("a", "zzz")) - self.assertEqual("yyy", context.get_value("a", context=old_context)) + token = context.attach(context.set_value("a", "zzz")) self.assertEqual("zzz", context.get_value("a")) - context.set_current(old_context) + context.detach(token) self.assertEqual("yyy", context.get_value("a")) diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py index ebc15d6d9a3..f79cee7724b 100644 --- a/opentelemetry-api/tests/context/test_contextvars_context.py +++ b/opentelemetry-api/tests/context/test_contextvars_context.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +from logging import ERROR from unittest.mock import patch from opentelemetry import context @@ -27,7 +28,7 @@ def do_work() -> None: - context.set_current(context.set_value("say", "bar")) + context.attach(context.set_value("say", "bar")) class TestContextVarsContext(unittest.TestCase): @@ -35,10 +36,11 @@ 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): + 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") diff --git a/opentelemetry-api/tests/context/test_threadlocal_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py index aca6b69de72..70bc32b9622 100644 --- a/opentelemetry-api/tests/context/test_threadlocal_context.py +++ b/opentelemetry-api/tests/context/test_threadlocal_context.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +from logging import ERROR from unittest.mock import patch from opentelemetry import context @@ -20,7 +21,7 @@ def do_work() -> None: - context.set_current(context.set_value("say", "bar")) + context.attach(context.set_value("say", "bar")) class TestThreadLocalContext(unittest.TestCase): @@ -28,10 +29,11 @@ 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): + 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") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 6d249e65080..2b0fcc6037f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -541,14 +541,13 @@ def use_span( ) -> Iterator[trace_api.Span]: """See `opentelemetry.trace.Tracer.use_span`.""" try: - context_snapshot = context_api.get_current() - context_api.set_current( + token = context_api.attach( context_api.set_value(self.source.key, span) ) try: yield span finally: - context_api.set_current(context_snapshot) + context_api.detach(token) except Exception as error: # pylint: disable=broad-except if ( diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 0a1b1c8041d..0f96808ea88 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -19,7 +19,7 @@ import typing from enum import Enum -from opentelemetry.context import get_current, set_current, set_value +from opentelemetry.context import attach, detach, get_current, set_value from opentelemetry.trace import DefaultSpan from opentelemetry.util import time_ns @@ -75,14 +75,13 @@ def on_start(self, span: Span) -> None: pass def on_end(self, span: Span) -> None: - backup_context = get_current() - set_current(set_value("suppress_instrumentation", True)) + token = attach(set_value("suppress_instrumentation", True)) try: self.span_exporter.export((span,)) # pylint: disable=broad-except except Exception: logger.exception("Exception while exporting Span.") - set_current(backup_context) + detach(token) def shutdown(self) -> None: self.span_exporter.shutdown() @@ -202,8 +201,7 @@ def export(self) -> None: else: self.spans_list[idx] = span idx += 1 - backup_context = get_current() - set_current(set_value("suppress_instrumentation", True)) + token = attach(set_value("suppress_instrumentation", True)) try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy @@ -211,7 +209,7 @@ def export(self) -> None: # pylint: disable=broad-except except Exception: logger.exception("Exception while exporting Span batch.") - set_current(backup_context) + detach(token) if notify_flush: with self.flush_condition: diff --git a/opentelemetry-sdk/tests/context/test_asyncio.py b/opentelemetry-sdk/tests/context/test_asyncio.py index e1cb90f452a..b297386e85a 100644 --- a/opentelemetry-sdk/tests/context/test_asyncio.py +++ b/opentelemetry-sdk/tests/context/test_asyncio.py @@ -63,8 +63,7 @@ def submit_another_task(self, name): self.loop.create_task(self.task(name)) def setUp(self): - self.previous_context = context.get_current() - context.set_current(context.Context()) + self.token = context.attach(context.Context()) self.tracer_source = trace.TracerSource() self.tracer = self.tracer_source.get_tracer(__name__) self.memory_exporter = InMemorySpanExporter() @@ -73,7 +72,7 @@ def setUp(self): self.loop = asyncio.get_event_loop() def tearDown(self): - context.set_current(self.previous_context) + context.detach(self.token) @patch( "opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() From e3567c4e1e66007a8a30a6500b62f487c9fcdfd3 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 19 Feb 2020 08:35:33 -0800 Subject: [PATCH 02/17] use a _Token for ThreadLocalRuntimeContext Signed-off-by: Alex Boten --- .../src/opentelemetry/context/threadlocal_context.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py index af2a6ed4952..7e1bef71f44 100644 --- a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py +++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py @@ -23,6 +23,10 @@ class ThreadLocalRuntimeContext(RuntimeContext): implementation is available for usage with Python 3.4. """ + class _Token: + def __init__(self, context: Context) -> None: + self.context = context + _CONTEXT_KEY = "current_context" def __init__(self) -> None: @@ -31,9 +35,8 @@ def __init__(self) -> None: def set_current(self, context: Context) -> object: """See `opentelemetry.context.RuntimeContext.set_current`.""" current = self.get_current() - setattr(self._current_context, str(id(current)), current) setattr(self._current_context, self._CONTEXT_KEY, context) - return str(id(current)) + return self._Token(current) def get_current(self) -> Context: """See `opentelemetry.context.RuntimeContext.get_current`.""" @@ -48,10 +51,9 @@ def get_current(self) -> Context: def reset(self, token: object) -> None: """See `opentelemetry.context.RuntimeContext.reset`.""" - if not hasattr(self._current_context, str(token)): + if not isinstance(token, self._Token): raise ValueError("invalid token") - context = getattr(self._current_context, str(token)) # type: Context - setattr(self._current_context, self._CONTEXT_KEY, context) + setattr(self._current_context, self._CONTEXT_KEY, token.context) __all__ = ["ThreadLocalRuntimeContext"] From 6c65d6247d39f948417e7decd9fd5e094e7983d0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 19 Feb 2020 08:59:03 -0800 Subject: [PATCH 03/17] remove instance check, ContextVar reset will do this Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- .../src/opentelemetry/context/contextvars_context.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 69afc58c15c..c00064c54b3 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -107,7 +107,7 @@ def detach(token: object) -> None: """ try: _RUNTIME_CONTEXT.reset(token) # type: ignore - except ValueError: + except (TypeError, ValueError): logger.error("Failed to detach context") diff --git a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py index 16cf08892ac..b4da1eff8eb 100644 --- a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py +++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py @@ -45,9 +45,7 @@ def get_current(self) -> Context: def reset(self, token: object) -> None: """See `opentelemetry.context.RuntimeContext.reset`.""" - if not isinstance(token, Token): - raise ValueError("invalid token") - self._current_context.reset(token) + self._current_context.reset(token) # type: ignore __all__ = ["ContextVarsRuntimeContext"] From 674658af408caf21e550abdd35d0d6db5d681924 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 19 Feb 2020 13:48:39 -0800 Subject: [PATCH 04/17] fix lint, catch AttributeError as well Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- .../src/opentelemetry/context/contextvars_context.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index c00064c54b3..e83f66e3099 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -107,7 +107,7 @@ def detach(token: object) -> None: """ try: _RUNTIME_CONTEXT.reset(token) # type: ignore - except (TypeError, ValueError): + except (AttributeError, TypeError, ValueError): logger.error("Failed to detach context") diff --git a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py index b4da1eff8eb..51dd75832be 100644 --- a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py +++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from contextvars import ContextVar, Token +from contextvars import ContextVar from sys import version_info from opentelemetry.context.context import Context, RuntimeContext From c0ede6c9d544e25cc56ce5fc66b414d85d551419 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 24 Feb 2020 11:54:55 -0800 Subject: [PATCH 05/17] lint fix --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 559789d7a05..dd0169ea9f7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -543,9 +543,7 @@ def use_span( ) -> Iterator[trace_api.Span]: """See `opentelemetry.trace.Tracer.use_span`.""" try: - token = context_api.attach( - context_api.set_value(SPAN_KEY, span) - ) + token = context_api.attach(context_api.set_value(SPAN_KEY, span)) try: yield span finally: From e6988ff73cfd779f4eb94af936411ca7e0de1755 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 08:45:56 -0800 Subject: [PATCH 06/17] update set_current/reset to attach/detach in RuntimeContext Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 4 ++-- opentelemetry-api/src/opentelemetry/context/context.py | 4 ++-- .../src/opentelemetry/context/contextvars_context.py | 8 ++++---- .../src/opentelemetry/context/threadlocal_context.py | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index e83f66e3099..d77ff549a63 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -95,7 +95,7 @@ def attach(context: Context) -> object: """ get_current() - return _RUNTIME_CONTEXT.set_current(context) # type:ignore + return _RUNTIME_CONTEXT.attach(context) # type:ignore def detach(token: object) -> None: @@ -106,7 +106,7 @@ def detach(token: object) -> None: token: The Token that was returned by a previous call to attach a Context. """ try: - _RUNTIME_CONTEXT.reset(token) # type: ignore + _RUNTIME_CONTEXT.detach(token) # type: ignore except (AttributeError, TypeError, ValueError): logger.error("Failed to detach context") diff --git a/opentelemetry-api/src/opentelemetry/context/context.py b/opentelemetry-api/src/opentelemetry/context/context.py index 3e94c126f15..1c7cfba9634 100644 --- a/opentelemetry-api/src/opentelemetry/context/context.py +++ b/opentelemetry-api/src/opentelemetry/context/context.py @@ -29,7 +29,7 @@ class RuntimeContext(ABC): """ @abstractmethod - def set_current(self, context: Context) -> object: + def attach(self, context: Context) -> object: """ Sets the current `Context` object. Returns a token that can be used to reset to the previous `Context`. @@ -42,7 +42,7 @@ def get_current(self) -> Context: """ Returns the current `Context` object. """ @abstractmethod - def reset(self, token: object) -> None: + def detach(self, token: object) -> None: """ Resets Context to a previous value Args: diff --git a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py index 51dd75832be..0d075e0776a 100644 --- a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py +++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py @@ -35,16 +35,16 @@ def __init__(self) -> None: self._CONTEXT_KEY, default=Context() ) - def set_current(self, context: Context) -> object: - """See `opentelemetry.context.RuntimeContext.set_current`.""" + def attach(self, context: Context) -> object: + """See `opentelemetry.context.RuntimeContext.attach`.""" return self._current_context.set(context) def get_current(self) -> Context: """See `opentelemetry.context.RuntimeContext.get_current`.""" return self._current_context.get() - def reset(self, token: object) -> None: - """See `opentelemetry.context.RuntimeContext.reset`.""" + def detach(self, token: object) -> None: + """See `opentelemetry.context.RuntimeContext.detach`.""" self._current_context.reset(token) # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py index 7e1bef71f44..b97da896294 100644 --- a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py +++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py @@ -32,8 +32,8 @@ def __init__(self, context: Context) -> None: def __init__(self) -> None: self._current_context = threading.local() - def set_current(self, context: Context) -> object: - """See `opentelemetry.context.RuntimeContext.set_current`.""" + def attach(self, context: Context) -> object: + """See `opentelemetry.context.RuntimeContext.attach`.""" current = self.get_current() setattr(self._current_context, self._CONTEXT_KEY, context) return self._Token(current) @@ -49,8 +49,8 @@ def get_current(self) -> Context: ) # type: Context return context - def reset(self, token: object) -> None: - """See `opentelemetry.context.RuntimeContext.reset`.""" + def detach(self, token: object) -> None: + """See `opentelemetry.context.RuntimeContext.detach`.""" if not isinstance(token, self._Token): raise ValueError("invalid token") setattr(self._current_context, self._CONTEXT_KEY, token.context) From 71256f3219f3809e2ed65de3282f62a0fba9ffd5 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 09:54:37 -0800 Subject: [PATCH 07/17] catch all exceptions --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index d77ff549a63..c0556c0e9b6 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -107,7 +107,7 @@ def detach(token: object) -> None: """ try: _RUNTIME_CONTEXT.detach(token) # type: ignore - except (AttributeError, TypeError, ValueError): + except Exception: # pylint: disable=broad-except logger.error("Failed to detach context") From a72befe6a2565cb2366ac6ca568bff364920d12b Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 10:35:59 -0800 Subject: [PATCH 08/17] adding _load_runtime_context decorator Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index c0556c0e9b6..6956b75a27a 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -25,6 +25,43 @@ _RUNTIME_CONTEXT = None # type: typing.Optional[RuntimeContext] +_F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + +def _load_runtime_context(func: _F) -> _F: + """Initializes the global RuntimeContext + """ + + def wrapper( + *args: typing.Tuple[typing.Any, typing.Any], + **kwargs: typing.Dict[typing.Any, typing.Any], + ) -> typing.Optional[typing.Any]: + global _RUNTIME_CONTEXT # pylint: disable=global-statement + if _RUNTIME_CONTEXT is None: + # FIXME use a better implementation of a configuration manager to avoid having + # to get configuration values straight from environment variables + if version_info < (3, 5): + # contextvars are not supported in 3.4, use thread-local storage + default_context = "threadlocal_context" + else: + default_context = "contextvars_context" + + configured_context = environ.get( + "OPENTELEMETRY_CONTEXT", default_context + ) # type: str + try: + _RUNTIME_CONTEXT = next( + iter_entry_points( + "opentelemetry_context", configured_context + ) + ).load()() + except Exception: # pylint: disable=broad-except + logger.error("Failed to load context: %s", configured_context) + return func(*args, **kwargs) # type: ignore + + return wrapper # type:ignore + + def get_value(key: str, context: typing.Optional[Context] = None) -> "object": """To access the local state of a concern, the RuntimeContext API provides a function which takes a context and a key as input, @@ -57,35 +94,16 @@ def set_value( return Context(new_values) +@_load_runtime_context # type: ignore def get_current() -> Context: """To access the context associated with program execution, the RuntimeContext API provides a function which takes no arguments and returns a RuntimeContext. """ - - global _RUNTIME_CONTEXT # pylint: disable=global-statement - if _RUNTIME_CONTEXT is None: - # FIXME use a better implementation of a configuration manager to avoid having - # to get configuration values straight from environment variables - if version_info < (3, 5): - # contextvars are not supported in 3.4, use thread-local storage - default_context = "threadlocal_context" - else: - default_context = "contextvars_context" - - configured_context = environ.get( - "OPENTELEMETRY_CONTEXT", default_context - ) # type: str - try: - _RUNTIME_CONTEXT = next( - iter_entry_points("opentelemetry_context", configured_context) - ).load()() - except Exception: # pylint: disable=broad-except - logger.error("Failed to load context: %s", configured_context) - return _RUNTIME_CONTEXT.get_current() # type:ignore +@_load_runtime_context # type: ignore def attach(context: Context) -> object: """Associates a Context with the caller's current execution unit. Returns a token that can be used to restore the previous Context. @@ -93,11 +111,10 @@ def attach(context: Context) -> object: Args: context: The Context to set as current. """ - get_current() - return _RUNTIME_CONTEXT.attach(context) # type:ignore +@_load_runtime_context # type: ignore def detach(token: object) -> None: """Resets the Context associated with the caller's current execution unit to the value it had before attaching a specified Context. From 2e60e84a3575626d521162461a6f1e6bbca60d96 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 10:54:23 -0800 Subject: [PATCH 09/17] rename _Token to Token --- .../src/opentelemetry/context/threadlocal_context.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py index b97da896294..6a0e76bb693 100644 --- a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py +++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py @@ -23,9 +23,9 @@ class ThreadLocalRuntimeContext(RuntimeContext): implementation is available for usage with Python 3.4. """ - class _Token: + class Token: def __init__(self, context: Context) -> None: - self.context = context + self._context = context _CONTEXT_KEY = "current_context" @@ -36,7 +36,7 @@ def attach(self, context: Context) -> object: """See `opentelemetry.context.RuntimeContext.attach`.""" current = self.get_current() setattr(self._current_context, self._CONTEXT_KEY, context) - return self._Token(current) + return self.Token(current) def get_current(self) -> Context: """See `opentelemetry.context.RuntimeContext.get_current`.""" @@ -51,9 +51,10 @@ def get_current(self) -> Context: def detach(self, token: object) -> None: """See `opentelemetry.context.RuntimeContext.detach`.""" - if not isinstance(token, self._Token): + if not isinstance(token, self.Token): raise ValueError("invalid token") - setattr(self._current_context, self._CONTEXT_KEY, token.context) + # pylint: disable=protected-access + setattr(self._current_context, self._CONTEXT_KEY, token._context) __all__ = ["ThreadLocalRuntimeContext"] From ea4e7320dc6f3e0f4158abf23367e3cd106b5e3a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 11:00:41 -0800 Subject: [PATCH 10/17] fixing tests Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 6956b75a27a..61bbf0fe2c6 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -34,7 +34,7 @@ def _load_runtime_context(func: _F) -> _F: def wrapper( *args: typing.Tuple[typing.Any, typing.Any], - **kwargs: typing.Dict[typing.Any, typing.Any], + **kwargs: typing.Dict[typing.Any, typing.Any] ) -> typing.Optional[typing.Any]: global _RUNTIME_CONTEXT # pylint: disable=global-statement if _RUNTIME_CONTEXT is None: From 34c5990cb899378cce74dbedc2dbbfa303142a70 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 11:19:50 -0800 Subject: [PATCH 11/17] fixing docs --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 61bbf0fe2c6..fa4725a830e 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -14,6 +14,7 @@ import logging import typing +from functools import wraps from os import environ from sys import version_info @@ -32,6 +33,7 @@ def _load_runtime_context(func: _F) -> _F: """Initializes the global RuntimeContext """ + @wraps(func) def wrapper( *args: typing.Tuple[typing.Any, typing.Any], **kwargs: typing.Dict[typing.Any, typing.Any] From abadcac35b65883f098f61fa6a23fdacb5636bf5 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 12:47:23 -0800 Subject: [PATCH 12/17] fix mypy Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index fa4725a830e..276eca211b0 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -33,7 +33,7 @@ def _load_runtime_context(func: _F) -> _F: """Initializes the global RuntimeContext """ - @wraps(func) + @wraps(func) # type: ignore def wrapper( *args: typing.Tuple[typing.Any, typing.Any], **kwargs: typing.Dict[typing.Any, typing.Any] From fd954c76032d68e953ab7609818d29bc61c1cc16 Mon Sep 17 00:00:00 2001 From: alrex Date: Tue, 25 Feb 2020 13:04:28 -0800 Subject: [PATCH 13/17] Apply suggestions from code review Co-Authored-By: Chris Kleinknecht --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 ++ opentelemetry-api/tests/context/test_contextvars_context.py | 2 +- opentelemetry-api/tests/context/test_threadlocal_context.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 276eca211b0..917442d6ffb 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -112,6 +112,8 @@ def attach(context: Context) -> object: Args: context: The Context to set as current. + Returns: + A token that can be used with `detach` to reset the context. """ return _RUNTIME_CONTEXT.attach(context) # type:ignore diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py index f79cee7724b..42cbda73543 100644 --- a/opentelemetry-api/tests/context/test_contextvars_context.py +++ b/opentelemetry-api/tests/context/test_contextvars_context.py @@ -74,7 +74,7 @@ def test_set_value(self): "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore ContextVarsRuntimeContext(), ) - def test_set_current(self): + def test_attach(self): context.attach(context.set_value("a", "yyy")) token = context.attach(context.set_value("a", "zzz")) diff --git a/opentelemetry-api/tests/context/test_threadlocal_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py index 70bc32b9622..12053ec6a80 100644 --- a/opentelemetry-api/tests/context/test_threadlocal_context.py +++ b/opentelemetry-api/tests/context/test_threadlocal_context.py @@ -67,7 +67,7 @@ def test_set_value(self): "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore ThreadLocalRuntimeContext(), ) - def test_set_current(self): + def test_attach(self): context.attach(context.set_value("a", "yyy")) token = context.attach(context.set_value("a", "zzz")) From 10cd989a32c090f081c4d28a266a09ce595bc78e Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 13:12:58 -0800 Subject: [PATCH 14/17] adding docs, removing unused method Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 917442d6ffb..1ac837f51c5 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -30,7 +30,10 @@ def _load_runtime_context(func: _F) -> _F: - """Initializes the global RuntimeContext + """A decorator used to initialize the global RuntimeContext + + Returns: + A wrapper of the decorated method. """ @wraps(func) # type: ignore @@ -72,6 +75,9 @@ def get_value(key: str, context: typing.Optional[Context] = None) -> "object": Args: key: The key of the value to retrieve. context: The context from which to retrieve the value, if None, the current context is used. + + Returns: + The value associated with the key. """ return context.get(key) if context is not None else get_current().get(key) @@ -85,9 +91,12 @@ def set_value( which contains the new value. Args: - key: The key of the entry to set - value: The value of the entry to set - context: The context to copy, if None, the current context is used + key: The key of the entry to set. + value: The value of the entry to set. + context: The context to copy, if None, the current context is used. + + Returns: + A new `Context` containing the value set. """ if context is None: context = get_current() @@ -99,8 +108,11 @@ def set_value( @_load_runtime_context # type: ignore def get_current() -> Context: """To access the context associated with program execution, - the RuntimeContext API provides a function which takes no arguments - and returns a RuntimeContext. + the Context API provides a function which takes no arguments + and returns a Context. + + Returns: + The current `Context` object. """ return _RUNTIME_CONTEXT.get_current() # type:ignore @@ -112,8 +124,9 @@ def attach(context: Context) -> object: Args: context: The Context to set as current. + Returns: - A token that can be used with `detach` to reset the context. + A token that can be used with `detach` to reset the context. """ return _RUNTIME_CONTEXT.attach(context) # type:ignore @@ -130,22 +143,3 @@ def detach(token: object) -> None: _RUNTIME_CONTEXT.detach(token) # type: ignore except Exception: # pylint: disable=broad-except logger.error("Failed to detach context") - - -def with_current_context( - func: typing.Callable[..., "object"] -) -> typing.Callable[..., "object"]: - """Capture the current context and apply it to the provided func.""" - - caller_context = get_current() - - def call_with_current_context( - *args: "object", **kwargs: "object" - ) -> "object": - try: - token = attach(caller_context) - return func(*args, **kwargs) - finally: - detach(token) - - return call_with_current_context From d288715ee2dde9982639cd6d0f7e8ea2361be8b9 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 13:50:39 -0800 Subject: [PATCH 15/17] refactoring test into a common class --- .../tests/context/base_context.py | 67 +++++++++++++++++++ .../tests/context/test_contextvars_context.py | 66 +++--------------- .../tests/context/test_threadlocal_context.py | 65 +++--------------- 3 files changed, 86 insertions(+), 112 deletions(-) create mode 100644 opentelemetry-api/tests/context/base_context.py diff --git a/opentelemetry-api/tests/context/base_context.py b/opentelemetry-api/tests/context/base_context.py new file mode 100644 index 00000000000..43cd374f554 --- /dev/null +++ b/opentelemetry-api/tests/context/base_context.py @@ -0,0 +1,67 @@ +# Copyright 2020, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from logging import ERROR + +from opentelemetry import context + + +def do_work() -> None: + context.attach(context.set_value("say", "bar")) + + +class ContextTestCases: + class BaseTest(unittest.TestCase): + def setUp(self): + self.previous_context = context.get_current() + + def tearDown(self): + context.attach(self.previous_context) + + def test_context(self): + self.assertIsNone(context.get_value("say")) + empty = context.get_current() + second = context.set_value("say", "foo") + + self.assertEqual(context.get_value("say", context=second), "foo") + + do_work() + self.assertEqual(context.get_value("say"), "bar") + third = context.get_current() + + self.assertIsNone(context.get_value("say", context=empty)) + self.assertEqual(context.get_value("say", context=second), "foo") + self.assertEqual(context.get_value("say", context=third), "bar") + + def test_set_value(self): + first = context.set_value("a", "yyy") + second = context.set_value("a", "zzz") + third = context.set_value("a", "---", first) + self.assertEqual("yyy", context.get_value("a", context=first)) + self.assertEqual("zzz", context.get_value("a", context=second)) + self.assertEqual("---", context.get_value("a", context=third)) + self.assertEqual(None, context.get_value("a")) + + def test_attach(self): + 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") diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py index 42cbda73543..6da26fd5e23 100644 --- a/opentelemetry-api/tests/context/test_contextvars_context.py +++ b/opentelemetry-api/tests/context/test_contextvars_context.py @@ -13,11 +13,12 @@ # limitations under the License. import unittest -from logging import ERROR from unittest.mock import patch from opentelemetry import context +from .base_context import ContextTestCases + try: import contextvars # pylint: disable=unused-import from opentelemetry.context.contextvars_context import ( @@ -27,61 +28,14 @@ raise unittest.SkipTest("contextvars not available") -def do_work() -> None: - context.attach(context.set_value("say", "bar")) - - -class TestContextVarsContext(unittest.TestCase): +class TestContextVarsContext(ContextTestCases.BaseTest): def setUp(self): - self.previous_context = context.get_current() + super(TestContextVarsContext, self).setUp() + self.mock_runtime = patch.object( + context, "_RUNTIME_CONTEXT", ContextVarsRuntimeContext(), + ) + self.mock_runtime.start() def tearDown(self): - context.attach(self.previous_context) - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore - ContextVarsRuntimeContext(), - ) - def test_context(self): - self.assertIsNone(context.get_value("say")) - empty = context.get_current() - second = context.set_value("say", "foo") - - self.assertEqual(context.get_value("say", context=second), "foo") - - do_work() - self.assertEqual(context.get_value("say"), "bar") - third = context.get_current() - - self.assertIsNone(context.get_value("say", context=empty)) - self.assertEqual(context.get_value("say", context=second), "foo") - self.assertEqual(context.get_value("say", context=third), "bar") - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore - ContextVarsRuntimeContext(), - ) - def test_set_value(self): - first = context.set_value("a", "yyy") - second = context.set_value("a", "zzz") - third = context.set_value("a", "---", first) - self.assertEqual("yyy", context.get_value("a", context=first)) - 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_attach(self): - 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") + super(TestContextVarsContext, self).tearDown() + self.mock_runtime.stop() diff --git a/opentelemetry-api/tests/context/test_threadlocal_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py index 12053ec6a80..61435149e81 100644 --- a/opentelemetry-api/tests/context/test_threadlocal_context.py +++ b/opentelemetry-api/tests/context/test_threadlocal_context.py @@ -12,69 +12,22 @@ # See the License for the specific language governing permissions and # 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 +from .base_context import ContextTestCases -def do_work() -> None: - context.attach(context.set_value("say", "bar")) - -class TestThreadLocalContext(unittest.TestCase): +class TestThreadLocalContext(ContextTestCases.BaseTest): def setUp(self): - self.previous_context = context.get_current() + super(TestThreadLocalContext, self).setUp() + self.mock_runtime = patch.object( + context, "_RUNTIME_CONTEXT", ThreadLocalRuntimeContext(), + ) + self.mock_runtime.start() def tearDown(self): - context.attach(self.previous_context) - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore - ThreadLocalRuntimeContext(), - ) - def test_context(self): - self.assertIsNone(context.get_value("say")) - empty = context.get_current() - second = context.set_value("say", "foo") - - self.assertEqual(context.get_value("say", context=second), "foo") - - do_work() - self.assertEqual(context.get_value("say"), "bar") - third = context.get_current() - - self.assertIsNone(context.get_value("say", context=empty)) - self.assertEqual(context.get_value("say", context=second), "foo") - self.assertEqual(context.get_value("say", context=third), "bar") - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", # type: ignore - ThreadLocalRuntimeContext(), - ) - def test_set_value(self): - first = context.set_value("a", "yyy") - second = context.set_value("a", "zzz") - third = context.set_value("a", "---", first) - self.assertEqual("yyy", context.get_value("a", context=first)) - 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_attach(self): - 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") + super(TestThreadLocalContext, self).tearDown() + self.mock_runtime.stop() From 2b4e958b55735df11f43602bbec07754b17daf56 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 13:55:44 -0800 Subject: [PATCH 16/17] fix mypy --- opentelemetry-api/tests/context/base_context.py | 4 ++-- opentelemetry-api/tests/context/test_contextvars_context.py | 4 ++-- opentelemetry-api/tests/context/test_threadlocal_context.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opentelemetry-api/tests/context/base_context.py b/opentelemetry-api/tests/context/base_context.py index 43cd374f554..92919a802c2 100644 --- a/opentelemetry-api/tests/context/base_context.py +++ b/opentelemetry-api/tests/context/base_context.py @@ -24,10 +24,10 @@ def do_work() -> None: class ContextTestCases: class BaseTest(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.previous_context = context.get_current() - def tearDown(self): + def tearDown(self) -> None: context.attach(self.previous_context) def test_context(self): diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py index 6da26fd5e23..d19ac5ca126 100644 --- a/opentelemetry-api/tests/context/test_contextvars_context.py +++ b/opentelemetry-api/tests/context/test_contextvars_context.py @@ -29,13 +29,13 @@ class TestContextVarsContext(ContextTestCases.BaseTest): - def setUp(self): + def setUp(self) -> None: super(TestContextVarsContext, self).setUp() self.mock_runtime = patch.object( context, "_RUNTIME_CONTEXT", ContextVarsRuntimeContext(), ) self.mock_runtime.start() - def tearDown(self): + def tearDown(self) -> None: super(TestContextVarsContext, self).tearDown() self.mock_runtime.stop() diff --git a/opentelemetry-api/tests/context/test_threadlocal_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py index 61435149e81..342163020ed 100644 --- a/opentelemetry-api/tests/context/test_threadlocal_context.py +++ b/opentelemetry-api/tests/context/test_threadlocal_context.py @@ -21,13 +21,13 @@ class TestThreadLocalContext(ContextTestCases.BaseTest): - def setUp(self): + def setUp(self) -> None: super(TestThreadLocalContext, self).setUp() self.mock_runtime = patch.object( context, "_RUNTIME_CONTEXT", ThreadLocalRuntimeContext(), ) self.mock_runtime.start() - def tearDown(self): + def tearDown(self) -> None: super(TestThreadLocalContext, self).tearDown() self.mock_runtime.stop() From ae5d8f6cf775ac087fc7d8862e8851b5456fdc86 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 25 Feb 2020 14:03:39 -0800 Subject: [PATCH 17/17] adding test for out of order detach Signed-off-by: Alex Boten --- opentelemetry-api/tests/context/base_context.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/opentelemetry-api/tests/context/base_context.py b/opentelemetry-api/tests/context/base_context.py index 92919a802c2..66e6df97a2d 100644 --- a/opentelemetry-api/tests/context/base_context.py +++ b/opentelemetry-api/tests/context/base_context.py @@ -65,3 +65,13 @@ def test_attach(self): with self.assertLogs(level=ERROR): context.detach("some garbage") + + def test_detach_out_of_order(self): + t1 = context.attach(context.set_value("c", 1)) + self.assertEqual(context.get_current(), {"c": 1}) + t2 = context.attach(context.set_value("c", 2)) + self.assertEqual(context.get_current(), {"c": 2}) + context.detach(t1) + self.assertEqual(context.get_current(), {}) + context.detach(t2) + self.assertEqual(context.get_current(), {"c": 1})