Skip to content

Commit 6c6155b

Browse files
authored
Implement mypy annotations (#557)
Squashed commit: see https://github.com/64bitpandas/ocfweb/tree/mypy for full commit history * Add mypy annotations * Upgrade mypy version to 0.730 * Replace request: Any with request: HttpRequest * Add warn_unused_ignores, clean up comments * Fix form._errors call, clean comments/typeignores * Remove field_order unnecessary comments
1 parent b05d4cd commit 6c6155b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+641
-352
lines changed

mypy.ini

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
[mypy]
2-
# TODO: enable more flags like in https://github.com/ocf/slackbridge/blob/master/mypy.ini
32
show_traceback = True
43
ignore_missing_imports = True
4+
check_untyped_defs = True
5+
56
plugins =
67
mypy_django_plugin.main
8+
9+
disallow_untyped_defs = True
10+
disallow_untyped_calls = True
11+
disallow_any_generics = True
12+
13+
warn_no_return = True
14+
warn_redundant_casts = True
15+
warn_unused_configs = True
16+
warn_unused_ignores = True
17+
18+
[mypy.plugins.django-stubs]
19+
django_settings_module = ocfweb.settings

ocfweb/about/lab.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from django.http import HttpRequest
2+
from django.http import HttpResponse
13
from django.shortcuts import render
24

35

4-
def lab_open_source(request):
6+
def lab_open_source(request: HttpRequest) -> HttpResponse:
57
return render(
68
request,
79
'about/lab-open-source.html',
@@ -11,7 +13,7 @@ def lab_open_source(request):
1113
)
1214

1315

14-
def lab_vote(request):
16+
def lab_vote(request: HttpRequest) -> HttpResponse:
1517
return render(
1618
request,
1719
'about/lab-vote.html',

ocfweb/about/staff.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from django.http import HttpRequest
2+
from django.http import HttpResponse
13
from django.shortcuts import render
24

35

4-
def about_staff(request):
6+
def about_staff(request: HttpRequest) -> HttpResponse:
57
return render(
68
request,
79
'about/staff.html',

ocfweb/account/chpass.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
from typing import Any
2+
from typing import Iterator
3+
from typing import List
4+
15
from django import forms
6+
from django.http import HttpRequest
7+
from django.http import HttpResponse
28
from django.shortcuts import render
39
from django.template.loader import render_to_string
410
from django.utils.safestring import mark_safe
@@ -15,15 +21,14 @@
1521
from ocfweb.component.celery import change_password as change_password_task
1622
from ocfweb.component.forms import Form
1723

18-
1924
CALLINK_ERROR_MSG = (
2025
"Couldn't connect to CalLink API. Resetting group "
2126
'account passwords online is unavailable.'
2227
)
2328

2429

25-
def get_accounts_signatory_for(calnet_uid):
26-
def flatten(lst):
30+
def get_accounts_signatory_for(calnet_uid: str) -> List[Any]:
31+
def flatten(lst: Iterator[Any]) -> List[Any]:
2732
return [item for sublist in lst for item in sublist]
2833

2934
group_accounts = flatten(
@@ -40,7 +45,7 @@ def flatten(lst):
4045
return group_accounts
4146

4247

43-
def get_accounts_for(calnet_uid):
48+
def get_accounts_for(calnet_uid: str) -> List[Any]:
4449
accounts = users_by_calnet_uid(calnet_uid)
4550

4651
if calnet_uid in TESTER_CALNET_UIDS:
@@ -51,7 +56,7 @@ def get_accounts_for(calnet_uid):
5156

5257

5358
@calnet_required
54-
def change_password(request):
59+
def change_password(request: HttpRequest) -> HttpResponse:
5560
calnet_uid = request.session['calnet_uid']
5661
error = None
5762
accounts = get_accounts_for(calnet_uid)
@@ -117,19 +122,20 @@ def change_password(request):
117122

118123

119124
class ChpassForm(Form):
120-
121-
def __init__(self, ocf_accounts, calnet_uid, *args, **kwargs):
125+
# fix self.fields.keyOrder type error in mypy
126+
field_order = [
127+
'ocf_account',
128+
'new_password',
129+
'confirm_password',
130+
]
131+
132+
def __init__(self, ocf_accounts: List[str], calnet_uid: str, *args: Any, **kwargs: Any) -> None:
122133
super().__init__(*args, **kwargs)
123134
self.calnet_uid = calnet_uid
124135
self.fields['ocf_account'] = forms.ChoiceField(
125136
choices=[(x, x) for x in ocf_accounts],
126137
label='OCF account',
127138
)
128-
self.fields.keyOrder = [
129-
'ocf_account',
130-
'new_password',
131-
'confirm_password',
132-
]
133139

134140
new_password = forms.CharField(
135141
widget=forms.PasswordInput,
@@ -141,7 +147,7 @@ def __init__(self, ocf_accounts, calnet_uid, *args, **kwargs):
141147
label='Confirm password',
142148
)
143149

144-
def clean_ocf_account(self):
150+
def clean_ocf_account(self) -> str:
145151
data = self.cleaned_data['ocf_account']
146152
if not user_exists(data):
147153
raise forms.ValidationError('OCF user account does not exist.')
@@ -161,7 +167,7 @@ def clean_ocf_account(self):
161167

162168
return data
163169

164-
def clean_confirm_password(self):
170+
def clean_confirm_password(self) -> str:
165171
new_password = self.cleaned_data.get('new_password')
166172
confirm_password = self.cleaned_data.get('confirm_password')
167173

ocfweb/account/commands.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django import forms
22
from django.forms import widgets
3+
from django.http import HttpRequest
4+
from django.http import HttpResponse
35
from django.shortcuts import render
46
from paramiko import AuthenticationException
57
from paramiko import SSHClient
@@ -8,7 +10,7 @@
810
from ocfweb.component.forms import Form
911

1012

11-
def commands(request):
13+
def commands(request: HttpRequest) -> HttpResponse:
1214
command_to_run = ''
1315
output = ''
1416
error = ''

ocfweb/account/recommender.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
from random import randint
2+
from typing import Any
3+
from typing import List
24

35
from ocflib.account.creation import validate_username
46
from ocflib.account.creation import ValidationError
57
from ocflib.account.creation import ValidationWarning
68

79

8-
def recommend(real_name, n):
9-
name_fields = [name.lower() for name in real_name.split()]
10+
def recommend(real_name: str, n: int) -> List[Any]:
11+
name_fields: List[str] = [name.lower() for name in real_name.split()]
1012

1113
# Can reimplement name_field_abbrevs to only remove vowels or consonants
12-
name_field_abbrevs = [[] for i in range(len(name_fields))]
14+
name_field_abbrevs: List[List[str]] = [[] for i in range(len(name_fields))]
1315
for i in range(len(name_fields)):
1416
name_field = name_fields[i]
1517
for j in range(1, len(name_field) + 1):
@@ -23,7 +25,7 @@ def recommend(real_name, n):
2325
new_unvalidated_recs.append(rec + name_field_abbrev)
2426
unvalidated_recs = new_unvalidated_recs
2527

26-
validated_recs = []
28+
validated_recs: List[Any] = []
2729
while len(validated_recs) < n and len(unvalidated_recs) > 0:
2830
rec = unvalidated_recs.pop(randint(0, len(unvalidated_recs) - 1))
2931
try:

ocfweb/account/register.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
from typing import Union
2+
13
import ocflib.account.search as search
24
import ocflib.account.validators as validators
35
import ocflib.misc.validators
46
import ocflib.ucb.directory as directory
57
from Crypto.PublicKey import RSA
68
from django import forms
7-
from django.core.exceptions import NON_FIELD_ERRORS
9+
from django.http import HttpRequest
10+
from django.http import HttpResponse
811
from django.http import HttpResponseBadRequest
912
from django.http import HttpResponseRedirect
1013
from django.http import JsonResponse
@@ -29,7 +32,7 @@
2932

3033

3134
@calnet_required
32-
def request_account(request):
35+
def request_account(request: HttpRequest) -> Union[HttpResponseRedirect, HttpResponse]:
3336
calnet_uid = request.session['calnet_uid']
3437
status = 'new_request'
3538

@@ -87,10 +90,10 @@ def request_account(request):
8790
if isinstance(task.result, NewAccountResponse):
8891
if task.result.status == NewAccountResponse.REJECTED:
8992
status = 'has_errors'
90-
form._errors[NON_FIELD_ERRORS] = form.error_class(task.result.errors)
93+
form.add_error('NON_FIELD_ERRORS', task.result.errors)
9194
elif task.result.status == NewAccountResponse.FLAGGED:
9295
status = 'has_warnings'
93-
form._errors[NON_FIELD_ERRORS] = form.error_class(task.result.errors)
96+
form.add_error('NON_FIELD_ERRORS', task.result.errors)
9497
elif task.result.status == NewAccountResponse.PENDING:
9598
return HttpResponseRedirect(reverse('account_pending'))
9699
else:
@@ -114,7 +117,7 @@ def request_account(request):
114117
)
115118

116119

117-
def recommend(request):
120+
def recommend(request: HttpRequest) -> Union[JsonResponse, HttpResponseBadRequest]:
118121
real_name = request.GET.get('real_name', None)
119122
if real_name is None:
120123
return HttpResponseBadRequest('No real_name in recommend request')
@@ -127,7 +130,7 @@ def recommend(request):
127130
)
128131

129132

130-
def validate(request):
133+
def validate(request: HttpRequest) -> Union[HttpResponseBadRequest, JsonResponse]:
131134
real_name = request.GET.get('real_name', None)
132135
if real_name is None:
133136
return HttpResponseBadRequest('No real_name in validate request')
@@ -149,7 +152,7 @@ def validate(request):
149152
})
150153

