diff --git a/zerver/lib/email_mirror.py b/zerver/lib/email_mirror.py index 4e9d0b6ee2..8ab1d3e369 100644 --- a/zerver/lib/email_mirror.py +++ b/zerver/lib/email_mirror.py @@ -126,7 +126,9 @@ def send_to_missed_message_address(address, message): # Testing with basestring so we don't depend on the list return type from # get_display_recipient if not isinstance(display_recipient, six.string_types): - display_recipient = ','.join([user['email'] for user in display_recipient]) + recipient_str = ','.join([user['email'] for user in display_recipient]) + else: + recipient_str = display_recipient body = filter_footer(extract_body(message)) body += extract_and_upload_attachments(message, user_profile.realm) @@ -139,7 +141,7 @@ def send_to_missed_message_address(address, message): recipient_type_name = 'private' internal_send_message(user_profile.email, recipient_type_name, - display_recipient, subject, body) + recipient_str, subject, body) ## Sending the Zulip ## diff --git a/zerver/lib/notifications.py b/zerver/lib/notifications.py index f48def71cb..42ec0734e0 100644 --- a/zerver/lib/notifications.py +++ b/zerver/lib/notifications.py @@ -1,5 +1,7 @@ from __future__ import print_function -from typing import Any, Tuple, Iterable, Optional + +from six import text_type +from typing import cast, Any, Iterable, Mapping, Optional, Sequence, Tuple import mandrill from confirmation.models import Confirmation @@ -46,7 +48,7 @@ def one_click_unsubscribe_link(user_profile, endpoint): return "%s/%s" % (base_url.rstrip("/"), resource_path) def hashchange_encode(string): - # type: (str) -> str + # type: (text_type) -> text_type # Do the same encoding operation as hashchange.encodeHashComponent on the # frontend. # `safe` has a default value of "/", but we want those encoded, too. @@ -54,20 +56,20 @@ def hashchange_encode(string): string.encode("utf-8"), safe="").replace(".", "%2E").replace("%", ".") def pm_narrow_url(participants): - # type: (List[str]) -> str + # type: (List[text_type]) -> text_type participants.sort() - base_url = "https://%s/#narrow/pm-with/" % (settings.EXTERNAL_HOST,) + base_url = u"https://%s/#narrow/pm-with/" % (settings.EXTERNAL_HOST,) return base_url + hashchange_encode(",".join(participants)) def stream_narrow_url(stream): - # type: (str) -> str - base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,) + # type: (text_type) -> text_type + base_url = u"https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,) return base_url + hashchange_encode(stream) def topic_narrow_url(stream, topic): - # type: (str, str) -> str - base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,) - return "%s%s/topic/%s" % (base_url, hashchange_encode(stream), + # type: (text_type, text_type) -> text_type + base_url = u"https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,) + return u"%s%s/topic/%s" % (base_url, hashchange_encode(stream), hashchange_encode(topic)) def build_message_list(user_profile, messages): @@ -142,13 +144,14 @@ def build_message_list(user_profile, messages): 'content': [build_message_payload(message)]} def message_header(user_profile, message): - # type: (UserProfile, Message) -> Dict[str, str] + # type: (UserProfile, Message) -> Dict[str, text_type] disp_recipient = get_display_recipient(message.recipient) if message.recipient.type == Recipient.PERSONAL: header = "You and %s" % (message.sender.full_name) html_link = pm_narrow_url([message.sender.email]) header_html = "%s" % (html_link, header) elif message.recipient.type == Recipient.HUDDLE: + assert not isinstance(disp_recipient, text_type) other_recipients = [r['full_name'] for r in disp_recipient if r['email'] != user_profile.email] header = "You and %s" % (", ".join(other_recipients),) @@ -156,6 +159,7 @@ def build_message_list(user_profile, messages): if r["email"] != user_profile.email]) header_html = "%s" % (html_link, header) else: + assert isinstance(disp_recipient, text_type) header = "%s > %s" % (disp_recipient, message.subject) stream_link = stream_narrow_url(disp_recipient) topic_link = topic_narrow_url(disp_recipient, message.subject) @@ -303,7 +307,7 @@ def do_send_missedmessage_events(user_profile, missed_messages, message_count): # If we have one huddle, set a reply-to to all of the members # of the huddle except the user herself disp_recipients = [", ".join(recipient['email'] - for recipient in get_display_recipient(mesg.recipient) + for recipient in cast(Sequence[Mapping[str, Any]], get_display_recipient(mesg.recipient)) if recipient['email'] != user_profile.email) for mesg in missed_messages] if all(msg.recipient.type == Recipient.HUDDLE for msg in missed_messages) and \ diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index 13ec410ec4..c4fae481f6 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from typing import Any, Callable, Generator, Iterable, Tuple, Sized, Union, Optional +from typing import cast, Any, Callable, Generator, Iterable, Tuple, Sized, Union, Optional from django.test import TestCase from django.template import loader @@ -292,7 +292,7 @@ class AuthedTestCase(TestCase): user_profile=user_profile, active=True, recipient__type=Recipient.STREAM) - return [get_display_recipient(sub.recipient) for sub in subs] + return [cast(text_type, get_display_recipient(sub.recipient)) for sub in subs] def send_message(self, sender_name, raw_recipients, message_type, content=u"test content", subject=u"test", **kwargs): diff --git a/zerver/models.py b/zerver/models.py index a7c162fec1..125d4e34ce 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -50,16 +50,16 @@ STREAM_NAMES = TypeVar('STREAM_NAMES', Sequence[text_type], AbstractSet[text_typ # Doing 1000 remote cache requests to get_display_recipient is quite slow, # so add a local cache as well as the remote cache cache. -per_request_display_recipient_cache = {} # type: Dict[int, List[Dict[text_type, Any]]] +per_request_display_recipient_cache = {} # type: Dict[int, List[Dict[str, Any]]] def get_display_recipient_by_id(recipient_id, recipient_type, recipient_type_id): - ## type: (int, int, int) -> Union[text_type, List[Dict[text_type, Any]]] + # type: (int, int, int) -> Union[text_type, List[Dict[str, Any]]] if recipient_id not in per_request_display_recipient_cache: result = get_display_recipient_remote_cache(recipient_id, recipient_type, recipient_type_id) per_request_display_recipient_cache[recipient_id] = result return per_request_display_recipient_cache[recipient_id] def get_display_recipient(recipient): - ## type: (Recipient) -> Union[text_type, List[Dict[text_type, Any]]] + # type: (Recipient) -> Union[text_type, List[Dict[str, Any]]] return get_display_recipient_by_id( recipient.id, recipient.type, @@ -76,7 +76,7 @@ def flush_per_request_caches(): @cache_with_key(lambda *args: display_recipient_cache_key(args[0]), timeout=3600*24*7) def get_display_recipient_remote_cache(recipient_id, recipient_type, recipient_type_id): - ## type: (int, int, int) -> Union[text_type, List[Dict[text_type, Any]]] + # type: (int, int, int) -> Union[text_type, List[Dict[str, Any]]] """ returns: an appropriate object describing the recipient. For a stream this will be the stream name as a string. For a huddle or @@ -962,6 +962,7 @@ class Message(ModelReprMixin, models.Model): if recipient_type == Recipient.STREAM: display_type = "stream" elif recipient_type in (Recipient.HUDDLE, Recipient.PERSONAL): + assert not isinstance(display_recipient, text_type) display_type = "private" if len(display_recipient) == 1: # add the sender in if this isn't a message between diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 7d74b901f4..66293ac9b5 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Mapping, Optional, Sequence from zerver.lib import cache @@ -974,6 +974,10 @@ class SubscriptionAPITest(AuthedTestCase): self.assertEqual(msg.content, expected_msg) recipients = get_display_recipient(msg.recipient) self.assertEqual(len(recipients), 1) + assert isinstance(recipients, Sequence) + assert isinstance(recipients[0], Mapping) + # The 2 assert statements above are required to make the mypy check pass. + # They inform mypy that in the line below, recipients is a Sequence of Mappings. self.assertEqual(recipients[0]['email'], invitee) def test_multi_user_subscription(self):