Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make immutable attributes consistent #1909

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ignore calls to `Span.set_status` with `StatusCode.UNSET` and also if previous status already
had `StatusCode.OK`.
([#1902](https://github.com/open-telemetry/opentelemetry-python/pull/1902))
- Attributes for `Link` and `Resource` are immutable as they are for `Event`, which means
any attempt to modify attributes directly will result in a `TypeError` exception.
([#1909](https://github.com/open-telemetry/opentelemetry-python/pull/1909))

## [1.3.0-0.22b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.3.0-0.22b0) - 2021-06-01

Expand Down
9 changes: 7 additions & 2 deletions opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
from typing import Iterator, Optional, Sequence, cast

from opentelemetry import context as context_api
from opentelemetry.attributes import (
_create_immutable_attributes, # type: ignore
)
from opentelemetry.context.context import Context
from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER
from opentelemetry.trace.propagation import (
Expand Down Expand Up @@ -126,7 +129,7 @@ def attributes(self) -> types.Attributes:


class Link(_LinkBase):
"""A link to a `Span`.
"""A link to a `Span`. The attributes of a Link are immutable.

Args:
context: `SpanContext` of the `Span` to link to.
Expand All @@ -139,7 +142,9 @@ def __init__(
attributes: types.Attributes = None,
) -> None:
super().__init__(context)
self._attributes = attributes
self._attributes = _create_immutable_attributes(
attributes
) # type: types.Attributes

@property
def attributes(self) -> types.Attributes:
Expand Down
13 changes: 8 additions & 5 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@

import pkg_resources

from opentelemetry.attributes import _filter_attributes
from opentelemetry.attributes import (
_create_immutable_attributes,
_filter_attributes,
)
from opentelemetry.sdk.environment_variables import (
OTEL_RESOURCE_ATTRIBUTES,
OTEL_SERVICE_NAME,
Expand Down Expand Up @@ -145,7 +148,7 @@ def __init__(
self, attributes: Attributes, schema_url: typing.Optional[str] = None
):
_filter_attributes(attributes)
self._attributes = attributes.copy()
self._attributes = _create_immutable_attributes(attributes)
if schema_url is None:
schema_url = ""
self._schema_url = schema_url
Expand Down Expand Up @@ -187,7 +190,7 @@ def get_empty() -> "Resource":

@property
def attributes(self) -> Attributes:
return self._attributes.copy()
return self._attributes

@property
def schema_url(self) -> str:
Expand All @@ -210,7 +213,7 @@ def merge(self, other: "Resource") -> "Resource":
Returns:
The newly-created Resource.
"""
merged_attributes = self.attributes
merged_attributes = self.attributes.copy()
merged_attributes.update(other.attributes)

if self.schema_url == "":
Expand Down Expand Up @@ -239,7 +242,7 @@ def __eq__(self, other: object) -> bool:

def __hash__(self):
return hash(
f"{dumps(self._attributes, sort_keys=True)}|{self._schema_url}"
f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}"
)


Expand Down
5 changes: 3 additions & 2 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ def attributes(self) -> types.Attributes:


class Event(EventBase):
"""A text annotation with a set of attributes.
"""A text annotation with a set of attributes. The attributes of an event
are immutable.

Args:
name: Name of the event.
Expand Down Expand Up @@ -456,7 +457,7 @@ def to_json(self, indent=4):
f_span["attributes"] = self._format_attributes(self._attributes)
f_span["events"] = self._format_events(self._events)
f_span["links"] = self._format_links(self._links)
f_span["resource"] = self._resource.attributes
f_span["resource"] = self._format_attributes(self._resource.attributes)

return json.dumps(f_span, indent=indent)

Expand Down
3 changes: 2 additions & 1 deletion opentelemetry-sdk/tests/resources/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ def test_immutability(self):
resource = resources.Resource.create(attributes)
self.assertEqual(resource.attributes, attributes_copy)

resource.attributes["has_bugs"] = False
with self.assertRaises(TypeError):
resource.attributes["has_bugs"] = False
self.assertEqual(resource.attributes, attributes_copy)

attributes["cost"] = 999.91
Expand Down
5 changes: 4 additions & 1 deletion opentelemetry-sdk/tests/trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ def test_links(self):
self.assertEqual(
root.links[0].context.span_id, other_context1.span_id
)
self.assertEqual(root.links[0].attributes, None)
self.assertEqual(0, len(root.links[0].attributes))
self.assertEqual(
root.links[1].context.trace_id, other_context2.trace_id
)
Expand All @@ -814,6 +814,9 @@ def test_links(self):
)
self.assertEqual(root.links[1].attributes, {"name": "neighbor"})

with self.assertRaises(TypeError):
root.links[1].attributes["name"] = "new_neighbour"

def test_update_name(self):
with self.tracer.start_as_current_span("root") as root:
# name
Expand Down