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" + }))