-
-
Notifications
You must be signed in to change notification settings - Fork 274
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
Add support for all streams #1408
base: main
Are you sure you want to change the base?
Conversation
This commit creates data structures, similar to stream_dict, to support unsubscribed and never-subscribed streams. These streams are available in the fetched initial data which is used to populate the new data structures using the _register_non_subscribed_streams function.
This commit adds two accessor methods - _get_stream_from_id and get_all_stream_ids. These two accessor methods help in preparation for including specific stream property accessor methods in the next set of commits. Tests added.
62e7272
to
76d71ca
Compare
This commit replaces the usage of directly indexing stream_dict for stream "name" with the new stream property accessor method "get_stream_name". Test added.
…ict. This commit replaces the usage of directly indexing stream_dict for "subscribers" with the new stream property accessor method "get_stream_subscribers". Test added.
This commit replaces the usage of directly indexing stream_dict for "date_created" data with the new stream property accessor methods "get_stream_date_created" and "set_stream_date_created". Tests added.
76d71ca
to
ec9f1d8
Compare
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 the PR @theViz343!
This is a neat refactor; I've just left some minor points of feedback.
zulipterminal/model.py
Outdated
@@ -174,11 +175,18 @@ def __init__(self, controller: Any) -> None: | |||
self.users = self.get_all_users() | |||
|
|||
self.stream_dict: Dict[int, Any] = {} |
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 can now be typed as a Dict
with int
keys and Subscription
values; reducing the use of Any
will improve our type-checking.
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.
Resolved in #1419 👍
zulipterminal/model.py
Outdated
@@ -1204,6 +1204,9 @@ def get_stream_date_created(self, stream_id: int) -> Optional[int]: | |||
def set_stream_date_created(self, stream_id: int, value: Optional[int]) -> None: | |||
self._get_stream_from_id(stream_id)["date_created"] = value | |||
|
|||
def is_stream_web_public(self, stream_id: int) -> Optional[bool]: | |||
return self._get_stream_from_id(stream_id).get("is_web_public") |
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 the is_web_public
key is guaranteed to be present and is typed as a bool in api_types
, you can index the dict
instead of using get
so the return type for this method can be bool
, consistent with its type in the Stream
class.
zulipterminal/model.py
Outdated
return self.stream_dict[stream_id]["email_address"] | ||
elif stream_id in self._unsubscribed_streams: | ||
return self._unsubscribed_streams[stream_id]["email_address"] | ||
return None |
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 this method is only being used in the StreamInfoView
and you haven't added the feature of viewing stream info for a never subscribed stream, can the base condition of returning None
be avoided? The return value of this method is currently attached to a copy command, and having to copy a None
to the clipboard doesn't seem appropriate.
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've made it so that the method raises a RuntimeError if the stream is not a subscription (or does not exist).
This commit replaces the usage of directly indexing stream_dict for "is_web_public" data with the new stream property accessor method "is_stream_web_public". Test added.
This commit replaces the usage of directly indexing stream_dict for "message_retention_days" data with the new stream property accessor methods "get_stream_message_retention_days" and "set_stream_message_retention_days". Test added.
This commit replaces the usage of directly indexing stream_dict for "is_invite_only" data with the new stream property accessor method "is_stream_invite_only". Test added.
This commit replaces the usage of directly indexing stream_dict for "stream_post_policy" with the new stream property accessor method "get_stream_post_policy". Test added.
This commit replaces the usage of directly indexing stream_dict for "is_announcement_only" data with the new stream property accessor method "is_stream_announcement_only". Test added.
…thod. This commit replaces the usage of directly indexing stream_dict for "history_public_to_subscribers" data with the new stream property accessor method "is_stream_history_public_to_subscribers". Test added.
This commit replaces the usage of directly indexing stream_dict for "stream_weekly_traffic" data with the new stream property accessor method "get_stream_weekly_traffic". Test added.
This commit replaces the usage of directly indexing stream_dict for "rendered_description" data with the new stream property accessor method "get_stream_rendered_description". Test added.
This commit adds the subscription ids accessor method "get_all_stream_ids". This helps in preparation for including subscription color accessor method added in the next commit. Tests added.
This commit replaces the usage of directly indexing stream_dict for "color" data with the new stream property accessor method "get_subscription_color". Test added.
…ict. This commit replaces the usage of directly indexing stream_dict for "email_address" data with the new stream property accessor method "get_subscription_email". Test added.
This commit improves the typing of stream_dict by using Subscription TypedDict instead of Dict[str, Any]. Tests updated.
This commit renames stream_dict to _subscribed_streams. This change is necessary due to the introduction of _unsubscribed_streams and never_subscribed streams in the model. Tests updated.
ec9f1d8
to
308fa13
Compare
MODEL + "._get_stream_from_id", return_value=get_stream_from_id_fixture | ||
) | ||
assert model.get_stream_name(stream_id) == expected_value | ||
model._get_stream_from_id.assert_called_once() |
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 assert_called_once_with
be used here?
@theViz343 Thanks for working on this! This seems to work well and was pretty easy to understand because of the commit structure and commit messages. |
Heads up @theViz343, we just merged some commits that conflict with the changes you made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
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.
@theViz343 This has grown quite a lot since I last looked! I started a review of this before Hari, so apologies if this overlaps a little. The end result is great, though I think we can shrink it a little.
The PR seems to be split into two parts:
- adding the un/never-subscribed data, improving the data types, and generalizing the current code to avoid eg. the KeyErrors
- adding the encapsulation via the accessors, followed by the rename
Both of these parts have value, but I'd suggest grouping the first set together at the start so that we can consider them together, and so potentially merge the earlier commits in this PR first.
There are a lot of commits, particularly for the encapsulating functions, and isolating adding different methods and tests per commit is great. However, some of these functions are not used outside the model, and don't appear to be preparing for anything, or the effect is replicated in other functions already. You also have some 'setters', which we likely don't need for users of the model.
With a smaller set of new methods I think the changes will be clearer, but it may be worth discussing whether adding so many functions is the way to go, or rather using more general stream_properties and subscription_properties functions?
@@ -237,6 +226,21 @@ class Subscription(TypedDict): | |||
# in_home_view: bool # Replaced by is_muted in Zulip 2.1; still present in updates |
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 was a personal setting.
stream_id: int | ||
name: str | ||
description: str | ||
rendered_description: str | ||
date_created: int # NOTE: new in Zulip 4.0 / ZFL 30 | ||
date_created: Optional[int] # NOTE: new in Zulip 4.0 / ZFL 30 |
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.
Did you find this started to fail a type check? We do set it explicitly to None!
The tricky thing here is that we reuse the existing assumed datastructures from the server for simplicity. For the purposes of loading the data it is NotRequired
and is never None
; once loaded it is expected to be present and may be Optional
.
Maybe Union[NotRequired[int], Optional[int]]
? I've not tested for validity of that syntax, and I'm unsure if it would help with our typing beyond what you have.
In any case, assuming we include the Optional[int] then we should add a comment that the None part is ZT-specific.
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.
Yes, the part where we add the date_created
key and set it to None fails the typecheck. Since we get the data from the server initially, mypy doesn't catch that date_created
is not present. We could account for this by using NotRequired[Optional[int]]
instead, but that would purely be for documentation since it passes the typecheck with just Optional[int]
. I'll also add a comment.
|
||
is_muted: bool | ||
|
||
role: NotRequired[int] # NOTE: new in Zulip 4.0 / ZFL 31 |
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 seems accurate, and we may want to apply NotRequired more often in general - it wasn't an option until quite recently. However, the API notes indicate it was removed again in 6.0/133, so I guess we can include it commented-out as with in_home_view. (I think it's being replaced by the user-group approach for stream management)
Like with the other change to date_created, I'd suggest mentioning these kinds of changes in the commit summary. This applies to any commit with a change which looks like only the move of code but also has minor adjustments that wouldn't necessarily be noted by a reviewer or someone looking at changes later.
(git tools can show lines moved vs changed, but GitHub doesn't :/)
However, given this and other changes in the fields, both that you've included and that seem present in the updated API docs, I'd suggest splitting those out into a separate commit - unless the changes are necessary for type-checking?
self.muted_streams: Set[int] = set() | ||
self.pinned_streams: List[StreamData] = [] | ||
self.unpinned_streams: List[StreamData] = [] | ||
self.visual_notified_streams: Set[int] = set() | ||
|
||
self._register_non_subscribed_streams( |
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 was going to refer back to my suggestion in #1387 (comment) to inline this function since we only use it once, but we may need to reinitialize these values if we lose connection while ZT is running.
def get_stream_message_retention_days(self, stream_id: int) -> Optional[int]: | ||
return self._get_stream_from_id(stream_id).get("message_retention_days") | ||
|
||
def set_stream_message_retention_days( | ||
self, stream_id: int, value: Optional[int] | ||
) -> None: | ||
self._get_stream_from_id(stream_id)["message_retention_days"] = value |
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.
The accessor here has more purpose - but only if we use it to wrap accessing the .cached_retention_text
data, which is what is currently accessed externally :) That will require updating the code you introduced above this.
We already have the _get_stream_from_id helper, and there's only one place it's used, and that's inside the model - so I'm unconvinced this is necessary.
def is_stream_invite_only(self, stream_id: int) -> bool: | ||
return self._get_stream_from_id(stream_id)["invite_only"] |
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.
Similarly to is_web_public, we already have a method for this, which you've updated to use this accessor.
There is no use of this data outside of the model in any case, other than through stream_access_type(), so there is no motivation for introducing this accessor.
zulipterminal/ui_tools/views.py
Outdated
if controller.model.get_stream_post_policy(self.stream_id): | ||
stream_policy = STREAM_POST_POLICY[ | ||
controller.model.get_stream_post_policy(self.stream_id) | ||
] | ||
else: | ||
if stream.get("is_announcement_only"): |
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 existing UI code incorporates implicit checking of the server version, which would be better wrapped into the model under get_stream_post_policy
, rather than the two functions introduced in this commit and the next.
zulipterminal/model.py
Outdated
@@ -174,7 +174,7 @@ def __init__(self, controller: Any) -> None: | |||
|
|||
self.users = self.get_all_users() | |||
|
|||
self.stream_dict: Dict[int, Any] = {} | |||
self.stream_dict: Dict[int, Subscription] = {} |
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 change, and possibly others in this commit seem much more related to those earlier in the PR than here at the end?
@@ -1268,7 +1268,7 @@ class StreamInfoView(PopUpView): | |||
def __init__(self, controller: Any, stream_id: int) -> None: | |||
self.stream_id = stream_id | |||
self.controller = controller | |||
stream = controller.model.stream_dict[stream_id] | |||
stream = controller.model._subscribed_streams[stream_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.
This suggests we haven't covered accessing the stream or subscription properties sufficiently?
What does this PR do, and why?
This PR adds support for unsubscribed streams and never_subscribed streams in ZT. It creates new data structures and accessor methods to access them. This PR is needed for further work on reducing stream related issues and adding support for stream (un)subscription.
Outstanding aspect(s)
External discussion & connections
Adding streams throws key error
How did you test this?
Self-review checklist for each commit
Visual changes