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

Add Link and Event classes #130

Merged

Conversation

mauriciovasquezbernal
Copy link
Member

This PR creates classes for Link and Event, also implements the add_lazy_{event,link} methods.

Having a proper class would make it easier to extend in the future. Those classes are implemented directly in the API as they are general enough and different implementations could reuse them.

Solves #76
Solves #77

Put all actions of the same member together so it is easier to update the tests.
@@ -70,6 +70,47 @@
ParentSpan = typing.Optional[typing.Union["Span", "SpanContext"]]


class Link:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these classes are so simple, why did you not keep using namedtuple?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the idea is that users can extend these classes (hence the lazy prefix). And personally I'd prefer to keep them as very simple classes (otherwise, I'd totally be up for named tuples ;) )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if those classes are very simple, I think having a proper class allows more flexibility, for instance as mentioned by @carlosalberto, when users want to extend those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namedtuples can be extended, they are full classes. In fact, a common pattern, that's even mentioned in the docs of namedtuple is something like:

class MyDataCls(namedtuple('MyDataCls', 'foo bar')):
  def baz(self):
    return self.foo + self.bar

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namedtuples can be extended

Sure, but IHMO it's still better to have this as simple classes, as it's clearer for users who want to extend it, instead of having to go read the namedtuple documentation ;)

(If we had this classes as private or SDK specific, I'd be also more open to have them as namedtuples)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Oberon00 what's the benefit of using namedtuples here? If the goal is to avoid boilerplate we might want to use the attrs library. The docs include some reasons not to use namedtuples for data classes. One big reason is that subclasses of namedtuples have a MRO that looks very different from plain old classes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, these reasons sound valid enough. I retract my objections then 😃 I think since the goal of the API is to be a low level building block we should prefer a bit of boilerplate code over an additional dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I think there's a stronger reason for using attrs in the SDK than the API, but we may not want it at all.

Copy link
Contributor

@lzchen lzchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

"""A link to a `Span`."""

def __init__(
self, context: "SpanContext", attributes: types.Attributes = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self, context: "SpanContext", attributes: types.Attributes = {}
self, context: "SpanContext", attributes: types.Attributes = None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used that because mypy was complaining, but then lint complained about this one. I had to change types.Attributes to typing.Optional[typing.Dict[str, AttributeValue]] and it worked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make Link a class, also implement addLazyLink
Make Event a class and implement add_lazy_event
def __init__(
self, name: str, timestamp: int, attributes: types.Attributes = None
) -> None:
self._name = name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general question - do we expect this to be implemented in the API package or the SDK?
I think it makes sense to have it the API package, and I don't expect people to override this in the SDK.
Just to put the question here to see if someone has a different understanding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd +1 for keeping it in the API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By keeping it on the API we are providing a reference implementation and also allowing users to extend it if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to have it the API package

Agreed, that's how we do it in Java too, btw.

Copy link
Member

@toumorokoshi toumorokoshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

"""A link to a `Span`."""

def __init__(
self, context: "SpanContext", attributes: types.Attributes = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should attributes be typing.Optional? since it defaults to none.

Copy link
Member Author

@mauriciovasquezbernal mauriciovasquezbernal Sep 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I directly changed the type of types.Attributes to be Attributes = typing.Optional[typing.Dict[str, AttributeValue]]

def __init__(
self, name: str, timestamp: int, attributes: types.Attributes = None
) -> None:
self._name = name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd +1 for keeping it in the API.

`SpanContext` passed as argument.
"""

def add_lazy_link(self, link: "Link") -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would almost argue that add_link should just always take a link object. Is it that much harder to do:

span.add_link(context, [attribute_1])

instead of

span.add_link(Link(context, [attribute_1]))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it seems a bit redundant as of now, though I'd vote for having only span.add_link(context, [attribute_1]). I don't think I fully grasp the design of these "lazy" method yet, I can't see what's lazy about them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion on any of both options, I just followed the specs: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-tracing.md#add-links, there the two methods are required add_xxx that receives the parameters and add_lazy_xxx that receives an object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could optionally have only the add_link(link: Link) method, to reduce API surface - but we won't easily be able in the future to add an overload that takes a SpanContext + attributes, because of the name (add_nonlazy_link()?)

For historical reference: we added these as in Java this is a usual thing to add there (overload with different parameters, that is). We could drop this for Python if we feel like that, I feel.

Copy link
Member

@c24t c24t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but we could benefit from a better policy on which classes get implemented in the API vs. SDK. So far we've only implemented the classes needed to let applications pass along the trace context without taking a dependency on the SDK.

self._attributes = attributes
self._timestamp = timestamp

@property
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making these properties in the API means subclasses can't make them regular attributes. Is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I could say it is, subclasses would have to re-implement this property if needed.

Copy link
Member

@c24t c24t Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But they could never choose to make these regular attributes. That is, you can always replace attributes with properties in a subclass, but not vice-versa.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good point with the replacing! But if the derived class defines a read-only property, it would break the base class' __init__.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not totally sure I am following this conversation.
I want to go back a step first, the specifications states that Link class SHOULD be immutable, that's the reason I decided to use properties. (Maybe I should use slots too). Do you think there is a better way to make it an immutable type?

@Oberon00 Oberon00 merged commit 731ed02 into open-telemetry:master Sep 13, 2019
Oberon00 added a commit to dynatrace-oss-contrib/opentelemetry-python that referenced this pull request Sep 13, 2019
c24t pushed a commit that referenced this pull request Sep 13, 2019
@mauriciovasquezbernal mauriciovasquezbernal deleted the mauricio/add_event_and_link_classes branch April 14, 2020 21:50
srikanthccv pushed a commit to srikanthccv/opentelemetry-python that referenced this pull request Nov 1, 2020
* Refactor: rename tracer to node-tracer

* style: fix typo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants