mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	zerver/lib: Change use of typing.Text to str.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							5416d137d3
						
					
				
				
					commit
					a68376e2ba
				
			@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Set, Tuple, Text
 | 
					from typing import Any, Dict, Iterable, List, Mapping, Optional, Set, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.initial_password import initial_password
 | 
					from zerver.lib.initial_password import initial_password
 | 
				
			||||||
from zerver.models import Realm, Stream, UserProfile, Huddle, \
 | 
					from zerver.models import Realm, Stream, UserProfile, Huddle, \
 | 
				
			||||||
@@ -6,11 +6,11 @@ from zerver.models import Realm, Stream, UserProfile, Huddle, \
 | 
				
			|||||||
from zerver.lib.create_user import create_user_profile
 | 
					from zerver.lib.create_user import create_user_profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def bulk_create_users(realm: Realm,
 | 
					def bulk_create_users(realm: Realm,
 | 
				
			||||||
                      users_raw: Set[Tuple[Text, Text, Text, bool]],
 | 
					                      users_raw: Set[Tuple[str, str, str, bool]],
 | 
				
			||||||
                      bot_type: Optional[int]=None,
 | 
					                      bot_type: Optional[int]=None,
 | 
				
			||||||
                      bot_owner: Optional[UserProfile]=None,
 | 
					                      bot_owner: Optional[UserProfile]=None,
 | 
				
			||||||
                      tos_version: Optional[Text]=None,
 | 
					                      tos_version: Optional[str]=None,
 | 
				
			||||||
                      timezone: Text="") -> None:
 | 
					                      timezone: str="") -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Creates and saves a UserProfile with the given email.
 | 
					    Creates and saves a UserProfile with the given email.
 | 
				
			||||||
    Has some code based off of UserManage.create_user, but doesn't .save()
 | 
					    Has some code based off of UserManage.create_user, but doesn't .save()
 | 
				
			||||||
@@ -35,7 +35,7 @@ def bulk_create_users(realm: Realm,
 | 
				
			|||||||
                       event_type='user_created', event_time=profile_.date_joined)
 | 
					                       event_type='user_created', event_time=profile_.date_joined)
 | 
				
			||||||
         for profile_ in profiles_to_create])
 | 
					         for profile_ in profiles_to_create])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    profiles_by_email = {}  # type: Dict[Text, UserProfile]
 | 
					    profiles_by_email = {}  # type: Dict[str, UserProfile]
 | 
				
			||||||
    profiles_by_id = {}  # type: Dict[int, UserProfile]
 | 
					    profiles_by_id = {}  # type: Dict[int, UserProfile]
 | 
				
			||||||
    for profile in UserProfile.objects.select_related().filter(realm=realm):
 | 
					    for profile in UserProfile.objects.select_related().filter(realm=realm):
 | 
				
			||||||
        profiles_by_email[profile.email] = profile
 | 
					        profiles_by_email[profile.email] = profile
 | 
				
			||||||
@@ -47,7 +47,7 @@ def bulk_create_users(realm: Realm,
 | 
				
			|||||||
                                              type=Recipient.PERSONAL))
 | 
					                                              type=Recipient.PERSONAL))
 | 
				
			||||||
    Recipient.objects.bulk_create(recipients_to_create)
 | 
					    Recipient.objects.bulk_create(recipients_to_create)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    recipients_by_email = {}  # type: Dict[Text, Recipient]
 | 
					    recipients_by_email = {}  # type: Dict[str, Recipient]
 | 
				
			||||||
    for recipient in recipients_to_create:
 | 
					    for recipient in recipients_to_create:
 | 
				
			||||||
        recipients_by_email[profiles_by_id[recipient.type_id].email] = recipient
 | 
					        recipients_by_email[profiles_by_id[recipient.type_id].email] = recipient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -60,7 +60,7 @@ def bulk_create_users(realm: Realm,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# This is only sed in populate_db, so doesn't realy need tests
 | 
					# This is only sed in populate_db, so doesn't realy need tests
 | 
				
			||||||
def bulk_create_streams(realm: Realm,
 | 
					def bulk_create_streams(realm: Realm,
 | 
				
			||||||
                        stream_dict: Dict[Text, Dict[Text, Any]]) -> None:  # nocoverage
 | 
					                        stream_dict: Dict[str, Dict[str, Any]]) -> None:  # nocoverage
 | 
				
			||||||
    existing_streams = frozenset([name.lower() for name in
 | 
					    existing_streams = frozenset([name.lower() for name in
 | 
				
			||||||
                                  Stream.objects.filter(realm=realm)
 | 
					                                  Stream.objects.filter(realm=realm)
 | 
				
			||||||
                                  .values_list('name', flat=True)])
 | 
					                                  .values_list('name', flat=True)])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ from django.conf import settings
 | 
				
			|||||||
from django.db.models import Q
 | 
					from django.db.models import Q
 | 
				
			||||||
from django.core.cache.backends.base import BaseCache
 | 
					from django.core.cache.backends.base import BaseCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import cast, Any, Callable, Dict, Iterable, List, Optional, Union, Set, TypeVar, Text, Tuple
 | 
					from typing import cast, Any, Callable, Dict, Iterable, List, Optional, Union, Set, TypeVar, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.utils import statsd, statsd_key, make_safe_digest
 | 
					from zerver.lib.utils import statsd, statsd_key, make_safe_digest
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
@@ -51,7 +51,7 @@ def remote_cache_stats_finish() -> None:
 | 
				
			|||||||
    remote_cache_total_requests += 1
 | 
					    remote_cache_total_requests += 1
 | 
				
			||||||
    remote_cache_total_time += (time.time() - remote_cache_time_start)
 | 
					    remote_cache_total_time += (time.time() - remote_cache_time_start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_or_create_key_prefix() -> Text:
 | 
					def get_or_create_key_prefix() -> str:
 | 
				
			||||||
    if settings.CASPER_TESTS:
 | 
					    if settings.CASPER_TESTS:
 | 
				
			||||||
        # This sets the prefix for the benefit of the Casper tests.
 | 
					        # This sets the prefix for the benefit of the Casper tests.
 | 
				
			||||||
        #
 | 
					        #
 | 
				
			||||||
@@ -70,7 +70,7 @@ def get_or_create_key_prefix() -> Text:
 | 
				
			|||||||
    filename = os.path.join(settings.DEPLOY_ROOT, "var", "remote_cache_prefix")
 | 
					    filename = os.path.join(settings.DEPLOY_ROOT, "var", "remote_cache_prefix")
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o444)
 | 
					        fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o444)
 | 
				
			||||||
        random_hash = hashlib.sha256(Text(random.getrandbits(256)).encode('utf-8')).digest()
 | 
					        random_hash = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest()
 | 
				
			||||||
        prefix = base64.b16encode(random_hash)[:32].decode('utf-8').lower() + ':'
 | 
					        prefix = base64.b16encode(random_hash)[:32].decode('utf-8').lower() + ':'
 | 
				
			||||||
        # This does close the underlying file
 | 
					        # This does close the underlying file
 | 
				
			||||||
        with os.fdopen(fd, 'w') as f:
 | 
					        with os.fdopen(fd, 'w') as f:
 | 
				
			||||||
@@ -93,11 +93,11 @@ def get_or_create_key_prefix() -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return prefix
 | 
					    return prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
KEY_PREFIX = get_or_create_key_prefix()  # type: Text
 | 
					KEY_PREFIX = get_or_create_key_prefix()  # type: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def bounce_key_prefix_for_testing(test_name: Text) -> None:
 | 
					def bounce_key_prefix_for_testing(test_name: str) -> None:
 | 
				
			||||||
    global KEY_PREFIX
 | 
					    global KEY_PREFIX
 | 
				
			||||||
    KEY_PREFIX = test_name + ':' + Text(os.getpid()) + ':'
 | 
					    KEY_PREFIX = test_name + ':' + str(os.getpid()) + ':'
 | 
				
			||||||
    # We are taking the hash of the KEY_PREFIX to decrease the size of the key.
 | 
					    # We are taking the hash of the KEY_PREFIX to decrease the size of the key.
 | 
				
			||||||
    # Memcached keys should have a length of less than 256.
 | 
					    # Memcached keys should have a length of less than 256.
 | 
				
			||||||
    KEY_PREFIX = hashlib.sha1(KEY_PREFIX.encode('utf-8')).hexdigest()
 | 
					    KEY_PREFIX = hashlib.sha1(KEY_PREFIX.encode('utf-8')).hexdigest()
 | 
				
			||||||
