From e3f8ef0592456de6f92569c4e0be26199ecad8d5 Mon Sep 17 00:00:00 2001 From: Yusuke Tsutsumi Date: Sun, 28 Jul 2019 21:52:36 -0700 Subject: [PATCH] Adding the Resource API (#61) Design Decisions include: The API package should be minimal, and example implementations should live in SDK when possible. The precedent that exists is to name both the interface and the implementation in the sdk the same. This avoids the generally unhelpful "Default" prefix. opentelemetry-python is standardizing on Google style docstrings. As such resolving formatting that is inconsistent with the style guilde. --- .../src/opentelemetry/resources/__init__.py | 39 ++++++++++++++++ .../opentelemetry/sdk/resources/__init__.py | 44 +++++++++++++++++++ opentelemetry-sdk/tests/resources/__init__.py | 0 .../tests/resources/test_init.py | 33 ++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py create mode 100644 opentelemetry-sdk/tests/resources/__init__.py create mode 100644 opentelemetry-sdk/tests/resources/test_init.py diff --git a/opentelemetry-api/src/opentelemetry/resources/__init__.py b/opentelemetry-api/src/opentelemetry/resources/__init__.py index d853a7bcf6..0d1e34dddb 100644 --- a/opentelemetry-api/src/opentelemetry/resources/__init__.py +++ b/opentelemetry-api/src/opentelemetry/resources/__init__.py @@ -11,3 +11,42 @@ # 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 + + +class Resource(abc.ABC): + """The interface that resources must implement.""" + @staticmethod + @abc.abstractmethod + def create(labels: typing.Dict[str, str]) -> "Resource": + """Create a new resource. + + Args: + labels: the labels that define the resource + + Returns: + The resource with the labels in question + + """ + @property + @abc.abstractmethod + def labels(self) -> typing.Dict[str, str]: + """Return the label dictionary associated with this resource. + + Returns: + A dictionary with the labels of the resource + + """ + @abc.abstractmethod + def merge(self, other: typing.Optional["Resource"]) -> "Resource": + """Return a resource with the union of labels for both resources. + + Labels that exist in the main Resource take precedence unless the + label value is the empty string. + + Args: + other: The resource to merge in + + """ diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py new file mode 100644 index 0000000000..b488c0a0c7 --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -0,0 +1,44 @@ +# 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 opentelemetry.resources as resources + + +class Resource(resources.Resource): + def __init__(self, labels): + self._labels = labels + + @staticmethod + def create(labels): + return Resource(labels) + + @property + def labels(self): + return self._labels + + def merge(self, other): + if other is None: + return self + if not self._labels: + return other + merged_labels = self.labels.copy() + for key, value in other.labels.items(): + if key not in merged_labels or merged_labels[key] == "": + merged_labels[key] = value + return Resource(merged_labels) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Resource): + return False + return self.labels == other.labels diff --git a/opentelemetry-sdk/tests/resources/__init__.py b/opentelemetry-sdk/tests/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-sdk/tests/resources/test_init.py b/opentelemetry-sdk/tests/resources/test_init.py new file mode 100644 index 0000000000..953b4c2517 --- /dev/null +++ b/opentelemetry-sdk/tests/resources/test_init.py @@ -0,0 +1,33 @@ +import unittest +from opentelemetry.sdk import resources + + +class TestResources(unittest.TestCase): + def test_resource_merge(self): + left = resources.Resource({"service": "ui"}) + right = resources.Resource({"host": "service-host"}) + self.assertEqual( + left.merge(right), + resources.Resource({ + "service": "ui", + "host": "service-host" + })) + + def test_resource_merge_empty_string(self): + """Verify Resource.merge behavior with the empty string. + + Labels from the source Resource take precedence, with + the exception of the empty string. + + """ + left = resources.Resource({"service": "ui", "host": ""}) + right = resources.Resource({ + "host": "service-host", + "service": "not-ui" + }) + self.assertEqual( + left.merge(right), + resources.Resource({ + "service": "ui", + "host": "service-host" + }))