mirror of
https://github.com/zulip/zulip.git
synced 2025-11-09 16:37:23 +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