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

Redefine ops.relation so multi relationships work both ways #603

Closed

Conversation

rbarry82
Copy link
Contributor

@rbarry82 rbarry82 commented Sep 4, 2021

Add .ready() and .unready() to ConsumerBase.

multi= is left as an argument to __init__ for now so we don't break libraries as soon as it's instantiated, but it does not actually do anything anymore. Instead, it can now multiplex in both directions.

Add .relation to ConsumerEvents by destructuring the relation and fetching it back once a snapshot is loaded, so ConsumerEvents have event.relation like RelationiEvents do.

Simplify ConsumerEvents so they all inherit from a single base class, considering that none of them had invidualized behavior.

Remove TooManyProviders and add a test to ensure that multiple consumers can work with one provider.

Add type hinting everywhere.

Closes #586

Add .ready() and .unready() to ConsumerBase.

multi= is left as an argument to `__init__` for now so we don't
break libraries as soon as it's instantiated, but it does not
actually do anything anymore. Instead, it can now multiplex
in both directions.

Add `.relation` to `ConsumerEvents` by destructuring the relation
and fetching it back once a snapshot is loaded, so `ConsumerEvents`
have `event.relation` like `RelationiEvents` do.

Simplify `ConsumerEvents` so they all inherit from a single base
class, considering that none of them had invidualized behavior.

Remove `TooManyProviders` and add a test to ensure that multiple
consumers can work with one provider.

Closes canonical#586
Copy link
Collaborator

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

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

It might be nice to fix some of the method names before moving the current convention further. It's a bit unfortunate because I'm late to this review, as I didn't see it coming back in July. "ready" and "unready" are not typically used as verbs in this context, and as such are not great options for actions that inflict changes on these objects. They look more like questions ("is it ready?"), and I suspect that's how we'll see existing code using such conventions.

This could instead be something like foo.set_ready(True/False).

Let's talk to see how we can get out of the current situation without breaking code.

@rbarry82
Copy link
Contributor Author

rbarry82 commented Sep 8, 2021

@niemeyer Fortunately, as far as I know, the o11y team is the only one really using this part of the framework so far, so we can probably rename these without too much fallout

@niemeyer
Copy link
Collaborator

niemeyer commented Sep 8, 2021

@rbarry82 That's great news. In general we cannot change public APIs after we release them, as the framework is a very important central part of the system. With that said, I've learned that we haven't yet made these changes available in an actual release that would be fetched by charmcraft, so this is probably internal use only. Let's please have a conversation to detail how we can backtrack this in the least disruptive way possible, and also try to get a mechanism in place for us to create strong conventions that will be carried on in the future.

data: typing.Optional[typing.Dict] = None):
super().__init__(handle)
self.relation = relation
self.data = data
Copy link
Contributor

Choose a reason for hiding this comment

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

On second thought would it be more consistent if we nested data under relation event.relation.data ? I do appreciate data was not nested previously and it may not be directly related to this PR but if you agree then might as well ...

@@ -295,54 +290,29 @@ class ConsumerBase(Object):

multi: a Boolean flag that indicates if the :class:`ConsumerBase` derived
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have expected we could get rid of this multi argument entirely. I mean ops.relation always works in multi mode, i.e. always allowing the relation between providers and consumers to be many to many. If charms want to impose such limits, they can do so with limit in metadata.yaml which is now exposed to charms through a recent merged PR by Michele.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe add deprecation warning if we know its fate is doomed.

@balbirthomas
Copy link
Contributor

Since you say this closes #586 I take it that the explicit choices are

  1. Consumers validate providers but the other way around is not supported.
  2. Providers are not notified of changes in relation with consumer through any bespoke events other than standard relation events already provided.

@@ -81,7 +86,8 @@ class ProviderBase(Object):
"""
_stored = StoredState()

def __init__(self, charm, name, service, version=None):
def __init__(self, charm: CharmBase, name: str,
Copy link
Contributor

Choose a reason for hiding this comment

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

charm: CharmBase

Is this intentional ("restrict" to only CharmBase methods) or you actually intended to somehow use typing.Generic?

"""Set provider state to unready."""
logger.debug("Provider is not ready")
self._stored.ready = False
self._notify_consumers()

def _provider_data(self):
def _provider_data(self) -> typing.Dict:
Copy link
Contributor

Choose a reason for hiding this comment

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

Without using generics, there is no difference between dict and typing.Dict. Also, in newer python versions dict gains additional typing capabilities.
https://www.python.org/dev/peps/pep-0585/

raised in response to a relation joined event for each additional
unit of the same or any additional provider.
"""
pass


class ConsumerEvents(CharmEvents):
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency maybe also match with class ProviderEvents(CharmEvents)?

@@ -295,54 +290,29 @@ class ConsumerBase(Object):

multi: a Boolean flag that indicates if the :class:`ConsumerBase` derived
Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe add deprecation warning if we know its fate is doomed.


def _meets_requirements(self, provides, consumes):
def _meets_requirements(self, provides: typing.Dict, consumes: typing.Dict) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd replace bare typing.Dict with dict.

return True
return False
@property
def is_ready(self) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't by convention methods with is_ prefix are never properties?

@jnsgruk
Copy link
Member

jnsgruk commented Sep 9, 2021

See #606

@jnsgruk jnsgruk closed this Sep 9, 2021
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.

Redefine relation library in terms of sockets rather than multiplicity
5 participants