From 747d10046f1c6cf6c54bc8fc23ad5b40cd658a97 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Fri, 16 Aug 2019 13:50:02 -0700 Subject: [PATCH 01/23] Add initial DistributedContext implementation. --- .../distributedcontext/__init__.py | 54 +++++++++++++++ .../propagation/__init__.py | 0 .../sdk/distributedcontext/__init__.py | 68 +++++++++++++++++++ .../propagation/__init__.py | 0 4 files changed, 122 insertions(+) create mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index d853a7bcf65..ecd0042d67a 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -11,3 +11,57 @@ # 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 contextlib import contextmanager +import string +import typing + + +class EntryMetadata: + NO_PROPAGATION = 0 + UNLIMITED_PROPAGATION = -1 + + def __init__(self, entry_ttl: int) -> None: + self.entry_ttl = entry_ttl + + +class EntryKey(str): + def __new__(cls, value): + if any(c not in string.printable for c in value) or len(value) > 255: + raise ValueError("Invalid EntryKey", value) + return str.__new__(cls, value) + + +class EntryValue(str): + def __new__(cls, value): + if any(c not in string.printable for c in value): + raise ValueError("Invalid EntryValue", value) + return str.__new__(cls, value) + + +class Entry: + def __init__( + self, metadata: EntryMetadata, key: EntryKey, value: EntryValue + ) -> None: + self.metadata = metadata + self.key = key + self.value = value + + +class DistributedContext: + def get_entries(self) -> typing.Iterable[Entry]: + pass + + def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: + pass + + +class DistributedContextManager: + def get_current_context(self) -> typing.Optional[DistributedContext]: + pass + + @contextmanager + def use_context( + self, context: DistributedContext + ) -> typing.Iterator[DistributedContext]: + yield context diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py new file mode 100644 index 00000000000..81b44b9fb7b --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py @@ -0,0 +1,68 @@ +# 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. + +from contextlib import contextmanager +import contextvars +import typing + +from opentelemetry import distributedcontext as dctx_api + +_CURRENT_DISTRIBUTEDCONTEXT_CV = contextvars.ContextVar( + 'distributed_context', + default=None, +) + + +class EntryMetadata(dctx_api.EntryMetadata): + pass + + +class EntryKey(dctx_api.EntryKey): + pass + + +class EntryValue(dctx_api.EntryValue): + pass + + +class Entry(dctx_api.Entry): + pass + + +class DistributedContext(dict, dctx_api.DistributedContext): + def get_entries(self) -> typing.Iterable[Entry]: + return self.values() + + def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: + return self.get(key) + + +class DistributedContextManager(dctx_api.DistributedContextManager): + def __init__(self, + cv: 'contextvars.ContextVar' = _CURRENT_DISTRIBUTEDCONTEXT_CV, + ) -> None: + self._cv = cv + + def get_current_context(self) -> typing.Optional[DistributedContext]: + return self._cv.get(default=None) + + @contextmanager + def use_context( + self, context: DistributedContext + ) -> typing.Iterator[DistributedContext]: + token = self._cv.set(context) + try: + yield context + finally: + self._cv.reset(token) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 859583341f92424366565fd5042008b4da2a6e5c Mon Sep 17 00:00:00 2001 From: Allan Feldman <6374032+a-feld@users.noreply.github.com> Date: Mon, 19 Aug 2019 14:53:21 -0700 Subject: [PATCH 02/23] Put length check first. Co-Authored-By: Reiley Yang --- .../src/opentelemetry/distributedcontext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index ecd0042d67a..9e7a39479b1 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -27,7 +27,7 @@ def __init__(self, entry_ttl: int) -> None: class EntryKey(str): def __new__(cls, value): - if any(c not in string.printable for c in value) or len(value) > 255: + if len(value) > 255 or any(c not in string.printable for c in value): raise ValueError("Invalid EntryKey", value) return str.__new__(cls, value) From 6b163dbded406190cc49b48951b3cc8f14b28da9 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 20 Aug 2019 09:57:19 -0700 Subject: [PATCH 03/23] Improve performance. --- .../distributedcontext/__init__.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 9e7a39479b1..fe1a05c0f2a 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -17,26 +17,39 @@ import typing -class EntryMetadata: +PRINTABLE = set(string.printable) + + +class EntryMetadata(dict): NO_PROPAGATION = 0 UNLIMITED_PROPAGATION = -1 def __init__(self, entry_ttl: int) -> None: self.entry_ttl = entry_ttl + def __getattr__(self, key: str) -> "object": + return self[key] + + def __setattr__(self, key: str, value: "object") -> None: + self[key] = value -class EntryKey(str): - def __new__(cls, value): - if len(value) > 255 or any(c not in string.printable for c in value): + +class EntryKey: + @staticmethod + def create(value): + if len(value) > 255 or any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryKey", value) - return str.__new__(cls, value) + + return typing.cast(EntryKey, value) -class EntryValue(str): - def __new__(cls, value): - if any(c not in string.printable for c in value): +class EntryValue: + @staticmethod + def create(value): + if any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryValue", value) - return str.__new__(cls, value) + + return typing.cast(EntryValue, value) class Entry: From 51369ab229271c432d07db8b10bf9f8f384d163a Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 20 Aug 2019 15:46:12 -0700 Subject: [PATCH 04/23] Update distributed context documentation. --- .../distributedcontext/__init__.py | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index fe1a05c0f2a..a44c4a37a62 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc from contextlib import contextmanager import string import typing @@ -21,6 +22,14 @@ class EntryMetadata(dict): + """A class representing metadata of a DistributedContext entry + + Args: + entry_ttl: The time to live (in service hops) of an entry. Must be + initially set to either :attr:`EntryMetadata.NO_PROPAGATION` + or :attr:`EntryMetadata.UNLIMITED_PROPAGATION`. + """ + NO_PROPAGATION = 0 UNLIMITED_PROPAGATION = -1 @@ -35,8 +44,10 @@ def __setattr__(self, key: str, value: "object") -> None: class EntryKey: + """A class representing a key for a DistributedContext entry""" + @staticmethod - def create(value): + def create(value: str) -> "EntryKey": if len(value) > 255 or any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryKey", value) @@ -44,8 +55,10 @@ def create(value): class EntryValue: + """A class representing the value of a DistributedContext entry""" + @staticmethod - def create(value): + def create(value: str) -> "EntryValue": if any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryValue", value) @@ -61,20 +74,45 @@ def __init__( self.value = value -class DistributedContext: +class DistributedContext(abc.ABC): + """A container for distributed context entries""" + + @abc.abstractmethod def get_entries(self) -> typing.Iterable[Entry]: + """Returns an immutable iterator to entries.""" pass + @abc.abstractmethod def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: + """Returns the entry associated with a key or None + + Args: + key: the key with which to perform a lookup + """ pass class DistributedContextManager: def get_current_context(self) -> typing.Optional[DistributedContext]: + """Gets the current DistributedContext. + + Returns: + A DistributedContext instance representing the current context. + """ pass @contextmanager def use_context( self, context: DistributedContext ) -> typing.Iterator[DistributedContext]: + """Context manager for controlling a DistributedContext lifetime. + + Set the context as the active DistributedContext. + + On exiting, the context manager will restore the parent + DistributedContext. + + Args: + context: A DistributedContext instance to make current. + """ yield context From f3b00966644230c7339ebd7777adcfb4bb4d6afb Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 20 Aug 2019 15:49:28 -0700 Subject: [PATCH 05/23] Allow EntryKey and EntryValue to be used as str. --- .../src/opentelemetry/distributedcontext/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index a44c4a37a62..ce733f9ae77 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -43,9 +43,12 @@ def __setattr__(self, key: str, value: "object") -> None: self[key] = value -class EntryKey: +class EntryKey(str): """A class representing a key for a DistributedContext entry""" + def __new__(cls, value: str): + return cls.create(value) + @staticmethod def create(value: str) -> "EntryKey": if len(value) > 255 or any(c not in PRINTABLE for c in value): @@ -54,9 +57,12 @@ def create(value: str) -> "EntryKey": return typing.cast(EntryKey, value) -class EntryValue: +class EntryValue(str): """A class representing the value of a DistributedContext entry""" + def __new__(cls, value: str): + return cls.create(value) + @staticmethod def create(value: str) -> "EntryValue": if any(c not in PRINTABLE for c in value): From dc1fdeb89ad88c6b395ddff61b23d669eb6fd8fb Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 20 Aug 2019 17:00:44 -0700 Subject: [PATCH 06/23] Update SDK to use context. --- .../sdk/distributedcontext/__init__.py | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py index 81b44b9fb7b..2df10286d00 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py @@ -13,56 +13,70 @@ # limitations under the License. from contextlib import contextmanager -import contextvars import typing +from opentelemetry.context import Context from opentelemetry import distributedcontext as dctx_api -_CURRENT_DISTRIBUTEDCONTEXT_CV = contextvars.ContextVar( - 'distributed_context', - default=None, -) - - -class EntryMetadata(dctx_api.EntryMetadata): - pass - - -class EntryKey(dctx_api.EntryKey): - pass +class DistributedContext(dict, dctx_api.DistributedContext): + """A container for distributed context entries""" -class EntryValue(dctx_api.EntryValue): - pass + def get_entries(self) -> typing.Iterable[dctx_api.Entry]: + """Returns an immutable iterator to entries.""" + return self.values() + def get_entry_value( + self, key: dctx_api.EntryKey + ) -> typing.Optional[dctx_api.EntryValue]: + """Returns the entry associated with a key or None -class Entry(dctx_api.Entry): - pass + Args: + key: the key with which to perform a lookup + """ + return self.get(key) -class DistributedContext(dict, dctx_api.DistributedContext): - def get_entries(self) -> typing.Iterable[Entry]: - return self.values() +class DistributedContextManager(dctx_api.DistributedContextManager): + """See `opentelemetry.distributedcontext.DistributedContextManager` - def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: - return self.get(key) + Args: + name: The name of the context manager + """ + def __init__(self, name: str = "") -> None: + if name: + slot_name = "DistributedContext.{}".format(name) + else: + slot_name = "DistributedContext" -class DistributedContextManager(dctx_api.DistributedContextManager): - def __init__(self, - cv: 'contextvars.ContextVar' = _CURRENT_DISTRIBUTEDCONTEXT_CV, - ) -> None: - self._cv = cv + self._current_context = Context.register_slot(slot_name) def get_current_context(self) -> typing.Optional[DistributedContext]: - return self._cv.get(default=None) + """Gets the current DistributedContext. + + Returns: + A DistributedContext instance representing the current context. + """ + return self._current_context.get() @contextmanager def use_context( self, context: DistributedContext ) -> typing.Iterator[DistributedContext]: - token = self._cv.set(context) + """Context manager for controlling a DistributedContext lifetime. + + Set the context as the active DistributedContext. + + On exiting, the context manager will restore the parent + DistributedContext. + + Args: + context: A DistributedContext instance to make current. + """ + snapshot = self._current_context.get() + self._current_context.set(context) try: yield context finally: - self._cv.reset(token) + self._current_context.set(snapshot) From 30c47e8856496c748ca066fc24029ffd9ad1e45a Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 20 Aug 2019 17:03:10 -0700 Subject: [PATCH 07/23] Fix EntryMetadata. --- .../src/opentelemetry/distributedcontext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index ce733f9ae77..58b3c1e42a2 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -34,7 +34,7 @@ class EntryMetadata(dict): UNLIMITED_PROPAGATION = -1 def __init__(self, entry_ttl: int) -> None: - self.entry_ttl = entry_ttl + super().__init__(entry_ttl=entry_ttl) def __getattr__(self, key: str) -> "object": return self[key] From d1bca0ec41a239b4a9cd68c294c38c2c501b4b37 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Wed, 21 Aug 2019 13:35:52 -0700 Subject: [PATCH 08/23] Change EntryMetadata to object type. --- .../src/opentelemetry/distributedcontext/__init__.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 58b3c1e42a2..3ad3d69f027 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -21,7 +21,7 @@ PRINTABLE = set(string.printable) -class EntryMetadata(dict): +class EntryMetadata: """A class representing metadata of a DistributedContext entry Args: @@ -34,13 +34,7 @@ class EntryMetadata(dict): UNLIMITED_PROPAGATION = -1 def __init__(self, entry_ttl: int) -> None: - super().__init__(entry_ttl=entry_ttl) - - def __getattr__(self, key: str) -> "object": - return self[key] - - def __setattr__(self, key: str, value: "object") -> None: - self[key] = value + self.entry_ttl = entry_ttl class EntryKey(str): From 94decac5364450fe23746dc6809ac7e560b7e836 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Wed, 21 Aug 2019 13:44:48 -0700 Subject: [PATCH 09/23] Add api for distributed context propagation. --- .../propagation/__init__.py | 18 +++ .../propagation/binaryformat.py | 61 ++++++++++ .../propagation/httptextformat.py | 109 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py create mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py index e69de29bb2d..c8706281ad7 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py @@ -0,0 +1,18 @@ +# 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. + +from .binaryformat import BinaryFormat +from .httptextformat import HTTPTextFormat + +__all__ = ["BinaryFormat", "HTTPTextFormat"] diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py new file mode 100644 index 00000000000..0441eac5e3b --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py @@ -0,0 +1,61 @@ +# 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 abc +import typing + +from opentelemetry.distributedcontext import DistributedContext + + +class BinaryFormat(abc.ABC): + """API for serialization of span context into binary formats. + + This class provides an interface that enables converting span contexts + to and from a binary format. + """ + + @staticmethod + @abc.abstractmethod + def to_bytes(context: DistributedContext) -> bytes: + """Creates a byte representation of a DistributedContext. + + to_bytes should read values from a DistributedContext and return a data + format to represent it, in bytes. + + Args: + context: the DistributedContext to serialize + + Returns: + A bytes representation of the DistributedContext. + + """ + + @staticmethod + @abc.abstractmethod + def from_bytes( + byte_representation: bytes) -> typing.Optional[DistributedContext]: + """Return a DistributedContext that was represented by bytes. + + from_bytes should return back a DistributedContext that was constructed + from the data serialized in the byte_representation passed. If it is + not possible to read in a proper DistributedContext, return None. + + Args: + byte_representation: the bytes to deserialize + + Returns: + A bytes representation of the DistributedContext if it is valid. + Otherwise return None. + + """ diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py new file mode 100644 index 00000000000..57a1545e887 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py @@ -0,0 +1,109 @@ +# 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 abc +import typing + +from opentelemetry.distributedcontext import DistributedContext + +Setter = typing.Callable[[object, str, str], None] +Getter = typing.Callable[[object, str], typing.List[str]] + + +class HTTPTextFormat(abc.ABC): + """API for propagation of span context via headers. + + This class provides an interface that enables extracting and injecting + span context into headers of HTTP requests. HTTP frameworks and clients + can integrate with HTTPTextFormat by providing the object containing the + headers, and a getter and setter function for the extraction and + injection of values, respectively. + + Example:: + + import flask + import requests + from opentelemetry.context.propagation import HTTPTextFormat + + PROPAGATOR = HTTPTextFormat() + + + + def get_header_from_flask_request(request, key): + return request.headers.get_all(key) + + def set_header_into_requests_request(request: requests.Request, + key: str, value: str): + request.headers[key] = value + + def example_route(): + distributed_context = PROPAGATOR.extract( + get_header_from_flask_request, + flask.request + ) + request_to_downstream = requests.Request( + "GET", "http://httpbin.org/get" + ) + PROPAGATOR.inject( + distributed_context, + set_header_into_requests_request, + request_to_downstream + ) + session = requests.Session() + session.send(request_to_downstream.prepare()) + + + .. _Propagation API Specification: + https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md + """ + @abc.abstractmethod + def extract(self, get_from_carrier: Getter, + carrier: object) -> DistributedContext: + """Create a DistributedContext from values in the carrier. + + The extract function should retrieve values from the carrier + object using get_from_carrier, and use values to populate a + DistributedContext value and return it. + + Args: + get_from_carrier: a function that can retrieve zero + or more values from the carrier. In the case that + the value does not exist, return an empty list. + carrier: and object which contains values that are + used to construct a DistributedContext. This object + must be paired with an appropriate get_from_carrier + which understands how to extract a value from it. + Returns: + A DistributedContext with configuration found in the carrier. + + """ + @abc.abstractmethod + def inject(self, context: DistributedContext, set_in_carrier: Setter, + carrier: object) -> None: + """Inject values from a DistributedContext into a carrier. + + inject enables the propagation of values into HTTP clients or + other objects which perform an HTTP request. Implementations + should use the set_in_carrier method to set values on the + carrier. + + Args: + context: The DistributedContext to read values from. + set_in_carrier: A setter function that can set values + on the carrier. + carrier: An object that a place to define HTTP headers. + Should be paired with set_in_carrier, which should + know how to set header values on the carrier. + + """ From 9225b00cf02eb340e4c708ea7a08a120faf12af6 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 15:05:45 -0700 Subject: [PATCH 10/23] Add initial API testing for distributed context. --- .../tests/distributedcontext/__init__.py | 13 ++++ .../test_distributed_context.py | 76 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 opentelemetry-api/tests/distributedcontext/__init__.py create mode 100644 opentelemetry-api/tests/distributedcontext/test_distributed_context.py diff --git a/opentelemetry-api/tests/distributedcontext/__init__.py b/opentelemetry-api/tests/distributedcontext/__init__.py new file mode 100644 index 00000000000..d853a7bcf65 --- /dev/null +++ b/opentelemetry-api/tests/distributedcontext/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py new file mode 100644 index 00000000000..248bfdaa5c5 --- /dev/null +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -0,0 +1,76 @@ +# 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 unittest + +from opentelemetry import distributedcontext + + +class TestEntryMetadata(unittest.TestCase): + def test_entry_ttl_no_propagation(self): + metadata = distributedcontext.EntryMetadata( + distributedcontext.EntryMetadata.NO_PROPAGATION, + ) + self.assertEqual(metadata.entry_ttl, 0) + + def test_entry_ttl_unlimited_propagation(self): + metadata = distributedcontext.EntryMetadata( + distributedcontext.EntryMetadata.UNLIMITED_PROPAGATION, + ) + self.assertEqual(metadata.entry_ttl, -1) + + +class TestEntryKey(unittest.TestCase): + def test_create_too_long(self): + with self.assertRaises(ValueError): + distributedcontext.EntryKey.create("a" * 256) + + def test_create_invalid_character(self): + with self.assertRaises(ValueError): + distributedcontext.EntryKey.create("\x00") + + def test_create_valid(self): + key = distributedcontext.EntryKey.create("ok") + self.assertEqual(key, "ok") + + def test_key_new(self): + key = distributedcontext.EntryKey("ok") + self.assertEqual(key, "ok") + + +class TestEntryValue(unittest.TestCase): + def test_create_invalid_character(self): + with self.assertRaises(ValueError): + distributedcontext.EntryValue.create("\x00") + + def test_create_valid(self): + key = distributedcontext.EntryValue.create("ok") + self.assertEqual(key, "ok") + + def test_key_new(self): + key = distributedcontext.EntryValue("ok") + self.assertEqual(key, "ok") + + +class TestDistributedContextManager(unittest.TestCase): + def setUp(self): + self.manager = distributedcontext.DistributedContextManager() + + def test_get_current_context(self): + self.assertIsNone(self.manager.get_current_context()) + + def test_use_context(self): + o = object() + with self.manager.use_context(o) as output: + self.assertIs(output, o) From 633e13e0b1e3d2cad0a8245f8715f971b7d27463 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 15:12:45 -0700 Subject: [PATCH 11/23] Fix lint issues. --- .../opentelemetry/distributedcontext/__init__.py | 15 ++++++++------- .../test_distributed_context.py | 6 +++--- .../sdk/distributedcontext/__init__.py | 8 +++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 3ad3d69f027..a12093e4814 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import abc from contextlib import contextmanager +import abc import string import typing - PRINTABLE = set(string.printable) @@ -67,7 +66,10 @@ def create(value: str) -> "EntryValue": class Entry: def __init__( - self, metadata: EntryMetadata, key: EntryKey, value: EntryValue + self, + metadata: EntryMetadata, + key: EntryKey, + value: EntryValue, ) -> None: self.metadata = metadata self.key = key @@ -80,7 +82,6 @@ class DistributedContext(abc.ABC): @abc.abstractmethod def get_entries(self) -> typing.Iterable[Entry]: """Returns an immutable iterator to entries.""" - pass @abc.abstractmethod def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: @@ -89,7 +90,6 @@ def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: Args: key: the key with which to perform a lookup """ - pass class DistributedContextManager: @@ -99,11 +99,11 @@ def get_current_context(self) -> typing.Optional[DistributedContext]: Returns: A DistributedContext instance representing the current context. """ - pass @contextmanager def use_context( - self, context: DistributedContext + self, + context: DistributedContext, ) -> typing.Iterator[DistributedContext]: """Context manager for controlling a DistributedContext lifetime. @@ -115,4 +115,5 @@ def use_context( Args: context: A DistributedContext instance to make current. """ + # pylint: disable=no-self-use yield context diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index 248bfdaa5c5..6ca32db558b 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -71,6 +71,6 @@ def test_get_current_context(self): self.assertIsNone(self.manager.get_current_context()) def test_use_context(self): - o = object() - with self.manager.use_context(o) as output: - self.assertIs(output, o) + expected = object() + with self.manager.use_context(expected) as output: + self.assertIs(output, expected) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py index 2df10286d00..c0314c7c6c8 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py @@ -15,8 +15,8 @@ from contextlib import contextmanager import typing -from opentelemetry.context import Context from opentelemetry import distributedcontext as dctx_api +from opentelemetry.context import Context class DistributedContext(dict, dctx_api.DistributedContext): @@ -27,7 +27,8 @@ def get_entries(self) -> typing.Iterable[dctx_api.Entry]: return self.values() def get_entry_value( - self, key: dctx_api.EntryKey + self, + key: dctx_api.EntryKey ) -> typing.Optional[dctx_api.EntryValue]: """Returns the entry associated with a key or None @@ -62,7 +63,8 @@ def get_current_context(self) -> typing.Optional[DistributedContext]: @contextmanager def use_context( - self, context: DistributedContext + self, + context: DistributedContext, ) -> typing.Iterator[DistributedContext]: """Context manager for controlling a DistributedContext lifetime. From face08d500fcbbc28b61863299913fb9ffb0e97b Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 15:28:11 -0700 Subject: [PATCH 12/23] Fix type errors. --- .../src/opentelemetry/distributedcontext/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index a12093e4814..db4e913a653 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -39,7 +39,7 @@ def __init__(self, entry_ttl: int) -> None: class EntryKey(str): """A class representing a key for a DistributedContext entry""" - def __new__(cls, value: str): + def __new__(cls, value: str) -> "EntryKey": return cls.create(value) @staticmethod @@ -53,7 +53,7 @@ def create(value: str) -> "EntryKey": class EntryValue(str): """A class representing the value of a DistributedContext entry""" - def __new__(cls, value: str): + def __new__(cls, value: str) -> "EntryValue": return cls.create(value) @staticmethod @@ -100,7 +100,7 @@ def get_current_context(self) -> typing.Optional[DistributedContext]: A DistributedContext instance representing the current context. """ - @contextmanager + @contextmanager # type: ignore def use_context( self, context: DistributedContext, From a804f5e9272dd0bbb69a6ac64469d0b407e7b360 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 15:47:55 -0700 Subject: [PATCH 13/23] Add SDK distributed context testing. --- .../tests/distributedcontext/__init__.py | 13 +++ .../test_distributed_context.py | 83 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 opentelemetry-sdk/tests/distributedcontext/__init__.py create mode 100644 opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py diff --git a/opentelemetry-sdk/tests/distributedcontext/__init__.py b/opentelemetry-sdk/tests/distributedcontext/__init__.py new file mode 100644 index 00000000000..d853a7bcf65 --- /dev/null +++ b/opentelemetry-sdk/tests/distributedcontext/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py new file mode 100644 index 00000000000..b160aabaf1b --- /dev/null +++ b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py @@ -0,0 +1,83 @@ +# 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 unittest + + +from opentelemetry.distributedcontext import ( + Entry, + EntryMetadata, + EntryKey, + EntryValue, +) + +from opentelemetry.sdk import distributedcontext + + +class TestDistributedContext(unittest.TestCase): + def setUp(self): + entry = self.entry = Entry( + EntryMetadata(EntryMetadata.NO_PROPAGATION), + EntryKey("key"), + EntryValue("value"), + ) + context = self.context = distributedcontext.DistributedContext() + context[entry.key] = entry + + def test_get_entries(self): + self.assertIn(self.entry, self.context.get_entries()) + + def test_get_entry_value_present(self): + value = self.context.get_entry_value( + self.entry.key, + ) + self.assertIs(value, self.entry) + + def test_get_entry_value_missing(self): + key = EntryKey("missing") + value = self.context.get_entry_value(key) + self.assertIsNone(value) + + +class TestDistributedContextManager(unittest.TestCase): + def setUp(self): + self.manager = distributedcontext.DistributedContextManager() + + def test_use_context(self): + # Context is None initially + self.assertIsNone(self.manager.get_current_context()) + + # Start initial context + dctx = distributedcontext.DistributedContext() + with self.manager.use_context(dctx) as current: + self.assertIs(current, dctx) + self.assertIs( + self.manager.get_current_context(), + dctx, + ) + + # Context is overridden + nested_dctx = distributedcontext.DistributedContext() + with self.manager.use_context(nested_dctx) as current: + self.assertIs(current, nested_dctx) + self.assertIs( + self.manager.get_current_context(), + nested_dctx, + ) + + # Context is restored + self.assertIs( + self.manager.get_current_context(), + dctx, + ) From e5b52f593e916b04952f6311a673468577ee4783 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 15:58:41 -0700 Subject: [PATCH 14/23] Change the definition of printable. --- .../src/opentelemetry/distributedcontext/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index db4e913a653..891159f3c5e 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -14,10 +14,9 @@ from contextlib import contextmanager import abc -import string import typing -PRINTABLE = set(string.printable) +PRINTABLE = set(chr(num) for num in range(32, 127)) class EntryMetadata: From 135b1348d01f3bba09945b2260a14ad3613262b3 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 16:14:52 -0700 Subject: [PATCH 15/23] Move DistributedContext implementation into api level. --- .../distributedcontext/__init__.py | 12 +++--- .../test_distributed_context.py | 27 +++++++++++++ .../sdk/distributedcontext/__init__.py | 27 +++---------- .../test_distributed_context.py | 38 ++----------------- 4 files changed, 42 insertions(+), 62 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 891159f3c5e..54e3c93b9f9 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -13,7 +13,6 @@ # limitations under the License. from contextlib import contextmanager -import abc import typing PRINTABLE = set(chr(num) for num in range(32, 127)) @@ -75,20 +74,23 @@ def __init__( self.value = value -class DistributedContext(abc.ABC): +class DistributedContext(dict): """A container for distributed context entries""" - @abc.abstractmethod def get_entries(self) -> typing.Iterable[Entry]: """Returns an immutable iterator to entries.""" + return self.values() - @abc.abstractmethod - def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: + def get_entry_value( + self, + key: EntryKey + ) -> typing.Optional[EntryValue]: """Returns the entry associated with a key or None Args: key: the key with which to perform a lookup """ + return self.get(key) class DistributedContextManager: diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index 6ca32db558b..c88481b04ef 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -63,6 +63,33 @@ def test_key_new(self): self.assertEqual(key, "ok") +class TestDistributedContext(unittest.TestCase): + def setUp(self): + entry = self.entry = distributedcontext.Entry( + distributedcontext.EntryMetadata( + distributedcontext.EntryMetadata.NO_PROPAGATION, + ), + distributedcontext.EntryKey("key"), + distributedcontext.EntryValue("value"), + ) + context = self.context = distributedcontext.DistributedContext() + context[entry.key] = entry + + def test_get_entries(self): + self.assertIn(self.entry, self.context.get_entries()) + + def test_get_entry_value_present(self): + value = self.context.get_entry_value( + self.entry.key, + ) + self.assertIs(value, self.entry) + + def test_get_entry_value_missing(self): + key = distributedcontext.EntryKey("missing") + value = self.context.get_entry_value(key) + self.assertIsNone(value) + + class TestDistributedContextManager(unittest.TestCase): def setUp(self): self.manager = distributedcontext.DistributedContextManager() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py index c0314c7c6c8..fc449cfe2df 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py @@ -19,25 +19,6 @@ from opentelemetry.context import Context -class DistributedContext(dict, dctx_api.DistributedContext): - """A container for distributed context entries""" - - def get_entries(self) -> typing.Iterable[dctx_api.Entry]: - """Returns an immutable iterator to entries.""" - return self.values() - - def get_entry_value( - self, - key: dctx_api.EntryKey - ) -> typing.Optional[dctx_api.EntryValue]: - """Returns the entry associated with a key or None - - Args: - key: the key with which to perform a lookup - """ - return self.get(key) - - class DistributedContextManager(dctx_api.DistributedContextManager): """See `opentelemetry.distributedcontext.DistributedContextManager` @@ -53,7 +34,9 @@ def __init__(self, name: str = "") -> None: self._current_context = Context.register_slot(slot_name) - def get_current_context(self) -> typing.Optional[DistributedContext]: + def get_current_context( + self, + ) -> typing.Optional[dctx_api.DistributedContext]: """Gets the current DistributedContext. Returns: @@ -64,8 +47,8 @@ def get_current_context(self) -> typing.Optional[DistributedContext]: @contextmanager def use_context( self, - context: DistributedContext, - ) -> typing.Iterator[DistributedContext]: + context: dctx_api.DistributedContext, + ) -> typing.Iterator[dctx_api.DistributedContext]: """Context manager for controlling a DistributedContext lifetime. Set the context as the active DistributedContext. diff --git a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py index b160aabaf1b..75c84c65381 100644 --- a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py @@ -14,42 +14,10 @@ import unittest - -from opentelemetry.distributedcontext import ( - Entry, - EntryMetadata, - EntryKey, - EntryValue, -) - +from opentelemetry import distributedcontext as dctx_api from opentelemetry.sdk import distributedcontext -class TestDistributedContext(unittest.TestCase): - def setUp(self): - entry = self.entry = Entry( - EntryMetadata(EntryMetadata.NO_PROPAGATION), - EntryKey("key"), - EntryValue("value"), - ) - context = self.context = distributedcontext.DistributedContext() - context[entry.key] = entry - - def test_get_entries(self): - self.assertIn(self.entry, self.context.get_entries()) - - def test_get_entry_value_present(self): - value = self.context.get_entry_value( - self.entry.key, - ) - self.assertIs(value, self.entry) - - def test_get_entry_value_missing(self): - key = EntryKey("missing") - value = self.context.get_entry_value(key) - self.assertIsNone(value) - - class TestDistributedContextManager(unittest.TestCase): def setUp(self): self.manager = distributedcontext.DistributedContextManager() @@ -59,7 +27,7 @@ def test_use_context(self): self.assertIsNone(self.manager.get_current_context()) # Start initial context - dctx = distributedcontext.DistributedContext() + dctx = dctx_api.DistributedContext() with self.manager.use_context(dctx) as current: self.assertIs(current, dctx) self.assertIs( @@ -68,7 +36,7 @@ def test_use_context(self): ) # Context is overridden - nested_dctx = distributedcontext.DistributedContext() + nested_dctx = dctx_api.DistributedContext() with self.manager.use_context(nested_dctx) as current: self.assertIs(current, nested_dctx) self.assertIs( From 5de7f647337f825a17094d0d058e032e8b881f8a Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Mon, 26 Aug 2019 16:45:30 -0700 Subject: [PATCH 16/23] Fix mypy issues. --- .../src/opentelemetry/distributedcontext/__init__.py | 11 +++++++---- .../distributedcontext/test_distributed_context.py | 5 +++-- .../distributedcontext/test_distributed_context.py | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 54e3c93b9f9..9fdcd0f7084 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -74,23 +74,26 @@ def __init__( self.value = value -class DistributedContext(dict): +class DistributedContext: """A container for distributed context entries""" + def __init__(self, entries: typing.Iterable[Entry]) -> None: + self._container = {entry.key: entry for entry in entries} + def get_entries(self) -> typing.Iterable[Entry]: """Returns an immutable iterator to entries.""" - return self.values() + return self._container.values() def get_entry_value( self, key: EntryKey - ) -> typing.Optional[EntryValue]: + ) -> typing.Optional[Entry]: """Returns the entry associated with a key or None Args: key: the key with which to perform a lookup """ - return self.get(key) + return self._container.get(key) class DistributedContextManager: diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index c88481b04ef..31b1cccf39b 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -72,8 +72,9 @@ def setUp(self): distributedcontext.EntryKey("key"), distributedcontext.EntryValue("value"), ) - context = self.context = distributedcontext.DistributedContext() - context[entry.key] = entry + self.context = distributedcontext.DistributedContext(( + entry, + )) def test_get_entries(self): self.assertIn(self.entry, self.context.get_entries()) diff --git a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py index 75c84c65381..fedcf7c9dd2 100644 --- a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py @@ -27,7 +27,7 @@ def test_use_context(self): self.assertIsNone(self.manager.get_current_context()) # Start initial context - dctx = dctx_api.DistributedContext() + dctx = dctx_api.DistributedContext(()) with self.manager.use_context(dctx) as current: self.assertIs(current, dctx) self.assertIs( @@ -36,7 +36,7 @@ def test_use_context(self): ) # Context is overridden - nested_dctx = dctx_api.DistributedContext() + nested_dctx = dctx_api.DistributedContext(()) with self.manager.use_context(nested_dctx) as current: self.assertIs(current, nested_dctx) self.assertIs( From 23ea35d4003d806a0353e8ead3494ebb8896fd6f Mon Sep 17 00:00:00 2001 From: Allan Feldman <6374032+a-feld@users.noreply.github.com> Date: Tue, 27 Aug 2019 08:02:34 -0700 Subject: [PATCH 17/23] Update opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Christian Neumüller --- .../src/opentelemetry/distributedcontext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 9fdcd0f7084..f8c812b9a2e 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -15,7 +15,7 @@ from contextlib import contextmanager import typing -PRINTABLE = set(chr(num) for num in range(32, 127)) +PRINTABLE = frozenset(chr(num) for num in range(32, 127)) class EntryMetadata: From 7eab1e8b2a64b9eb07191bdccd44a274511678cc Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 10:19:32 -0700 Subject: [PATCH 18/23] Return EntryValue from get_entry_value. --- .../src/opentelemetry/distributedcontext/__init__.py | 5 +++-- .../tests/distributedcontext/test_distributed_context.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index f8c812b9a2e..45ae417360b 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -87,13 +87,14 @@ def get_entries(self) -> typing.Iterable[Entry]: def get_entry_value( self, key: EntryKey - ) -> typing.Optional[Entry]: + ) -> typing.Optional[EntryValue]: """Returns the entry associated with a key or None Args: key: the key with which to perform a lookup """ - return self._container.get(key) + if key in self._container: + return self._container[key].value class DistributedContextManager: diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index 31b1cccf39b..936f0aa78ac 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -83,7 +83,7 @@ def test_get_entry_value_present(self): value = self.context.get_entry_value( self.entry.key, ) - self.assertIs(value, self.entry) + self.assertIs(value, self.entry.value) def test_get_entry_value_missing(self): key = distributedcontext.EntryKey("missing") From 6d86bd39d405eea6b513ed6aac91cf5052b3dbc6 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 10:26:19 -0700 Subject: [PATCH 19/23] Fix pylint. --- .../src/opentelemetry/distributedcontext/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 45ae417360b..70b38287f3c 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -95,6 +95,7 @@ def get_entry_value( """ if key in self._container: return self._container[key].value + return None class DistributedContextManager: From fd08d8034541800114c06483cc60e7952ab92098 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 10:32:00 -0700 Subject: [PATCH 20/23] Remove extra lines. --- .../distributedcontext/propagation/httptextformat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py index 57a1545e887..3d11b7a3528 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py @@ -38,8 +38,6 @@ class HTTPTextFormat(abc.ABC): PROPAGATOR = HTTPTextFormat() - - def get_header_from_flask_request(request, key): return request.headers.get_all(key) From 938a0b36e4bbe8f0add2e4d1123260dc53507f81 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 10:42:15 -0700 Subject: [PATCH 21/23] Remove magic numbers from distributed context. --- .../src/opentelemetry/distributedcontext/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 70b38287f3c..7b57b94173d 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -13,9 +13,16 @@ # limitations under the License. from contextlib import contextmanager +import itertools +import string import typing -PRINTABLE = frozenset(chr(num) for num in range(32, 127)) +PRINTABLE = frozenset(itertools.chain( + string.ascii_letters, + string.digits, + string.punctuation, + " ", +)) class EntryMetadata: From ae4f5704f90882b7b77079ab518c39cd8c1d2c74 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 10:57:06 -0700 Subject: [PATCH 22/23] Fail if key is empty. --- .../src/opentelemetry/distributedcontext/__init__.py | 2 +- .../tests/distributedcontext/test_distributed_context.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 7b57b94173d..dd58824b364 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -49,7 +49,7 @@ def __new__(cls, value: str) -> "EntryKey": @staticmethod def create(value: str) -> "EntryKey": - if len(value) > 255 or any(c not in PRINTABLE for c in value): + if not 0 < len(value) <= 255 or any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryKey", value) return typing.cast(EntryKey, value) diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index 936f0aa78ac..be1aadac9a1 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -32,6 +32,10 @@ def test_entry_ttl_unlimited_propagation(self): class TestEntryKey(unittest.TestCase): + def test_create_empty(self): + with self.assertRaises(ValueError): + distributedcontext.EntryKey.create("") + def test_create_too_long(self): with self.assertRaises(ValueError): distributedcontext.EntryKey.create("a" * 256) From 5794bb8f0c7d713f4ac924c7aeef984919b1ceeb Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Tue, 27 Aug 2019 11:18:28 -0700 Subject: [PATCH 23/23] Disable len-as-condition for distributedcontext check. --- .../src/opentelemetry/distributedcontext/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index dd58824b364..859bb0cb4f6 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -49,6 +49,7 @@ def __new__(cls, value: str) -> "EntryKey": @staticmethod def create(value: str) -> "EntryKey": + # pylint: disable=len-as-condition if not 0 < len(value) <= 255 or any(c not in PRINTABLE for c in value): raise ValueError("Invalid EntryKey", value)