-
Notifications
You must be signed in to change notification settings - Fork 129
feat: typed testing relation getters #2326
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
Changes from all commits
20f7b31
802e991
d5a8a41
4e047f5
975fd48
d64a86e
5e05a51
bd144b7
f65dab0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,7 @@ class _StateKwargs(TypedDict, total=False): | |
| UnitID = int | ||
|
|
||
| CharmType = TypeVar('CharmType', bound=CharmBase) | ||
| _RelationType = TypeVar('_RelationType', bound='RelationBase') | ||
|
|
||
| logger = scenario_logger.getChild('state') | ||
|
|
||
|
|
@@ -1746,6 +1747,38 @@ def get_relation(self, relation: int, /) -> RelationBase: | |
| return state_relation | ||
| raise KeyError(f'relation: id={relation} not found in the State') | ||
|
|
||
| def get_regular_relation(self, relation: int, /) -> Relation: | ||
| """Get a regular relation from this State by relation id. | ||
|
|
||
| Raises: | ||
| TypeError: If this relation is not a ``Relation``. | ||
| """ | ||
| return self._get_typed_relation(relation, kind=Relation) | ||
|
|
||
| def get_peer_relation(self, relation: int, /) -> PeerRelation: | ||
| """Get a peer relation from this State by relation id. | ||
|
|
||
| Raises: | ||
| TypeError: If this relation is not a ``PeerRelation``. | ||
| """ | ||
| return self._get_typed_relation(relation, kind=PeerRelation) | ||
|
|
||
| def get_subordinate_relation(self, relation: int, /) -> SubordinateRelation: | ||
| """Get a subordinate relation from this State by relation id. | ||
|
|
||
| Raises: | ||
| TypeError: If this relation is not a ``SubordinateRelation``. | ||
| """ | ||
| return self._get_typed_relation(relation, kind=SubordinateRelation) | ||
|
|
||
| def _get_typed_relation(self, relation: int, kind: type[_RelationType]) -> _RelationType: | ||
| rel = self.get_relation(relation) | ||
| if not isinstance(rel, kind): | ||
| raise TypeError( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this should be:
|
||
| f"Relation {relation} is not a {kind.__name__}, it's a {rel.__class__.__name__}" | ||
| ) | ||
| return rel | ||
|
|
||
| def get_relations(self, endpoint: str) -> tuple[RelationBase, ...]: | ||
| """Get all relations on this endpoint from the current state.""" | ||
| # we rather normalize the endpoint than worry about cursed metadata situations such as: | ||
|
|
@@ -1758,6 +1791,45 @@ def get_relations(self, endpoint: str) -> tuple[RelationBase, ...]: | |
| r for r in self.relations if _normalise_name(r.endpoint) == normalized_endpoint | ||
| ) | ||
|
|
||
| def get_regular_relations(self, endpoint: str) -> tuple[Relation, ...]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced about adding the plural/endpoint ones. I'm pretty sure when we've looked at use of this in the past, it's almost always code like: state_in = State(..., relations={relation})
state_out = ctx.run(..., state=state_in)
assert state_out.get_relations('endpoint')[0]...And that is cleaner as: state_in = State(..., relations={relation})
state_out = ctx.run(..., state=state_in)
assert state_out.get_relation(relation.id)...I had a quick look over a few random selections in charms, and they were all of that type. I'm sure there are exceptions, but this is already adding 3 new methods for getting by ID - I'm not convinced there is enough value to add another 3 as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. I'll let @dimaqq weigh in too, but I'm happy to remove the plural ones.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose my one hesitation would be that getting relations by endpoint name rather than by ID feels more like the right level of expression for state-transition tests. But the current usage argument is pretty compelling. |
||
| """Get regular relations on this endpoint from the current state. | ||
|
|
||
| Raises: | ||
| TypeError: If any of the relations on this endpoint is not | ||
| a ``Relation``. | ||
| """ | ||
| return self._get_typed_relations(endpoint, kind=Relation) | ||
|
|
||
| def get_peer_relations(self, endpoint: str) -> tuple[PeerRelation, ...]: | ||
| """Get peer relations on this endpoint from the current state. | ||
|
|
||
| Raises: | ||
| TypeError: If any of the relations on this endpoint is not | ||
| a ``PeerRelation``. | ||
| """ | ||
| return self._get_typed_relations(endpoint, kind=PeerRelation) | ||
|
|
||
| def get_subordinate_relations(self, endpoint: str) -> tuple[SubordinateRelation, ...]: | ||
| """Get subordinate relations on this endpoint from the current state. | ||
|
|
||
| Raises: | ||
| TypeError: If any of the relations on this endpoint is not | ||
| a ``SubordinateRelation``. | ||
| """ | ||
| return self._get_typed_relations(endpoint, kind=SubordinateRelation) | ||
|
|
||
| def _get_typed_relations( | ||
| self, endpoint: str, kind: type[_RelationType] | ||
| ) -> tuple[_RelationType, ...]: | ||
| rels = self.get_relations(endpoint) | ||
| for rel in rels: | ||
| if not isinstance(rel, kind): | ||
|
Comment on lines
+1825
to
+1826
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A zero-length return is valid, isn't it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's correct, though I don't think we can do any better here. The |
||
| raise TypeError( | ||
| f'Relation on endpoint {endpoint} is not a {kind.__name__}' | ||
| f", it's a {rel.__class__.__name__}" | ||
| ) | ||
| return rels | ||
|
|
||
| @classmethod | ||
| def from_context( | ||
| cls, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really love the word "regular here", but I'm also not sure what to suggest instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't love it either. I went with it because the Juju docs on relations basically go Peer, Non-Peer, Subordinate, Non-Subordinate, and then:
I'm guessing that's why Dima suggested it too. Open to alternatives though.