From 989c3e750c6d57c579e1a9f2244a95a0286abc3e Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 5 Jul 2019 11:26:01 -0700 Subject: [PATCH 01/20] steal context lib from OpenCensus --- .../src/opentelemetry/context/__init__.py | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index d853a7bcf65..e02402e33f4 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -11,3 +11,135 @@ # 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. + + +try: + import contextvars +except ImportError: + contextvars = None + +import threading + +__all__ = ['RuntimeContext'] + + +class _RuntimeContext(object): + _lock = threading.Lock() + _slots = {} + + @classmethod + def clear(cls): + """Clear all slots to their default value.""" + for name in cls._slots.keys(): + slot = cls._slots[name] + slot.clear() + + @classmethod + def register_slot(cls, name, default=None): + """Register a context slot with an optional default value. + + :type name: str + :param name: The name of the context slot. + + :type default: object + :param name: The default value of the slot, can be a value or lambda. + + :returns: The registered slot. + """ + with cls._lock: + if name in cls._slots: + raise ValueError('slot {} already registered'.format(name)) + slot = cls.Slot(name, default) + cls._slots[name] = slot + return slot + + def apply(self, snapshot): + """Set the current context from a given snapshot dictionary""" + + for name in snapshot: + setattr(self, name, snapshot[name]) + + def snapshot(self): + """Return a dictionary of current slots by reference.""" + + return dict((n, self._slots[n].get()) for n in self._slots.keys()) + + def __repr__(self): + return ('{}({})'.format(type(self).__name__, self.snapshot())) + + def __getattr__(self, name): + if name not in self._slots: + self.register_slot(name, None) + slot = self._slots[name] + return slot.get() + + def __setattr__(self, name, value): + if name not in self._slots: + self.register_slot(name, None) + slot = self._slots[name] + slot.set(value) + + def with_current_context(self, func): + """Capture the current context and apply it to the provided func""" + + caller_context = self.snapshot() + + def call_with_current_context(*args, **kwargs): + try: + backup_context = self.snapshot() + self.apply(caller_context) + return func(*args, **kwargs) + finally: + self.apply(backup_context) + + return call_with_current_context + + +class _ThreadLocalRuntimeContext(_RuntimeContext): + class Slot(object): + _thread_local = threading.local() + + def __init__(self, name, default): + self.name = name + self.default = default if callable(default) else (lambda: default) + + def clear(self): + setattr(self._thread_local, self.name, self.default()) + + def get(self): + try: + return getattr(self._thread_local, self.name) + except AttributeError: + value = self.default() + self.set(value) + return value + + def set(self, value): + setattr(self._thread_local, self.name, value) + + +class _AsyncRuntimeContext(_RuntimeContext): + class Slot(object): + def __init__(self, name, default): + self.name = name + self.contextvar = contextvars.ContextVar(name) + self.default = default if callable(default) else (lambda: default) + + def clear(self): + self.contextvar.set(self.default()) + + def get(self): + try: + return self.contextvar.get() + except LookupError: + value = self.default() + self.set(value) + return value + + def set(self, value): + self.contextvar.set(value) + + +RuntimeContext = _ThreadLocalRuntimeContext() +if contextvars: + RuntimeContext = _AsyncRuntimeContext() From dd979b2790414e27b83b219e314b36659e4afb2e Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 14:20:31 -0700 Subject: [PATCH 02/20] lint --- .../src/opentelemetry/context/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index e02402e33f4..48a85faf0d3 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -23,7 +23,7 @@ __all__ = ['RuntimeContext'] -class _RuntimeContext(object): +class _RuntimeContext: _lock = threading.Lock() _slots = {} @@ -65,7 +65,7 @@ def snapshot(self): return dict((n, self._slots[n].get()) for n in self._slots.keys()) def __repr__(self): - return ('{}({})'.format(type(self).__name__, self.snapshot())) + return '{}({})'.format(type(self).__name__, self.snapshot()) def __getattr__(self, name): if name not in self._slots: @@ -96,7 +96,7 @@ def call_with_current_context(*args, **kwargs): class _ThreadLocalRuntimeContext(_RuntimeContext): - class Slot(object): + class Slot: _thread_local = threading.local() def __init__(self, name, default): @@ -140,6 +140,6 @@ def set(self, value): self.contextvar.set(value) -RuntimeContext = _ThreadLocalRuntimeContext() +RuntimeContext = _ThreadLocalRuntimeContext() # pylint:disable=C0201 if contextvars: - RuntimeContext = _AsyncRuntimeContext() + RuntimeContext = _AsyncRuntimeContext() # pylint:disable=C0201 From e6fbe91d8ce5bcc3531db78d1885429489493941 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 21:21:47 -0700 Subject: [PATCH 03/20] lint --- .../src/opentelemetry/context/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 48a85faf0d3..f558e7e0e38 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -30,7 +30,7 @@ class _RuntimeContext: @classmethod def clear(cls): """Clear all slots to their default value.""" - for name in cls._slots.keys(): + for name in cls._slots.keys(): # pylint:disable=consider-iterating-dictionary slot = cls._slots[name] slot.clear() @@ -62,7 +62,7 @@ def apply(self, snapshot): def snapshot(self): """Return a dictionary of current slots by reference.""" - return dict((n, self._slots[n].get()) for n in self._slots.keys()) + return dict((n, self._slots[n].get()) for n in self._slots.keys()) # pylint:disable=consider-iterating-dictionary def __repr__(self): return '{}({})'.format(type(self).__name__, self.snapshot()) @@ -119,7 +119,7 @@ def set(self, value): class _AsyncRuntimeContext(_RuntimeContext): - class Slot(object): + class Slot: def __init__(self, name, default): self.name = name self.contextvar = contextvars.ContextVar(name) @@ -140,6 +140,6 @@ def set(self, value): self.contextvar.set(value) -RuntimeContext = _ThreadLocalRuntimeContext() # pylint:disable=C0201 +RuntimeContext = _ThreadLocalRuntimeContext() # pylint:disable=invalid-name if contextvars: - RuntimeContext = _AsyncRuntimeContext() # pylint:disable=C0201 + RuntimeContext = _AsyncRuntimeContext() # pylint:disable=invalid-name From 44e5897ed4555a446fd7716b4703f67773a34652 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 21:25:45 -0700 Subject: [PATCH 04/20] lint --- opentelemetry-api/src/opentelemetry/context/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index f558e7e0e38..56df9a10bb3 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -30,7 +30,8 @@ class _RuntimeContext: @classmethod def clear(cls): """Clear all slots to their default value.""" - for name in cls._slots.keys(): # pylint:disable=consider-iterating-dictionary + keys = cls._slots.keys() + for name in keys: slot = cls._slots[name] slot.clear() @@ -62,7 +63,8 @@ def apply(self, snapshot): def snapshot(self): """Return a dictionary of current slots by reference.""" - return dict((n, self._slots[n].get()) for n in self._slots.keys()) # pylint:disable=consider-iterating-dictionary + keys = self._slots.keys() + return dict((n, self._slots[n].get()) for n in keys) def __repr__(self): return '{}({})'.format(type(self).__name__, self.snapshot()) From beaa000c9ba89e72d7a7e559760769cf94de0754 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 21:35:33 -0700 Subject: [PATCH 05/20] type hint --- opentelemetry-api/src/opentelemetry/context/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 56df9a10bb3..f22d19e9f75 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -19,13 +19,14 @@ contextvars = None import threading +import typing __all__ = ['RuntimeContext'] class _RuntimeContext: _lock = threading.Lock() - _slots = {} + _slots : typing.Dict[str, typing.Any] = {} @classmethod def clear(cls): From 9f71780f47609e35cd1234d5a83d03cef1a43fcb Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 21:42:39 -0700 Subject: [PATCH 06/20] type hint --- .../src/opentelemetry/context/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index f22d19e9f75..6d2f92bee39 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -26,7 +26,7 @@ class _RuntimeContext: _lock = threading.Lock() - _slots : typing.Dict[str, typing.Any] = {} + _slots : typing.Dict[str, Slot] = {} @classmethod def clear(cls): @@ -55,6 +55,18 @@ def register_slot(cls, name, default=None): cls._slots[name] = slot return slot + + class Slot: + def clear(self): + raise NotImplementedError + + def get(self): + raise NotImplementedError + + def set(self, value): + raise NotImplementedError + + def apply(self, snapshot): """Set the current context from a given snapshot dictionary""" @@ -99,7 +111,7 @@ def call_with_current_context(*args, **kwargs): class _ThreadLocalRuntimeContext(_RuntimeContext): - class Slot: + class Slot(_RuntimeContext.Slot): _thread_local = threading.local() def __init__(self, name, default): @@ -122,7 +134,7 @@ def set(self, value): class _AsyncRuntimeContext(_RuntimeContext): - class Slot: + class Slot(_RuntimeContext.Slot): def __init__(self, name, default): self.name = name self.contextvar = contextvars.ContextVar(name) From 90fde070c3996c501a5c9b0c8592be402f0cce95 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 21:45:04 -0700 Subject: [PATCH 07/20] type hint --- .../src/opentelemetry/context/__init__.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 6d2f92bee39..12d8ca2ad03 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -25,8 +25,18 @@ class _RuntimeContext: + class Slot: + def clear(self): + raise NotImplementedError + + def get(self): + raise NotImplementedError + + def set(self, value): + raise NotImplementedError + _lock = threading.Lock() - _slots : typing.Dict[str, Slot] = {} + _slots: typing.Dict[str, Slot] = {} @classmethod def clear(cls): @@ -55,18 +65,6 @@ def register_slot(cls, name, default=None): cls._slots[name] = slot return slot - - class Slot: - def clear(self): - raise NotImplementedError - - def get(self): - raise NotImplementedError - - def set(self, value): - raise NotImplementedError - - def apply(self, snapshot): """Set the current context from a given snapshot dictionary""" From b8ca3e487848f7eaa7f7d12cc5efadaaedc67375 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 22:05:09 -0700 Subject: [PATCH 08/20] type hint --- opentelemetry-api/src/opentelemetry/context/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 12d8ca2ad03..41d88ff4356 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import threading +import types +import typing try: import contextvars except ImportError: - contextvars = None - -import threading -import typing + contextvars: types.ModuleType = None __all__ = ['RuntimeContext'] From 00a256c0b2f3b57e17d3d4a6f5803424fdb99903 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 22:57:20 -0700 Subject: [PATCH 09/20] refactor --- .../src/opentelemetry/context/__init__.py | 148 +----------------- .../opentelemetry/context/async_context.py | 38 +++++ .../src/opentelemetry/context/base_context.py | 99 ++++++++++++ .../context/thread_local_context.py | 40 +++++ 4 files changed, 185 insertions(+), 140 deletions(-) create mode 100644 opentelemetry-api/src/opentelemetry/context/async_context.py create mode 100644 opentelemetry-api/src/opentelemetry/context/base_context.py create mode 100644 opentelemetry-api/src/opentelemetry/context/thread_local_context.py diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 41d88ff4356..c8e4087af38 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -12,147 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import threading -import types -import typing - -try: - import contextvars -except ImportError: - contextvars: types.ModuleType = None +from .base_context import BaseRuntimeContext __all__ = ['RuntimeContext'] +RuntimeContext: BaseRuntimeContext = None -class _RuntimeContext: - class Slot: - def clear(self): - raise NotImplementedError - - def get(self): - raise NotImplementedError - - def set(self, value): - raise NotImplementedError - - _lock = threading.Lock() - _slots: typing.Dict[str, Slot] = {} - - @classmethod - def clear(cls): - """Clear all slots to their default value.""" - keys = cls._slots.keys() - for name in keys: - slot = cls._slots[name] - slot.clear() - - @classmethod - def register_slot(cls, name, default=None): - """Register a context slot with an optional default value. - - :type name: str - :param name: The name of the context slot. - - :type default: object - :param name: The default value of the slot, can be a value or lambda. - - :returns: The registered slot. - """ - with cls._lock: - if name in cls._slots: - raise ValueError('slot {} already registered'.format(name)) - slot = cls.Slot(name, default) - cls._slots[name] = slot - return slot - - def apply(self, snapshot): - """Set the current context from a given snapshot dictionary""" - - for name in snapshot: - setattr(self, name, snapshot[name]) - - def snapshot(self): - """Return a dictionary of current slots by reference.""" - - keys = self._slots.keys() - return dict((n, self._slots[n].get()) for n in keys) - - def __repr__(self): - return '{}({})'.format(type(self).__name__, self.snapshot()) - - def __getattr__(self, name): - if name not in self._slots: - self.register_slot(name, None) - slot = self._slots[name] - return slot.get() - - def __setattr__(self, name, value): - if name not in self._slots: - self.register_slot(name, None) - slot = self._slots[name] - slot.set(value) - - def with_current_context(self, func): - """Capture the current context and apply it to the provided func""" - - caller_context = self.snapshot() - - def call_with_current_context(*args, **kwargs): - try: - backup_context = self.snapshot() - self.apply(caller_context) - return func(*args, **kwargs) - finally: - self.apply(backup_context) - - return call_with_current_context - - -class _ThreadLocalRuntimeContext(_RuntimeContext): - class Slot(_RuntimeContext.Slot): - _thread_local = threading.local() - - def __init__(self, name, default): - self.name = name - self.default = default if callable(default) else (lambda: default) - - def clear(self): - setattr(self._thread_local, self.name, self.default()) - - def get(self): - try: - return getattr(self._thread_local, self.name) - except AttributeError: - value = self.default() - self.set(value) - return value - - def set(self, value): - setattr(self._thread_local, self.name, value) - - -class _AsyncRuntimeContext(_RuntimeContext): - class Slot(_RuntimeContext.Slot): - def __init__(self, name, default): - self.name = name - self.contextvar = contextvars.ContextVar(name) - self.default = default if callable(default) else (lambda: default) - - def clear(self): - self.contextvar.set(self.default()) - - def get(self): - try: - return self.contextvar.get() - except LookupError: - value = self.default() - self.set(value) - return value - - def set(self, value): - self.contextvar.set(value) - - -RuntimeContext = _ThreadLocalRuntimeContext() # pylint:disable=invalid-name -if contextvars: - RuntimeContext = _AsyncRuntimeContext() # pylint:disable=invalid-name +try: + from .async_context import AsyncRuntimeContext + RuntimeContext = AsyncRuntimeContext() +except ImportError: + from .thread_local_context import ThreadLocalRuntimeContext + RuntimeContext = ThreadLocalRuntimeContext() diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py new file mode 100644 index 00000000000..b59fc33361e --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -0,0 +1,38 @@ +# Copyright 2019, 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 contextvars + +from .base_context import BaseRuntimeContext + +class AsyncRuntimeContext(BaseRuntimeContext): + class Slot(BaseRuntimeContext.Slot): + def __init__(self, name, default): + self.name = name + self.contextvar = contextvars.ContextVar(name) + self.default = default if callable(default) else (lambda: default) + + def clear(self): + self.contextvar.set(self.default()) + + def get(self): + try: + return self.contextvar.get() + except LookupError: + value = self.default() + self.set(value) + return value + + def set(self, value): + self.contextvar.set(value) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py new file mode 100644 index 00000000000..0e0fbdc9a15 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -0,0 +1,99 @@ +# Copyright 2019, 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 threading +import typing + +class BaseRuntimeContext: + class Slot: + def clear(self): + raise NotImplementedError + + def get(self): + raise NotImplementedError + + def set(self, value): + raise NotImplementedError + + _lock = threading.Lock() + _slots: typing.Dict[str, Slot] = {} + + @classmethod + def clear(cls): + """Clear all slots to their default value.""" + keys = cls._slots.keys() + for name in keys: + slot = cls._slots[name] + slot.clear() + + @classmethod + def register_slot(cls, name, default=None): + """Register a context slot with an optional default value. + + :type name: str + :param name: The name of the context slot. + + :type default: object + :param name: The default value of the slot, can be a value or lambda. + + :returns: The registered slot. + """ + with cls._lock: + if name in cls._slots: + raise ValueError('slot {} already registered'.format(name)) + slot = cls.Slot(name, default) + cls._slots[name] = slot + return slot + + def apply(self, snapshot): + """Set the current context from a given snapshot dictionary""" + + for name in snapshot: + setattr(self, name, snapshot[name]) + + def snapshot(self): + """Return a dictionary of current slots by reference.""" + + keys = self._slots.keys() + return dict((n, self._slots[n].get()) for n in keys) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, self.snapshot()) + + def __getattr__(self, name): + if name not in self._slots: + self.register_slot(name, None) + slot = self._slots[name] + return slot.get() + + def __setattr__(self, name, value): + if name not in self._slots: + self.register_slot(name, None) + slot = self._slots[name] + slot.set(value) + + def with_current_context(self, func): + """Capture the current context and apply it to the provided func""" + + caller_context = self.snapshot() + + def call_with_current_context(*args, **kwargs): + try: + backup_context = self.snapshot() + self.apply(caller_context) + return func(*args, **kwargs) + finally: + self.apply(backup_context) + + return call_with_current_context diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py new file mode 100644 index 00000000000..be1392557ca --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -0,0 +1,40 @@ +# Copyright 2019, 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 threading +import typing + +from .base_context import BaseRuntimeContext + +class ThreadLocalRuntimeContext(BaseRuntimeContext): + class Slot(BaseRuntimeContext.Slot): + _thread_local = threading.local() + + def __init__(self, name, default): + self.name = name + self.default = default if callable(default) else (lambda: default) + + def clear(self): + setattr(self._thread_local, self.name, self.default()) + + def get(self): + try: + return getattr(self._thread_local, self.name) + except AttributeError: + value = self.default() + self.set(value) + return value + + def set(self, value): + setattr(self._thread_local, self.name, value) From 199054adbd35afa5c5a84efebd6107c3718b6db6 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:04:37 -0700 Subject: [PATCH 10/20] type hint --- .../src/opentelemetry/context/base_context.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 0e0fbdc9a15..bb514a2d6d9 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -17,20 +17,20 @@ class BaseRuntimeContext: class Slot: - def clear(self): + def clear(self) -> None: raise NotImplementedError - def get(self): + def get(self) -> typing.Any: raise NotImplementedError - def set(self, value): + def set(self, value) -> None: raise NotImplementedError _lock = threading.Lock() _slots: typing.Dict[str, Slot] = {} @classmethod - def clear(cls): + def clear(cls) -> None: """Clear all slots to their default value.""" keys = cls._slots.keys() for name in keys: @@ -38,7 +38,7 @@ def clear(cls): slot.clear() @classmethod - def register_slot(cls, name, default=None): + def register_slot(cls, name: str, default: typing.Any = None) -> None: """Register a context slot with an optional default value. :type name: str @@ -56,39 +56,39 @@ def register_slot(cls, name, default=None): cls._slots[name] = slot return slot - def apply(self, snapshot): + def apply(self, snapshot) -> None: """Set the current context from a given snapshot dictionary""" for name in snapshot: setattr(self, name, snapshot[name]) - def snapshot(self): + def snapshot(self) -> typing.Dict[str, typing.Any]: """Return a dictionary of current slots by reference.""" keys = self._slots.keys() return dict((n, self._slots[n].get()) for n in keys) - def __repr__(self): + def __repr__(self) -> str: return '{}({})'.format(type(self).__name__, self.snapshot()) - def __getattr__(self, name): + def __getattr__(self, name) -> typing.Any: if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] return slot.get() - def __setattr__(self, name, value): + def __setattr__(self, name, value) -> None: if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] slot.set(value) - def with_current_context(self, func): + def with_current_context(self, func) -> typing.Callable: """Capture the current context and apply it to the provided func""" caller_context = self.snapshot() - def call_with_current_context(*args, **kwargs): + def call_with_current_context(*args, **kwargs) -> typing.Any: try: backup_context = self.snapshot() self.apply(caller_context) From 023eac9cf1f9757a884f25149ac33e18469f747a Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:08:38 -0700 Subject: [PATCH 11/20] type hint --- opentelemetry-api/src/opentelemetry/context/__init__.py | 4 ++-- .../src/opentelemetry/context/thread_local_context.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index c8e4087af38..8220f629ecf 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -20,7 +20,7 @@ try: from .async_context import AsyncRuntimeContext - RuntimeContext = AsyncRuntimeContext() + RuntimeContext = AsyncRuntimeContext() # pylint:disable=invalid-name except ImportError: from .thread_local_context import ThreadLocalRuntimeContext - RuntimeContext = ThreadLocalRuntimeContext() + RuntimeContext = ThreadLocalRuntimeContext() # pylint:disable=invalid-name diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index be1392557ca..fcded9f8a31 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -13,7 +13,6 @@ # limitations under the License. import threading -import typing from .base_context import BaseRuntimeContext From f83cf89e4976cf55ef3796d36e494edfe1cf856e Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:13:54 -0700 Subject: [PATCH 12/20] type hint --- opentelemetry-api/src/opentelemetry/context/__init__.py | 8 ++++---- .../src/opentelemetry/context/async_context.py | 1 + .../src/opentelemetry/context/base_context.py | 1 + .../src/opentelemetry/context/thread_local_context.py | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 8220f629ecf..e9b8678fb06 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -14,13 +14,13 @@ from .base_context import BaseRuntimeContext -__all__ = ['RuntimeContext'] +__all__ = ['Context'] -RuntimeContext: BaseRuntimeContext = None +Context: BaseRuntimeContext = None try: from .async_context import AsyncRuntimeContext - RuntimeContext = AsyncRuntimeContext() # pylint:disable=invalid-name + Context = AsyncRuntimeContext() # pylint:disable=invalid-name except ImportError: from .thread_local_context import ThreadLocalRuntimeContext - RuntimeContext = ThreadLocalRuntimeContext() # pylint:disable=invalid-name + Context = ThreadLocalRuntimeContext() # pylint:disable=invalid-name diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index b59fc33361e..5fd94dc0b37 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -16,6 +16,7 @@ from .base_context import BaseRuntimeContext + class AsyncRuntimeContext(BaseRuntimeContext): class Slot(BaseRuntimeContext.Slot): def __init__(self, name, default): diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index bb514a2d6d9..769092a385d 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -15,6 +15,7 @@ import threading import typing + class BaseRuntimeContext: class Slot: def clear(self) -> None: diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index fcded9f8a31..4fa0eeb733d 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -16,6 +16,7 @@ from .base_context import BaseRuntimeContext + class ThreadLocalRuntimeContext(BaseRuntimeContext): class Slot(BaseRuntimeContext.Slot): _thread_local = threading.local() From 62517f0db1b8f62ea03eb13b4e9c80f79cdb7431 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:17:45 -0700 Subject: [PATCH 13/20] type hint --- mypy-relaxed.ini | 2 +- mypy.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy-relaxed.ini b/mypy-relaxed.ini index 8f2be85affc..205688353e3 100644 --- a/mypy-relaxed.ini +++ b/mypy-relaxed.ini @@ -4,7 +4,7 @@ disallow_any_unimported = True ; disallow_any_expr = True disallow_any_decorated = True - disallow_any_explicit = True +; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True diff --git a/mypy.ini b/mypy.ini index 5b46777838d..ba375b62b1d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ disallow_any_unimported = True disallow_any_expr = True disallow_any_decorated = True - disallow_any_explicit = True +; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True From 937240e73f8bef01f2dfb87c9f0b6397d4089e48 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:22:18 -0700 Subject: [PATCH 14/20] type hint --- opentelemetry-api/src/opentelemetry/context/base_context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 769092a385d..3898fb57ba2 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -18,13 +18,16 @@ class BaseRuntimeContext: class Slot: + def __init__(self, name: str, default: typing.Any): + raise NotImplementedError + def clear(self) -> None: raise NotImplementedError def get(self) -> typing.Any: raise NotImplementedError - def set(self, value) -> None: + def set(self, value: typing.Any) -> None: raise NotImplementedError _lock = threading.Lock() From 2b2f553d78f50bc5dfa87124ff0a2c7a8609e209 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:26:20 -0700 Subject: [PATCH 15/20] type hint --- mypy-relaxed.ini | 2 +- mypy.ini | 2 +- opentelemetry-api/src/opentelemetry/context/base_context.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy-relaxed.ini b/mypy-relaxed.ini index 205688353e3..9cd56789a1e 100644 --- a/mypy-relaxed.ini +++ b/mypy-relaxed.ini @@ -3,7 +3,7 @@ [mypy] disallow_any_unimported = True ; disallow_any_expr = True - disallow_any_decorated = True +; disallow_any_decorated = True ; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True diff --git a/mypy.ini b/mypy.ini index ba375b62b1d..abb7aba2961 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ [mypy] disallow_any_unimported = True disallow_any_expr = True - disallow_any_decorated = True +; disallow_any_decorated = True ; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 3898fb57ba2..67d42a10b49 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -42,7 +42,7 @@ def clear(cls) -> None: slot.clear() @classmethod - def register_slot(cls, name: str, default: typing.Any = None) -> None: + def register_slot(cls, name: str, default: typing.Any = None) -> 'Slot': """Register a context slot with an optional default value. :type name: str @@ -87,7 +87,7 @@ def __setattr__(self, name, value) -> None: slot = self._slots[name] slot.set(value) - def with_current_context(self, func) -> typing.Callable: + def with_current_context(self, func: typing.Callable) -> typing.Callable: """Capture the current context and apply it to the provided func""" caller_context = self.snapshot() From 533fd2adb2924b08b204d2880fbb1645a05009f5 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 17 Jul 2019 23:36:06 -0700 Subject: [PATCH 16/20] type hint --- opentelemetry-api/src/opentelemetry/context/async_context.py | 1 + .../src/opentelemetry/context/thread_local_context.py | 1 + 2 files changed, 2 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index 5fd94dc0b37..614cfaacc0d 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -20,6 +20,7 @@ class AsyncRuntimeContext(BaseRuntimeContext): class Slot(BaseRuntimeContext.Slot): def __init__(self, name, default): + # pylint: disable=super-init-not-called self.name = name self.contextvar = contextvars.ContextVar(name) self.default = default if callable(default) else (lambda: default) diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index 4fa0eeb733d..9830aa83b03 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -22,6 +22,7 @@ class Slot(BaseRuntimeContext.Slot): _thread_local = threading.local() def __init__(self, name, default): + # pylint: disable=super-init-not-called self.name = name self.default = default if callable(default) else (lambda: default) From 123887e96babe0bb2e16d1272b0996342efa154e Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Thu, 18 Jul 2019 00:12:28 -0700 Subject: [PATCH 17/20] type hint --- .../src/opentelemetry/context/__init__.py | 4 +++- .../opentelemetry/context/async_context.py | 13 +++++++------ .../src/opentelemetry/context/base_context.py | 19 +++++++++++++------ .../context/thread_local_context.py | 9 +++++---- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index e9b8678fb06..c2fe840cb44 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import typing + from .base_context import BaseRuntimeContext __all__ = ['Context'] -Context: BaseRuntimeContext = None +Context: typing.Union[BaseRuntimeContext, None] = None try: from .async_context import AsyncRuntimeContext diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index 614cfaacc0d..6e5f0c0fce3 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -12,23 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -import contextvars +from contextvars import ContextVar +import typing from .base_context import BaseRuntimeContext class AsyncRuntimeContext(BaseRuntimeContext): class Slot(BaseRuntimeContext.Slot): - def __init__(self, name, default): + def __init__(self, name: str, default: typing.Any): # pylint: disable=super-init-not-called self.name = name - self.contextvar = contextvars.ContextVar(name) + self.contextvar: typing.Any = ContextVar(name) self.default = default if callable(default) else (lambda: default) - def clear(self): + def clear(self) -> None: self.contextvar.set(self.default()) - def get(self): + def get(self) -> typing.Any: try: return self.contextvar.get() except LookupError: @@ -36,5 +37,5 @@ def get(self): self.set(value) return value - def set(self, value): + def set(self, value: typing.Any) -> None: self.contextvar.set(value) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 67d42a10b49..5cc1794cd06 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -60,7 +60,7 @@ def register_slot(cls, name: str, default: typing.Any = None) -> 'Slot': cls._slots[name] = slot return slot - def apply(self, snapshot) -> None: + def apply(self, snapshot: typing.Dict[str, typing.Any]) -> None: """Set the current context from a given snapshot dictionary""" for name in snapshot: @@ -75,24 +75,31 @@ def snapshot(self) -> typing.Dict[str, typing.Any]: def __repr__(self) -> str: return '{}({})'.format(type(self).__name__, self.snapshot()) - def __getattr__(self, name) -> typing.Any: + def __getattr__(self, name: str) -> typing.Any: if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] return slot.get() - def __setattr__(self, name, value) -> None: + def __setattr__(self, name: str, value: typing.Any) -> None: if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] slot.set(value) - def with_current_context(self, func: typing.Callable) -> typing.Callable: - """Capture the current context and apply it to the provided func""" + def with_current_context( + self, + func: typing.Callable[..., typing.Any], + ) -> typing.Callable[..., typing.Any]: + """Capture the current context and apply it to the provided func. + """ caller_context = self.snapshot() - def call_with_current_context(*args, **kwargs) -> typing.Any: + def call_with_current_context( + *args: typing.Any, + **kwargs: typing.Any, + ) -> typing.Any: try: backup_context = self.snapshot() self.apply(caller_context) diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index 9830aa83b03..25f07c98069 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -13,6 +13,7 @@ # limitations under the License. import threading +import typing from .base_context import BaseRuntimeContext @@ -21,15 +22,15 @@ class ThreadLocalRuntimeContext(BaseRuntimeContext): class Slot(BaseRuntimeContext.Slot): _thread_local = threading.local() - def __init__(self, name, default): + def __init__(self, name: str, default: typing.Any): # pylint: disable=super-init-not-called self.name = name self.default = default if callable(default) else (lambda: default) - def clear(self): + def clear(self) -> None: setattr(self._thread_local, self.name, self.default()) - def get(self): + def get(self) -> typing.Any: try: return getattr(self._thread_local, self.name) except AttributeError: @@ -37,5 +38,5 @@ def get(self): self.set(value) return value - def set(self, value): + def set(self, value: typing.Any) -> None: setattr(self._thread_local, self.name, value) From 1f93a36b5786112cce8ac5c3c0b7b4d92ac8a6c1 Mon Sep 17 00:00:00 2001 From: Chris Kleinknecht Date: Fri, 19 Jul 2019 10:10:42 -0700 Subject: [PATCH 18/20] Type hint edits for #57 (#59) --- mypy-relaxed.ini | 2 +- mypy.ini | 2 +- .../src/opentelemetry/context/__init__.py | 4 ++- .../opentelemetry/context/async_context.py | 19 +++++------ .../src/opentelemetry/context/base_context.py | 32 +++++++++++-------- .../context/thread_local_context.py | 18 ++++++----- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/mypy-relaxed.ini b/mypy-relaxed.ini index 9cd56789a1e..205688353e3 100644 --- a/mypy-relaxed.ini +++ b/mypy-relaxed.ini @@ -3,7 +3,7 @@ [mypy] disallow_any_unimported = True ; disallow_any_expr = True -; disallow_any_decorated = True + disallow_any_decorated = True ; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True diff --git a/mypy.ini b/mypy.ini index abb7aba2961..ba375b62b1d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ [mypy] disallow_any_unimported = True disallow_any_expr = True -; disallow_any_decorated = True + disallow_any_decorated = True ; disallow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index c2fe840cb44..db975f9e510 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -16,9 +16,11 @@ from .base_context import BaseRuntimeContext + __all__ = ['Context'] -Context: typing.Union[BaseRuntimeContext, None] = None + +Context: typing.Optional[BaseRuntimeContext] try: from .async_context import AsyncRuntimeContext diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index 6e5f0c0fce3..f118870ecea 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -12,24 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -from contextvars import ContextVar import typing +from contextvars import ContextVar -from .base_context import BaseRuntimeContext +from . import base_context -class AsyncRuntimeContext(BaseRuntimeContext): - class Slot(BaseRuntimeContext.Slot): - def __init__(self, name: str, default: typing.Any): +class AsyncRuntimeContext(base_context.BaseRuntimeContext): + class Slot(base_context.BaseRuntimeContext.Slot): + def __init__(self, name: str, default: 'object'): # pylint: disable=super-init-not-called self.name = name - self.contextvar: typing.Any = ContextVar(name) - self.default = default if callable(default) else (lambda: default) + self.contextvar: 'ContextVar[object]' = ContextVar(name) + self.default: typing.Callable[..., object] + self.default = base_context.wrap_callable(default) def clear(self) -> None: self.contextvar.set(self.default()) - def get(self) -> typing.Any: + def get(self) -> 'object': try: return self.contextvar.get() except LookupError: @@ -37,5 +38,5 @@ def get(self) -> typing.Any: self.set(value) return value - def set(self, value: typing.Any) -> None: + def set(self, value: 'object') -> None: self.contextvar.set(value) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 5cc1794cd06..35ee179a4b8 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -16,18 +16,24 @@ import typing +def wrap_callable(target: 'object') -> typing.Callable[[], object]: + if callable(target): + return target + return lambda: target + + class BaseRuntimeContext: class Slot: - def __init__(self, name: str, default: typing.Any): + def __init__(self, name: str, default: 'object'): raise NotImplementedError def clear(self) -> None: raise NotImplementedError - def get(self) -> typing.Any: + def get(self) -> 'object': raise NotImplementedError - def set(self, value: typing.Any) -> None: + def set(self, value: 'object') -> None: raise NotImplementedError _lock = threading.Lock() @@ -42,7 +48,7 @@ def clear(cls) -> None: slot.clear() @classmethod - def register_slot(cls, name: str, default: typing.Any = None) -> 'Slot': + def register_slot(cls, name: str, default: 'object' = None) -> 'Slot': """Register a context slot with an optional default value. :type name: str @@ -60,13 +66,13 @@ def register_slot(cls, name: str, default: typing.Any = None) -> 'Slot': cls._slots[name] = slot return slot - def apply(self, snapshot: typing.Dict[str, typing.Any]) -> None: + def apply(self, snapshot: typing.Dict[str, 'object']) -> None: """Set the current context from a given snapshot dictionary""" for name in snapshot: setattr(self, name, snapshot[name]) - def snapshot(self) -> typing.Dict[str, typing.Any]: + def snapshot(self) -> typing.Dict[str, 'object']: """Return a dictionary of current slots by reference.""" keys = self._slots.keys() @@ -75,13 +81,13 @@ def snapshot(self) -> typing.Dict[str, typing.Any]: def __repr__(self) -> str: return '{}({})'.format(type(self).__name__, self.snapshot()) - def __getattr__(self, name: str) -> typing.Any: + def __getattr__(self, name: str) -> 'object': if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] return slot.get() - def __setattr__(self, name: str, value: typing.Any) -> None: + def __setattr__(self, name: str, value: 'object') -> None: if name not in self._slots: self.register_slot(name, None) slot = self._slots[name] @@ -89,17 +95,17 @@ def __setattr__(self, name: str, value: typing.Any) -> None: def with_current_context( self, - func: typing.Callable[..., typing.Any], - ) -> typing.Callable[..., typing.Any]: + func: typing.Callable[..., 'object'], + ) -> typing.Callable[..., 'object']: """Capture the current context and apply it to the provided func. """ caller_context = self.snapshot() def call_with_current_context( - *args: typing.Any, - **kwargs: typing.Any, - ) -> typing.Any: + *args: 'object', + **kwargs: 'object', + ) -> 'object': try: backup_context = self.snapshot() self.apply(caller_context) diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index 25f07c98069..dd11128b7ac 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -15,28 +15,30 @@ import threading import typing -from .base_context import BaseRuntimeContext +from . import base_context -class ThreadLocalRuntimeContext(BaseRuntimeContext): - class Slot(BaseRuntimeContext.Slot): +class ThreadLocalRuntimeContext(base_context.BaseRuntimeContext): + class Slot(base_context.BaseRuntimeContext.Slot): _thread_local = threading.local() - def __init__(self, name: str, default: typing.Any): + def __init__(self, name: str, default: 'object'): # pylint: disable=super-init-not-called self.name = name - self.default = default if callable(default) else (lambda: default) + self.default: typing.Callable[..., object] + self.default = base_context.wrap_callable(default) def clear(self) -> None: setattr(self._thread_local, self.name, self.default()) - def get(self) -> typing.Any: + def get(self) -> 'object': try: - return getattr(self._thread_local, self.name) + got: object = getattr(self._thread_local, self.name) + return got except AttributeError: value = self.default() self.set(value) return value - def set(self, value: typing.Any) -> None: + def set(self, value: 'object') -> None: setattr(self._thread_local, self.name, value) From 0b114e40d647ee919f6dd7301fb058fbf055978e Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 19 Jul 2019 10:30:31 -0700 Subject: [PATCH 19/20] fix import order --- opentelemetry-api/src/opentelemetry/context/__init__.py | 1 - opentelemetry-api/src/opentelemetry/context/async_context.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index db975f9e510..d902d866b6b 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -16,7 +16,6 @@ from .base_context import BaseRuntimeContext - __all__ = ['Context'] diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index f118870ecea..413e7b2543f 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import typing from contextvars import ContextVar +import typing from . import base_context From e557afd2e3c8ce2f2191aefb364affcc1668020b Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Thu, 25 Jul 2019 18:09:57 -0700 Subject: [PATCH 20/20] add module doc and example --- .../src/opentelemetry/context/__init__.py | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index d902d866b6b..a02bc8cb4e2 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -12,6 +12,131 @@ # See the License for the specific language governing permissions and # limitations under the License. + +""" +The OpenTelemetry context module provides abstraction layer on top of +thread-local storage and contextvars. The long term direction is to switch to +contextvars provided by the Python runtime library. + +A global object ``Context`` is provided to access all the context related +functionalities: + + >>> from opentelemetry.context import Context + >>> Context.foo = 1 + >>> Context.foo = 2 + >>> Context.foo + 2 + +When explicit thread is used, a helper function `Context.with_current_context` +can be used to carry the context across threads: + + from threading import Thread + from opentelemetry.context import Context + + def work(name): + print('Entering worker:', Context) + Context.operation_id = name + print('Exiting worker:', Context) + + if __name__ == '__main__': + print('Main thread:', Context) + Context.operation_id = 'main' + + print('Main thread:', Context) + + # by default context is not propagated to worker thread + thread = Thread(target=work, args=('foo',)) + thread.start() + thread.join() + + print('Main thread:', Context) + + # user can propagate context explicitly + thread = Thread( + target=Context.with_current_context(work), + args=('bar',), + ) + thread.start() + thread.join() + + print('Main thread:', Context) + +Here goes another example using thread pool: + + import time + import threading + + from multiprocessing.dummy import Pool as ThreadPool + from opentelemetry.context import Context + + _console_lock = threading.Lock() + + def println(msg): + with _console_lock: + print(msg) + + def work(name): + println('Entering worker[{}]: {}'.format(name, Context)) + Context.operation_id = name + time.sleep(0.01) + println('Exiting worker[{}]: {}'.format(name, Context)) + + if __name__ == "__main__": + println('Main thread: {}'.format(Context)) + Context.operation_id = 'main' + pool = ThreadPool(2) # create a thread pool with 2 threads + pool.map(Context.with_current_context(work), [ + 'bear', + 'cat', + 'dog', + 'horse', + 'rabbit', + ]) + pool.close() + pool.join() + println('Main thread: {}'.format(Context)) + +Here goes a simple demo of how async could work in Python 3.7+: + + import asyncio + + from opentelemetry.context import Context + + class Span(object): + def __init__(self, name): + self.name = name + self.parent = Context.current_span + + def __repr__(self): + return ('{}(name={}, parent={})' + .format( + type(self).__name__, + self.name, + self.parent, + )) + + async def __aenter__(self): + Context.current_span = self + + async def __aexit__(self, exc_type, exc, tb): + Context.current_span = self.parent + + async def main(): + print(Context) + async with Span('foo'): + print(Context) + await asyncio.sleep(0.1) + async with Span('bar'): + print(Context) + await asyncio.sleep(0.1) + print(Context) + await asyncio.sleep(0.1) + print(Context) + + if __name__ == '__main__': + asyncio.run(main()) +""" + import typing from .base_context import BaseRuntimeContext