151154

152-
def wait_for_account(request):
155+
def wait_for_account(request: HttpRequest) -> Union[HttpResponse, HttpResponseRedirect]:
153156
if 'approve_task_id' not in request.session:
154157
return render(
155158
request,
@@ -180,11 +183,11 @@ def wait_for_account(request):
180183
return render(request, 'account/register/wait/error-probably-not-created.html', {})
181184

182185

183-
def account_pending(request):
186+
def account_pending(request: HttpRequest) -> HttpResponse:
184187
return render(request, 'account/register/pending.html', {'title': 'Account request pending'})
185188

186189

187-
def account_created(request):
190+
def account_created(request: HttpRequest) -> HttpResponse:
188191
return render(request, 'account/register/success.html', {'title': 'Account request successful'})
189192

190193

@@ -232,7 +235,7 @@ class ApproveForm(Form):
232235
},
233236
)
234237

235-
def clean_verify_password(self):
238+
def clean_verify_password(self) -> str:
236239
password = self.cleaned_data.get('password')
237240
verify_password = self.cleaned_data.get('verify_password')
238241

@@ -241,7 +244,7 @@ def clean_verify_password(self):
241244
raise forms.ValidationError("Your passwords don't match.")
242245
return verify_password
243246

244-
def clean_verify_contact_email(self):
247+
def clean_verify_contact_email(self) -> str:
245248
email = self.cleaned_data.get('contact_email')
246249
verify_contact_email = self.cleaned_data.get('verify_contact_email')
247250