@@ -108,7 +108,7 @@ def get_cache_backend(cache_name: Optional[str]) -> BaseCache:
 | 
				
			|||||||
    return caches[cache_name]
 | 
					    return caches[cache_name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_cache_with_key(
 | 
					def get_cache_with_key(
 | 
				
			||||||
        keyfunc: Callable[..., Text],
 | 
					        keyfunc: Callable[..., str],
 | 
				
			||||||
        cache_name: Optional[str]=None
 | 
					        cache_name: Optional[str]=None
 | 
				
			||||||
) -> Callable[[Callable[..., ReturnT]], Callable[..., ReturnT]]:
 | 
					) -> Callable[[Callable[..., ReturnT]], Callable[..., ReturnT]]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -130,7 +130,7 @@ def get_cache_with_key(
 | 
				
			|||||||
    return decorator
 | 
					    return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_with_key(
 | 
					def cache_with_key(
 | 
				
			||||||
        keyfunc: Callable[..., Text], cache_name: Optional[str]=None,
 | 
					        keyfunc: Callable[..., str], cache_name: Optional[str]=None,
 | 
				
			||||||
        timeout: Optional[int]=None, with_statsd_key: Optional[str]=None
 | 
					        timeout: Optional[int]=None, with_statsd_key: Optional[str]=None
 | 
				
			||||||
) -> Callable[[Callable[..., ReturnT]], Callable[..., ReturnT]]:
 | 
					) -> Callable[[Callable[..., ReturnT]], Callable[..., ReturnT]]:
 | 
				
			||||||
    """Decorator which applies Django caching to a function.
 | 
					    """Decorator which applies Django caching to a function.
 | 
				
			||||||
@@ -174,27 +174,27 @@ def cache_with_key(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return decorator
 | 
					    return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_set(key: Text, val: Any, cache_name: Optional[str]=None, timeout: Optional[int]=None) -> None:
 | 
					def cache_set(key: str, val: Any, cache_name: Optional[str]=None, timeout: Optional[int]=None) -> None:
 | 
				
			||||||
    remote_cache_stats_start()
 | 
					    remote_cache_stats_start()
 | 
				
			||||||
    cache_backend = get_cache_backend(cache_name)
 | 
					    cache_backend = get_cache_backend(cache_name)
 | 
				
			||||||
    cache_backend.set(KEY_PREFIX + key, (val,), timeout=timeout)
 | 
					    cache_backend.set(KEY_PREFIX + key, (val,), timeout=timeout)
 | 
				
			||||||
    remote_cache_stats_finish()
 | 
					    remote_cache_stats_finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_get(key: Text, cache_name: Optional[str]=None) -> Any:
 | 
					def cache_get(key: str, cache_name: Optional[str]=None) -> Any:
 | 
				
			||||||
    remote_cache_stats_start()
 | 
					    remote_cache_stats_start()
 | 
				
			||||||
    cache_backend = get_cache_backend(cache_name)
 | 
					    cache_backend = get_cache_backend(cache_name)
 | 
				
			||||||
    ret = cache_backend.get(KEY_PREFIX + key)
 | 
					    ret = cache_backend.get(KEY_PREFIX + key)
 | 
				
			||||||
    remote_cache_stats_finish()
 | 
					    remote_cache_stats_finish()
 | 
				
			||||||
    return ret
 | 
					    return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_get_many(keys: List[Text], cache_name: Optional[str]=None) -> Dict[Text, Any]:
 | 
					def cache_get_many(keys: List[str], cache_name: Optional[str]=None) -> Dict[str, Any]:
 | 
				
			||||||
    keys = [KEY_PREFIX + key for key in keys]
 | 
					    keys = [KEY_PREFIX + key for key in keys]
 | 
				
			||||||
    remote_cache_stats_start()
 | 
					    remote_cache_stats_start()
 | 
				
			||||||
    ret = get_cache_backend(cache_name).get_many(keys)
 | 
					    ret = get_cache_backend(cache_name).get_many(keys)
 | 
				
			||||||
    remote_cache_stats_finish()
 | 
					    remote_cache_stats_finish()
 | 
				
			||||||
    return dict([(key[len(KEY_PREFIX):], value) for key, value in ret.items()])
 | 
					    return dict([(key[len(KEY_PREFIX):], value) for key, value in ret.items()])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_set_many(items: Dict[Text, Any], cache_name: Optional[str]=None,
 | 
					def cache_set_many(items: Dict[str, Any], cache_name: Optional[str]=None,
 | 
				
			||||||
                   timeout: Optional[int]=None) -> None:
 | 
					                   timeout: Optional[int]=None) -> None:
 | 
				
			||||||
    new_items = {}
 | 
					    new_items = {}
 | 
				
			||||||
    for key in items:
 | 
					    for key in items:
 | 
				
			||||||
@@ -204,12 +204,12 @@ def cache_set_many(items: Dict[Text, Any], cache_name: Optional[str]=None,
 | 
				
			|||||||
    get_cache_backend(cache_name).set_many(items, timeout=timeout)
 | 
					    get_cache_backend(cache_name).set_many(items, timeout=timeout)
 | 
				
			||||||
    remote_cache_stats_finish()
 | 
					    remote_cache_stats_finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_delete(key: Text, cache_name: Optional[str]=None) -> None:
 | 
					def cache_delete(key: str, cache_name: Optional[str]=None) -> None:
 | 
				
			||||||
    remote_cache_stats_start()
 | 
					    remote_cache_stats_start()
 | 
				
			||||||
    get_cache_backend(cache_name).delete(KEY_PREFIX + key)
 | 
					    get_cache_backend(cache_name).delete(KEY_PREFIX + key)
 | 
				
			||||||
    remote_cache_stats_finish()
 | 
					    remote_cache_stats_finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_delete_many(items: Iterable[Text], cache_name: Optional[str]=None) -> None:
 | 
					def cache_delete_many(items: Iterable[str], cache_name: Optional[str]=None) -> None:
 | 
				
			||||||
    remote_cache_stats_start()
 | 
					    remote_cache_stats_start()
 | 
				
			||||||
    get_cache_backend(cache_name).delete_many(
 | 
					    get_cache_backend(cache_name).delete_many(
 | 
				
			||||||
        KEY_PREFIX + item for item in items)
 | 
					        KEY_PREFIX + item for item in items)
 | 
				
			||||||
@@ -247,7 +247,7 @@ def default_cache_transformer(obj: ItemT) -> ItemT:
 | 
				
			|||||||
#   value for cache (in case the values that we're caching are some
 | 
					#   value for cache (in case the values that we're caching are some
 | 
				
			||||||
#   function of the objects, not the objects themselves)
 | 
					#   function of the objects, not the objects themselves)
 | 
				
			||||||
def generic_bulk_cached_fetch(
 | 
					def generic_bulk_cached_fetch(
 | 
				
			||||||
        cache_key_function: Callable[[ObjKT], Text],
 | 
					        cache_key_function: Callable[[ObjKT], str],
 | 
				
			||||||
        query_function: Callable[[List[ObjKT]], Iterable[Any]],
 | 
					        query_function: Callable[[List[ObjKT]], Iterable[Any]],
 | 
				
			||||||
        object_ids: Iterable[ObjKT],
 | 
					        object_ids: Iterable[ObjKT],
 | 
				
			||||||
        extractor: Callable[[CompressedItemT], ItemT] = default_extractor,
 | 
					        extractor: Callable[[CompressedItemT], ItemT] = default_extractor,
 | 
				
			||||||
@@ -255,19 +255,19 @@ def generic_bulk_cached_fetch(
 | 
				
			|||||||
        id_fetcher: Callable[[ItemT], ObjKT] = default_id_fetcher,
 | 
					        id_fetcher: Callable[[ItemT], ObjKT] = default_id_fetcher,
 | 
				
			||||||
        cache_transformer: Callable[[ItemT], ItemT] = default_cache_transformer
 | 
					        cache_transformer: Callable[[ItemT], ItemT] = default_cache_transformer
 | 
				
			||||||
) -> Dict[ObjKT, ItemT]:
 | 
					) -> Dict[ObjKT, ItemT]:
 | 
				
			||||||
    cache_keys = {}  # type: Dict[ObjKT, Text]
 | 
					    cache_keys = {}  # type: Dict[ObjKT, str]
 | 
				
			||||||
    for object_id in object_ids:
 | 
					    for object_id in object_ids:
 | 
				
			||||||
        cache_keys[object_id] = cache_key_function(object_id)
 | 
					        cache_keys[object_id] = cache_key_function(object_id)
 | 
				
			||||||
    cached_objects_compressed = cache_get_many([cache_keys[object_id]
 | 
					    cached_objects_compressed = cache_get_many([cache_keys[object_id]
 | 
				
			||||||
                                                for object_id in object_ids])  # type: Dict[Text, Tuple[CompressedItemT]]
 | 
					                                                for object_id in object_ids])  # type: Dict[str, Tuple[CompressedItemT]]
 | 
				
			||||||
    cached_objects = {}  # type: Dict[Text, ItemT]
 | 
					    cached_objects = {}  # type: Dict[str, ItemT]
 | 
				
			||||||
    for (key, val) in cached_objects_compressed.items():
 | 
					    for (key, val) in cached_objects_compressed.items():
 | 
				
			||||||
        cached_objects[key] = extractor(cached_objects_compressed[key][0])
 | 
					        cached_objects[key] = extractor(cached_objects_compressed[key][0])
 | 
				
			||||||
    needed_ids = [object_id for object_id in object_ids if
 | 
					    needed_ids = [object_id for object_id in object_ids if
 | 
				
			||||||
                  cache_keys[object_id] not in cached_objects]
 | 
					                  cache_keys[object_id] not in cached_objects]
 | 
				
			||||||
    db_objects = query_function(needed_ids)
 | 
					    db_objects = query_function(needed_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    items_for_remote_cache = {}  # type: Dict[Text, Tuple[CompressedItemT]]
 | 
					    items_for_remote_cache = {}  # type: Dict[str, Tuple[CompressedItemT]]
 | 
				
			||||||
    for obj in db_objects:
 | 
					    for obj in db_objects:
 | 
				
			||||||
        key = cache_keys[id_fetcher(obj)]
 | 
					        key = cache_keys[id_fetcher(obj)]
 | 
				
			||||||
        item = cache_transformer(obj)
 | 
					        item = cache_transformer(obj)
 | 
				
			||||||
@@ -294,28 +294,28 @@ def cache(func: Callable[..., ReturnT]) -> Callable[..., ReturnT]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return cache_with_key(keyfunc)(func)
 | 
					    return cache_with_key(keyfunc)(func)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def display_recipient_cache_key(recipient_id: int) -> Text:
 | 
					def display_recipient_cache_key(recipient_id: int) -> str:
 | 
				
			||||||
    return "display_recipient_dict:%d" % (recipient_id,)
 | 
					    return "display_recipient_dict:%d" % (recipient_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profile_by_email_cache_key(email: Text) -> Text:
 | 
					def user_profile_by_email_cache_key(email: str) -> str:
 | 
				
			||||||
    # See the comment in zerver/lib/avatar_hash.py:gravatar_hash for why we
 | 
					    # See the comment in zerver/lib/avatar_hash.py:gravatar_hash for why we
 | 
				
			||||||
    # are proactively encoding email addresses even though they will
 | 
					    # are proactively encoding email addresses even though they will
 | 
				
			||||||
    # with high likelihood be ASCII-only for the foreseeable future.
 | 
					    # with high likelihood be ASCII-only for the foreseeable future.
 | 
				
			||||||
    return 'user_profile_by_email:%s' % (make_safe_digest(email.strip()),)
 | 
					    return 'user_profile_by_email:%s' % (make_safe_digest(email.strip()),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profile_cache_key_id(email: Text, realm_id: int) -> Text:
 | 
					def user_profile_cache_key_id(email: str, realm_id: int) -> str:
 | 
				
			||||||
    return u"user_profile:%s:%s" % (make_safe_digest(email.strip()), realm_id,)
 | 
					    return u"user_profile:%s:%s" % (make_safe_digest(email.strip()), realm_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profile_cache_key(email: Text, realm: 'Realm') -> Text:
 | 
					def user_profile_cache_key(email: str, realm: 'Realm') -> str:
 | 
				
			||||||
    return user_profile_cache_key_id(email, realm.id)
 | 
					    return user_profile_cache_key_id(email, realm.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def bot_profile_cache_key(email: Text) -> Text:
 | 
					def bot_profile_cache_key(email: str) -> str:
 | 
				
			||||||
    return "bot_profile:%s" % (make_safe_digest(email.strip()))
 | 
					    return "bot_profile:%s" % (make_safe_digest(email.strip()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profile_by_id_cache_key(user_profile_id: int) -> Text:
 | 
					def user_profile_by_id_cache_key(user_profile_id: int) -> str:
 | 
				
			||||||
    return "user_profile_by_id:%s" % (user_profile_id,)
 | 
					    return "user_profile_by_id:%s" % (user_profile_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profile_by_api_key_cache_key(api_key: Text) -> Text:
 | 
					def user_profile_by_api_key_cache_key(api_key: str) -> str:
 | 
				
			||||||
    return "user_profile_by_api_key:%s" % (api_key,)
 | 
					    return "user_profile_by_api_key:%s" % (api_key,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
realm_user_dict_fields = [
 | 
					realm_user_dict_fields = [
 | 
				
			||||||
@@ -323,10 +323,10 @@ realm_user_dict_fields = [
 | 
				
			|||||||
    'avatar_source', 'avatar_version', 'is_active',
 | 
					    'avatar_source', 'avatar_version', 'is_active',
 | 
				
			||||||
    'is_realm_admin', 'is_bot', 'realm_id', 'timezone']  # type: List[str]
 | 
					    'is_realm_admin', 'is_bot', 'realm_id', 'timezone']  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def realm_user_dicts_cache_key(realm_id: int) -> Text:
 | 
					def realm_user_dicts_cache_key(realm_id: int) -> str:
 | 
				
			||||||
    return "realm_user_dicts:%s" % (realm_id,)
 | 
					    return "realm_user_dicts:%s" % (realm_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def active_user_ids_cache_key(realm_id: int) -> Text:
 | 
					def active_user_ids_cache_key(realm_id: int) -> str:
 | 
				
			||||||
    return "active_user_ids:%s" % (realm_id,)
 | 
					    return "active_user_ids:%s" % (realm_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bot_dict_fields = ['id', 'full_name', 'short_name', 'bot_type', 'email',
 | 
					bot_dict_fields = ['id', 'full_name', 'short_name', 'bot_type', 'email',
 | 
				
			||||||
@@ -337,10 +337,10 @@ bot_dict_fields = ['id', 'full_name', 'short_name', 'bot_type', 'email',
 | 
				
			|||||||
                   'bot_owner__email', 'avatar_source',
 | 
					                   'bot_owner__email', 'avatar_source',
 | 
				
			||||||
                   'avatar_version']  # type: List[str]
 | 
					                   'avatar_version']  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def bot_dicts_in_realm_cache_key(realm: 'Realm') -> Text:
 | 
					def bot_dicts_in_realm_cache_key(realm: 'Realm') -> str:
 | 
				
			||||||
    return "bot_dicts_in_realm:%s" % (realm.id,)
 | 
					    return "bot_dicts_in_realm:%s" % (realm.id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_stream_cache_key(stream_name: Text, realm_id: int) -> Text:
 | 
					def get_stream_cache_key(stream_name: str, realm_id: int) -> str:
 | 
				
			||||||
    return "stream_by_realm_and_name:%s:%s" % (
 | 
					    return "stream_by_realm_and_name:%s:%s" % (
 | 
				
			||||||
        realm_id, make_safe_digest(stream_name.strip().lower()))
 | 
					        realm_id, make_safe_digest(stream_name.strip().lower()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -419,10 +419,10 @@ def flush_realm(sender: Any, **kwargs: Any) -> None:
 | 
				
			|||||||
        cache_delete(bot_dicts_in_realm_cache_key(realm))
 | 
					        cache_delete(bot_dicts_in_realm_cache_key(realm))
 | 
				
			||||||
        cache_delete(realm_alert_words_cache_key(realm))
 | 
					        cache_delete(realm_alert_words_cache_key(realm))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def realm_alert_words_cache_key(realm: 'Realm') -> Text:
 | 
					def realm_alert_words_cache_key(realm: 'Realm') -> str:
 | 
				
			||||||
    return "realm_alert_words:%s" % (realm.string_id,)
 | 
					    return "realm_alert_words:%s" % (realm.string_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def realm_first_visible_message_id_cache_key(realm: 'Realm') -> Text:
 | 
					def realm_first_visible_message_id_cache_key(realm: 'Realm') -> str:
 | 
				
			||||||
    return u"realm_first_visible_message_id:%s" % (realm.string_id,)
 | 
					    return u"realm_first_visible_message_id:%s" % (realm.string_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Called by models.py to flush the stream cache whenever we save a stream
 | 
					# Called by models.py to flush the stream cache whenever we save a stream
 | 
				
			||||||
@@ -440,10 +440,10 @@ def flush_stream(sender: Any, **kwargs: Any) -> None:
 | 
				
			|||||||
           Q(default_events_register_stream=stream)).exists():
 | 
					           Q(default_events_register_stream=stream)).exists():
 | 
				
			||||||
        cache_delete(bot_dicts_in_realm_cache_key(stream.realm))
 | 
					        cache_delete(bot_dicts_in_realm_cache_key(stream.realm))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_dict_cache_key_id(message_id: int) -> Text:
 | 
					def to_dict_cache_key_id(message_id: int) -> str:
 | 
				
			||||||
    return 'message_dict:%d' % (message_id,)
 | 
					    return 'message_dict:%d' % (message_id,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_dict_cache_key(message: 'Message') -> Text:
 | 
					def to_dict_cache_key(message: 'Message') -> str:
 | 
				
			||||||
    return to_dict_cache_key_id(message.id)
 | 
					    return to_dict_cache_key_id(message.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def flush_message(sender: Any, **kwargs: Any) -> None:
 | 
					def flush_message(sender: Any, **kwargs: Any) -> None:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import Any, Callable, Dict, List, Tuple, Text
 | 
					from typing import Any, Callable, Dict, List, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# This file needs to be different from cache.py because cache.py
 | 
					# This file needs to be different from cache.py because cache.py
 | 
				
			||||||
# cannot import anything from zerver.models or we'd have an import
 | 
					# cannot import anything from zerver.models or we'd have an import
 | 
				
			||||||
@@ -30,7 +30,7 @@ def message_fetch_objects() -> List[Any]:
 | 
				
			|||||||
    return Message.objects.select_related().filter(~Q(sender__email='tabbott/extra@mit.edu'),
 | 
					    return Message.objects.select_related().filter(~Q(sender__email='tabbott/extra@mit.edu'),
 | 
				
			||||||
                                                   id__gt=max_id - MESSAGE_CACHE_SIZE)
 | 
					                                                   id__gt=max_id - MESSAGE_CACHE_SIZE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def message_cache_items(items_for_remote_cache: Dict[Text, Tuple[bytes]],
 | 
					def message_cache_items(items_for_remote_cache: Dict[str, Tuple[bytes]],
 | 
				
			||||||
                        message: Message) -> None:
 | 
					                        message: Message) -> None:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Note: this code is untested, and the caller has been
 | 
					    Note: this code is untested, and the caller has been
 | 
				
			||||||
@@ -40,31 +40,31 @@ def message_cache_items(items_for_remote_cache: Dict[Text, Tuple[bytes]],
 | 
				
			|||||||
    value = MessageDict.to_dict_uncached(message)
 | 
					    value = MessageDict.to_dict_uncached(message)
 | 
				
			||||||
    items_for_remote_cache[key] = (value,)
 | 
					    items_for_remote_cache[key] = (value,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_cache_items(items_for_remote_cache: Dict[Text, Tuple[UserProfile]],
 | 
					def user_cache_items(items_for_remote_cache: Dict[str, Tuple[UserProfile]],
 | 
				
			||||||
                     user_profile: UserProfile) -> None:
 | 
					                     user_profile: UserProfile) -> None:
 | 
				
			||||||
    items_for_remote_cache[user_profile_by_email_cache_key(user_profile.email)] = (user_profile,)
 | 
					    items_for_remote_cache[user_profile_by_email_cache_key(user_profile.email)] = (user_profile,)
 | 
				
			||||||
    items_for_remote_cache[user_profile_by_id_cache_key(user_profile.id)] = (user_profile,)
 | 
					    items_for_remote_cache[user_profile_by_id_cache_key(user_profile.id)] = (user_profile,)
 | 
				
			||||||
    items_for_remote_cache[user_profile_by_api_key_cache_key(user_profile.api_key)] = (user_profile,)
 | 
					    items_for_remote_cache[user_profile_by_api_key_cache_key(user_profile.api_key)] = (user_profile,)
 | 
				
			||||||
    items_for_remote_cache[user_profile_cache_key(user_profile.email, user_profile.realm)] = (user_profile,)
 | 
					    items_for_remote_cache[user_profile_cache_key(user_profile.email, user_profile.realm)] = (user_profile,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def stream_cache_items(items_for_remote_cache: Dict[Text, Tuple[Stream]],
 | 
					def stream_cache_items(items_for_remote_cache: Dict[str, Tuple[Stream]],
 | 
				
			||||||
                       stream: Stream) -> None:
 | 
					                       stream: Stream) -> None:
 | 
				
			||||||
    items_for_remote_cache[get_stream_cache_key(stream.name, stream.realm_id)] = (stream,)
 | 
					    items_for_remote_cache[get_stream_cache_key(stream.name, stream.realm_id)] = (stream,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def client_cache_items(items_for_remote_cache: Dict[Text, Tuple[Client]],
 | 
					def client_cache_items(items_for_remote_cache: Dict[str, Tuple[Client]],
 | 
				
			||||||
                       client: Client) -> None:
 | 
					                       client: Client) -> None:
 | 
				
			||||||
    items_for_remote_cache[get_client_cache_key(client.name)] = (client,)
 | 
					    items_for_remote_cache[get_client_cache_key(client.name)] = (client,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def huddle_cache_items(items_for_remote_cache: Dict[Text, Tuple[Huddle]],
 | 
					def huddle_cache_items(items_for_remote_cache: Dict[str, Tuple[Huddle]],
 | 
				
			||||||
                       huddle: Huddle) -> None:
 | 
					                       huddle: Huddle) -> None:
 | 
				
			||||||
    items_for_remote_cache[huddle_hash_cache_key(huddle.huddle_hash)] = (huddle,)
 | 
					    items_for_remote_cache[huddle_hash_cache_key(huddle.huddle_hash)] = (huddle,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def recipient_cache_items(items_for_remote_cache: Dict[Text, Tuple[Recipient]],
 | 
					def recipient_cache_items(items_for_remote_cache: Dict[str, Tuple[Recipient]],
 | 
				
			||||||
                          recipient: Recipient) -> None:
 | 
					                          recipient: Recipient) -> None:
 | 
				
			||||||
    items_for_remote_cache[get_recipient_cache_key(recipient.type, recipient.type_id)] = (recipient,)
 | 
					    items_for_remote_cache[get_recipient_cache_key(recipient.type, recipient.type_id)] = (recipient,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
session_engine = import_module(settings.SESSION_ENGINE)
 | 
					session_engine = import_module(settings.SESSION_ENGINE)
 | 
				
			||||||
def session_cache_items(items_for_remote_cache: Dict[Text, Text],
 | 
					def session_cache_items(items_for_remote_cache: Dict[str, str],
 | 
				
			||||||
                        session: Session) -> None:
 | 
					                        session: Session) -> None:
 | 
				
			||||||
    store = session_engine.SessionStore(session_key=session.session_key)  # type: ignore # import_module
 | 
					    store = session_engine.SessionStore(session_key=session.session_key)  # type: ignore # import_module
 | 
				
			||||||
    items_for_remote_cache[store.cache_key] = store.decode(session.session_data)
 | 
					    items_for_remote_cache[store.cache_key] = store.decode(session.session_data)
 | 
				
			||||||
@@ -86,12 +86,12 @@ cache_fillers = {
 | 
				
			|||||||
    #    'message': (message_fetch_objects, message_cache_items, 3600 * 24, 1000),
 | 
					    #    'message': (message_fetch_objects, message_cache_items, 3600 * 24, 1000),
 | 
				
			||||||
    'huddle': (lambda: Huddle.objects.select_related().all(), huddle_cache_items, 3600*24*7, 10000),
 | 
					    'huddle': (lambda: Huddle.objects.select_related().all(), huddle_cache_items, 3600*24*7, 10000),
 | 
				
			||||||
    'session': (lambda: Session.objects.all(), session_cache_items, 3600*24*7, 10000),
 | 
					    'session': (lambda: Session.objects.all(), session_cache_items, 3600*24*7, 10000),
 | 
				
			||||||
}  # type: Dict[str, Tuple[Callable[[], List[Any]], Callable[[Dict[Text, Any], Any], None], int, int]]
 | 
					}  # type: Dict[str, Tuple[Callable[[], List[Any]], Callable[[Dict[str, Any], Any], None], int, int]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fill_remote_cache(cache: str) -> None:
 | 
					def fill_remote_cache(cache: str) -> None:
 | 
				
			||||||
    remote_cache_time_start = get_remote_cache_time()
 | 
					    remote_cache_time_start = get_remote_cache_time()
 | 
				
			||||||
    remote_cache_requests_start = get_remote_cache_requests()
 | 
					    remote_cache_requests_start = get_remote_cache_requests()
 | 
				
			||||||
    items_for_remote_cache = {}  # type: Dict[Text, Any]
 | 
					    items_for_remote_cache = {}  # type: Dict[str, Any]
 | 
				
			||||||
    (objects, items_filler, timeout, batch_size) = cache_fillers[cache]
 | 
					    (objects, items_filler, timeout, batch_size) = cache_fillers[cache]
 | 
				
			||||||
    count = 0
 | 
					    count = 0
 | 
				
			||||||
    for obj in objects():
 | 
					    for obj in objects():
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,9 @@ import ujson
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import string
 | 
					import string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Optional, Text
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def random_api_key() -> Text:
 | 
					def random_api_key() -> str:
 | 
				
			||||||
    choices = string.ascii_letters + string.digits
 | 
					    choices = string.ascii_letters + string.digits
 | 
				
			||||||
    altchars = ''.join([choices[ord(os.urandom(1)) % 62] for _ in range(2)]).encode("utf-8")
 | 
					    altchars = ''.join([choices[ord(os.urandom(1)) % 62] for _ in range(2)]).encode("utf-8")
 | 
				
			||||||
    return base64.b64encode(os.urandom(24), altchars=altchars).decode("utf-8")
 | 
					    return base64.b64encode(os.urandom(24), altchars=altchars).decode("utf-8")
 | 
				
			||||||
@@ -21,12 +21,12 @@ def random_api_key() -> Text:
 | 
				
			|||||||
# Only use this for bulk_create -- for normal usage one should use
 | 
					# Only use this for bulk_create -- for normal usage one should use
 | 
				
			||||||
# create_user (below) which will also make the Subscription and
 | 
					# create_user (below) which will also make the Subscription and
 | 
				
			||||||
# Recipient objects
 | 
					# Recipient objects
 | 
				
			||||||
def create_user_profile(realm: Realm, email: Text, password: Optional[Text],
 | 
					def create_user_profile(realm: Realm, email: str, password: Optional[str],
 | 
				
			||||||
                        active: bool, bot_type: Optional[int], full_name: Text,
 | 
					                        active: bool, bot_type: Optional[int], full_name: str,
 | 
				
			||||||
                        short_name: Text, bot_owner: Optional[UserProfile],
 | 
					                        short_name: str, bot_owner: Optional[UserProfile],
 | 
				
			||||||
                        is_mirror_dummy: bool, tos_version: Optional[Text],
 | 
					                        is_mirror_dummy: bool, tos_version: Optional[str],
 | 
				
			||||||
                        timezone: Optional[Text],
 | 
					                        timezone: Optional[str],
 | 
				
			||||||
                        tutorial_status: Optional[Text] = UserProfile.TUTORIAL_WAITING,
 | 
					                        tutorial_status: Optional[str] = UserProfile.TUTORIAL_WAITING,
 | 
				
			||||||
                        enter_sends: bool = False) -> UserProfile:
 | 
					                        enter_sends: bool = False) -> UserProfile:
 | 
				
			||||||
    now = timezone_now()
 | 
					    now = timezone_now()
 | 
				
			||||||
    email = UserManager.normalize_email(email)
 | 
					    email = UserManager.normalize_email(email)
 | 
				
			||||||
@@ -51,12 +51,12 @@ def create_user_profile(realm: Realm, email: Text, password: Optional[Text],
 | 
				
			|||||||
    user_profile.api_key = random_api_key()
 | 
					    user_profile.api_key = random_api_key()
 | 
				
			||||||
    return user_profile
 | 
					    return user_profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_user(email: Text, password: Optional[Text], realm: Realm,
 | 
					def create_user(email: str, password: Optional[str], realm: Realm,
 | 
				
			||||||
                full_name: Text, short_name: Text, active: bool = True,
 | 
					                full_name: str, short_name: str, active: bool = True,
 | 
				
			||||||
                is_realm_admin: bool = False, bot_type: Optional[int] = None,
 | 
					                is_realm_admin: bool = False, bot_type: Optional[int] = None,
 | 
				
			||||||
                bot_owner: Optional[UserProfile] = None,
 | 
					                bot_owner: Optional[UserProfile] = None,
 | 
				
			||||||
                tos_version: Optional[Text] = None, timezone: Text = "",
 | 
					                tos_version: Optional[str] = None, timezone: str = "",
 | 
				
			||||||
                avatar_source: Text = UserProfile.AVATAR_FROM_GRAVATAR,
 | 
					                avatar_source: str = UserProfile.AVATAR_FROM_GRAVATAR,
 | 
				
			||||||
                is_mirror_dummy: bool = False,
 | 
					                is_mirror_dummy: bool = False,
 | 
				
			||||||
                default_sending_stream: Optional[Stream] = None,
 | 
					                default_sending_stream: Optional[Stream] = None,
 | 
				
			||||||
                default_events_register_stream: Optional[Stream] = None,
 | 
					                default_events_register_stream: Optional[Stream] = None,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _
 | 
				
			|||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from importlib import import_module
 | 
					from importlib import import_module
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    cast, Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Text, Tuple, Union
 | 
					    cast, Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
session_engine = import_module(settings.SESSION_ENGINE)
 | 
					session_engine = import_module(settings.SESSION_ENGINE)
 | 
				
			||||||
@@ -50,7 +50,7 @@ from zproject.backends import email_auth_enabled, password_auth_enabled
 | 
				
			|||||||
from version import ZULIP_VERSION
 | 
					from version import ZULIP_VERSION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_raw_user_data(realm_id: int, client_gravatar: bool) -> Dict[int, Dict[str, Text]]:
 | 
					def get_raw_user_data(realm_id: int, client_gravatar: bool) -> Dict[int, Dict[str, str]]:
 | 
				
			||||||
    user_dicts = get_realm_user_dicts(realm_id)
 | 
					    user_dicts = get_realm_user_dicts(realm_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: Consider optimizing this query away with caching.
 | 
					    # TODO: Consider optimizing this query away with caching.
 | 
				
			||||||
@@ -476,7 +476,7 @@ def apply_event(state: Dict[str, Any],
 | 
				
			|||||||
                    event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i])
 | 
					                    event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i])
 | 
				
			||||||
                    del event['subscriptions'][i]['subscribers']
 | 
					                    del event['subscriptions'][i]['subscribers']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def name(sub: Dict[str, Any]) -> Text:
 | 
					        def name(sub: Dict[str, Any]) -> str:
 | 
				
			||||||
            return sub['name'].lower()
 | 
					            return sub['name'].lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if event['op'] == "add":
 | 
					        if event['op'] == "add":
 | 
				
			||||||
@@ -629,7 +629,7 @@ def do_events_register(user_profile: UserProfile, user_client: Client,
 | 
				
			|||||||
                       queue_lifespan_secs: int = 0,
 | 
					                       queue_lifespan_secs: int = 0,
 | 
				
			||||||
                       all_public_streams: bool = False,
 | 
					                       all_public_streams: bool = False,
 | 
				
			||||||
                       include_subscribers: bool = True,
 | 
					                       include_subscribers: bool = True,
 | 
				
			||||||
                       narrow: Iterable[Sequence[Text]] = [],
 | 
					                       narrow: Iterable[Sequence[str]] = [],
 | 
				
			||||||
                       fetch_event_types: Optional[Iterable[str]] = None) -> Dict[str, Any]:
 | 
					                       fetch_event_types: Optional[Iterable[str]] = None) -> Dict[str, Any]:
 | 
				
			||||||
    # Technically we don't need to check this here because
 | 
					    # Technically we don't need to check this here because
 | 
				
			||||||
    # build_narrow_filter will check it, but it's nicer from an error
 | 
					    # build_narrow_filter will check it, but it's nicer from an error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
from enum import Enum
 | 
					from enum import Enum
 | 
				
			||||||
from typing import Any, Dict, List, Optional, Text, Type
 | 
					from typing import Any, Dict, List, Optional, Type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.exceptions import PermissionDenied
 | 
					from django.core.exceptions import PermissionDenied
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
@@ -93,15 +93,15 @@ class JsonableError(Exception):
 | 
				
			|||||||
    # like 403 or 404.
 | 
					    # like 403 or 404.
 | 
				
			||||||
    http_status_code = 400  # type: int
 | 
					    http_status_code = 400  # type: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, msg: Text, code: Optional[ErrorCode]=None) -> None:
 | 
					    def __init__(self, msg: str, code: Optional[ErrorCode]=None) -> None:
 | 
				
			||||||
        if code is not None:
 | 
					        if code is not None:
 | 
				
			||||||
            self.code = code
 | 
					            self.code = code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # `_msg` is an implementation detail of `JsonableError` itself.
 | 
					        # `_msg` is an implementation detail of `JsonableError` itself.
 | 
				
			||||||
        self._msg = msg  # type: Text
 | 
					        self._msg = msg  # type: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def msg_format() -> Text:
 | 
					    def msg_format() -> str:
 | 
				
			||||||
        '''Override in subclasses.  Gets the items in `data_fields` as format args.
 | 
					        '''Override in subclasses.  Gets the items in `data_fields` as format args.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        This should return (a translation of) a string literal.
 | 
					        This should return (a translation of) a string literal.
 | 
				
			||||||
@@ -119,7 +119,7 @@ class JsonableError(Exception):
 | 
				
			|||||||
    #
 | 
					    #
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def msg(self) -> Text:
 | 
					    def msg(self) -> str:
 | 
				
			||||||
        format_data = dict(((f, getattr(self, f)) for f in self.data_fields),
 | 
					        format_data = dict(((f, getattr(self, f)) for f in self.data_fields),
 | 
				
			||||||
                           _msg=getattr(self, '_msg', None))
 | 
					                           _msg=getattr(self, '_msg', None))
 | 
				
			||||||
        return self.msg_format().format(**format_data)
 | 
					        return self.msg_format().format(**format_data)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ from zerver.models import UserProfile, Realm, Client, Huddle, Stream, \
 | 
				
			|||||||
    get_display_recipient, Attachment, get_system_bot
 | 
					    get_display_recipient, Attachment, get_system_bot
 | 
				
			||||||
from zerver.lib.parallel import run_parallel
 | 
					from zerver.lib.parallel import run_parallel
 | 
				
			||||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, \
 | 
					from typing import Any, Callable, Dict, List, Optional, Set, Tuple, \
 | 
				
			||||||
    Iterable, Text
 | 
					    Iterable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Custom mypy types follow:
 | 
					# Custom mypy types follow:
 | 
				
			||||||
Record = Dict[str, Any]
 | 
					Record = Dict[str, Any]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.mail import EmailMessage
 | 
					from django.core.mail import EmailMessage
 | 
				
			||||||
from typing import Any, Mapping, Optional, Text
 | 
					from typing import Any, Mapping, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.actions import internal_send_message
 | 
					from zerver.lib.actions import internal_send_message
 | 
				
			||||||
from zerver.lib.send_email import FromAddress
 | 
					from zerver.lib.send_email import FromAddress
 | 
				
			||||||
@@ -13,7 +13,7 @@ import time
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
client = get_redis_client()
 | 
					client = get_redis_client()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def has_enough_time_expired_since_last_message(sender_email: Text, min_delay: float) -> bool:
 | 
					def has_enough_time_expired_since_last_message(sender_email: str, min_delay: float) -> bool:
 | 
				
			||||||
    # This function returns a boolean, but it also has the side effect
 | 
					    # This function returns a boolean, but it also has the side effect
 | 
				
			||||||
    # of noting that a new message was received.
 | 
					    # of noting that a new message was received.
 | 
				
			||||||
    key = 'zilencer:feedback:%s' % (sender_email,)
 | 
					    key = 'zilencer:feedback:%s' % (sender_email,)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,12 @@ from django.utils.translation import ugettext as _
 | 
				
			|||||||
from django.utils.lru_cache import lru_cache
 | 
					from django.utils.lru_cache import lru_cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from itertools import zip_longest
 | 
					from itertools import zip_longest
 | 
				
			||||||
from typing import Any, List, Dict, Optional, Text
 | 
					from typing import Any, List, Dict, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def with_language(string: Text, language: Text) -> Text:
 | 
					def with_language(string: str, language: str) -> str:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    This is an expensive function. If you are using it in a loop, it will
 | 
					    This is an expensive function. If you are using it in a loop, it will
 | 
				
			||||||
    make your code slow.
 | 
					    make your code slow.
 | 
				
			||||||
@@ -30,7 +30,7 @@ def get_language_list() -> List[Dict[str, Any]]:
 | 
				
			|||||||
        languages = ujson.load(reader)
 | 
					        languages = ujson.load(reader)
 | 
				
			||||||
        return languages['name_map']
 | 
					        return languages['name_map']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_language_list_for_templates(default_language: Text) -> List[Dict[str, Dict[str, str]]]:
 | 
					def get_language_list_for_templates(default_language: str) -> List[Dict[str, Dict[str, str]]]:
 | 
				
			||||||
    language_list = [l for l in get_language_list()
 | 
					    language_list = [l for l in get_language_list()
 | 
				
			||||||
                     if 'percent_translated' not in l or
 | 
					                     if 'percent_translated' not in l or
 | 
				
			||||||
                        l['percent_translated'] >= 5.]
 | 
					                        l['percent_translated'] >= 5.]
 | 
				
			||||||
@@ -67,13 +67,13 @@ def get_language_list_for_templates(default_language: Text) -> List[Dict[str, Di
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return formatted_list
 | 
					    return formatted_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_language_name(code: str) -> Optional[Text]:
 | 
					def get_language_name(code: str) -> Optional[str]:
 | 
				
			||||||
    for lang in get_language_list():
 | 
					    for lang in get_language_list():
 | 
				
			||||||
        if code in (lang['code'], lang['locale']):
 | 
					        if code in (lang['code'], lang['locale']):
 | 
				
			||||||
            return lang['name']
 | 
					            return lang['name']
 | 
				
			||||||
    return None
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_available_language_codes() -> List[Text]:
 | 
					def get_available_language_codes() -> List[str]:
 | 
				
			||||||
    language_list = get_language_list()
 | 
					    language_list = get_language_list()
 | 
				
			||||||
    codes = [language['code'] for language in language_list]
 | 
					    codes = [language['code'] for language in language_list]
 | 
				
			||||||
    return codes
 | 
					    return codes
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ from django.conf import settings
 | 
				
			|||||||
from django.db import connection
 | 
					from django.db import connection
 | 
				
			||||||
from django.utils.timezone import utc as timezone_utc
 | 
					from django.utils.timezone import utc as timezone_utc
 | 
				
			||||||
from typing import Any, Dict, List, Optional, Set, Tuple, \
 | 
					from typing import Any, Dict, List, Optional, Set, Tuple, \
 | 
				
			||||||
    Iterable, Text
 | 
					    Iterable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.avatar_hash import user_avatar_path_from_ids
 | 
					from zerver.lib.avatar_hash import user_avatar_path_from_ids
 | 
				
			||||||
from zerver.lib.bulk_create import bulk_create_users
 | 
					from zerver.lib.bulk_create import bulk_create_users
 | 
				
			||||||
@@ -567,7 +567,7 @@ def do_import_system_bots(realm: Any) -> None:
 | 
				
			|||||||
    create_users(realm, names, bot_type=UserProfile.DEFAULT_BOT)
 | 
					    create_users(realm, names, bot_type=UserProfile.DEFAULT_BOT)
 | 
				
			||||||
    print("Finished importing system bots.")
 | 
					    print("Finished importing system bots.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_users(realm: Realm, name_list: Iterable[Tuple[Text, Text]],
 | 
					def create_users(realm: Realm, name_list: Iterable[Tuple[str, str]],
 | 
				
			||||||
                 bot_type: Optional[int]=None) -> None:
 | 
					                 bot_type: Optional[int]=None) -> None:
 | 
				
			||||||
    user_set = set()
 | 
					    user_set = set()
 | 
				
			||||||
    for full_name, email in name_list:
 | 
					    for full_name, email in name_list:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Dict, List, Optional, TypeVar, Any, Text
 | 
					from typing import Dict, List, Optional, TypeVar, Any
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.conf.urls import url
 | 
					from django.conf.urls import url
 | 
				
			||||||
from django.urls.resolvers import LocaleRegexProvider
 | 
					from django.urls.resolvers import LocaleRegexProvider
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ from argparse import ArgumentParser
 | 
				
			|||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.exceptions import MultipleObjectsReturned
 | 
					from django.core.exceptions import MultipleObjectsReturned
 | 
				
			||||||
from django.core.management.base import BaseCommand, CommandError
 | 
					from django.core.management.base import BaseCommand, CommandError
 | 
				
			||||||
from typing import Any, Dict, Optional, Text, List
 | 
					from typing import Any, Dict, Optional, List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.models import Realm, UserProfile
 | 
					from zerver.models import Realm, UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,7 +107,7 @@ You can use the command list_realms to find ID of the realms in this server."""
 | 
				
			|||||||
            user_profiles.append(self.get_user(email, realm))
 | 
					            user_profiles.append(self.get_user(email, realm))
 | 
				
			||||||
        return user_profiles
 | 
					        return user_profiles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_user(self, email: Text, realm: Optional[Realm]) -> UserProfile:
 | 
					    def get_user(self, email: str, realm: Optional[Realm]) -> UserProfile:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If a realm is specified, try to find the user there, and
 | 
					        # If a realm is specified, try to find the user there, and
 | 
				
			||||||
        # throw an error if they don't exist.
 | 
					        # throw an error if they don't exist.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import Optional, Set, Text
 | 
					from typing import Optional, Set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,10 +10,10 @@ user_group_mentions = r'(?<![^\s\'\"\(,:<])@(\*[^\*]+\*)'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
wildcards = ['all', 'everyone', 'stream']
 | 
					wildcards = ['all', 'everyone', 'stream']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_mention_matches_wildcard(mention: Text) -> bool:
 | 
					def user_mention_matches_wildcard(mention: str) -> bool:
 | 
				
			||||||
    return mention in wildcards
 | 
					    return mention in wildcards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_name(s: Text) -> Optional[Text]:
 | 
					def extract_name(s: str) -> Optional[str]:
 | 
				
			||||||
    if s.startswith("**") and s.endswith("**"):
 | 
					    if s.startswith("**") and s.endswith("**"):
 | 
				
			||||||
        name = s[2:-2]
 | 
					        name = s[2:-2]
 | 
				
			||||||
        if name in wildcards:
 | 
					        if name in wildcards:
 | 
				
			||||||
@@ -23,15 +23,15 @@ def extract_name(s: Text) -> Optional[Text]:
 | 
				
			|||||||
    # We don't care about @all, @everyone or @stream
 | 
					    # We don't care about @all, @everyone or @stream
 | 
				
			||||||
    return None
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def possible_mentions(content: Text) -> Set[Text]:
 | 
					def possible_mentions(content: str) -> Set[str]:
 | 
				
			||||||
    matches = re.findall(find_mentions, content)
 | 
					    matches = re.findall(find_mentions, content)
 | 
				
			||||||
    names_with_none = (extract_name(match) for match in matches)
 | 
					    names_with_none = (extract_name(match) for match in matches)
 | 
				
			||||||
    names = {name for name in names_with_none if name}
 | 
					    names = {name for name in names_with_none if name}
 | 
				
			||||||
    return names
 | 
					    return names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_user_group(matched_text: Text) -> Text:
 | 
					def extract_user_group(matched_text: str) -> str:
 | 
				
			||||||
    return matched_text[1:-1]
 | 
					    return matched_text[1:-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def possible_user_group_mentions(content: Text) -> Set[Text]:
 | 
					def possible_user_group_mentions(content: str) -> Set[str]:
 | 
				
			||||||
    matches = re.findall(user_group_mentions, content)
 | 
					    matches = re.findall(user_group_mentions, content)
 | 
				
			||||||
    return {extract_user_group(match) for match in matches}
 | 
					    return {extract_user_group(match) for match in matches}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,10 +43,10 @@ from zerver.models import (
 | 
				
			|||||||
    Reaction
 | 
					    Reaction
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, List, Optional, Set, Tuple, Text, Union
 | 
					from typing import Any, Dict, List, Optional, Set, Tuple, Union
 | 
				
			||||||
from mypy_extensions import TypedDict
 | 
					from mypy_extensions import TypedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RealmAlertWords = Dict[int, List[Text]]
 | 
					RealmAlertWords = Dict[int, List[str]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RawUnreadMessagesResult = TypedDict('RawUnreadMessagesResult', {
 | 
					RawUnreadMessagesResult = TypedDict('RawUnreadMessagesResult', {
 | 
				
			||||||
    'pm_dict': Dict[int, Any],
 | 
					    'pm_dict': Dict[int, Any],
 | 
				
			||||||
@@ -69,7 +69,7 @@ MAX_UNREAD_MESSAGES = 5000
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def messages_for_ids(message_ids: List[int],
 | 
					def messages_for_ids(message_ids: List[int],
 | 
				
			||||||
                     user_message_flags: Dict[int, List[str]],
 | 
					                     user_message_flags: Dict[int, List[str]],
 | 
				
			||||||
                     search_fields: Dict[int, Dict[str, Text]],
 | 
					                     search_fields: Dict[int, Dict[str, str]],
 | 
				
			||||||
                     apply_markdown: bool,
 | 
					                     apply_markdown: bool,
 | 
				
			||||||
                     client_gravatar: bool,
 | 
					                     client_gravatar: bool,
 | 
				
			||||||
                     allow_edit_history: bool) -> List[Dict[str, Any]]:
 | 
					                     allow_edit_history: bool) -> List[Dict[str, Any]]:
 | 
				
			||||||
@@ -267,15 +267,15 @@ class MessageDict:
 | 
				
			|||||||
            message: Optional[Message],
 | 
					            message: Optional[Message],
 | 
				
			||||||
            message_id: int,
 | 
					            message_id: int,
 | 
				
			||||||
            last_edit_time: Optional[datetime.datetime],
 | 
					            last_edit_time: Optional[datetime.datetime],
 | 
				
			||||||
            edit_history: Optional[Text],
 | 
					            edit_history: Optional[str],
 | 
				
			||||||
            content: Text,
 | 
					            content: str,
 | 
				
			||||||
            subject: Text,
 | 
					            subject: str,
 | 
				
			||||||
            pub_date: datetime.datetime,
 | 
					            pub_date: datetime.datetime,
 | 
				
			||||||
            rendered_content: Optional[Text],
 | 
					            rendered_content: Optional[str],
 | 
				
			||||||
            rendered_content_version: Optional[int],
 | 
					            rendered_content_version: Optional[int],
 | 
				
			||||||
            sender_id: int,
 | 
					            sender_id: int,
 | 
				
			||||||
            sender_realm_id: int,
 | 
					            sender_realm_id: int,
 | 
				
			||||||
            sending_client_name: Text,
 | 
					            sending_client_name: str,
 | 
				
			||||||
            recipient_id: int,
 | 
					            recipient_id: int,
 | 
				
			||||||
            recipient_type: int,
 | 
					            recipient_type: int,
 | 
				
			||||||
            recipient_type_id: int,
 | 
					            recipient_type_id: int,
 | 
				
			||||||
@@ -406,7 +406,7 @@ class MessageDict:
 | 
				
			|||||||
        if recipient_type == Recipient.STREAM:
 | 
					        if recipient_type == Recipient.STREAM:
 | 
				
			||||||
            display_type = "stream"
 | 
					            display_type = "stream"
 | 
				
			||||||
        elif recipient_type in (Recipient.HUDDLE, Recipient.PERSONAL):
 | 
					        elif recipient_type in (Recipient.HUDDLE, Recipient.PERSONAL):
 | 
				
			||||||
            assert not isinstance(display_recipient, Text)
 | 
					            assert not isinstance(display_recipient, str)
 | 
				
			||||||
            display_type = "private"
 | 
					            display_type = "private"
 | 
				
			||||||
            if len(display_recipient) == 1:
 | 
					            if len(display_recipient) == 1:
 | 
				
			||||||
                # add the sender in if this isn't a message between
 | 
					                # add the sender in if this isn't a message between
 | 
				
			||||||
@@ -507,12 +507,12 @@ def access_message(user_profile: UserProfile, message_id: int) -> Tuple[Message,
 | 
				
			|||||||
    return (message, user_message)
 | 
					    return (message, user_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def render_markdown(message: Message,
 | 
					def render_markdown(message: Message,
 | 
				
			||||||
                    content: Text,
 | 
					                    content: str,
 | 
				
			||||||
                    realm: Optional[Realm]=None,
 | 
					                    realm: Optional[Realm]=None,
 | 
				
			||||||
                    realm_alert_words: Optional[RealmAlertWords]=None,
 | 
					                    realm_alert_words: Optional[RealmAlertWords]=None,
 | 
				
			||||||
                    user_ids: Optional[Set[int]]=None,
 | 
					                    user_ids: Optional[Set[int]]=None,
 | 
				
			||||||
                    mention_data: Optional[bugdown.MentionData]=None,
 | 
					                    mention_data: Optional[bugdown.MentionData]=None,
 | 
				
			||||||
                    email_gateway: Optional[bool]=False) -> Text:
 | 
					                    email_gateway: Optional[bool]=False) -> str:
 | 
				
			||||||
    """Return HTML for given markdown. Bugdown may add properties to the
 | 
					    """Return HTML for given markdown. Bugdown may add properties to the
 | 
				
			||||||
    message object such as `mentions_user_ids`, `mentions_user_group_ids`, and
 | 
					    message object such as `mentions_user_ids`, `mentions_user_group_ids`, and
 | 
				
			||||||
    `mentions_wildcard`.  These are only on this Django object and are not
 | 
					    `mentions_wildcard`.  These are only on this Django object and are not
 | 
				
			||||||
@@ -533,7 +533,7 @@ def render_markdown(message: Message,
 | 
				
			|||||||
    if realm is None:
 | 
					    if realm is None:
 | 
				
			||||||
        realm = message.get_realm()
 | 
					        realm = message.get_realm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    possible_words = set()  # type: Set[Text]
 | 
					    possible_words = set()  # type: Set[str]
 | 
				
			||||||
    if realm_alert_words is not None:
 | 
					    if realm_alert_words is not None:
 | 
				
			||||||
        for user_id, words in realm_alert_words.items():
 | 
					        for user_id, words in realm_alert_words.items():
 | 
				
			||||||
            if user_id in message_user_ids:
 | 
					            if user_id in message_user_ids:
 | 
				
			||||||
@@ -566,10 +566,10 @@ def render_markdown(message: Message,
 | 
				
			|||||||
def huddle_users(recipient_id: int) -> str:
 | 
					def huddle_users(recipient_id: int) -> str:
 | 
				
			||||||
    display_recipient = get_display_recipient_by_id(recipient_id,
 | 
					    display_recipient = get_display_recipient_by_id(recipient_id,
 | 
				
			||||||
                                                    Recipient.HUDDLE,
 | 
					                                                    Recipient.HUDDLE,
 | 
				
			||||||
                                                    None)  # type: Union[Text, List[Dict[str, Any]]]
 | 
					                                                    None)  # type: Union[str, List[Dict[str, Any]]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Text is for streams.
 | 
					    # str is for streams.
 | 
				
			||||||
    assert not isinstance(display_recipient, Text)
 | 
					    assert not isinstance(display_recipient, str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user_ids = [obj['id'] for obj in display_recipient]  # type: List[int]
 | 
					    user_ids = [obj['id'] for obj in display_recipient]  # type: List[int]
 | 
				
			||||||
    user_ids = sorted(user_ids)
 | 
					    user_ids = sorted(user_ids)
 | 
				
			||||||
@@ -689,7 +689,7 @@ def get_raw_unread_data(user_profile: UserProfile) -> RawUnreadMessagesResult:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    topic_mute_checker = build_topic_mute_checker(user_profile)
 | 
					    topic_mute_checker = build_topic_mute_checker(user_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_row_muted(stream_id: int, recipient_id: int, topic: Text) -> bool:
 | 
					    def is_row_muted(stream_id: int, recipient_id: int, topic: str) -> bool:
 | 
				
			||||||
        if stream_id in muted_stream_ids:
 | 
					        if stream_id in muted_stream_ids:
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import cast, Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Text
 | 
					from typing import cast, Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from confirmation.models import Confirmation, create_confirmation_link
 | 
					from confirmation.models import Confirmation, create_confirmation_link
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -42,34 +42,34 @@ def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> st
 | 
				
			|||||||
                                    Confirmation.UNSUBSCRIBE,
 | 
					                                    Confirmation.UNSUBSCRIBE,
 | 
				
			||||||
                                    url_args = {'email_type': email_type})
 | 
					                                    url_args = {'email_type': email_type})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def hash_util_encode(string: Text) -> Text:
 | 
					def hash_util_encode(string: str) -> str:
 | 
				
			||||||
    # Do the same encoding operation as hash_util.encodeHashComponent on the
 | 
					    # Do the same encoding operation as hash_util.encodeHashComponent on the
 | 
				
			||||||
    # frontend.
 | 
					    # frontend.
 | 
				
			||||||
    # `safe` has a default value of "/", but we want those encoded, too.
 | 
					    # `safe` has a default value of "/", but we want those encoded, too.
 | 
				
			||||||
    return urllib.parse.quote(
 | 
					    return urllib.parse.quote(
 | 
				
			||||||
        string.encode("utf-8"), safe=b"").replace(".", "%2E").replace("%", ".")
 | 
					        string.encode("utf-8"), safe=b"").replace(".", "%2E").replace("%", ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def encode_stream(stream_id: int, stream_name: Text) -> Text:
 | 
					def encode_stream(stream_id: int, stream_name: str) -> str:
 | 
				
			||||||
    # We encode streams for urls as something like 99-Verona.
 | 
					    # We encode streams for urls as something like 99-Verona.
 | 
				
			||||||
    stream_name = stream_name.replace(' ', '-')
 | 
					    stream_name = stream_name.replace(' ', '-')
 | 
				
			||||||
    return str(stream_id) + '-' + hash_util_encode(stream_name)
 | 
					    return str(stream_id) + '-' + hash_util_encode(stream_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pm_narrow_url(realm: Realm, participants: List[Text]) -> Text:
 | 
					def pm_narrow_url(realm: Realm, participants: List[str]) -> str:
 | 
				
			||||||
    participants.sort()
 | 
					    participants.sort()
 | 
				
			||||||
    base_url = "%s/#narrow/pm-with/" % (realm.uri,)
 | 
					    base_url = "%s/#narrow/pm-with/" % (realm.uri,)
 | 
				
			||||||
    return base_url + hash_util_encode(",".join(participants))
 | 
					    return base_url + hash_util_encode(",".join(participants))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def stream_narrow_url(realm: Realm, stream: Stream) -> Text:
 | 
					def stream_narrow_url(realm: Realm, stream: Stream) -> str:
 | 
				
			||||||
    base_url = "%s/#narrow/stream/" % (realm.uri,)
 | 
					    base_url = "%s/#narrow/stream/" % (realm.uri,)
 | 
				
			||||||
    return base_url + encode_stream(stream.id, stream.name)
 | 
					    return base_url + encode_stream(stream.id, stream.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def topic_narrow_url(realm: Realm, stream: Stream, topic: Text) -> Text:
 | 
					def topic_narrow_url(realm: Realm, stream: Stream, topic: str) -> str:
 | 
				
			||||||
    base_url = "%s/#narrow/stream/" % (realm.uri,)
 | 
					    base_url = "%s/#narrow/stream/" % (realm.uri,)
 | 
				
			||||||
    return "%s%s/topic/%s" % (base_url,
 | 
					    return "%s%s/topic/%s" % (base_url,
 | 
				
			||||||
                              encode_stream(stream.id, stream.name),
 | 
					                              encode_stream(stream.id, stream.name),
 | 
				
			||||||
                              hash_util_encode(topic))
 | 
					                              hash_util_encode(topic))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def relative_to_full_url(base_url: Text, content: Text) -> Text:
 | 
					def relative_to_full_url(base_url: str, content: str) -> str:
 | 
				
			||||||
    # Convert relative URLs to absolute URLs.
 | 
					    # Convert relative URLs to absolute URLs.
 | 
				
			||||||
    fragment = lxml.html.fromstring(content)
 | 
					    fragment = lxml.html.fromstring(content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -114,7 +114,7 @@ def relative_to_full_url(base_url: Text, content: Text) -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return content
 | 
					    return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fix_emojis(content: Text, base_url: Text, emojiset: Text) -> Text:
 | 
					def fix_emojis(content: str, base_url: str, emojiset: str) -> str:
 | 
				
			||||||
    def make_emoji_img_elem(emoji_span_elem: Any) -> Dict[str, Any]:
 | 
					    def make_emoji_img_elem(emoji_span_elem: Any) -> Dict[str, Any]:
 | 
				
			||||||
        # Convert the emoji spans to img tags.
 | 
					        # Convert the emoji spans to img tags.
 | 
				
			||||||
        classes = emoji_span_elem.get('class')
 | 
					        classes = emoji_span_elem.get('class')
 | 
				
			||||||
@@ -157,19 +157,19 @@ def build_message_list(user_profile: UserProfile, messages: List[Message]) -> Li
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    messages_to_render = []  # type: List[Dict[str, Any]]
 | 
					    messages_to_render = []  # type: List[Dict[str, Any]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sender_string(message: Message) -> Text:
 | 
					    def sender_string(message: Message) -> str:
 | 
				
			||||||
        if message.recipient.type in (Recipient.STREAM, Recipient.HUDDLE):
 | 
					        if message.recipient.type in (Recipient.STREAM, Recipient.HUDDLE):
 | 
				
			||||||
            return message.sender.full_name
 | 
					            return message.sender.full_name
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return ''
 | 
					            return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def fix_plaintext_image_urls(content: Text) -> Text:
 | 
					    def fix_plaintext_image_urls(content: str) -> str:
 | 
				
			||||||
        # Replace image URLs in plaintext content of the form
 | 
					        # Replace image URLs in plaintext content of the form
 | 
				
			||||||
        #     [image name](image url)
 | 
					        #     [image name](image url)
 | 
				
			||||||
        # with a simple hyperlink.
 | 
					        # with a simple hyperlink.
 | 
				
			||||||
        return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content)
 | 
					        return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def build_message_payload(message: Message) -> Dict[str, Text]:
 | 
					    def build_message_payload(message: Message) -> Dict[str, str]:
 | 
				
			||||||
        plain = message.content
 | 
					        plain = message.content
 | 
				
			||||||
        plain = fix_plaintext_image_urls(plain)
 | 
					        plain = fix_plaintext_image_urls(plain)
 | 
				
			||||||
        # There's a small chance of colliding with non-Zulip URLs containing
 | 
					        # There's a small chance of colliding with non-Zulip URLs containing
 | 
				
			||||||
@@ -200,7 +200,7 @@ def build_message_list(user_profile: UserProfile, messages: List[Message]) -> Li
 | 
				
			|||||||
            header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header)
 | 
					            header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header)
 | 
				
			||||||
        elif message.recipient.type == Recipient.HUDDLE:
 | 
					        elif message.recipient.type == Recipient.HUDDLE:
 | 
				
			||||||
            disp_recipient = get_display_recipient(message.recipient)
 | 
					            disp_recipient = get_display_recipient(message.recipient)
 | 
				
			||||||
            assert not isinstance(disp_recipient, Text)
 | 
					            assert not isinstance(disp_recipient, str)
 | 
				
			||||||
            other_recipients = [r['full_name'] for r in disp_recipient
 | 
					            other_recipients = [r['full_name'] for r in disp_recipient
 | 
				
			||||||
                                if r['email'] != user_profile.email]
 | 
					                                if r['email'] != user_profile.email]
 | 
				
			||||||
            header = "You and %s" % (", ".join(other_recipients),)
 | 
					            header = "You and %s" % (", ".join(other_recipients),)
 | 
				
			||||||
@@ -332,7 +332,7 @@ def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
 | 
				
			|||||||
    if (missed_messages[0].recipient.type == Recipient.HUDDLE):
 | 
					    if (missed_messages[0].recipient.type == Recipient.HUDDLE):
 | 
				
			||||||
        display_recipient = get_display_recipient(missed_messages[0].recipient)
 | 
					        display_recipient = get_display_recipient(missed_messages[0].recipient)
 | 
				
			||||||
        # Make sure that this is a list of strings, not a string.
 | 
					        # Make sure that this is a list of strings, not a string.
 | 
				
			||||||
        assert not isinstance(display_recipient, Text)
 | 
					        assert not isinstance(display_recipient, str)
 | 
				
			||||||
        other_recipients = [r['full_name'] for r in display_recipient
 | 
					        other_recipients = [r['full_name'] for r in display_recipient
 | 
				
			||||||
                            if r['id'] != user_profile.id]
 | 
					                            if r['id'] != user_profile.id]
 | 
				
			||||||
        context.update({'group_pm': True})
 | 
					        context.update({'group_pm': True})
 | 
				
			||||||
@@ -375,7 +375,7 @@ def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
 | 
				
			|||||||
            'realm_str': user_profile.realm.name,
 | 
					            'realm_str': user_profile.realm.name,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from_name = "Zulip missed messages"  # type: Text
 | 
					    from_name = "Zulip missed messages"  # type: str
 | 
				
			||||||
    from_address = FromAddress.NOREPLY
 | 
					    from_address = FromAddress.NOREPLY
 | 
				
			||||||
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
 | 
					    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
 | 
				
			||||||
        # If this setting is enabled, you can reply to the Zulip
 | 
					        # If this setting is enabled, you can reply to the Zulip
 | 
				
			||||||
@@ -420,7 +420,7 @@ def handle_missedmessage_emails(user_profile_id: int,
 | 
				
			|||||||
    if not messages:
 | 
					    if not messages:
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    messages_by_recipient_subject = defaultdict(list)  # type: Dict[Tuple[int, Text], List[Message]]
 | 
					    messages_by_recipient_subject = defaultdict(list)  # type: Dict[Tuple[int, str], List[Message]]
 | 
				
			||||||
    for msg in messages:
 | 
					    for msg in messages:
 | 
				
			||||||
        if msg.recipient.type == Recipient.PERSONAL:
 | 
					        if msg.recipient.type == Recipient.PERSONAL:
 | 
				
			||||||
            # For PM's group using (recipient, sender).
 | 
					            # For PM's group using (recipient, sender).
 | 
				
			||||||
@@ -460,7 +460,7 @@ def clear_scheduled_emails(user_id: int, email_type: Optional[int]=None) -> None
 | 
				
			|||||||
        items = items.filter(type=email_type)
 | 
					        items = items.filter(type=email_type)
 | 
				
			||||||
    items.delete()
 | 
					    items.delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def log_digest_event(msg: Text) -> None:
 | 
					def log_digest_event(msg: str) -> None:
 | 
				
			||||||
    import logging
 | 
					    import logging
 | 
				
			||||||
    logging.basicConfig(filename=settings.DIGEST_LOG_PATH, level=logging.INFO)
 | 
					    logging.basicConfig(filename=settings.DIGEST_LOG_PATH, level=logging.INFO)
 | 
				
			||||||
    logging.info(msg)
 | 
					    logging.info(msg)
 | 
				
			||||||
@@ -512,7 +512,7 @@ def enqueue_welcome_emails(user: UserProfile) -> None:
 | 
				
			|||||||
        "zerver/emails/followup_day2", user.realm, to_user_id=user.id, from_name=from_name,
 | 
					        "zerver/emails/followup_day2", user.realm, to_user_id=user.id, from_name=from_name,
 | 
				
			||||||
        from_address=from_address, context=context, delay=followup_day2_email_delay(user))
 | 
					        from_address=from_address, context=context, delay=followup_day2_email_delay(user))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def convert_html_to_markdown(html: Text) -> Text:
 | 
					def convert_html_to_markdown(html: str) -> str:
 | 
				
			||||||
    # On Linux, the tool installs as html2markdown, and there's a command called
 | 
					    # On Linux, the tool installs as html2markdown, and there's a command called
 | 
				
			||||||
    # html2text that does something totally different. On OSX, the tool installs
 | 
					    # html2text that does something totally different. On OSX, the tool installs
 | 
				
			||||||
    # as html2text.
 | 
					    # as html2text.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ from zerver.lib.actions import set_default_streams, bulk_add_subscriptions, \
 | 
				
			|||||||
    do_add_reaction_legacy, create_users
 | 
					    do_add_reaction_legacy, create_users
 | 
				
			||||||
from zerver.models import Realm, UserProfile, Message, Reaction, get_system_bot
 | 
					from zerver.models import Realm, UserProfile, Message, Reaction, get_system_bot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, List, Mapping, Text
 | 
					from typing import Any, Dict, List, Mapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def setup_realm_internal_bots(realm: Realm) -> None:
 | 
					def setup_realm_internal_bots(realm: Realm) -> None:
 | 
				
			||||||
    """Create this realm's internal bots.
 | 
					    """Create this realm's internal bots.
 | 
				
			||||||
@@ -96,7 +96,7 @@ def send_initial_realm_messages(realm: Realm) -> None:
 | 
				
			|||||||
         'content': "This is a message in a second topic.\n\nTopics are similar to email subjects, "
 | 
					         'content': "This is a message in a second topic.\n\nTopics are similar to email subjects, "
 | 
				
			||||||
         "in that each conversation should get its own topic. Keep them short, though; one "
 | 
					         "in that each conversation should get its own topic. Keep them short, though; one "
 | 
				
			||||||
         "or two words will do it!"},
 | 
					         "or two words will do it!"},
 | 
				
			||||||
    ]  # type: List[Dict[str, Text]]
 | 
					    ]  # type: List[Dict[str, str]]
 | 
				
			||||||
    messages = [internal_prep_stream_message(
 | 
					    messages = [internal_prep_stream_message(
 | 
				
			||||||
        realm, welcome_bot,
 | 
					        realm, welcome_bot,
 | 
				
			||||||
        message['stream'], message['topic'], message['content']) for message in welcome_messages]
 | 
					        message['stream'], message['topic'], message['content']) for message in welcome_messages]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, AnyStr, Iterable, Dict, Tuple, Callable, Text, Mapping, Optional
 | 
					from typing import Any, AnyStr, Iterable, Dict, Tuple, Callable, Mapping, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
@@ -22,11 +22,11 @@ from zerver.decorator import JsonableError
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class OutgoingWebhookServiceInterface:
 | 
					class OutgoingWebhookServiceInterface:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, base_url: Text, token: Text, user_profile: UserProfile, service_name: Text) -> None:
 | 
					    def __init__(self, base_url: str, token: str, user_profile: UserProfile, service_name: str) -> None:
 | 
				
			||||||
        self.base_url = base_url  # type: Text
 | 
					        self.base_url = base_url  # type: str
 | 
				
			||||||
        self.token = token  # type: Text
 | 
					        self.token = token  # type: str
 | 
				
			||||||
        self.user_profile = user_profile  # type: Text
 | 
					        self.user_profile = user_profile  # type: str
 | 
				
			||||||
        self.service_name = service_name  # type: Text
 | 
					        self.service_name = service_name  # type: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Given an event that triggers an outgoing webhook operation, returns:
 | 
					    # Given an event that triggers an outgoing webhook operation, returns:
 | 
				
			||||||
    # - The REST operation that should be performed
 | 
					    # - The REST operation that should be performed
 | 
				
			||||||
@@ -37,7 +37,7 @@ class OutgoingWebhookServiceInterface:
 | 
				
			|||||||
    # - base_url
 | 
					    # - base_url
 | 
				
			||||||
    # - relative_url_path
 | 
					    # - relative_url_path
 | 
				
			||||||
    # - request_kwargs
 | 
					    # - request_kwargs
 | 
				
			||||||
    def process_event(self, event: Dict[Text, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
					    def process_event(self, event: Dict[str, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Given a successful outgoing webhook REST operation, returns two-element tuple
 | 
					    # Given a successful outgoing webhook REST operation, returns two-element tuple
 | 
				
			||||||
@@ -46,12 +46,12 @@ class OutgoingWebhookServiceInterface:
 | 
				
			|||||||
    # and right-hand value contains a failure message
 | 
					    # and right-hand value contains a failure message
 | 
				
			||||||
    # to sent back to the user (or None if no failure message should be sent)
 | 
					    # to sent back to the user (or None if no failure message should be sent)
 | 
				
			||||||
    def process_success(self, response: Response,
 | 
					    def process_success(self, response: Response,
 | 
				
			||||||
                        event: Dict[Text, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
					                        event: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
					class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_event(self, event: Dict[Text, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
					    def process_event(self, event: Dict[str, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
				
			||||||
        rest_operation = {'method': 'POST',
 | 
					        rest_operation = {'method': 'POST',
 | 
				
			||||||
                          'relative_url_path': '',
 | 
					                          'relative_url_path': '',
 | 
				
			||||||
                          'base_url': self.base_url,
 | 
					                          'base_url': self.base_url,
 | 
				
			||||||
@@ -62,7 +62,7 @@ class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			|||||||
        return rest_operation, json.dumps(request_data)
 | 
					        return rest_operation, json.dumps(request_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_success(self, response: Response,
 | 
					    def process_success(self, response: Response,
 | 
				
			||||||
                        event: Dict[Text, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
					                        event: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
				
			||||||
        response_json = json.loads(response.text)
 | 
					        response_json = json.loads(response.text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "response_not_required" in response_json and response_json['response_not_required']:
 | 
					        if "response_not_required" in response_json and response_json['response_not_required']:
 | 
				
			||||||
@@ -74,7 +74,7 @@ class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
					class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_event(self, event: Dict[Text, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
					    def process_event(self, event: Dict[str, Any]) -> Tuple[Dict[str, Any], Any]:
 | 
				
			||||||
        rest_operation = {'method': 'POST',
 | 
					        rest_operation = {'method': 'POST',
 | 
				
			||||||
                          'relative_url_path': '',
 | 
					                          'relative_url_path': '',
 | 
				
			||||||
                          'base_url': self.base_url,
 | 
					                          'base_url': self.base_url,
 | 
				
			||||||
@@ -100,7 +100,7 @@ class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			|||||||
        return rest_operation, request_data
 | 
					        return rest_operation, request_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_success(self, response: Response,
 | 
					    def process_success(self, response: Response,
 | 
				
			||||||
                        event: Dict[Text, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
					                        event: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
 | 
				
			||||||
        response_json = json.loads(response.text)
 | 
					        response_json = json.loads(response.text)
 | 
				
			||||||
        if "text" in response_json:
 | 
					        if "text" in response_json:
 | 
				
			||||||
            return response_json["text"], None
 | 
					            return response_json["text"], None
 | 
				
			||||||
@@ -110,9 +110,9 @@ class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface):
 | 
				
			|||||||
AVAILABLE_OUTGOING_WEBHOOK_INTERFACES = {
 | 
					AVAILABLE_OUTGOING_WEBHOOK_INTERFACES = {
 | 
				
			||||||
    GENERIC_INTERFACE: GenericOutgoingWebhookService,
 | 
					    GENERIC_INTERFACE: GenericOutgoingWebhookService,
 | 
				
			||||||
    SLACK_INTERFACE: SlackOutgoingWebhookService,
 | 
					    SLACK_INTERFACE: SlackOutgoingWebhookService,
 | 
				
			||||||
}   # type: Dict[Text, Any]
 | 
					}   # type: Dict[str, Any]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_service_interface_class(interface: Text) -> Any:
 | 
					def get_service_interface_class(interface: str) -> Any:
 | 
				
			||||||
    if interface is None or interface not in AVAILABLE_OUTGOING_WEBHOOK_INTERFACES:
 | 
					    if interface is None or interface not in AVAILABLE_OUTGOING_WEBHOOK_INTERFACES:
 | 
				
			||||||
        return AVAILABLE_OUTGOING_WEBHOOK_INTERFACES[GENERIC_INTERFACE]
 | 
					        return AVAILABLE_OUTGOING_WEBHOOK_INTERFACES[GENERIC_INTERFACE]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
@@ -127,7 +127,7 @@ def get_outgoing_webhook_service_handler(service: Service) -> Any:
 | 
				
			|||||||
                                                service_name=service.name)
 | 
					                                                service_name=service.name)
 | 
				
			||||||
    return service_interface
 | 
					    return service_interface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def send_response_message(bot_id: str, message: Dict[str, Any], response_message_content: Text) -> None:
 | 
					def send_response_message(bot_id: str, message: Dict[str, Any], response_message_content: str) -> None:
 | 
				
			||||||
    recipient_type_name = message['type']
 | 
					    recipient_type_name = message['type']
 | 
				
			||||||
    bot_user = get_user_profile_by_id(bot_id)
 | 
					    bot_user = get_user_profile_by_id(bot_id)
 | 
				
			||||||
    realm = bot_user.realm
 | 
					    realm = bot_user.realm
 | 
				
			||||||
@@ -143,15 +143,15 @@ def send_response_message(bot_id: str, message: Dict[str, Any], response_message
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise JsonableError(_("Invalid message type"))
 | 
					        raise JsonableError(_("Invalid message type"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def succeed_with_message(event: Dict[str, Any], success_message: Text) -> None:
 | 
					def succeed_with_message(event: Dict[str, Any], success_message: str) -> None:
 | 
				
			||||||
    success_message = "Success! " + success_message
 | 
					    success_message = "Success! " + success_message
 | 
				
			||||||
    send_response_message(event['user_profile_id'], event['message'], success_message)
 | 
					    send_response_message(event['user_profile_id'], event['message'], success_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fail_with_message(event: Dict[str, Any], failure_message: Text) -> None:
 | 
					def fail_with_message(event: Dict[str, Any], failure_message: str) -> None:
 | 
				
			||||||
    failure_message = "Failure! " + failure_message
 | 
					    failure_message = "Failure! " + failure_message
 | 
				
			||||||
    send_response_message(event['user_profile_id'], event['message'], failure_message)
 | 
					    send_response_message(event['user_profile_id'], event['message'], failure_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_message_url(event: Dict[str, Any], request_data: Dict[str, Any]) -> Text:
 | 
					def get_message_url(event: Dict[str, Any], request_data: Dict[str, Any]) -> str:
 | 
				
			||||||
    bot_user = get_user_profile_by_id(event['user_profile_id'])
 | 
					    bot_user = get_user_profile_by_id(event['user_profile_id'])
 | 
				
			||||||
    message = event['message']
 | 
					    message = event['message']
 | 
				
			||||||
    if message['type'] == 'stream':
 | 
					    if message['type'] == 'stream':
 | 
				
			||||||
@@ -194,7 +194,7 @@ def notify_bot_owner(event: Dict[str, Any],
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def request_retry(event: Dict[str, Any],
 | 
					def request_retry(event: Dict[str, Any],
 | 
				
			||||||
                  request_data: Dict[str, Any],
 | 
					                  request_data: Dict[str, Any],
 | 
				
			||||||
                  failure_message: Text,
 | 
					                  failure_message: str,
 | 
				
			||||||
                  exception: Optional[Exception]=None) -> None:
 | 
					                  exception: Optional[Exception]=None) -> None:
 | 
				
			||||||
    def failure_processor(event: Dict[str, Any]) -> None:
 | 
					    def failure_processor(event: Dict[str, Any]) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import re
 | 
				
			|||||||
import time
 | 
					import time
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, List, Optional, SupportsInt, Text, Tuple, Type, Union
 | 
					from typing import Any, Dict, List, Optional, SupportsInt, Tuple, Type, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from apns2.client import APNsClient
 | 
					from apns2.client import APNsClient
 | 
				
			||||||
from apns2.payload import Payload as APNsPayload
 | 
					from apns2.payload import Payload as APNsPayload
 | 
				
			||||||
@@ -44,10 +44,10 @@ else:  # nocoverage  -- Not convenient to add test for this.
 | 
				
			|||||||
DeviceToken = Union[PushDeviceToken, RemotePushDeviceToken]
 | 
					DeviceToken = Union[PushDeviceToken, RemotePushDeviceToken]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# We store the token as b64, but apns-client wants hex strings
 | 
					# We store the token as b64, but apns-client wants hex strings
 | 
				
			||||||
def b64_to_hex(data: bytes) -> Text:
 | 
					def b64_to_hex(data: bytes) -> str:
 | 
				
			||||||
    return binascii.hexlify(base64.b64decode(data)).decode('utf-8')
 | 
					    return binascii.hexlify(base64.b64decode(data)).decode('utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def hex_to_b64(data: Text) -> bytes:
 | 
					def hex_to_b64(data: str) -> bytes:
 | 
				
			||||||
    return base64.b64encode(binascii.unhexlify(data.encode('utf-8')))
 | 
					    return base64.b64encode(binascii.unhexlify(data.encode('utf-8')))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@@ -255,7 +255,7 @@ class PushNotificationBouncerException(Exception):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def send_to_push_bouncer(method: str,
 | 
					def send_to_push_bouncer(method: str,
 | 
				
			||||||
                         endpoint: str,
 | 
					                         endpoint: str,
 | 
				
			||||||
                         post_data: Union[Text, Dict[str, Any]],
 | 
					                         post_data: Union[str, Dict[str, Any]],
 | 
				
			||||||
                         extra_headers: Optional[Dict[str, Any]]=None) -> None:
 | 
					                         extra_headers: Optional[Dict[str, Any]]=None) -> None:
 | 
				
			||||||
    """While it does actually send the notice, this function has a lot of
 | 
					    """While it does actually send the notice, this function has a lot of
 | 
				
			||||||
    code and comments around error handling for the push notifications
 | 
					    code and comments around error handling for the push notifications
 | 
				
			||||||
@@ -413,7 +413,7 @@ def push_notifications_enabled() -> bool:
 | 
				
			|||||||
        return True
 | 
					        return True
 | 
				
			||||||
    return False
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_alert_from_message(message: Message) -> Text:
 | 
					def get_alert_from_message(message: Message) -> str:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Determine what alert string to display based on the missed messages.
 | 
					    Determine what alert string to display based on the missed messages.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -430,8 +430,8 @@ def get_alert_from_message(message: Message) -> Text:
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return "New Zulip mentions and private messages from %s" % (sender_str,)
 | 
					        return "New Zulip mentions and private messages from %s" % (sender_str,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_mobile_push_content(rendered_content: Text) -> Text:
 | 
					def get_mobile_push_content(rendered_content: str) -> str:
 | 
				
			||||||
    def get_text(elem: LH.HtmlElement) -> Text:
 | 
					    def get_text(elem: LH.HtmlElement) -> str:
 | 
				
			||||||
        # Convert default emojis to their unicode equivalent.
 | 
					        # Convert default emojis to their unicode equivalent.
 | 
				
			||||||
        classes = elem.get("class", "")
 | 
					        classes = elem.get("class", "")
 | 
				
			||||||
        if "emoji" in classes:
 | 
					        if "emoji" in classes:
 | 
				
			||||||
@@ -449,13 +449,13 @@ def get_mobile_push_content(rendered_content: Text) -> Text:
 | 
				
			|||||||
            return ''  # To avoid empty line before quote text
 | 
					            return ''  # To avoid empty line before quote text
 | 
				
			||||||
        return elem.text or ''
 | 
					        return elem.text or ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format_as_quote(quote_text: Text) -> Text:
 | 
					    def format_as_quote(quote_text: str) -> str:
 | 
				
			||||||
        quote_text_list = filter(None, quote_text.split('\n'))  # Remove empty lines
 | 
					        quote_text_list = filter(None, quote_text.split('\n'))  # Remove empty lines
 | 
				
			||||||
        quote_text = '\n'.join(map(lambda x: "> "+x, quote_text_list))
 | 
					        quote_text = '\n'.join(map(lambda x: "> "+x, quote_text_list))
 | 
				
			||||||
        quote_text += '\n'
 | 
					        quote_text += '\n'
 | 
				
			||||||
        return quote_text
 | 
					        return quote_text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process(elem: LH.HtmlElement) -> Text:
 | 
					    def process(elem: LH.HtmlElement) -> str:
 | 
				
			||||||
        plain_text = get_text(elem)
 | 
					        plain_text = get_text(elem)
 | 
				
			||||||
        sub_text = ''
 | 
					        sub_text = ''
 | 
				
			||||||
        for child in elem:
 | 
					        for child in elem:
 | 
				
			||||||
@@ -473,7 +473,7 @@ def get_mobile_push_content(rendered_content: Text) -> Text:
 | 
				
			|||||||
        plain_text = process(elem)
 | 
					        plain_text = process(elem)
 | 
				
			||||||
        return plain_text
 | 
					        return plain_text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def truncate_content(content: Text) -> Tuple[Text, bool]:
 | 
					def truncate_content(content: str) -> Tuple[str, bool]:
 | 
				
			||||||
    # We use unicode character 'HORIZONTAL ELLIPSIS' (U+2026) instead
 | 
					    # We use unicode character 'HORIZONTAL ELLIPSIS' (U+2026) instead
 | 
				
			||||||
    # of three dots as this saves two extra characters for textual
 | 
					    # of three dots as this saves two extra characters for textual
 | 
				
			||||||
    # content. This function will need to be updated to handle unicode
 | 
					    # content. This function will need to be updated to handle unicode
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Iterator, List, Optional, Tuple, Text
 | 
					from typing import Any, Iterator, List, Optional, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from zerver.lib.redis_utils import get_redis_client
 | 
					from zerver.lib.redis_utils import get_redis_client
 | 
				
			||||||
@@ -21,23 +21,23 @@ rules = settings.RATE_LIMITING_RULES  # type: List[Tuple[int, int]]
 | 
				
			|||||||
KEY_PREFIX = ''
 | 
					KEY_PREFIX = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RateLimitedObject:
 | 
					class RateLimitedObject:
 | 
				
			||||||
    def get_keys(self) -> List[Text]:
 | 
					    def get_keys(self) -> List[str]:
 | 
				
			||||||
        key_fragment = self.key_fragment()
 | 
					        key_fragment = self.key_fragment()
 | 
				
			||||||
        return ["{}ratelimit:{}:{}".format(KEY_PREFIX, key_fragment, keytype)
 | 
					        return ["{}ratelimit:{}:{}".format(KEY_PREFIX, key_fragment, keytype)
 | 
				
			||||||
                for keytype in ['list', 'zset', 'block']]
 | 
					                for keytype in ['list', 'zset', 'block']]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def key_fragment(self) -> Text:
 | 
					    def key_fragment(self) -> str:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rules(self) -> List[Tuple[int, int]]:
 | 
					    def rules(self) -> List[Tuple[int, int]]:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RateLimitedUser(RateLimitedObject):
 | 
					class RateLimitedUser(RateLimitedObject):
 | 
				
			||||||
    def __init__(self, user: UserProfile, domain: Text='all') -> None:
 | 
					    def __init__(self, user: UserProfile, domain: str='all') -> None:
 | 
				
			||||||
        self.user = user
 | 
					        self.user = user
 | 
				
			||||||
        self.domain = domain
 | 
					        self.domain = domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def key_fragment(self) -> Text:
 | 
					    def key_fragment(self) -> str:
 | 
				
			||||||
        return "{}:{}:{}".format(type(self.user), self.user.id, self.domain)
 | 
					        return "{}:{}:{}".format(type(self.user), self.user.id, self.domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rules(self) -> List[Tuple[int, int]]:
 | 
					    def rules(self) -> List[Tuple[int, int]]:
 | 
				
			||||||
@@ -49,9 +49,9 @@ class RateLimitedUser(RateLimitedObject):
 | 
				
			|||||||
            return result
 | 
					            return result
 | 
				
			||||||
        return rules
 | 
					        return rules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def bounce_redis_key_prefix_for_testing(test_name: Text) -> None:
 | 
					def bounce_redis_key_prefix_for_testing(test_name: str) -> None:
 | 
				
			||||||
    global KEY_PREFIX
 | 
					    global KEY_PREFIX
 | 
				
			||||||
    KEY_PREFIX = test_name + ':' + Text(os.getpid()) + ':'
 | 
					    KEY_PREFIX = test_name + ':' + str(os.getpid()) + ':'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def max_api_calls(entity: RateLimitedObject) -> int:
 | 
					def max_api_calls(entity: RateLimitedObject) -> int:
 | 
				
			||||||
    "Returns the API rate limit for the highest limit"
 | 
					    "Returns the API rate limit for the highest limit"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ import logging
 | 
				
			|||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Text
 | 
					from typing import Any, Dict, Iterable, List, Mapping, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.logging_util import log_to_file
 | 
					from zerver.lib.logging_util import log_to_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,8 +26,8 @@ class FromAddress:
 | 
				
			|||||||
    NOREPLY = parseaddr(settings.NOREPLY_EMAIL_ADDRESS)[1]
 | 
					    NOREPLY = parseaddr(settings.NOREPLY_EMAIL_ADDRESS)[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_email(template_prefix: str, to_user_id: Optional[int]=None,
 | 
					def build_email(template_prefix: str, to_user_id: Optional[int]=None,
 | 
				
			||||||
                to_email: Optional[Text]=None, from_name: Optional[Text]=None,
 | 
					                to_email: Optional[str]=None, from_name: Optional[str]=None,
 | 
				
			||||||
                from_address: Optional[Text]=None, reply_to_email: Optional[Text]=None,
 | 
					                from_address: Optional[str]=None, reply_to_email: Optional[str]=None,
 | 
				
			||||||
                context: Optional[Dict[str, Any]]=None) -> EmailMultiAlternatives:
 | 
					                context: Optional[Dict[str, Any]]=None) -> EmailMultiAlternatives:
 | 
				
			||||||
    # Callers should pass exactly one of to_user_id and to_email.
 | 
					    # Callers should pass exactly one of to_user_id and to_email.
 | 
				
			||||||
    assert (to_user_id is None) ^ (to_email is None)
 | 
					    assert (to_user_id is None) ^ (to_email is None)
 | 
				
			||||||
@@ -83,9 +83,9 @@ class EmailNotDeliveredException(Exception):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# When changing the arguments to this function, you may need to write a
 | 
					# When changing the arguments to this function, you may need to write a
 | 
				
			||||||
# migration to change or remove any emails in ScheduledEmail.
 | 
					# migration to change or remove any emails in ScheduledEmail.
 | 
				
			||||||
def send_email(template_prefix: str, to_user_id: Optional[int]=None, to_email: Optional[Text]=None,
 | 
					def send_email(template_prefix: str, to_user_id: Optional[int]=None, to_email: Optional[str]=None,
 | 
				
			||||||
               from_name: Optional[Text]=None, from_address: Optional[Text]=None,
 | 
					               from_name: Optional[str]=None, from_address: Optional[str]=None,
 | 
				
			||||||
               reply_to_email: Optional[Text]=None, context: Dict[str, Any]={}) -> None:
 | 
					               reply_to_email: Optional[str]=None, context: Dict[str, Any]={}) -> None:
 | 
				
			||||||
    mail = build_email(template_prefix, to_user_id=to_user_id, to_email=to_email, from_name=from_name,
 | 
					    mail = build_email(template_prefix, to_user_id=to_user_id, to_email=to_email, from_name=from_name,
 | 
				
			||||||
                       from_address=from_address, reply_to_email=reply_to_email, context=context)
 | 
					                       from_address=from_address, reply_to_email=reply_to_email, context=context)
 | 
				
			||||||
    template = template_prefix.split("/")[-1]
 | 
					    template = template_prefix.split("/")[-1]
 | 
				
			||||||
@@ -99,8 +99,8 @@ def send_email_from_dict(email_dict: Mapping[str, Any]) -> None:
 | 
				
			|||||||
    send_email(**dict(email_dict))
 | 
					    send_email(**dict(email_dict))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def send_future_email(template_prefix: str, realm: Realm, to_user_id: Optional[int]=None,
 | 
					def send_future_email(template_prefix: str, realm: Realm, to_user_id: Optional[int]=None,
 | 
				
			||||||
                      to_email: Optional[Text]=None, from_name: Optional[Text]=None,
 | 
					                      to_email: Optional[str]=None, from_name: Optional[str]=None,
 | 
				
			||||||
                      from_address: Optional[Text]=None, context: Dict[str, Any]={},
 | 
					                      from_address: Optional[str]=None, context: Dict[str, Any]={},
 | 
				
			||||||
                      delay: datetime.timedelta=datetime.timedelta(0)) -> None:
 | 
					                      delay: datetime.timedelta=datetime.timedelta(0)) -> None:
 | 
				
			||||||
    template_name = template_prefix.split('/')[-1]
 | 
					    template_name = template_prefix.split('/')[-1]
 | 
				
			||||||
    email_fields = {'template_prefix': template_prefix, 'to_user_id': to_user_id, 'to_email': to_email,
 | 
					    email_fields = {'template_prefix': template_prefix, 'to_user_id': to_user_id, 'to_email': to_email,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import (Dict, List, Text, Set)
 | 
					from typing import (Dict, List, Set)
 | 
				
			||||||
from django.db.models.query import QuerySet
 | 
					from django.db.models.query import QuerySet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.stream_subscription import (
 | 
					from zerver.lib.stream_subscription import (
 | 
				
			||||||
@@ -16,7 +16,7 @@ class StreamTopicTarget:
 | 
				
			|||||||
    places where we are are still using `subject` or
 | 
					    places where we are are still using `subject` or
 | 
				
			||||||
    `topic_name` as a key into tables.
 | 
					    `topic_name` as a key into tables.
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    def __init__(self, stream_id: int, topic_name: Text) -> None:
 | 
					    def __init__(self, stream_id: int, topic_name: str) -> None:
 | 
				
			||||||
        self.stream_id = stream_id
 | 
					        self.stream_id = stream_id
 | 
				
			||||||
        self.topic_name = topic_name
 | 
					        self.topic_name = topic_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import Any, Iterable, List, Mapping, Set, Text, Tuple, Optional
 | 
					from typing import Any, Iterable, List, Mapping, Set, Tuple, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.http import HttpRequest, HttpResponse
 | 
					from django.http import HttpRequest, HttpResponse
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
@@ -32,7 +32,7 @@ def access_stream_for_delete_or_update(user_profile: UserProfile, stream_id: int
 | 
				
			|||||||
# Only set allow_realm_admin flag to True when you want to allow realm admin to
 | 
					# Only set allow_realm_admin flag to True when you want to allow realm admin to
 | 
				
			||||||
# access unsubscribed private stream content.
 | 
					# access unsubscribed private stream content.
 | 
				
			||||||
def access_stream_common(user_profile: UserProfile, stream: Stream,
 | 
					def access_stream_common(user_profile: UserProfile, stream: Stream,
 | 
				
			||||||
                         error: Text,
 | 
					                         error: str,
 | 
				
			||||||
                         require_active: bool=True,
 | 
					                         require_active: bool=True,
 | 
				
			||||||
                         allow_realm_admin: bool=False) -> Tuple[Recipient, Optional[Subscription]]:
 | 
					                         allow_realm_admin: bool=False) -> Tuple[Recipient, Optional[Subscription]]:
 | 
				
			||||||
    """Common function for backend code where the target use attempts to
 | 
					    """Common function for backend code where the target use attempts to
 | 
				
			||||||
@@ -93,7 +93,7 @@ def get_stream_by_id(stream_id: int) -> Stream:
 | 
				
			|||||||
        raise JsonableError(error)
 | 
					        raise JsonableError(error)
 | 
				
			||||||
    return stream
 | 
					    return stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_stream_name_available(realm: Realm, name: Text) -> None:
 | 
					def check_stream_name_available(realm: Realm, name: str) -> None:
 | 
				
			||||||
    check_stream_name(name)
 | 
					    check_stream_name(name)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        get_stream(name, realm)
 | 
					        get_stream(name, realm)
 | 
				
			||||||
@@ -102,7 +102,7 @@ def check_stream_name_available(realm: Realm, name: Text) -> None:
 | 
				
			|||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def access_stream_by_name(user_profile: UserProfile,
 | 
					def access_stream_by_name(user_profile: UserProfile,
 | 
				
			||||||
                          stream_name: Text) -> Tuple[Stream, Recipient, Optional[Subscription]]:
 | 
					                          stream_name: str) -> Tuple[Stream, Recipient, Optional[Subscription]]:
 | 
				
			||||||
    error = _("Invalid stream name '%s'" % (stream_name,))
 | 
					    error = _("Invalid stream name '%s'" % (stream_name,))
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        stream = get_realm_stream(stream_name, user_profile.realm_id)
 | 
					        stream = get_realm_stream(stream_name, user_profile.realm_id)
 | 
				
			||||||
@@ -112,7 +112,7 @@ def access_stream_by_name(user_profile: UserProfile,
 | 
				
			|||||||
    (recipient, sub) = access_stream_common(user_profile, stream, error)
 | 
					    (recipient, sub) = access_stream_common(user_profile, stream, error)
 | 
				
			||||||
    return (stream, recipient, sub)
 | 
					    return (stream, recipient, sub)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def access_stream_for_unmute_topic(user_profile: UserProfile, stream_name: Text, error: Text) -> Stream:
 | 
					def access_stream_for_unmute_topic(user_profile: UserProfile, stream_name: str, error: str) -> Stream:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    It may seem a little silly to have this helper function for unmuting
 | 
					    It may seem a little silly to have this helper function for unmuting
 | 
				
			||||||
    topics, but it gets around a linter warning, and it helps to be able
 | 
					    topics, but it gets around a linter warning, and it helps to be able
 | 
				
			||||||
@@ -132,7 +132,7 @@ def access_stream_for_unmute_topic(user_profile: UserProfile, stream_name: Text,
 | 
				
			|||||||
        raise JsonableError(error)
 | 
					        raise JsonableError(error)
 | 
				
			||||||
    return stream
 | 
					    return stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def can_access_stream_history_by_name(user_profile: UserProfile, stream_name: Text) -> bool:
 | 
					def can_access_stream_history_by_name(user_profile: UserProfile, stream_name: str) -> bool:
 | 
				
			||||||
    """Determine whether the provided user is allowed to access the
 | 
					    """Determine whether the provided user is allowed to access the
 | 
				
			||||||
    history of the target stream.  The stream is specified by name.
 | 
					    history of the target stream.  The stream is specified by name.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
from contextlib import contextmanager
 | 
					from contextlib import contextmanager
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    cast, Any, Callable, Dict, Generator, Iterable, Iterator, List, Mapping,
 | 
					    cast, Any, Callable, Dict, Generator, Iterable, Iterator, List, Mapping,
 | 
				
			||||||
    Optional, Set, Sized, Tuple, Union, IO, Text, TypeVar
 | 
					    Optional, Set, Sized, Tuple, Union, IO, TypeVar
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core import signing
 | 
					from django.core import signing
 | 
				
			||||||
@@ -108,14 +108,14 @@ def tornado_redirected_to_list(lst: List[Mapping[str, Any]]) -> Iterator[None]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@contextmanager
 | 
					@contextmanager
 | 
				
			||||||
def simulated_empty_cache() -> Generator[
 | 
					def simulated_empty_cache() -> Generator[
 | 
				
			||||||
        List[Tuple[str, Union[Text, List[Text]], Text]], None, None]:
 | 
					        List[Tuple[str, Union[str, List[str]], str]], None, None]:
 | 
				
			||||||
    cache_queries = []  # type: List[Tuple[str, Union[Text, List[Text]], Text]]
 | 
					    cache_queries = []  # type: List[Tuple[str, Union[str, List[str]], str]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def my_cache_get(key: Text, cache_name: Optional[str]=None) -> Optional[Dict[Text, Any]]:
 | 
					    def my_cache_get(key: str, cache_name: Optional[str]=None) -> Optional[Dict[str, Any]]:
 | 
				
			||||||
        cache_queries.append(('get', key, cache_name))
 | 
					        cache_queries.append(('get', key, cache_name))
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def my_cache_get_many(keys: List[Text], cache_name: Optional[str]=None) -> Dict[Text, Any]:  # nocoverage -- simulated code doesn't use this
 | 
					    def my_cache_get_many(keys: List[str], cache_name: Optional[str]=None) -> Dict[str, Any]:  # nocoverage -- simulated code doesn't use this
 | 
				
			||||||
        cache_queries.append(('getmany', keys, cache_name))
 | 
					        cache_queries.append(('getmany', keys, cache_name))
 | 
				
			||||||
        return {}
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -186,7 +186,7 @@ def get_test_image_file(filename: str) -> IO[Any]:
 | 
				
			|||||||
    test_avatar_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../tests/images'))
 | 
					    test_avatar_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../tests/images'))
 | 
				
			||||||
    return open(os.path.join(test_avatar_dir, filename), 'rb')
 | 
					    return open(os.path.join(test_avatar_dir, filename), 'rb')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def avatar_disk_path(user_profile: UserProfile, medium: bool=False) -> Text:
 | 
					def avatar_disk_path(user_profile: UserProfile, medium: bool=False) -> str:
 | 
				
			||||||
    avatar_url_path = avatar_url(user_profile, medium)
 | 
					    avatar_url_path = avatar_url(user_profile, medium)
 | 
				
			||||||
    avatar_disk_path = os.path.join(settings.LOCAL_UPLOADS_DIR, "avatars",
 | 
					    avatar_disk_path = os.path.join(settings.LOCAL_UPLOADS_DIR, "avatars",
 | 
				
			||||||
                                    avatar_url_path.split("/")[-2],
 | 
					                                    avatar_url_path.split("/")[-2],
 | 
				
			||||||
@@ -197,7 +197,7 @@ def make_client(name: str) -> Client:
 | 
				
			|||||||
    client, _ = Client.objects.get_or_create(name=name)
 | 
					    client, _ = Client.objects.get_or_create(name=name)
 | 
				
			||||||
    return client
 | 
					    return client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_key_by_email(address: Text) -> Optional[Text]:
 | 
					def find_key_by_email(address: str) -> Optional[str]:
 | 
				
			||||||
    from django.core.mail import outbox
 | 
					    from django.core.mail import outbox
 | 
				
			||||||
    key_regex = re.compile("accounts/do_confirm/([a-z0-9]{24})>")
 | 
					    key_regex = re.compile("accounts/do_confirm/([a-z0-9]{24})>")
 | 
				
			||||||
    for message in reversed(outbox):
 | 
					    for message in reversed(outbox):
 | 
				
			||||||
@@ -222,7 +222,7 @@ def most_recent_message(user_profile: UserProfile) -> Message:
 | 
				
			|||||||
    usermessage = most_recent_usermessage(user_profile)
 | 
					    usermessage = most_recent_usermessage(user_profile)
 | 
				
			||||||
    return usermessage.message
 | 
					    return usermessage.message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_subscription(stream_name: Text, user_profile: UserProfile) -> Subscription:
 | 
					def get_subscription(stream_name: str, user_profile: UserProfile) -> Subscription:
 | 
				
			||||||
    stream = get_stream(stream_name, user_profile.realm)
 | 
					    stream = get_stream(stream_name, user_profile.realm)
 | 
				
			||||||
    recipient = get_stream_recipient(stream.id)
 | 
					    recipient = get_stream_recipient(stream.id)
 | 
				
			||||||
    return Subscription.objects.get(user_profile=user_profile,
 | 
					    return Subscription.objects.get(user_profile=user_profile,
 | 
				
			||||||
@@ -255,7 +255,7 @@ class HostRequestMock:
 | 
				
			|||||||
    """A mock request object where get_host() works.  Useful for testing
 | 
					    """A mock request object where get_host() works.  Useful for testing
 | 
				
			||||||
    routes that use Zulip's subdomains feature"""
 | 
					    routes that use Zulip's subdomains feature"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, user_profile: UserProfile=None, host: Text=settings.EXTERNAL_HOST) -> None:
 | 
					    def __init__(self, user_profile: UserProfile=None, host: str=settings.EXTERNAL_HOST) -> None:
 | 
				
			||||||
        self.host = host
 | 
					        self.host = host
 | 
				
			||||||
        self.GET = {}  # type: Dict[str, Any]
 | 
					        self.GET = {}  # type: Dict[str, Any]
 | 
				
			||||||
        self.POST = {}  # type: Dict[str, Any]
 | 
					        self.POST = {}  # type: Dict[str, Any]
 | 
				
			||||||
@@ -267,11 +267,11 @@ class HostRequestMock:
 | 
				
			|||||||
        self.content_type = ''
 | 
					        self.content_type = ''
 | 
				
			||||||
        self._email = ''
 | 
					        self._email = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_host(self) -> Text:
 | 
					    def get_host(self) -> str:
 | 
				
			||||||
        return self.host
 | 
					        return self.host
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockPythonResponse:
 | 
					class MockPythonResponse:
 | 
				
			||||||
    def __init__(self, text: Text, status_code: int) -> None:
 | 
					    def __init__(self, text: str, status_code: int) -> None:
 | 
				
			||||||
        self.text = text
 | 
					        self.text = text
 | 
				
			||||||
        self.status_code = status_code
 | 
					        self.status_code = status_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -291,7 +291,7 @@ def instrument_url(f: UrlFuncT) -> UrlFuncT:
 | 
				
			|||||||
    if not INSTRUMENTING:  # nocoverage -- option is always enabled; should we remove?
 | 
					    if not INSTRUMENTING:  # nocoverage -- option is always enabled; should we remove?
 | 
				
			||||||
        return f
 | 
					        return f
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        def wrapper(self: 'ZulipTestCase', url: Text, info: Dict[str, Any]={},
 | 
					        def wrapper(self: 'ZulipTestCase', url: str, info: Dict[str, Any]={},
 | 
				
			||||||
                    **kwargs: Any) -> HttpResponse:
 | 
					                    **kwargs: Any) -> HttpResponse:
 | 
				
			||||||
            start = time.time()
 | 
					            start = time.time()
 | 
				
			||||||
            result = f(self, url, info, **kwargs)
 | 
					            result = f(self, url, info, **kwargs)
 | 
				
			||||||
@@ -420,7 +420,7 @@ def get_all_templates() -> List[str]:
 | 
				
			|||||||
    isfile = os.path.isfile
 | 
					    isfile = os.path.isfile
 | 
				
			||||||
    path_exists = os.path.exists
 | 
					    path_exists = os.path.exists
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_valid_template(p: Text, n: Text) -> bool:
 | 
					    def is_valid_template(p: str, n: str) -> bool:
 | 
				
			||||||
        return 'webhooks' not in p \
 | 
					        return 'webhooks' not in p \
 | 
				
			||||||
               and not n.startswith('.') \
 | 
					               and not n.startswith('.') \
 | 
				
			||||||
               and not n.startswith('__init__') \
 | 
					               and not n.startswith('__init__') \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ from functools import partial
 | 
				
			|||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, \
 | 
					from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, \
 | 
				
			||||||
    Text, Type, cast, Union, TypeVar
 | 
					    Type, cast, Union, TypeVar
 | 
				
			||||||
from unittest import loader, runner  # type: ignore  # Mypy cannot pick these up.
 | 
					from unittest import loader, runner  # type: ignore  # Mypy cannot pick these up.
 | 
				
			||||||
from unittest.result import TestResult
 | 
					from unittest.result import TestResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -140,7 +140,7 @@ class TextTestResult(runner.TextTestResult):
 | 
				
			|||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self.failed_tests = []  # type: List[str]
 | 
					        self.failed_tests = []  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def addInfo(self, test: TestCase, msg: Text) -> None:
 | 
					    def addInfo(self, test: TestCase, msg: str) -> None:
 | 
				
			||||||
        self.stream.write(msg)
 | 
					        self.stream.write(msg)
 | 
				
			||||||
        self.stream.flush()
 | 
					        self.stream.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,7 +165,7 @@ class TextTestResult(runner.TextTestResult):
 | 
				
			|||||||
        test_name = full_test_name(args[0])
 | 
					        test_name = full_test_name(args[0])
 | 
				
			||||||
        self.failed_tests.append(test_name)
 | 
					        self.failed_tests.append(test_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def addSkip(self, test: TestCase, reason: Text) -> None:
 | 
					    def addSkip(self, test: TestCase, reason: str) -> None:
 | 
				
			||||||
        TestResult.addSkip(self, test, reason)
 | 
					        TestResult.addSkip(self, test, reason)
 | 
				
			||||||
        self.stream.writeln("** Skipping {}: {}".format(full_test_name(test),
 | 
					        self.stream.writeln("** Skipping {}: {}".format(full_test_name(test),
 | 
				
			||||||
                                                        reason))
 | 
					                                                        reason))
 | 
				
			||||||
@@ -176,7 +176,7 @@ class RemoteTestResult(django_runner.RemoteTestResult):
 | 
				
			|||||||
    The class follows the unpythonic style of function names of the
 | 
					    The class follows the unpythonic style of function names of the
 | 
				
			||||||
    base class.
 | 
					    base class.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    def addInfo(self, test: TestCase, msg: Text) -> None:
 | 
					    def addInfo(self, test: TestCase, msg: str) -> None:
 | 
				
			||||||
        self.events.append(('addInfo', self.test_index, msg))
 | 
					        self.events.append(('addInfo', self.test_index, msg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def addInstrumentation(self, test: TestCase, data: Dict[str, Any]) -> None:
 | 
					    def addInstrumentation(self, test: TestCase, data: Dict[str, Any]) -> None:
 | 
				
			||||||
@@ -353,7 +353,7 @@ class ParallelTestSuite(django_runner.ParallelTestSuite):
 | 
				
			|||||||
        # definitions.
 | 
					        # definitions.
 | 
				
			||||||
        self.subsuites = SubSuiteList(self.subsuites)  # type: ignore # Type of self.subsuites changes.
 | 
					        self.subsuites = SubSuiteList(self.subsuites)  # type: ignore # Type of self.subsuites changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_import_error(test_name: Text) -> None:
 | 
					def check_import_error(test_name: str) -> None:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        # Directly using __import__ is not recommeded, but here it gives
 | 
					        # Directly using __import__ is not recommeded, but here it gives
 | 
				
			||||||
        # clearer traceback as compared to importlib.import_module.
 | 
					        # clearer traceback as compared to importlib.import_module.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Callable, Dict, List, Optional, Text
 | 
					from typing import Any, Callable, Dict, List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.models import (
 | 
					from zerver.models import (
 | 
				
			||||||
    get_stream_recipient,
 | 
					    get_stream_recipient,
 | 
				
			||||||
@@ -15,7 +15,7 @@ from sqlalchemy.sql import (
 | 
				
			|||||||
    Selectable
 | 
					    Selectable
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_topic_mutes(user_profile: UserProfile) -> List[List[Text]]:
 | 
					def get_topic_mutes(user_profile: UserProfile) -> List[List[str]]:
 | 
				
			||||||
    rows = MutedTopic.objects.filter(
 | 
					    rows = MutedTopic.objects.filter(
 | 
				
			||||||
        user_profile=user_profile,
 | 
					        user_profile=user_profile,
 | 
				
			||||||
    ).values(
 | 
					    ).values(
 | 
				
			||||||
@@ -27,7 +27,7 @@ def get_topic_mutes(user_profile: UserProfile) -> List[List[Text]]:
 | 
				
			|||||||
        for row in rows
 | 
					        for row in rows
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_topic_mutes(user_profile: UserProfile, muted_topics: List[List[Text]]) -> None:
 | 
					def set_topic_mutes(user_profile: UserProfile, muted_topics: List[List[str]]) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    This is only used in tests.
 | 
					    This is only used in tests.
 | 
				
			||||||
@@ -64,7 +64,7 @@ def remove_topic_mute(user_profile: UserProfile, stream_id: int, topic_name: str
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    row.delete()
 | 
					    row.delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def topic_is_muted(user_profile: UserProfile, stream_id: int, topic_name: Text) -> bool:
 | 
					def topic_is_muted(user_profile: UserProfile, stream_id: int, topic_name: str) -> bool:
 | 
				
			||||||
    is_muted = MutedTopic.objects.filter(
 | 
					    is_muted = MutedTopic.objects.filter(
 | 
				
			||||||
        user_profile=user_profile,
 | 
					        user_profile=user_profile,
 | 
				
			||||||
        stream_id=stream_id,
 | 
					        stream_id=stream_id,
 | 
				
			||||||
@@ -103,7 +103,7 @@ def exclude_topic_mutes(conditions: List[Selectable],
 | 
				
			|||||||
    condition = not_(or_(*list(map(mute_cond, rows))))
 | 
					    condition = not_(or_(*list(map(mute_cond, rows))))
 | 
				
			||||||
    return conditions + [condition]
 | 
					    return conditions + [condition]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_topic_mute_checker(user_profile: UserProfile) -> Callable[[int, Text], bool]:
 | 
					def build_topic_mute_checker(user_profile: UserProfile) -> Callable[[int, str], bool]:
 | 
				
			||||||
    rows = MutedTopic.objects.filter(
 | 
					    rows = MutedTopic.objects.filter(
 | 
				
			||||||
        user_profile=user_profile,
 | 
					        user_profile=user_profile,
 | 
				
			||||||
    ).values(
 | 
					    ).values(
 | 
				
			||||||
@@ -118,7 +118,7 @@ def build_topic_mute_checker(user_profile: UserProfile) -> Callable[[int, Text],
 | 
				
			|||||||
        topic_name = row['topic_name']
 | 
					        topic_name = row['topic_name']
 | 
				
			||||||
        tups.add((recipient_id, topic_name.lower()))
 | 
					        tups.add((recipient_id, topic_name.lower()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_muted(recipient_id: int, topic: Text) -> bool:
 | 
					    def is_muted(recipient_id: int, topic: str) -> bool:
 | 
				
			||||||
        return (recipient_id, topic.lower()) in tups
 | 
					        return (recipient_id, topic.lower()) in tups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return is_muted
 | 
					    return is_muted
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Dict, Mapping, Optional, Tuple, Text
 | 
					from typing import Any, Dict, Mapping, Optional, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -55,12 +55,12 @@ class RealmUploadQuotaError(JsonableError):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
attachment_url_re = re.compile('[/\-]user[\-_]uploads[/\.-].*?(?=[ )]|\Z)')
 | 
					attachment_url_re = re.compile('[/\-]user[\-_]uploads[/\.-].*?(?=[ )]|\Z)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def attachment_url_to_path_id(attachment_url: Text) -> Text:
 | 
					def attachment_url_to_path_id(attachment_url: str) -> str:
 | 
				
			||||||
    path_id_raw = re.sub('[/\-]user[\-_]uploads[/\.-]', '', attachment_url)
 | 
					    path_id_raw = re.sub('[/\-]user[\-_]uploads[/\.-]', '', attachment_url)
 | 
				
			||||||
    # Remove any extra '.' after file extension. These are probably added by the user
 | 
					    # Remove any extra '.' after file extension. These are probably added by the user
 | 
				
			||||||
    return re.sub('[.]+$', '', path_id_raw, re.M)
 | 
					    return re.sub('[.]+$', '', path_id_raw, re.M)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def sanitize_name(value: NonBinaryStr) -> Text:
 | 
					def sanitize_name(value: NonBinaryStr) -> str:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Sanitizes a value to be safe to store in a Linux filesystem, in
 | 
					    Sanitizes a value to be safe to store in a Linux filesystem, in
 | 
				
			||||||
    S3, and in a URL.  So unicode is allowed, but not special
 | 
					    S3, and in a URL.  So unicode is allowed, but not special
 | 
				
			||||||
@@ -75,7 +75,7 @@ def sanitize_name(value: NonBinaryStr) -> Text:
 | 
				
			|||||||
    value = re.sub('[^\w\s._-]', '', value, flags=re.U).strip()
 | 
					    value = re.sub('[^\w\s._-]', '', value, flags=re.U).strip()
 | 
				
			||||||
    return mark_safe(re.sub('[-\s]+', '-', value, flags=re.U))
 | 
					    return mark_safe(re.sub('[-\s]+', '-', value, flags=re.U))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def random_name(bytes: int=60) -> Text:
 | 
					def random_name(bytes: int=60) -> str:
 | 
				
			||||||
    return base64.urlsafe_b64encode(os.urandom(bytes)).decode('utf-8')
 | 
					    return base64.urlsafe_b64encode(os.urandom(bytes)).decode('utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BadImageError(JsonableError):
 | 
					class BadImageError(JsonableError):
 | 
				
			||||||
@@ -118,10 +118,10 @@ def resize_emoji(image_data: bytes, size: int=DEFAULT_EMOJI_SIZE) -> bytes:
 | 
				
			|||||||
### Common
 | 
					### Common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ZulipUploadBackend:
 | 
					class ZulipUploadBackend:
 | 
				
			||||||
    def upload_message_file(self, uploaded_file_name: Text, uploaded_file_size: int,
 | 
					    def upload_message_file(self, uploaded_file_name: str, uploaded_file_size: int,
 | 
				
			||||||
                            content_type: Optional[Text], file_data: bytes,
 | 
					                            content_type: Optional[str], file_data: bytes,
 | 
				
			||||||
                            user_profile: UserProfile,
 | 
					                            user_profile: UserProfile,
 | 
				
			||||||
                            target_realm: Optional[Realm]=None) -> Text:
 | 
					                            target_realm: Optional[Realm]=None) -> str:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def upload_avatar_image(self, user_file: File,
 | 
					    def upload_avatar_image(self, user_file: File,
 | 
				
			||||||
@@ -129,10 +129,10 @@ class ZulipUploadBackend:
 | 
				
			|||||||
                            target_user_profile: UserProfile) -> None:
 | 
					                            target_user_profile: UserProfile) -> None:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_message_image(self, path_id: Text) -> bool:
 | 
					    def delete_message_image(self, path_id: str) -> bool:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_avatar_url(self, hash_key: Text, medium: bool=False) -> Text:
 | 
					    def get_avatar_url(self, hash_key: str, medium: bool=False) -> str:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
 | 
					    def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
 | 
				
			||||||
@@ -141,19 +141,19 @@ class ZulipUploadBackend:
 | 
				
			|||||||
    def upload_realm_icon_image(self, icon_file: File, user_profile: UserProfile) -> None:
 | 
					    def upload_realm_icon_image(self, icon_file: File, user_profile: UserProfile) -> None:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_realm_icon_url(self, realm_id: int, version: int) -> Text:
 | 
					    def get_realm_icon_url(self, realm_id: int, version: int) -> str:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def upload_emoji_image(self, emoji_file: File, emoji_file_name: Text, user_profile: UserProfile) -> None:
 | 
					    def upload_emoji_image(self, emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_emoji_url(self, emoji_file_name: Text, realm_id: int) -> Text:
 | 
					    def get_emoji_url(self, emoji_file_name: str, realm_id: int) -> str:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### S3
 | 
					### S3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bucket(conn: S3Connection, bucket_name: Text) -> Bucket:
 | 
					def get_bucket(conn: S3Connection, bucket_name: str) -> Bucket:
 | 
				
			||||||
    # Calling get_bucket() with validate=True can apparently lead
 | 
					    # Calling get_bucket() with validate=True can apparently lead
 | 
				
			||||||
    # to expensive S3 bills:
 | 
					    # to expensive S3 bills:
 | 
				
			||||||
    #    http://www.appneta.com/blog/s3-list-get-bucket-default/
 | 
					    #    http://www.appneta.com/blog/s3-list-get-bucket-default/
 | 
				
			||||||
@@ -167,8 +167,8 @@ def get_bucket(conn: S3Connection, bucket_name: Text) -> Bucket:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def upload_image_to_s3(
 | 
					def upload_image_to_s3(
 | 
				
			||||||
        bucket_name: NonBinaryStr,
 | 
					        bucket_name: NonBinaryStr,
 | 
				
			||||||
        file_name: Text,
 | 
					        file_name: str,
 | 
				
			||||||
        content_type: Optional[Text],
 | 
					        content_type: Optional[str],
 | 
				
			||||||
        user_profile: UserProfile,
 | 
					        user_profile: UserProfile,
 | 
				
			||||||
        contents: bytes) -> None:
 | 
					        contents: bytes) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -180,7 +180,7 @@ def upload_image_to_s3(
 | 
				
			|||||||
    key.set_metadata("realm_id", str(user_profile.realm_id))
 | 
					    key.set_metadata("realm_id", str(user_profile.realm_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if content_type is not None:
 | 
					    if content_type is not None:
 | 
				
			||||||
        headers = {'Content-Type': content_type}  # type: Optional[Dict[Text, Text]]
 | 
					        headers = {'Content-Type': content_type}  # type: Optional[Dict[str, str]]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        headers = None
 | 
					        headers = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -200,7 +200,7 @@ def check_upload_within_quota(realm: Realm, uploaded_file_size: int) -> None:
 | 
				
			|||||||
    if (used_space + uploaded_file_size) > upload_quota:
 | 
					    if (used_space + uploaded_file_size) > upload_quota:
 | 
				
			||||||
        raise RealmUploadQuotaError(_("Upload would exceed your organization's upload quota."))
 | 
					        raise RealmUploadQuotaError(_("Upload would exceed your organization's upload quota."))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_file_info(request: HttpRequest, user_file: File) -> Tuple[Text, int, Optional[Text]]:
 | 
					def get_file_info(request: HttpRequest, user_file: File) -> Tuple[str, int, Optional[str]]:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uploaded_file_name = user_file.name
 | 
					    uploaded_file_name = user_file.name
 | 
				
			||||||
    assert isinstance(uploaded_file_name, str)
 | 
					    assert isinstance(uploaded_file_name, str)
 | 
				
			||||||
@@ -221,11 +221,11 @@ def get_file_info(request: HttpRequest, user_file: File) -> Tuple[Text, int, Opt
 | 
				
			|||||||
    return uploaded_file_name, uploaded_file_size, content_type
 | 
					    return uploaded_file_name, uploaded_file_size, content_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_signed_upload_url(path: Text) -> Text:
 | 
					def get_signed_upload_url(path: str) -> str:
 | 
				
			||||||
    conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
					    conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
				
			||||||
    return conn.generate_url(15, 'GET', bucket=settings.S3_AUTH_UPLOADS_BUCKET, key=path)
 | 
					    return conn.generate_url(15, 'GET', bucket=settings.S3_AUTH_UPLOADS_BUCKET, key=path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_realm_for_filename(path: Text) -> Optional[int]:
 | 
					def get_realm_for_filename(path: str) -> Optional[int]:
 | 
				
			||||||
    conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
					    conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
				
			||||||
    key = get_bucket(conn, settings.S3_AUTH_UPLOADS_BUCKET).get_key(path)
 | 
					    key = get_bucket(conn, settings.S3_AUTH_UPLOADS_BUCKET).get_key(path)
 | 
				
			||||||
    if key is None:
 | 
					    if key is None:
 | 
				
			||||||
@@ -236,9 +236,9 @@ def get_realm_for_filename(path: Text) -> Optional[int]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class S3UploadBackend(ZulipUploadBackend):
 | 
					class S3UploadBackend(ZulipUploadBackend):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def upload_message_file(self, uploaded_file_name: Text, uploaded_file_size: int,
 | 
					    def upload_message_file(self, uploaded_file_name: str, uploaded_file_size: int,
 | 
				
			||||||
                            content_type: Optional[Text], file_data: bytes,
 | 
					                            content_type: Optional[str], file_data: bytes,
 | 
				
			||||||
                            user_profile: UserProfile, target_realm: Optional[Realm]=None) -> Text:
 | 
					                            user_profile: UserProfile, target_realm: Optional[Realm]=None) -> str:
 | 
				
			||||||
        bucket_name = settings.S3_AUTH_UPLOADS_BUCKET
 | 
					        bucket_name = settings.S3_AUTH_UPLOADS_BUCKET
 | 
				
			||||||
        if target_realm is None:
 | 
					        if target_realm is None:
 | 
				
			||||||
            target_realm = user_profile.realm
 | 
					            target_realm = user_profile.realm
 | 
				
			||||||
@@ -260,7 +260,7 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        create_attachment(uploaded_file_name, s3_file_name, user_profile, uploaded_file_size)
 | 
					        create_attachment(uploaded_file_name, s3_file_name, user_profile, uploaded_file_size)
 | 
				
			||||||
        return url
 | 
					        return url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_message_image(self, path_id: Text) -> bool:
 | 
					    def delete_message_image(self, path_id: str) -> bool:
 | 
				
			||||||
        conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
					        conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
 | 
				
			||||||
        bucket = get_bucket(conn, settings.S3_AUTH_UPLOADS_BUCKET)
 | 
					        bucket = get_bucket(conn, settings.S3_AUTH_UPLOADS_BUCKET)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -311,7 +311,7 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        # See avatar_url in avatar.py for URL.  (That code also handles the case
 | 
					        # See avatar_url in avatar.py for URL.  (That code also handles the case
 | 
				
			||||||
        # that users use gravatar.)
 | 
					        # that users use gravatar.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_avatar_url(self, hash_key: Text, medium: bool=False) -> Text:
 | 
					    def get_avatar_url(self, hash_key: str, medium: bool=False) -> str:
 | 
				
			||||||
        bucket = settings.S3_AVATAR_BUCKET
 | 
					        bucket = settings.S3_AVATAR_BUCKET
 | 
				
			||||||
        medium_suffix = "-medium.png" if medium else ""
 | 
					        medium_suffix = "-medium.png" if medium else ""
 | 
				
			||||||
        # ?x=x allows templates to append additional parameters with &s
 | 
					        # ?x=x allows templates to append additional parameters with &s
 | 
				
			||||||
@@ -342,7 +342,7 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        # See avatar_url in avatar.py for URL.  (That code also handles the case
 | 
					        # See avatar_url in avatar.py for URL.  (That code also handles the case
 | 
				
			||||||
        # that users use gravatar.)
 | 
					        # that users use gravatar.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_realm_icon_url(self, realm_id: int, version: int) -> Text:
 | 
					    def get_realm_icon_url(self, realm_id: int, version: int) -> str:
 | 
				
			||||||
        bucket = settings.S3_AVATAR_BUCKET
 | 
					        bucket = settings.S3_AVATAR_BUCKET
 | 
				
			||||||
        # ?x=x allows templates to append additional parameters with &s
 | 
					        # ?x=x allows templates to append additional parameters with &s
 | 
				
			||||||
        return "https://%s.s3.amazonaws.com/%s/realm/icon.png?version=%s" % (bucket, realm_id, version)
 | 
					        return "https://%s.s3.amazonaws.com/%s/realm/icon.png?version=%s" % (bucket, realm_id, version)
 | 
				
			||||||
@@ -366,7 +366,7 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
            resized_medium
 | 
					            resized_medium
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def upload_emoji_image(self, emoji_file: File, emoji_file_name: Text,
 | 
					    def upload_emoji_image(self, emoji_file: File, emoji_file_name: str,
 | 
				
			||||||
                           user_profile: UserProfile) -> None:
 | 
					                           user_profile: UserProfile) -> None:
 | 
				
			||||||
        content_type = guess_type(emoji_file.name)[0]
 | 
					        content_type = guess_type(emoji_file.name)[0]
 | 
				
			||||||
        bucket_name = settings.S3_AVATAR_BUCKET
 | 
					        bucket_name = settings.S3_AVATAR_BUCKET
 | 
				
			||||||
@@ -392,7 +392,7 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
            resized_image_data,
 | 
					            resized_image_data,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_emoji_url(self, emoji_file_name: Text, realm_id: int) -> Text:
 | 
					    def get_emoji_url(self, emoji_file_name: str, realm_id: int) -> str:
 | 
				
			||||||
        bucket = settings.S3_AVATAR_BUCKET
 | 
					        bucket = settings.S3_AVATAR_BUCKET
 | 
				
			||||||
        emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(realm_id=realm_id,
 | 
					        emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(realm_id=realm_id,
 | 
				
			||||||
                                                        emoji_file_name=emoji_file_name)
 | 
					                                                        emoji_file_name=emoji_file_name)
 | 
				
			||||||
@@ -401,13 +401,13 @@ class S3UploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Local
 | 
					### Local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def write_local_file(type: Text, path: Text, file_data: bytes) -> None:
 | 
					def write_local_file(type: str, path: str, file_data: bytes) -> None:
 | 
				
			||||||
    file_path = os.path.join(settings.LOCAL_UPLOADS_DIR, type, path)
 | 
					    file_path = os.path.join(settings.LOCAL_UPLOADS_DIR, type, path)
 | 
				
			||||||
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
 | 
					    os.makedirs(os.path.dirname(file_path), exist_ok=True)
 | 
				
			||||||
    with open(file_path, 'wb') as f:
 | 
					    with open(file_path, 'wb') as f:
 | 
				
			||||||
        f.write(file_data)
 | 
					        f.write(file_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_local_file_path(path_id: Text) -> Optional[Text]:
 | 
					def get_local_file_path(path_id: str) -> Optional[str]:
 | 
				
			||||||
    local_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'files', path_id)
 | 
					    local_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'files', path_id)
 | 
				
			||||||
    if os.path.isfile(local_path):
 | 
					    if os.path.isfile(local_path):
 | 
				
			||||||
        return local_path
 | 
					        return local_path
 | 
				
			||||||
@@ -415,9 +415,9 @@ def get_local_file_path(path_id: Text) -> Optional[Text]:
 | 
				
			|||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocalUploadBackend(ZulipUploadBackend):
 | 
					class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			||||||
    def upload_message_file(self, uploaded_file_name: Text, uploaded_file_size: int,
 | 
					    def upload_message_file(self, uploaded_file_name: str, uploaded_file_size: int,
 | 
				
			||||||
                            content_type: Optional[Text], file_data: bytes,
 | 
					                            content_type: Optional[str], file_data: bytes,
 | 
				
			||||||
                            user_profile: UserProfile, target_realm: Optional[Realm]=None) -> Text:
 | 
					                            user_profile: UserProfile, target_realm: Optional[Realm]=None) -> str:
 | 
				
			||||||
        # Split into 256 subdirectories to prevent directories from getting too big
 | 
					        # Split into 256 subdirectories to prevent directories from getting too big
 | 
				
			||||||
        path = "/".join([
 | 
					        path = "/".join([
 | 
				
			||||||
            str(user_profile.realm_id),
 | 
					            str(user_profile.realm_id),
 | 
				
			||||||
@@ -430,7 +430,7 @@ class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        create_attachment(uploaded_file_name, path, user_profile, uploaded_file_size)
 | 
					        create_attachment(uploaded_file_name, path, user_profile, uploaded_file_size)
 | 
				
			||||||
        return '/user_uploads/' + path
 | 
					        return '/user_uploads/' + path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_message_image(self, path_id: Text) -> bool:
 | 
					    def delete_message_image(self, path_id: str) -> bool:
 | 
				
			||||||
        file_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'files', path_id)
 | 
					        file_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'files', path_id)
 | 
				
			||||||
        if os.path.isfile(file_path):
 | 
					        if os.path.isfile(file_path):
 | 
				
			||||||
            # This removes the file but the empty folders still remain.
 | 
					            # This removes the file but the empty folders still remain.
 | 
				
			||||||
@@ -455,7 +455,7 @@ class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)
 | 
					        resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)
 | 
				
			||||||
        write_local_file('avatars', file_path + '-medium.png', resized_medium)
 | 
					        write_local_file('avatars', file_path + '-medium.png', resized_medium)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_avatar_url(self, hash_key: Text, medium: bool=False) -> Text:
 | 
					    def get_avatar_url(self, hash_key: str, medium: bool=False) -> str:
 | 
				
			||||||
        # ?x=x allows templates to append additional parameters with &s
 | 
					        # ?x=x allows templates to append additional parameters with &s
 | 
				
			||||||
        medium_suffix = "-medium" if medium else ""
 | 
					        medium_suffix = "-medium" if medium else ""
 | 
				
			||||||
        return "/user_avatars/%s%s.png?x=x" % (hash_key, medium_suffix)
 | 
					        return "/user_avatars/%s%s.png?x=x" % (hash_key, medium_suffix)
 | 
				
			||||||
@@ -472,7 +472,7 @@ class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        resized_data = resize_avatar(image_data)
 | 
					        resized_data = resize_avatar(image_data)
 | 
				
			||||||
        write_local_file(upload_path, 'icon.png', resized_data)
 | 
					        write_local_file(upload_path, 'icon.png', resized_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_realm_icon_url(self, realm_id: int, version: int) -> Text:
 | 
					    def get_realm_icon_url(self, realm_id: int, version: int) -> str:
 | 
				
			||||||
        # ?x=x allows templates to append additional parameters with &s
 | 
					        # ?x=x allows templates to append additional parameters with &s
 | 
				
			||||||
        return "/user_avatars/%s/realm/icon.png?version=%s" % (realm_id, version)
 | 
					        return "/user_avatars/%s/realm/icon.png?version=%s" % (realm_id, version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -488,7 +488,7 @@ class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
        resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)
 | 
					        resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)
 | 
				
			||||||
        write_local_file('avatars', file_path + '-medium.png', resized_medium)
 | 
					        write_local_file('avatars', file_path + '-medium.png', resized_medium)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def upload_emoji_image(self, emoji_file: File, emoji_file_name: Text,
 | 
					    def upload_emoji_image(self, emoji_file: File, emoji_file_name: str,
 | 
				
			||||||
                           user_profile: UserProfile) -> None:
 | 
					                           user_profile: UserProfile) -> None:
 | 
				
			||||||
        emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(
 | 
					        emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(
 | 
				
			||||||
            realm_id= user_profile.realm_id,
 | 
					            realm_id= user_profile.realm_id,
 | 
				
			||||||
@@ -506,7 +506,7 @@ class LocalUploadBackend(ZulipUploadBackend):
 | 
				
			|||||||
            emoji_path,
 | 
					            emoji_path,
 | 
				
			||||||
            resized_image_data)
 | 
					            resized_image_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_emoji_url(self, emoji_file_name: Text, realm_id: int) -> Text:
 | 
					    def get_emoji_url(self, emoji_file_name: str, realm_id: int) -> str:
 | 
				
			||||||
        return os.path.join(
 | 
					        return os.path.join(
 | 
				
			||||||
            "/user_avatars",
 | 
					            "/user_avatars",
 | 
				
			||||||
            RealmEmoji.PATH_ID_TEMPLATE.format(realm_id=realm_id, emoji_file_name=emoji_file_name))
 | 
					            RealmEmoji.PATH_ID_TEMPLATE.format(realm_id=realm_id, emoji_file_name=emoji_file_name))
 | 
				
			||||||
@@ -517,7 +517,7 @@ if settings.LOCAL_UPLOADS_DIR is not None:
 | 
				
			|||||||
else:
 | 
					else:
 | 
				
			||||||
    upload_backend = S3UploadBackend()
 | 
					    upload_backend = S3UploadBackend()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def delete_message_image(path_id: Text) -> bool:
 | 
					def delete_message_image(path_id: str) -> bool:
 | 
				
			||||||
    return upload_backend.delete_message_image(path_id)
 | 
					    return upload_backend.delete_message_image(path_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_avatar_image(user_file: File, acting_user_profile: UserProfile,
 | 
					def upload_avatar_image(user_file: File, acting_user_profile: UserProfile,
 | 
				
			||||||
@@ -527,18 +527,18 @@ def upload_avatar_image(user_file: File, acting_user_profile: UserProfile,
 | 
				
			|||||||
def upload_icon_image(user_file: File, user_profile: UserProfile) -> None:
 | 
					def upload_icon_image(user_file: File, user_profile: UserProfile) -> None:
 | 
				
			||||||
    upload_backend.upload_realm_icon_image(user_file, user_profile)
 | 
					    upload_backend.upload_realm_icon_image(user_file, user_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_emoji_image(emoji_file: File, emoji_file_name: Text, user_profile: UserProfile) -> None:
 | 
					def upload_emoji_image(emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
 | 
				
			||||||
    upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile)
 | 
					    upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_message_file(uploaded_file_name: Text, uploaded_file_size: int,
 | 
					def upload_message_file(uploaded_file_name: str, uploaded_file_size: int,
 | 
				
			||||||
                        content_type: Optional[Text], file_data: bytes,
 | 
					                        content_type: Optional[str], file_data: bytes,
 | 
				
			||||||
                        user_profile: UserProfile, target_realm: Optional[Realm]=None) -> Text:
 | 
					                        user_profile: UserProfile, target_realm: Optional[Realm]=None) -> str:
 | 
				
			||||||
    return upload_backend.upload_message_file(uploaded_file_name, uploaded_file_size,
 | 
					    return upload_backend.upload_message_file(uploaded_file_name, uploaded_file_size,
 | 
				
			||||||
                                              content_type, file_data, user_profile,
 | 
					                                              content_type, file_data, user_profile,
 | 
				
			||||||
                                              target_realm=target_realm)
 | 
					                                              target_realm=target_realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def claim_attachment(user_profile: UserProfile,
 | 
					def claim_attachment(user_profile: UserProfile,
 | 
				
			||||||
                     path_id: Text,
 | 
					                     path_id: str,
 | 
				
			||||||
                     message: Message,
 | 
					                     message: Message,
 | 
				
			||||||
                     is_message_realm_public: bool) -> Attachment:
 | 
					                     is_message_realm_public: bool) -> Attachment:
 | 
				
			||||||
    attachment = Attachment.objects.get(path_id=path_id)
 | 
					    attachment = Attachment.objects.get(path_id=path_id)
 | 
				
			||||||
@@ -547,7 +547,7 @@ def claim_attachment(user_profile: UserProfile,
 | 
				
			|||||||
    attachment.save()
 | 
					    attachment.save()
 | 
				
			||||||
    return attachment
 | 
					    return attachment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_attachment(file_name: Text, path_id: Text, user_profile: UserProfile,
 | 
					def create_attachment(file_name: str, path_id: str, user_profile: UserProfile,
 | 
				
			||||||
                      file_size: int) -> bool:
 | 
					                      file_size: int) -> bool:
 | 
				
			||||||
    attachment = Attachment.objects.create(file_name=file_name, path_id=path_id, owner=user_profile,
 | 
					    attachment = Attachment.objects.create(file_name=file_name, path_id=path_id, owner=user_profile,
 | 
				
			||||||
                                           realm=user_profile.realm, size=file_size)
 | 
					                                           realm=user_profile.realm, size=file_size)
 | 
				
			||||||
@@ -556,7 +556,7 @@ def create_attachment(file_name: Text, path_id: Text, user_profile: UserProfile,
 | 
				
			|||||||
    return True
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_message_image_from_request(request: HttpRequest, user_file: File,
 | 
					def upload_message_image_from_request(request: HttpRequest, user_file: File,
 | 
				
			||||||
                                      user_profile: UserProfile) -> Text:
 | 
					                                      user_profile: UserProfile) -> str:
 | 
				
			||||||
    uploaded_file_name, uploaded_file_size, content_type = get_file_info(request, user_file)
 | 
					    uploaded_file_name, uploaded_file_size, content_type = get_file_info(request, user_file)
 | 
				
			||||||
    return upload_message_file(uploaded_file_name, uploaded_file_size,
 | 
					    return upload_message_file(uploaded_file_name, uploaded_file_size,
 | 
				
			||||||
                               content_type, user_file.read(), user_profile)
 | 
					                               content_type, user_file.read(), user_profile)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Dict, List, Optional, Text
 | 
					from typing import Dict, List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.db.models.query import QuerySet
 | 
					from django.db.models.query import QuerySet
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
@@ -11,7 +11,7 @@ from zerver.models import UserProfile, Service, Realm, \
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from zulip_bots.custom_exceptions import ConfigValidationError
 | 
					from zulip_bots.custom_exceptions import ConfigValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_full_name(full_name_raw: Text) -> Text:
 | 
					def check_full_name(full_name_raw: str) -> str:
 | 
				
			||||||
    full_name = full_name_raw.strip()
 | 
					    full_name = full_name_raw.strip()
 | 
				
			||||||
    if len(full_name) > UserProfile.MAX_NAME_LENGTH:
 | 
					    if len(full_name) > UserProfile.MAX_NAME_LENGTH:
 | 
				
			||||||
        raise JsonableError(_("Name too long!"))
 | 
					        raise JsonableError(_("Name too long!"))
 | 
				
			||||||
@@ -21,7 +21,7 @@ def check_full_name(full_name_raw: Text) -> Text:
 | 
				
			|||||||
        raise JsonableError(_("Invalid characters in name!"))
 | 
					        raise JsonableError(_("Invalid characters in name!"))
 | 
				
			||||||
    return full_name
 | 
					    return full_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_short_name(short_name_raw: Text) -> Text:
 | 
					def check_short_name(short_name_raw: str) -> str:
 | 
				
			||||||
    short_name = short_name_raw.strip()
 | 
					    short_name = short_name_raw.strip()
 | 
				
			||||||
    if len(short_name) == 0:
 | 
					    if len(short_name) == 0:
 | 
				
			||||||
        raise JsonableError(_("Bad name or username"))
 | 
					        raise JsonableError(_("Bad name or username"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Callable, List, Optional, Sequence, TypeVar, Iterable, Set, Tuple, Text
 | 
					from typing import Any, Callable, List, Optional, Sequence, TypeVar, Iterable, Set, Tuple
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
import errno
 | 
					import errno
 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
@@ -85,8 +85,8 @@ def run_in_batches(all_list: Sequence[T],
 | 
				
			|||||||
        if i != limit - 1:
 | 
					        if i != limit - 1:
 | 
				
			||||||
            sleep(sleep_time)
 | 
					            sleep(sleep_time)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def make_safe_digest(string: Text,
 | 
					def make_safe_digest(string: str,
 | 
				
			||||||
                     hash_func: Callable[[bytes], Any]=hashlib.sha1) -> Text:
 | 
					                     hash_func: Callable[[bytes], Any]=hashlib.sha1) -> str:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return a hex digest of `string`.
 | 
					    return a hex digest of `string`.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -178,7 +178,7 @@ def split_by(array: List[Any], group_size: int, filler: Any) -> List[List[Any]]:
 | 
				
			|||||||
    args = [iter(array)] * group_size
 | 
					    args = [iter(array)] * group_size
 | 
				
			||||||
    return list(map(list, zip_longest(*args, fillvalue=filler)))
 | 
					    return list(map(list, zip_longest(*args, fillvalue=filler)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_remote_server(identifier: Text) -> bool:
 | 
					def is_remote_server(identifier: str) -> bool:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    This function can be used to identify the source of API auth
 | 
					    This function can be used to identify the source of API auth
 | 
				
			||||||
    request. We can have two types of sources, Remote Zulip Servers
 | 
					    request. We can have two types of sources, Remote Zulip Servers
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,7 @@ import ujson
 | 
				
			|||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
from django.core.validators import validate_email, URLValidator
 | 
					from django.core.validators import validate_email, URLValidator
 | 
				
			||||||
from typing import Callable, Iterable, Optional, Tuple, TypeVar, Text, cast, \
 | 
					from typing import Callable, Iterable, Optional, Tuple, TypeVar, cast, \
 | 
				
			||||||
    Dict
 | 
					    Dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
@@ -189,7 +189,7 @@ def equals(expected_val: object) -> Validator:
 | 
				
			|||||||
        return None
 | 
					        return None
 | 
				
			||||||
    return f
 | 
					    return f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_login_email(email: Text) -> None:
 | 
					def validate_login_email(email: str) -> None:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        validate_email(email)
 | 
					        validate_email(email)
 | 
				
			||||||
    except ValidationError as err:
 | 
					    except ValidationError as err:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.http import HttpRequest
 | 
					from django.http import HttpRequest
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from typing import Optional, Text
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.actions import check_send_stream_message, \
 | 
					from zerver.lib.actions import check_send_stream_message, \
 | 
				
			||||||
    check_send_private_message, send_rate_limited_pm_notification_to_bot_owner
 | 
					    check_send_private_message, send_rate_limited_pm_notification_to_bot_owner
 | 
				
			||||||
@@ -28,7 +28,7 @@ class MissingHTTPEventHeader(JsonableError):
 | 
				
			|||||||
    code = ErrorCode.MISSING_HTTP_EVENT_HEADER
 | 
					    code = ErrorCode.MISSING_HTTP_EVENT_HEADER
 | 
				
			||||||
    data_fields = ['header']
 | 
					    data_fields = ['header']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, header: Text) -> None:
 | 
					    def __init__(self, header: str) -> None:
 | 
				
			||||||
        self.header = header
 | 
					        self.header = header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -38,8 +38,8 @@ class MissingHTTPEventHeader(JsonableError):
 | 
				
			|||||||
@has_request_variables
 | 
					@has_request_variables
 | 
				
			||||||
def check_send_webhook_message(
 | 
					def check_send_webhook_message(
 | 
				
			||||||
        request: HttpRequest, user_profile: UserProfile,
 | 
					        request: HttpRequest, user_profile: UserProfile,
 | 
				
			||||||
        topic: Text, body: Text, stream: Optional[Text]=REQ(default=None),
 | 
					        topic: str, body: str, stream: Optional[str]=REQ(default=None),
 | 
				
			||||||
        user_specified_topic: Optional[Text]=REQ("topic", default=None)
 | 
					        user_specified_topic: Optional[str]=REQ("topic", default=None)
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if stream is None:
 | 
					    if stream is None:
 | 
				
			||||||
@@ -60,8 +60,8 @@ def check_send_webhook_message(
 | 
				
			|||||||
            # webhook-errors.log
 | 
					            # webhook-errors.log
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_extract_webhook_http_header(request: HttpRequest, header: Text,
 | 
					def validate_extract_webhook_http_header(request: HttpRequest, header: str,
 | 
				
			||||||
                                         integration_name: Text) -> Text:
 | 
					                                         integration_name: str) -> str:
 | 
				
			||||||
    extracted_header = request.META.get(DJANGO_HTTP_PREFIX + header)
 | 
					    extracted_header = request.META.get(DJANGO_HTTP_PREFIX + header)
 | 
				
			||||||
    if extracted_header is None:
 | 
					    if extracted_header is None:
 | 
				
			||||||
        message_body = MISSING_EVENT_HEADER_MESSAGE.format(
 | 
					        message_body = MISSING_EVENT_HEADER_MESSAGE.format(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user