-
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -45,7 +45,10 @@ def __init__(self, client, room_id): | |
| self.invite_only = None | ||
| self.guest_access = None | ||
| self._prev_batch = None | ||
| self._members = [] | ||
| self._members = {} | ||
| self.members_displaynames = { | ||
| # user_id: displayname | ||
| } | ||
| self.encrypted = False | ||
|
|
||
| def set_user_profile(self, | ||
|
|
@@ -83,19 +86,16 @@ def display_name(self): | |
| return self.canonical_alias | ||
|
|
||
| # Member display names without me | ||
| members = [u.get_display_name() for u in self.get_joined_members() if | ||
| members = [u.get_display_name(self) for u in self.get_joined_members() if | ||
| self.client.user_id != u.user_id] | ||
| first_two = members[:2] | ||
| if len(first_two) == 1: | ||
| return first_two[0] | ||
| members.sort() | ||
|
|
||
| if len(members) == 1: | ||
| return members[0] | ||
| elif len(members) == 2: | ||
| return "{0} and {1}".format( | ||
| first_two[0], | ||
| first_two[1]) | ||
| return "{0} and {1}".format(members[0], members[1]) | ||
| elif len(members) > 2: | ||
| return "{0} and {1} others".format( | ||
| first_two[0], | ||
| len(members) - 1) | ||
| return "{0} and {1} others".format(members[0], len(members) - 1) | ||
| else: # len(members) <= 0 or not an integer | ||
| # TODO i18n | ||
| return "Empty room" | ||
|
|
@@ -477,23 +477,23 @@ def add_room_alias(self, room_alias): | |
| def get_joined_members(self): | ||
| """Returns list of joined members (User objects).""" | ||
| if self._members: | ||
| return self._members | ||
| return list(self._members.values()) | ||
|
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. Doesn't need to be changed in this PR, but is there any reason to not deprecate this method and just change Sidenote: thanks for changing this list to the much more appropriate
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. Can't think of it either. |
||
| response = self.client.api.get_room_members(self.room_id) | ||
| for event in response["chunk"]: | ||
| if event["content"]["membership"] == "join": | ||
| self._mkmembers( | ||
| User(self.client.api, | ||
| event["state_key"], | ||
| event["content"].get("displayname")) | ||
| ) | ||
| return self._members | ||
|
|
||
| def _mkmembers(self, member): | ||
| if member.user_id not in [x.user_id for x in self._members]: | ||
| self._members.append(member) | ||
|
|
||
| def _rmmembers(self, user_id): | ||
| self._members[:] = [x for x in self._members if x.user_id != user_id] | ||
| user_id = event["state_key"] | ||
| self._add_member(user_id, event["content"].get("displayname")) | ||
| return list(self._members.values()) | ||
|
|
||
| def _add_member(self, user_id, displayname=None): | ||
| self.members_displaynames[user_id] = displayname | ||
| if user_id in self._members: | ||
| return | ||
| if user_id in self.client.users: | ||
| self._members[user_id] = self.client.users[user_id] | ||
| return | ||
| self._members[user_id] = User(self.client.api, user_id, displayname) | ||
| self.client.users[user_id] = self._members[user_id] | ||
|
|
||
| def backfill_previous_messages(self, reverse=False, limit=10): | ||
| """Backfill handling of previous messages. | ||
|
|
@@ -660,13 +660,10 @@ def _process_state_event(self, state_event): | |
| elif etype == "m.room.member" and clevel == clevel.ALL: | ||
| # tracking room members can be large e.g. #matrix:matrix.org | ||
| if econtent["membership"] == "join": | ||
| self._mkmembers( | ||
| User(self.client.api, | ||
| state_event["state_key"], | ||
| econtent.get("displayname")) | ||
| ) | ||
| user_id = state_event["state_key"] | ||
| self._add_member(user_id, econtent.get("displayname")) | ||
| elif econtent["membership"] in ("leave", "kick", "invite"): | ||
| self._rmmembers(state_event["state_key"]) | ||
| self._members.pop(state_event["state_key"], None) | ||
|
|
||
| for listener in self.state_listeners: | ||
| if ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import pytest | ||
| import responses | ||
|
|
||
| from matrix_client.api import MATRIX_V2_API_PATH | ||
| from matrix_client.client import MatrixClient | ||
| from matrix_client.user import User | ||
|
|
||
| HOSTNAME = "http://localhost" | ||
|
|
||
|
|
||
| class TestUser: | ||
| cli = MatrixClient(HOSTNAME) | ||
| user_id = "@test:localhost" | ||
| room_id = "!test:localhost" | ||
|
|
||
| @pytest.fixture() | ||
| def user(self): | ||
| return User(self.cli.api, self.user_id) | ||
|
|
||
| @pytest.fixture() | ||
| def room(self): | ||
| return self.cli._mkroom(self.room_id) | ||
|
|
||
| @responses.activate | ||
| def test_get_display_name(self, user, room): | ||
|
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. In future, please try to break tests that are testing multiple things into multiple methods. |
||
| displayname_url = HOSTNAME + MATRIX_V2_API_PATH + \ | ||
| "/profile/{}/displayname".format(user.user_id) | ||
| displayname = 'test' | ||
| room_displayname = 'room_test' | ||
|
|
||
| # No displayname | ||
| assert user.get_display_name(room) == user.user_id | ||
| responses.add(responses.GET, displayname_url, json={}) | ||
| assert user.get_display_name() == user.user_id | ||
| assert len(responses.calls) == 1 | ||
|
|
||
| # Get global displayname | ||
| responses.replace(responses.GET, displayname_url, | ||
| json={"displayname": displayname}) | ||
| assert user.get_display_name() == displayname | ||
| assert len(responses.calls) == 2 | ||
|
|
||
| # Global displayname already present | ||
| assert user.get_display_name() == displayname | ||
| # No new request | ||
| assert len(responses.calls) == 2 | ||
|
|
||
| # Per-room displayname | ||
| room.members_displaynames[user.user_id] = room_displayname | ||
| assert user.get_display_name(room) == room_displayname | ||
| # No new request | ||
| assert len(responses.calls) == 2 | ||
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.roomsbe 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 +
propertythen.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!