Skip to content

Conversation

@Zil0
Copy link
Contributor

@Zil0 Zil0 commented Jul 22, 2018

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>

Args:
user_id (str): The matrix user id of a user.
"""
warn("get_user is deprecated. Directly instanciate a User instead.",
Copy link
Collaborator

@non-Jedi non-Jedi Jul 29, 2018

Choose a reason for hiding this comment

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

spelling. "instantiate"

Copy link
Collaborator

@non-Jedi non-Jedi left a 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())
Copy link
Collaborator

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.

Copy link
Contributor Author

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 = {
Copy link
Collaborator

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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!


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"))
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Zil0 added 2 commits August 6, 2018 10:26
This should allow for better performance, by using appropriate data
structures, and avoiding to create multiple User objects for the same
user.
Copy link
Collaborator

@non-Jedi non-Jedi left a 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).

return self.canonical_alias

# Member display names without me
members = [u.get_display_name() for u in self.get_joined_members() if
Copy link
Collaborator

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).

Copy link
Contributor Author

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.

See also get_friendly_name().
Args:
room (Room): Optional. If absent, return the global displayname for the user.
Copy link
Collaborator

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.

Copy link
Contributor Author

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...

Copy link
Collaborator

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.

try:
return room.members_displaynames[self.user_id]
except KeyError:
pass
Copy link
Collaborator

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.

Copy link
Contributor Author

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 returns User.get_display_name().

How's all that?

Copy link
Collaborator

@non-Jedi non-Jedi Aug 8, 2018

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!

@Zil0
Copy link
Contributor Author

Zil0 commented Aug 9, 2018

Should be better now!
Somehow I forgot that I had already written tests, and me-now didn't put them in the same place than me-2-days-before. Since I'm consistent with my present self, I'd say they are better where they are now (new user_test.py rather than client_test.py).

return self.cli._mkroom(self.room_id)

@responses.activate
def test_get_display_name(self, user, room):
Copy link
Collaborator

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.

@non-Jedi non-Jedi merged commit fdcd726 into matrix-org:master Aug 9, 2018
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.

2 participants