-
Notifications
You must be signed in to change notification settings - Fork 121
better handling of membership in rooms #259
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
Conversation
matrix_client/client.py
Outdated
| Args: | ||
| user_id (str): The matrix user id of a user. | ||
| """ | ||
| warn("get_user is deprecated. Directly instanciate a User 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.
spelling. "instantiate"
non-Jedi
left a comment
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.
Thanks for this. Couple of points of concern. Also feel like I might be missing something else on this one because I'm tired, so I'll make sure I take another look at it another time.
| """Returns list of joined members (User objects).""" | ||
| if self._members: | ||
| return self._members | ||
| return list(self._members.values()) |
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.
Doesn't need to be changed in this PR, but is there any reason to not deprecate this method and just change Room._members to Room.members? Can't think of a situation where it'd be a good idea to get the displayname of a room without first having called sync.
Sidenote: thanks for changing this list to the much more appropriate dict. Definitely a helpful change that's been bugging me for a bit.
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.
Can't think of it either.
I don't think we should allow modifying the members of a room though, so we should keep Room._members and add Room.members as a property.
Also it would be better to return a dict than a list, since it's easy for the caller to use dict.values() if they want a list, and I can see the check user_id in Room.members having its use.
| self.rooms = { | ||
| # room_id: Room | ||
| } | ||
| self.users = { |
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.
Problem: exposing this dict like this implies that it should be fine for a user to modify it as well as read from it. This needs to be read-only in the class's public api I think.
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.
Sure. Shouldn't Client.rooms be the same?
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.
In fact, not 100% sure how you want class attributes to be read-only (maybe only an indication in docstring would have been enough?). I implemented this with a property, but to me it fixes half of the problem since the internal dict can still be modified. I'd prefer returning a copy, but a copy is expensive.
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.
Ya. Lack of richness in python type system makes expressing non-mutability of this with anything other than copy difficult to impossible. Note in a docstring is probably best bet. If anybody has written a python dict replacement that doesn't allow mutation, we could also look at using that and creating new dicts as things change (allowing old to be gced). Given python, performance characteristics of that solution would probably be bad, too. Docstring note is probably best bet for now.
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.
Settled for docstring note + property then.
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.
tbh, with still mutable dicts, I don't think changing it to a "property" really gains us much. :/ Definitely thanks for the docstring update though.
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.
Absolutely agree, just wasn't sure what you expected. Should be good now!
matrix_client/room.py
Outdated
|
|
||
| def _rmmembers(self, user_id): | ||
| self._members[:] = [x for x in self._members if x.user_id != user_id] | ||
| self._add_member(event["state_key"], event["content"].get("displayname")) |
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.
Since these user objects are now global rather than per-room, the displayname attached to each needs to be the global displayname rather than the per-room one. This gets the displayname in a particular room, right?
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 doesn't look like we have access to the global displayname here though.
So the fix would be to maintain a map from room IDs to displaynames in User, and to change the signature of User.get_displayname(self) to User.get_displayname(self, room), knowing that the first user.get_displayname() will always trigger a request to fetch the global displayname we don't have. Not super nice, what do you think?
Also the map of displaynames might get used as a hack to know in which rooms a particular user is, so we may want to implement something that allows that cleanly beforehand.
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.
Waiting on your approval on this to implement this not so satisfying solution + a new attribute of User which contains the Rooms it is a member of.
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.
Sorry on the delay here. I think the map mxid->displayname should live on the room object rather than the user object since that's how it's implemented in the protocol (per-room displaynames are just another state event). This also makes the hack about which rooms a user is in a bit more difficult, so we can kick that can down the road a bit.
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.
Done.
This should allow for better performance, by using appropriate data structures, and avoiding to create multiple User objects for the same user.
Also some style improvements.
non-Jedi
left a comment
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.
This is awesome, and I'm excited to merge as soon as comments are addressed (completed full review this time).
matrix_client/room.py
Outdated
| return self.canonical_alias | ||
|
|
||
| # Member display names without me | ||
| members = [u.get_display_name() for u in self.get_joined_members() if |
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.
This should be self.members_displaynames[u.user_id] or the equivalent with appropriate default value (the way I read the spec anyway: https://matrix.org/speculator/spec/HEAD/client_server/unstable.html#calculating-the-display-name-for-a-room).
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.
Good catch. Although I think it should be u.get_display_name(self) if you agree with what I wrote below, as this method will take care of the default value.
matrix_client/user.py
Outdated
| See also get_friendly_name(). | ||
| Args: | ||
| room (Room): Optional. If absent, return the global displayname for the user. |
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.
Docstring needs to note that this is a room object rather than a room id.
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.
Does it? I see how someone reading the doc too fast would pass a str were it's very clearly written Room, but for me they'd deserve their AttributeError. Also, in case you didn't notice, Sphinx is smart enough to automatically add a link (clicking Room brings you to the room class), so there is really 0 ambiguity.
Another concern is that for consistency we'd have to add those notes to every methods that take an attribute room (Room) or user (User), which has a very low appeal to me...
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.
You're right. This was me forgetting about the sphinx convention that already tells you the type of an argument. No need to change anything.
matrix_client/user.py
Outdated
| try: | ||
| return room.members_displaynames[self.user_id] | ||
| except KeyError: | ||
| pass |
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.
If KeyError here, we should raise the same error that gets raised if self.api.get_display_name fails. If a user doesn't have a displayname on their m.room.member, we shouldn't give a displayname in the context of a particular room.
Would be great if you could write a quick test-case for this particular scenario.
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.
Yep, defaulting to the global display name seems like a bad idea after all.
self.api.get_display_name doesn't raise anything and just returns None (which sure is unexpected according to the docstring of User.get_display_name), so here the try/except clause would just be replaced by return room.members_displaynames.get(self.user_id).
However, it seems like get_display_name should indeed raise an exception, while if someone doesn't want that they should call get_friendly_name which ideally wraps around get_display_name, returning the user id if an exception was raised.
While we're at it, I don't think this is a good design. The spec doesn't say anything that suggests having two methods (to me it says that only get_friendly_name should exist, and should be called get_display_name). Also I can't see any use of having an exception raised in this kind of method.
So my proposal would be:
User.get_display_name()gives the global display name, or the user id if not set.User.get_display_name(room)gives the display name corresponding to the room, or the user id if not set.User.get_friendly_name()is deprecated and returnsUser.get_display_name().
How's all that?
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.
Sounds great to me! I think that really cleans up the displayname part of the client sdk api. Good work!
|
Should be better now! |
| return self.cli._mkroom(self.room_id) | ||
|
|
||
| @responses.activate | ||
| def test_get_display_name(self, user, room): |
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.
In future, please try to break tests that are testing multiple things into multiple methods.
This should allow for better performance, by using appropriate data structures, and avoiding to create multiple User objects for the same user.
I need this for E2E because when verifying the devices of a user, their device list needs to be the same across rooms.
Signed-off-by: Valentin Deniaud <valentin.deniaud@inpt.fr>