diff --git a/README.md b/README.md index 05f4df29bc..97a76e1f0c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ pip install -e ./ext/opentelemetry-ext-{integration} ```python from opentelemetry import trace -from opentelemetry.context import Context from opentelemetry.sdk.trace import TracerSource from opentelemetry.sdk.trace.export import ConsoleSpanExporter from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor @@ -64,7 +63,7 @@ tracer = trace.tracer_source().get_tracer(__name__) with tracer.start_as_current_span('foo'): with tracer.start_as_current_span('bar'): with tracer.start_as_current_span('baz'): - print(Context) + print("Hello world from OpenTelemetry Python!") ``` ### Metrics diff --git a/examples/basic_tracer/tracer.py b/examples/basic_tracer/tracer.py index 4b392fd1ea..5093280a5a 100755 --- a/examples/basic_tracer/tracer.py +++ b/examples/basic_tracer/tracer.py @@ -17,7 +17,6 @@ import os from opentelemetry import trace -from opentelemetry.context import Context from opentelemetry.sdk.trace import TracerSource from opentelemetry.sdk.trace.export import ( BatchExportSpanProcessor, @@ -51,4 +50,4 @@ with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): - print(Context) + print("Hello world from OpenTelemetry Python!") diff --git a/opentelemetry-api/setup.py b/opentelemetry-api/setup.py index fad86f171b..20e7f58143 100644 --- a/opentelemetry-api/setup.py +++ b/opentelemetry-api/setup.py @@ -44,7 +44,10 @@ include_package_data=True, long_description=open("README.rst").read(), long_description_content_type="text/x-rst", - install_requires=["typing; python_version<'3.5'"], + install_requires=[ + "typing; python_version<'3.5'", + "aiocontextvars; python_version<'3.7' and python_version>='3.5'", + ], extras_require={}, license="Apache-2.0", package_dir={"": "src"}, @@ -58,9 +61,12 @@ zip_safe=False, entry_points={ "opentelemetry_context": [ - "default_context = " - "opentelemetry.context.default_context:" - "DefaultRuntimeContext", + "contextvars_context = " + "opentelemetry.context.contextvars_context:" + "ContextVarsRuntimeContext", + "threadlocal_context = " + "opentelemetry.context.threadlocal_context:" + "ThreadLocalRuntimeContext", ] }, ) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 63de570abc..1d1b53e7cb 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -15,6 +15,7 @@ import logging import typing from os import environ +from sys import version_info from pkg_resources import iter_entry_points @@ -84,9 +85,14 @@ def get_current() -> Context: 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" + "OPENTELEMETRY_CONTEXT", default_context ) # type: str try: _RUNTIME_CONTEXT = next( diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/aiocontextvarsfix.py b/opentelemetry-api/src/opentelemetry/context/aiocontextvarsfix.py similarity index 96% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/aiocontextvarsfix.py rename to opentelemetry-api/src/opentelemetry/context/aiocontextvarsfix.py index 6aa1779378..f0d4ce56ff 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/aiocontextvarsfix.py +++ b/opentelemetry-api/src/opentelemetry/context/aiocontextvarsfix.py @@ -55,7 +55,7 @@ def _get_event_loop(): asyncio._set_running_loop = asyncio.events._set_running_loop # noinspection PyUnresolvedReferences -import aiocontextvars # pylint: disable=unused-import,wrong-import-position # noqa # isort:skip +import aiocontextvars # pylint: disable=import-error,unused-import,wrong-import-position # noqa # isort:skip def _run_coroutine_threadsafe(coro, loop): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py similarity index 88% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/contextvars_context.py rename to opentelemetry-api/src/opentelemetry/context/contextvars_context.py index 0a350e2699..1fd202275a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/contextvars_context.py +++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py @@ -14,14 +14,13 @@ from contextvars import ContextVar from sys import version_info -from opentelemetry.context import Context -from opentelemetry.context.context import RuntimeContext +from opentelemetry.context.context import Context, RuntimeContext if (3, 5, 3) <= version_info < (3, 7): - import aiocontextvars # type: ignore # pylint:disable=unused-import + import aiocontextvars # type: ignore # pylint:disable=unused-import,import-error elif (3, 4) < version_info <= (3, 5, 2): - import opentelemetry.sdk.context.aiocontextvarsfix # pylint:disable=unused-import + import opentelemetry.context.aiocontextvarsfix # pylint:disable=unused-import class ContextVarsRuntimeContext(RuntimeContext): diff --git a/opentelemetry-api/src/opentelemetry/context/default_context.py b/opentelemetry-api/src/opentelemetry/context/default_context.py deleted file mode 100644 index 6c83f839d3..0000000000 --- a/opentelemetry-api/src/opentelemetry/context/default_context.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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. -from opentelemetry.context.context import Context, RuntimeContext - - -class DefaultRuntimeContext(RuntimeContext): - """A default implementation of the RuntimeContext interface using - a dictionary to store values. - """ - - def __init__(self) -> None: - self._current_context = Context() - - def set_current(self, context: Context) -> None: - """See `opentelemetry.context.RuntimeContext.set_current`.""" - self._current_context = context - - def get_current(self) -> Context: - """See `opentelemetry.context.RuntimeContext.get_current`.""" - return self._current_context - - -__all__ = ["DefaultRuntimeContext"] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py similarity index 88% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/threadlocal_context.py rename to opentelemetry-api/src/opentelemetry/context/threadlocal_context.py index 26d4329c52..899ab86326 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/threadlocal_context.py +++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py @@ -14,7 +14,7 @@ import threading -from opentelemetry.context import Context, RuntimeContext +from opentelemetry.context.context import Context, RuntimeContext class ThreadLocalRuntimeContext(RuntimeContext): @@ -38,7 +38,10 @@ def get_current(self) -> Context: setattr( self._current_context, self._CONTEXT_KEY, Context(), ) - return getattr(self._current_context, self._CONTEXT_KEY) + context = getattr( + self._current_context, self._CONTEXT_KEY + ) # type: Context + return context __all__ = ["ThreadLocalRuntimeContext"] diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py new file mode 100644 index 0000000000..ebc15d6d9a --- /dev/null +++ b/opentelemetry-api/tests/context/test_contextvars_context.py @@ -0,0 +1,68 @@ +# 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 unittest.mock import patch + +from opentelemetry import context + +try: + import contextvars # pylint: disable=unused-import + from opentelemetry.context.contextvars_context import ( + ContextVarsRuntimeContext, + ) +except ImportError: + raise unittest.SkipTest("contextvars not available") + + +def do_work() -> None: + context.set_current(context.set_value("say", "bar")) + + +class TestContextVarsContext(unittest.TestCase): + def setUp(self): + self.previous_context = context.get_current() + + def tearDown(self): + context.set_current(self.previous_context) + + @patch( + "opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() # type: ignore + ) + 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", ContextVarsRuntimeContext() # type: ignore + ) + 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")) diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py similarity index 93% rename from opentelemetry-sdk/tests/context/test_context.py rename to opentelemetry-api/tests/context/test_threadlocal_context.py index 88a63109d1..aca6b69de7 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-api/tests/context/test_threadlocal_context.py @@ -16,9 +16,7 @@ from unittest.mock import patch from opentelemetry import context -from opentelemetry.sdk.context.threadlocal_context import ( - ThreadLocalRuntimeContext, -) +from opentelemetry.context.threadlocal_context import ThreadLocalRuntimeContext def do_work() -> None: @@ -33,7 +31,7 @@ def tearDown(self): context.set_current(self.previous_context) @patch( - "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() + "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore ) def test_context(self): self.assertIsNone(context.get_value("say")) @@ -51,7 +49,7 @@ def test_context(self): self.assertEqual(context.get_value("say", context=third), "bar") @patch( - "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() + "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore ) def test_set_value(self): first = context.set_value("a", "yyy") diff --git a/opentelemetry-sdk/setup.py b/opentelemetry-sdk/setup.py index 7e88bb3bfe..cbfb0f075d 100644 --- a/opentelemetry-sdk/setup.py +++ b/opentelemetry-sdk/setup.py @@ -44,7 +44,7 @@ include_package_data=True, long_description=open("README.rst").read(), long_description_content_type="text/x-rst", - install_requires=["opentelemetry-api==0.4.dev0", "aiocontextvars"], + install_requires=["opentelemetry-api==0.4.dev0"], extras_require={}, license="Apache-2.0", package_dir={"": "src"}, @@ -56,14 +56,4 @@ "/tree/master/opentelemetry-sdk" ), zip_safe=False, - entry_points={ - "opentelemetry_context": [ - "contextvars_context = " - "opentelemetry.sdk.context.contextvars_context:" - "ContextVarsRuntimeContext", - "threadlocal_context = " - "opentelemetry.sdk.context.threadlocal_context:" - "ThreadLocalRuntimeContext", - ] - }, ) diff --git a/opentelemetry-sdk/tests/context/test_asyncio.py b/opentelemetry-sdk/tests/context/test_asyncio.py index 5dc3637598..e1cb90f452 100644 --- a/opentelemetry-sdk/tests/context/test_asyncio.py +++ b/opentelemetry-sdk/tests/context/test_asyncio.py @@ -25,7 +25,7 @@ try: import contextvars # pylint: disable=unused-import - from opentelemetry.sdk.context.contextvars_context import ( + from opentelemetry.context.contextvars_context import ( ContextVarsRuntimeContext, ) except ImportError: @@ -53,10 +53,6 @@ def stop_loop_when(loop, cond_func, timeout=5.0): loop.call_later(0.1, stop_loop_when, loop, cond_func, timeout) -def do_work() -> None: - context.set_current(context.set_value("say", "bar")) - - class TestAsyncio(unittest.TestCase): @asyncio.coroutine def task(self, name): @@ -114,41 +110,3 @@ def test_with_asyncio(self): if span is expected_parent: continue self.assertEqual(span.parent, expected_parent) - - -class TestContextVarsContext(unittest.TestCase): - def setUp(self): - self.previous_context = context.get_current() - - def tearDown(self): - context.set_current(self.previous_context) - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", 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", 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")) diff --git a/opentelemetry-sdk/tests/context/test_threads.py b/opentelemetry-sdk/tests/context/test_threads.py deleted file mode 100644 index e8552b9135..0000000000 --- a/opentelemetry-sdk/tests/context/test_threads.py +++ /dev/null @@ -1,87 +0,0 @@ -# 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 multiprocessing.dummy import Pool -from unittest.mock import patch - -from opentelemetry import context -from opentelemetry.sdk import trace -from opentelemetry.sdk.context.threadlocal_context import ( - ThreadLocalRuntimeContext, -) -from opentelemetry.sdk.trace import export -from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( - InMemorySpanExporter, -) - - -class TestThreads(unittest.TestCase): - span_names = [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - ] - - def do_work(self, name="default"): - with self.tracer.start_as_current_span(name): - context.set_value("say-something", "bar") - - def setUp(self): - self.previous_context = context.get_current() - context.set_current(context.Context()) - self.tracer_source = trace.TracerSource() - self.tracer = self.tracer_source.get_tracer(__name__) - self.memory_exporter = InMemorySpanExporter() - span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) - self.tracer_source.add_span_processor(span_processor) - - def tearDown(self): - context.set_current(self.previous_context) - - @patch( - "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() - ) - def test_with_threads(self): - with self.tracer.start_as_current_span("threads_test"): - pool = Pool(5) # create a thread pool - pool.map( - context.with_current_context(self.do_work), self.span_names - ) - pool.close() - pool.join() - span_list = self.memory_exporter.get_finished_spans() - span_names_list = [span.name for span in span_list] - expected = [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - "threads_test", - ] - self.assertCountEqual(span_names_list, expected) - span_names_list.sort() - expected.sort() - self.assertListEqual(span_names_list, expected) - expected_parent = next( - span for span in span_list if span.name == "threads_test" - ) - # FIXME - for span in span_list: - if span is expected_parent: - continue - self.assertEqual(span.parent, expected_parent) diff --git a/tox.ini b/tox.ini index 5f98277060..67b79b368e 100644 --- a/tox.ini +++ b/tox.ini @@ -12,14 +12,14 @@ envlist = ; pypy3-coverage lint - py37-tracecontext - py37-{mypy,mypyinstalled} + py38-tracecontext + py38-{mypy,mypyinstalled} docs docker-tests [travis] python = - 3.7: py37, lint, docs, docker-tests + 3.8: py38, lint, docs, docker-tests [testenv] deps = @@ -107,7 +107,7 @@ commands = [testenv:lint] -basepython: python3.7 +basepython: python3.8 recreate = True deps = -c dev-requirements.txt @@ -140,8 +140,8 @@ changedir = docs commands = sphinx-build -E -a -W --keep-going -b html -T . _build/html -[testenv:py37-tracecontext] -basepython: python3.7 +[testenv:py38-tracecontext] +basepython: python3.8 deps = # needed for tracecontext aiohttp~=3.6