mirror of https://github.com/zulip/zulip.git
python: Convert more variable type annotations to Python 3.6 style.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
708c6f4f11
commit
8cdf2801f7
|
@ -1024,7 +1024,7 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
|
|||
@require_server_admin
|
||||
@has_request_variables
|
||||
def get_activity(request: HttpRequest) -> HttpResponse:
|
||||
duration_content, realm_minutes = user_activity_intervals() # type: Tuple[mark_safe, Dict[str, float]]
|
||||
duration_content, realm_minutes = user_activity_intervals()
|
||||
counts_content: str = realm_summary_table(realm_minutes)
|
||||
data = [
|
||||
('Counts', counts_content),
|
||||
|
|
|
@ -102,9 +102,7 @@ automatically in Zulip's automated test suite. The code there will
|
|||
look something like this:
|
||||
|
||||
``` python
|
||||
def render_message(client):
|
||||
# type: (Client) -> None
|
||||
|
||||
def render_message(client: Client) -> None:
|
||||
# {code_example|start}
|
||||
# Render a message
|
||||
request = {
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
[mypy](http://mypy-lang.org/) is a compile-time static type checker
|
||||
for Python, allowing optional, gradual typing of Python code. Zulip
|
||||
was fully annotated with mypy's Python 2 syntax in 2016, before our
|
||||
migration to Python 3 in late 2017. In 2018, we migrated essentially
|
||||
the entire codebase to the nice PEP-484 (Python 3 only) syntax for
|
||||
static types:
|
||||
migration to Python 3 in late 2017. In 2018 and 2020, we migrated
|
||||
essentially the entire codebase to the nice PEP 484 (Python 3 only)
|
||||
and PEP 526 (Python 3.6) syntax for static types:
|
||||
|
||||
```
|
||||
user_dict = {} # type: Dict[str, UserProfile]
|
||||
user_dict: Dict[str, UserProfile] = {}
|
||||
|
||||
def get_user(email: str, realm: Realm) -> UserProfile:
|
||||
... # Actual code of the function here
|
||||
|
|
|
@ -173,9 +173,9 @@ boolean field, `mandatory_topics`, to the Realm model in
|
|||
|
||||
class Realm(models.Model):
|
||||
# ...
|
||||
emails_restricted_to_domains = models.BooleanField(default=True) # type: bool
|
||||
invite_required = models.BooleanField(default=False) # type: bool
|
||||
+ mandatory_topics = models.BooleanField(default=False) # type: bool
|
||||
emails_restricted_to_domains: bool = models.BooleanField(default=True)
|
||||
invite_required: bool = models.BooleanField(default=False)
|
||||
+ mandatory_topics: bool = models.BooleanField(default=False)
|
||||
```
|
||||
|
||||
The Realm model also contains an attribute, `property_types`, which
|
||||
|
@ -405,12 +405,15 @@ annotation).
|
|||
|
||||
# zerver/views/realm.py
|
||||
|
||||
def update_realm(request, user_profile, name=REQ(validator=check_string, default=None),
|
||||
# ...,
|
||||
+ mandatory_topics=REQ(validator=check_bool, default=None),
|
||||
# ...):
|
||||
+ # type: (HttpRequest, UserProfile, ..., Optional[bool], ...
|
||||
# ...
|
||||
def update_realm(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
name: Optional[str] = REQ(validator=check_string, default=None),
|
||||
# ...
|
||||
+ mandatory_topics: Optional[bool] = REQ(validator=check_bool, default=None),
|
||||
# ...
|
||||
):
|
||||
# ...
|
||||
```
|
||||
|
||||
If this feature fits the `property_types` framework and does
|
||||
|
|
|
@ -68,8 +68,7 @@ views, as an introduction to view decorators.
|
|||
```py
|
||||
|
||||
@require_post
|
||||
def accounts_register(request):
|
||||
# type: (HttpRequest) -> HttpResponse
|
||||
def accounts_register(request: HttpRequest) -> HttpResponse:
|
||||
```
|
||||
|
||||
This decorator ensures that the request was a POST--here, we're
|
||||
|
@ -91,8 +90,7 @@ specific to Zulip.
|
|||
|
||||
```py
|
||||
@zulip_login_required
|
||||
def home(request):
|
||||
# type: (HttpRequest) -> HttpResponse
|
||||
def home(request: HttpRequest) -> HttpResponse:
|
||||
```
|
||||
|
||||
[login-required-link]: https://docs.djangoproject.com/en/1.8/topics/auth/default/#django.contrib.auth.decorators.login_required
|
||||
|
@ -261,29 +259,15 @@ For example, in [zerver/views/realm.py](https://github.com/zulip/zulip/blob/mast
|
|||
```py
|
||||
@require_realm_admin
|
||||
@has_request_variables
|
||||
def update_realm(request, user_profile, name=REQ(validator=check_string, default=None), ...)):
|
||||
# type: (HttpRequest, UserProfile, ...) -> HttpResponse
|
||||
def update_realm(
|
||||
request: HttpRequest, user_profile: UserProfile,
|
||||
name: Optional[str]=REQ(validator=check_string, default=None),
|
||||
# ...
|
||||
):
|
||||
realm = user_profile.realm
|
||||
data = {} # type: Dict[str, Any]
|
||||
if name is not None and realm.name != name:
|
||||
do_set_realm_name(realm, name)
|
||||
data['name'] = 'updated'
|
||||
```
|
||||
|
||||
and in [zerver/lib/actions.py](https://github.com/zulip/zulip/blob/master/zerver/lib/actions.py):
|
||||
|
||||
```py
|
||||
def do_set_realm_name(realm, name):
|
||||
# type: (Realm, str) -> None
|
||||
realm.name = name
|
||||
realm.save(update_fields=['name'])
|
||||
event = dict(
|
||||
type="realm",
|
||||
op="update",
|
||||
property='name',
|
||||
value=name,
|
||||
)
|
||||
send_event(realm, event, active_user_ids(realm))
|
||||
# ...
|
||||
do_set_realm_property(realm, k, v)
|
||||
# ...
|
||||
```
|
||||
|
||||
`realm.save()` actually saves the changes to the realm to the
|
||||
|
|
|
@ -53,7 +53,7 @@ with open('/var/lib/nagios_state/last_postgres_backup', 'w') as f:
|
|||
f.write(now.isoformat())
|
||||
f.write("\n")
|
||||
|
||||
backups = {} # type: Dict[datetime, str]
|
||||
backups: Dict[datetime, str] = {}
|
||||
lines = run(['env-wal-e', 'backup-list']).split("\n")
|
||||
for line in lines[1:]:
|
||||
if line:
|
||||
|
|
|
@ -392,7 +392,7 @@ refactor them.
|
|||
from zulip_bots.test_lib import StubBotTestCase
|
||||
|
||||
class TestHelpBot(StubBotTestCase):
|
||||
bot_name = "helloworld" # type: str
|
||||
bot_name: str = "helloworld"
|
||||
|
||||
def test_bot(self) -> None:
|
||||
dialog = [
|
||||
|
|
|
@ -417,26 +417,26 @@ python_rules = RuleList(
|
|||
},
|
||||
'description': 'Comment-style function type annotation. Use Python3 style annotations instead.',
|
||||
},
|
||||
{'pattern': r' = models[.].*null=True.*\) # type: (?!Optional)',
|
||||
{'pattern': r': *(?!Optional)[^ ].*= models[.].*null=True',
|
||||
'include_only': {"zerver/models.py"},
|
||||
'description': 'Model variable with null=true not annotated as Optional.',
|
||||
'good_lines': ['desc = models.TextField(null=True) # type: Optional[Text]',
|
||||
'stream = models.ForeignKey(Stream, null=True, on_delete=CASCADE) # type: Optional[Stream]',
|
||||
'desc = models.TextField() # type: Text',
|
||||
'stream = models.ForeignKey(Stream, on_delete=CASCADE) # type: Stream'],
|
||||
'bad_lines': ['desc = models.CharField(null=True) # type: Text',
|
||||
'stream = models.ForeignKey(Stream, null=True, on_delete=CASCADE) # type: Stream'],
|
||||
'good_lines': ['desc: Optional[Text] = models.TextField(null=True)',
|
||||
'stream: Optional[Stream] = models.ForeignKey(Stream, null=True, on_delete=CASCADE)',
|
||||
'desc: Text = models.TextField()',
|
||||
'stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE)'],
|
||||
'bad_lines': ['desc: Text = models.CharField(null=True)',
|
||||
'stream: Stream = models.ForeignKey(Stream, null=True, on_delete=CASCADE)'],
|
||||
},
|
||||
{'pattern': r' = models[.].* # type: Optional', # Optional tag
|
||||
{'pattern': r': *Optional.*= models[.].*\)',
|
||||
'exclude_pattern': 'null=True',
|
||||
'include_only': {"zerver/models.py"},
|
||||
'description': 'Model variable annotated with Optional but variable does not have null=true.',
|
||||
'good_lines': ['desc = models.TextField(null=True) # type: Optional[Text]',
|
||||
'stream = models.ForeignKey(Stream, null=True, on_delete=CASCADE) # type: Optional[Stream]',
|
||||
'desc = models.TextField() # type: Text',
|
||||
'stream = models.ForeignKey(Stream, on_delete=CASCADE) # type: Stream'],
|
||||
'bad_lines': ['desc = models.TextField() # type: Optional[Text]',
|
||||
'stream = models.ForeignKey(Stream, on_delete=CASCADE) # type: Optional[Stream]'],
|
||||
'good_lines': ['desc: Optional[Text] = models.TextField(null=True)',
|
||||
'stream: Optional[Stream] = models.ForeignKey(Stream, null=True, on_delete=CASCADE)',
|
||||
'desc: Text = models.TextField()',
|
||||
'stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE)'],
|
||||
'bad_lines': ['desc: Optional[Text] = models.TextField()',
|
||||
'stream: Optional[Stream] = models.ForeignKey(Stream, on_delete=CASCADE)'],
|
||||
},
|
||||
{'pattern': r'[\s([]Text([^\s\w]|$)',
|
||||
'exclude': {
|
||||
|
|
|
@ -30,9 +30,9 @@ INACTIVE_ENTRY = (
|
|||
|
||||
FILE_TEMPLATE = (
|
||||
"from typing import Any, Dict\n\n"
|
||||
"EMOJI_NAME_MAPS = {"
|
||||
"EMOJI_NAME_MAPS: Dict[str, Dict[str, Any]] = {"
|
||||
"%(emoji_entries)s\n"
|
||||
"} # type: Dict[str, Dict[str, Any]]\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
emoji_names: Set[str] = set()
|
||||
|
|
|
@ -10,7 +10,7 @@ from typing import Dict, Iterable, List
|
|||
def alert_words_in_realm(realm: Realm) -> Dict[int, List[str]]:
|
||||
user_ids_and_words = AlertWord.objects.filter(
|
||||
realm=realm, user_profile__is_active=True).values("user_profile_id", "word")
|
||||
user_ids_with_words = dict() # type: Dict[int, List[str]]
|
||||
user_ids_with_words: Dict[int, List[str]] = dict()
|
||||
for id_and_word in user_ids_and_words:
|
||||
user_ids_with_words.setdefault(id_and_word["user_profile_id"], [])
|
||||
user_ids_with_words[id_and_word["user_profile_id"]].append(id_and_word["word"])
|
||||
|
|
|
@ -68,7 +68,7 @@ class JsonableError(Exception):
|
|||
data_fields = ['widget_name']
|
||||
|
||||
def __init__(self, widget_name: str) -> None:
|
||||
self.widget_name = widget_name # type: str
|
||||
self.widget_name: str = widget_name
|
||||
|
||||
@staticmethod
|
||||
def msg_format() -> str:
|
||||
|
|
|
@ -33,7 +33,10 @@ def move_missed_message_addresses_to_database(apps: StateApps, schema_editor: Da
|
|||
redis_client.delete(key)
|
||||
continue
|
||||
|
||||
user_profile_id, recipient_id, subject_b = result # type: (bytes, bytes, bytes)
|
||||
user_profile_id: bytes
|
||||
recipient_id: bytes
|
||||
subject_id: bytes
|
||||
user_profile_id, recipient_id, subject_b = result
|
||||
topic_name = subject_b.decode('utf-8')
|
||||
|
||||
# The data model for missed-message emails has changed in two
|
||||
|
|
|
@ -28,7 +28,7 @@ def move_back_to_user_profile(apps: StateApps, schema_editor: DatabaseSchemaEdit
|
|||
UserProfile = apps.get_model('zerver', 'UserProfile')
|
||||
|
||||
user_ids_and_words = AlertWord.objects.all().values("user_profile_id", "word")
|
||||
user_ids_with_words = dict() # type: Dict[int, List[str]]
|
||||
user_ids_with_words: Dict[int, List[str]] = dict()
|
||||
|
||||
for id_and_word in user_ids_and_words:
|
||||
user_ids_with_words.setdefault(id_and_word["user_profile_id"], [])
|
||||
|
|
|
@ -2894,10 +2894,10 @@ class AlertWord(models.Model):
|
|||
# never move to another realm, so it's static, and having Realm
|
||||
# here optimizes the main query on this table, which is fetching
|
||||
# all the alert words in a realm.
|
||||
realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE) # type: Realm
|
||||
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile
|
||||
realm: Realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE)
|
||||
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
|
||||
# Case-insensitive name for the alert word.
|
||||
word = models.TextField() # type: str
|
||||
word: str = models.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user_profile", "word")
|
||||
|
|
|
@ -2229,11 +2229,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
|
||||
def test_redirect_to_next_url_for_log_into_subdomain(self) -> None:
|
||||
def test_redirect_to_next_url(next: str='') -> HttpResponse:
|
||||
data = {'full_name': 'Hamlet',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': next} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'Hamlet',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': next,
|
||||
}
|
||||
user_profile = self.example_user('hamlet')
|
||||
with mock.patch(
|
||||
'zerver.views.auth.authenticate',
|
||||
|
@ -2254,22 +2256,26 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
self.assertEqual(res.url, 'http://zulip.testserver/#narrow/stream/7-test-here')
|
||||
|
||||
def test_log_into_subdomain_when_token_is_malformed(self) -> None:
|
||||
data = {'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': '',
|
||||
}
|
||||
with mock.patch("logging.warning") as mock_warn:
|
||||
result = self.get_log_into_subdomain(data, force_token='nonsense')
|
||||
mock_warn.assert_called_once_with("log_into_subdomain: Malformed token given: %s", "nonsense")
|
||||
self.assertEqual(result.status_code, 400)
|
||||
|
||||
def test_log_into_subdomain_when_token_not_found(self) -> None:
|
||||
data = {'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': '',
|
||||
}
|
||||
with mock.patch("logging.warning") as mock_warn:
|
||||
token = generate_random_token(ExternalAuthResult.LOGIN_TOKEN_LENGTH)
|
||||
result = self.get_log_into_subdomain(data, force_token=token)
|
||||
|
@ -2283,11 +2289,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
existing_user.email = 'whatever@zulip.com'
|
||||
existing_user.save()
|
||||
|
||||
data = {'full_name': 'Full Name',
|
||||
'email': 'existing@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'Full Name',
|
||||
'email': 'existing@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': '',
|
||||
}
|
||||
result = self.get_log_into_subdomain(data)
|
||||
|
||||
# Should simply get logged into the existing account:
|
||||
|
@ -2295,11 +2303,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
self.assert_logged_in_user_id(existing_user.id)
|
||||
|
||||
def test_log_into_subdomain_when_is_signup_is_true_and_new_user(self) -> None:
|
||||
data = {'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': '',
|
||||
}
|
||||
result = self.get_log_into_subdomain(data)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
confirmation = Confirmation.objects.all().first()
|
||||
|
@ -2318,11 +2328,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
self.assert_in_success_response(['id_full_name'], result)
|
||||
|
||||
def test_log_into_subdomain_when_is_signup_is_false_and_new_user(self) -> None:
|
||||
data = {'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': '',
|
||||
}
|
||||
result = self.get_log_into_subdomain(data)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assert_in_response('No account found for', result)
|
||||
|
@ -2346,11 +2358,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
self.assert_in_success_response(['id_full_name'], result)
|
||||
|
||||
def test_log_into_subdomain_when_using_invite_link(self) -> None:
|
||||
data = {'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'New User Name',
|
||||
'email': 'new@zulip.com',
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': True,
|
||||
'redirect_to': '',
|
||||
}
|
||||
|
||||
realm = get_realm("zulip")
|
||||
realm.invite_required = True
|
||||
|
@ -2406,11 +2420,13 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
self.assertEqual(sorted(new_streams), stream_names)
|
||||
|
||||
def test_log_into_subdomain_when_email_is_none(self) -> None:
|
||||
data = {'full_name': None,
|
||||
'email': None,
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': ''} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': None,
|
||||
'email': None,
|
||||
'subdomain': 'zulip',
|
||||
'is_signup': False,
|
||||
'redirect_to': '',
|
||||
}
|
||||
|
||||
with mock.patch('logging.warning') as mock_warn:
|
||||
result = self.get_log_into_subdomain(data)
|
||||
|
@ -2418,9 +2434,11 @@ class GoogleAuthBackendTest(SocialAuthBase):
|
|||
mock_warn.assert_called_once()
|
||||
|
||||
def test_user_cannot_log_into_wrong_subdomain(self) -> None:
|
||||
data = {'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zephyr'} # type: ExternalAuthDataDict
|
||||
data: ExternalAuthDataDict = {
|
||||
'full_name': 'Full Name',
|
||||
'email': self.example_email("hamlet"),
|
||||
'subdomain': 'zephyr',
|
||||
}
|
||||
result = self.get_log_into_subdomain(data)
|
||||
self.assert_json_error(result, "Invalid subdomain")
|
||||
|
||||
|
|
|
@ -677,7 +677,8 @@ class SlackImporter(ZulipTestCase):
|
|||
realm: Dict[str, Any] = {'zerver_subscription': []}
|
||||
user_list: List[Dict[str, Any]] = []
|
||||
reactions = [{"name": "grinning", "users": ["U061A5N1G"], "count": 1}]
|
||||
attachments = uploads = [] # type: List[Dict[str, Any]]
|
||||
attachments: List[Dict[str, Any]] = []
|
||||
uploads: List[Dict[str, Any]] = []
|
||||
|
||||
zerver_usermessage = [{'id': 3}, {'id': 5}, {'id': 6}, {'id': 9}]
|
||||
|
||||
|
|
|
@ -47,7 +47,9 @@ def _transform_commits_list_to_common_format(commits: List[Dict[str, Any]]) -> L
|
|||
def beanstalk_decoder(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@wraps(view_func)
|
||||
def _wrapped_view_func(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
auth_type, encoded_value = request.META['HTTP_AUTHORIZATION'].split() # type: str, str
|
||||
auth_type: str
|
||||
encoded_value: str
|
||||
auth_type, encoded_value = request.META['HTTP_AUTHORIZATION'].split()
|
||||
if auth_type.lower() == "basic":
|
||||
email, api_key = base64.b64decode(encoded_value).decode('utf-8').split(":")
|
||||
email = email.replace('%40', '@')
|
||||
|
|
Loading…
Reference in New Issue