@@ -250,7 +253,8 @@ def clean_verify_contact_email(self):
250253
raise forms.ValidationError("Your emails don't match.")
251254
return verify_contact_email
252255

253-
def clean(self):
256+
# clean incompatible with supertype BaseForm which is defined in django.
257+
def clean(self) -> None: # type: ignore
254258
cleaned_data = super().clean()
255259

256260
# validate password (requires username to check similarity)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from typing import List
2+
13
from django import template
24

35
register = template.Library()
46

57

68
@register.filter
7-
def address_to_parts(address):
9+
def address_to_parts(address: str) -> List[str]:
810
return address.split('@')

ocfweb/account/vhost.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
import re
33
import socket
44
from textwrap import dedent
5+
from typing import Any
56

67
from django import forms
78
from django.conf import settings
9+
from django.http import HttpRequest
10+
from django.http import HttpResponse
811
from django.shortcuts import redirect
912
from django.shortcuts import render
1013
from django.urls import reverse
@@ -23,18 +26,18 @@
2326
from ocfweb.component.session import logged_in_user
2427

2528

26-
def available_domain(domain):
29+
def available_domain(domain: str) -> bool:
2730
if not re.match(r'^[a-zA-Z0-9]+\.berkeley\.edu$', domain):
2831
return False
2932
return not host_exists(domain)
3033

3134

32-
def valid_domain_external(domain):
35+
def valid_domain_external(domain: str) -> bool:
3336
return bool(re.match(r'([a-zA-Z0-9]+\.)+[a-zA-Z0-9]{2,}', domain))
3437

3538

3639
@login_required
37-
def request_vhost(request):
40+
def request_vhost(request: HttpRequest) -> HttpResponse:
3841
user = logged_in_user(request)
3942
attrs = user_attrs(user)
4043
is_group = 'callinkOid' in attrs
@@ -137,7 +140,9 @@ def request_vhost(request):
137140
else:
138141
return redirect(reverse('request_vhost_success'))
139142
else:
140-
form = VirtualHostForm(is_group, initial={'requested_subdomain': user + '.berkeley.edu'})
143+
# Unsupported left operand type for + ("None") because form might not have been instantiated at this point...
144+
# but this doesn't matter because of if-else clause
145+
form = VirtualHostForm(is_group, initial={'requested_subdomain': user + '.berkeley.edu'}) # type: ignore
141146

142147
group_url = f'https://www.ocf.berkeley.edu/~{user}/'
143148

@@ -156,7 +161,7 @@ def request_vhost(request):
156161
)
157162

158163

159-
def request_vhost_success(request):
164+
def request_vhost_success(request: HttpRequest) -> HttpResponse:
160165
return render(
161166
request,
162167
'account/vhost/success.html',
@@ -231,7 +236,7 @@ class VirtualHostForm(Form):
231236
max_length=1024,
232237
)
233238

234-
def __init__(self, is_group=True, *args, **kwargs):
239+
def __init__(self, is_group: bool = True, *args: Any, **kwargs: Any) -> None:
235240
super(Form, self).__init__(*args, **kwargs)
236241

237242
# It's pretty derpy that we have to set the labels here, but we can't
@@ -266,7 +271,7 @@ def __init__(self, is_group=True, *args, **kwargs):
266271
max_length=64,
267272
)
268273

269-
def clean_requested_subdomain(self):
274+
def clean_requested_subdomain(self) -> str:
270275
requested_subdomain = self.cleaned_data['requested_subdomain'].lower().strip()
271276

272277
if self.cleaned_data['requested_own_domain']:
@@ -291,7 +296,7 @@ def clean_requested_subdomain(self):
291296

292297
return requested_subdomain
293298

294-
def clean_your_email(self):
299+
def clean_your_email(self) -> str:
295300
your_email = self.cleaned_data['your_email']
296301
if not valid_email(your_email):
297302
raise forms.ValidationError(

0 commit comments

Comments
 (0)