Skip to content

Commit 2bf69c6

Browse files
chore: update typehints via __future__.annotations
1 parent 1d1ff53 commit 2bf69c6

File tree

15 files changed

+128
-109
lines changed

15 files changed

+128
-109
lines changed

intranet/apps/eighth/forms/admin/activities.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import List # noqa
34

45
from django import forms, http
56
from django.contrib.auth import get_user_model
@@ -11,7 +12,7 @@
1112

1213

1314
class ActivityDisplayField(forms.ModelChoiceField):
14-
cancelled_acts = None # type: List[EighthActivity]
15+
cancelled_acts: list[EighthActivity] | None = None
1516

1617
def __init__(self, *args, **kwargs):
1718
if "block" in kwargs:

intranet/apps/eighth/models.py

+33-33
Large diffs are not rendered by default.

intranet/apps/eighth/tasks.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import calendar
24
import datetime
35
from typing import Collection

intranet/apps/eighth/views/admin/groups.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from __future__ import annotations
2+
13
import csv
24
import logging
35
import re
4-
from typing import List, Optional
56

67
from cacheops import invalidate_model, invalidate_obj
78
from formtools.wizard.views import SessionWizardView
@@ -156,7 +157,7 @@ def get_file_string(fileobj):
156157
return filetext
157158

158159

159-
def get_user_info(key: str, val) -> Optional[List[User]]:
160+
def get_user_info(key: str, val) -> list[User] | None:
160161
if key in ["username", "id"]:
161162
try:
162163
u = get_user_model().objects.filter(**{key: val})
@@ -200,7 +201,7 @@ def handle_group_input(filetext: str):
200201
return find_users_input(lines)
201202

202203

203-
def find_users_input(lines: List[str]):
204+
def find_users_input(lines: list[str]):
204205
sure_users = []
205206
unsure_users = []
206207
for line in lines:
@@ -487,7 +488,7 @@ def eighth_admin_signup_group_action(request, group_id, schact_id):
487488
)
488489

489490

490-
def eighth_admin_perform_group_signup(*, group_id: int, schact_id: int, request: Optional[http.HttpRequest], skip_users: set):
491+
def eighth_admin_perform_group_signup(*, group_id: int, schact_id: int, request: http.HttpRequest | None, skip_users: set):
491492
"""Performs sign up of all users in a specific group up for a
492493
specific scheduled activity.
493494

intranet/apps/eighth/views/admin/hybrid.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import Optional
34

45
from formtools.wizard.views import SessionWizardView
56

@@ -215,7 +216,7 @@ def eighth_admin_signup_group_action_hybrid(request, group_id, schact_virtual_id
215216
)
216217

217218

218-
def eighth_admin_perform_group_signup(*, group_id: int, schact_virtual_id: int, schact_person_id: int, request: Optional[http.HttpRequest]):
219+
def eighth_admin_perform_group_signup(*, group_id: int, schact_virtual_id: int, schact_person_id: int, request: http.HttpRequest | None):
219220
"""Performs sign up of all users in a specific group up for a
220221
specific scheduled activity.
221222

intranet/apps/features/helpers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Optional
1+
from __future__ import annotations
22

33

4-
def get_feature_context(request) -> Optional[str]:
4+
def get_feature_context(request) -> str | None:
55
"""Given a Django request, returns the 'context' that should be used to select feature
66
announcements to display (one of ``dashboard``, ``login``, ``eighth_signup``, or ``None``).
77

intranet/apps/notifications/emails.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import Collection, Mapping
4+
from typing import Mapping, MutableSequence
35

46
from django.conf import settings
57
from django.core.mail import EmailMultiAlternatives
@@ -13,11 +15,11 @@ def email_send(
1315
html_template: str,
1416
data: Mapping[str, object],
1517
subject: str,
16-
emails: Collection[str], # pylint: disable=unsubscriptable-object
17-
headers: Mapping[str, str] = None, # pylint: disable=unsubscriptable-object
18+
emails: MutableSequence[str],
19+
headers: Mapping[str, str] | None = None,
1820
bcc: bool = False,
1921
*,
20-
custom_logger: logging.Logger = None,
22+
custom_logger: logging.Logger | None = None,
2123
) -> EmailMultiAlternatives:
2224
"""Send an HTML/Plaintext email with the following fields.
2325
If we are not in production and settings.FORCE_EMAIL_SEND is not set, does not actually send the email

intranet/apps/printing/views.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
from __future__ import annotations
2+
13
import logging
24
import math
35
import os
46
import re
57
import subprocess
68
import tempfile
79
from io import BytesIO
8-
from typing import Dict, Optional
910

1011
import magic
1112
from sentry_sdk import add_breadcrumb, capture_exception
@@ -32,14 +33,15 @@ class InvalidInputPrintingError(Exception):
3233
"""An error occurred while printing, but it was due to invalid input from the user and is not worthy of a ``CRITICAL`` log message."""
3334

3435

35-
def get_printers() -> Dict[str, str]:
36+
def get_printers() -> dict[str, str] | list:
3637
"""Returns a dictionary mapping name:description for available printers.
3738
3839
This requires that a CUPS client be configured on the server.
3940
Otherwise, this returns an empty dictionary.
4041
4142
Returns:
42-
A dictionary mapping name:description for available printers.
43+
A dictionary mapping name:description for available printers, or
44+
an empty list if cups isn't installed or lpstat fails
4345
"""
4446

4547
key = "printing:printers"
@@ -86,7 +88,7 @@ def get_printers() -> Dict[str, str]:
8688
return printers
8789

8890

89-
def convert_soffice(tmpfile_name: str) -> Optional[str]:
91+
def convert_soffice(tmpfile_name: str) -> str | None:
9092
"""Converts a doc or docx to a PDF with soffice.
9193
9294
Args:
@@ -117,7 +119,7 @@ def convert_soffice(tmpfile_name: str) -> Optional[str]:
117119
return None
118120

119121

120-
def convert_pdf(tmpfile_name: str, cmdname: str = "ps2pdf") -> Optional[str]:
122+
def convert_pdf(tmpfile_name: str, cmdname: str = "ps2pdf") -> str | None:
121123
new_name = f"{tmpfile_name}.pdf"
122124
try:
123125
output = subprocess.check_output([cmdname, tmpfile_name, new_name], stderr=subprocess.STDOUT, universal_newlines=True)
@@ -179,7 +181,7 @@ def get_mimetype(tmpfile_name: str) -> str:
179181
return mimetype
180182

181183

182-
def convert_file(tmpfile_name: str, orig_fname: str) -> Optional[str]:
184+
def convert_file(tmpfile_name: str, orig_fname: str) -> str | None:
183185
detected = get_mimetype(tmpfile_name)
184186

185187
add_breadcrumb(category="printing", message=f"Detected file type {detected}", level="debug")
@@ -211,7 +213,7 @@ def convert_file(tmpfile_name: str, orig_fname: str) -> Optional[str]:
211213
raise InvalidInputPrintingError(f"Invalid file type {detected}")
212214

213215

214-
def check_page_range(page_range: str, max_pages: int) -> Optional[int]:
216+
def check_page_range(page_range: str, max_pages: int) -> int | None:
215217
"""Returns the number of pages included in the range, or None if it is an invalid range.
216218
217219
Args:

intranet/apps/signage/views.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from __future__ import annotations
2+
13
import datetime
24
import logging
3-
from typing import Optional
45

56
from django import http
67
from django.conf import settings
@@ -20,7 +21,7 @@
2021
logger = logging.getLogger(__name__)
2122

2223

23-
def check_internal_ip(request) -> Optional[HttpResponse]:
24+
def check_internal_ip(request) -> HttpResponse | None:
2425
"""
2526
A method to determine if a request is allowed to load a signage page.
2627

intranet/apps/templatetags/paginate.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Union
1+
from __future__ import annotations
22

33
from django import template
44

@@ -13,8 +13,8 @@ def query_transform(request, **kwargs):
1313
return query.urlencode()
1414

1515

16-
@register.filter # TODO: replace return type with list[int | None]
17-
def page_list(paginator, current_page) -> List[Union[int, None]]:
16+
@register.filter
17+
def page_list(paginator, current_page) -> list[int | None]:
1818
"""Pagination
1919
2020
If there is a ``None`` in the output, it should be replaced

intranet/apps/users/models.py

+22-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# pylint: disable=too-many-lines; Allow more than 1000 lines
2+
from __future__ import annotations
3+
24
import logging
35
from base64 import b64encode
46
from datetime import timedelta
5-
from typing import Collection, Dict, Optional, Union
7+
from typing import Collection
68

79
from dateutil.relativedelta import relativedelta
810

@@ -42,14 +44,14 @@ class UserManager(DjangoUserManager):
4244
4345
"""
4446

45-
def user_with_student_id(self, student_id: Union[int, str]) -> Optional["User"]:
47+
def user_with_student_id(self, student_id: int | str) -> "User | None":
4648
"""Get a unique user object by FCPS student ID. (Ex. 1624472)"""
4749
results = User.objects.filter(student_id=str(student_id))
4850
if len(results) == 1:
4951
return results.first()
5052
return None
5153

52-
def user_with_ion_id(self, student_id: Union[int, str]) -> Optional["User"]:
54+
def user_with_ion_id(self, student_id: int | str) -> "User | None":
5355
"""Get a unique user object by Ion ID. (Ex. 489)"""
5456
if isinstance(student_id, str) and not is_entirely_digit(student_id):
5557
return None
@@ -58,11 +60,11 @@ def user_with_ion_id(self, student_id: Union[int, str]) -> Optional["User"]:
5860
return results.first()
5961
return None
6062

61-
def users_in_year(self, year: int) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object
63+
def users_in_year(self, year: int) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object
6264
"""Get a list of users in a specific graduation year."""
6365
return User.objects.filter(graduation_year=year)
6466

65-
def user_with_name(self, given_name: Optional[str] = None, last_name: Optional[str] = None) -> "User": # pylint: disable=unsubscriptable-object
67+
def user_with_name(self, given_name: str | None = None, last_name: str | None = None) -> "User": # pylint: disable=unsubscriptable-object
6668
"""Get a unique user object by given name (first/nickname) and/or last name.
6769
6870
Args:
@@ -86,14 +88,14 @@ def user_with_name(self, given_name: Optional[str] = None, last_name: Optional[s
8688
except (User.DoesNotExist, User.MultipleObjectsReturned):
8789
return None
8890

89-
def get_students(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object
91+
def get_students(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object
9092
"""Get user objects that are students (quickly)."""
9193
users = User.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year())
9294
users = users.exclude(id__in=EXTRA)
9395

9496
return users
9597

96-
def get_teachers(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object
98+
def get_teachers(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object
9799
"""Get user objects that are teachers (quickly)."""
98100
users = User.objects.filter(user_type="teacher")
99101
users = users.exclude(id__in=EXTRA)
@@ -121,7 +123,7 @@ def get_teachers_attendance_users(self) -> "QuerySet[User]": # noqa
121123

122124
return users
123125

124-
def get_teachers_sorted(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object
126+
def get_teachers_sorted(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object
125127
"""Returns a ``QuerySet`` of teachers sorted by last name, then first name.
126128
127129
Returns:
@@ -171,8 +173,8 @@ def get_approve_announcements_users_sorted(self) -> "QuerySet[User]": # noqa
171173

172174
def exclude_from_search(
173175
self,
174-
existing_queryset: Optional[Union[Collection["User"], QuerySet]] = None, # pylint: disable=unsubscriptable-object
175-
) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object
176+
existing_queryset: Collection["User"] | QuerySet | None = None, # pylint: disable=unsubscriptable-object
177+
) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object
176178
if existing_queryset is None:
177179
existing_queryset = self
178180

@@ -260,7 +262,7 @@ def get_signage_user() -> "User":
260262
return User(id=99999)
261263

262264
@property
263-
def address(self) -> Optional["Address"]:
265+
def address(self) -> "Address" | None:
264266
"""Returns the ``Address`` object representing this user's address, or ``None`` if it is not
265267
set or the current user does not have permission to access it.
266268
@@ -272,7 +274,7 @@ def address(self) -> Optional["Address"]:
272274
return self.properties.address
273275

274276
@property
275-
def schedule(self) -> Optional[Union[QuerySet, Collection["Section"]]]: # pylint: disable=unsubscriptable-object
277+
def schedule(self) -> QuerySet | Collection["Section"] | None: # pylint: disable=unsubscriptable-object
276278
"""Returns a QuerySet of the ``Section`` objects representing the classes this student is
277279
in, or ``None`` if the current user does not have permission to list this student's classes.
278280
@@ -284,7 +286,7 @@ def schedule(self) -> Optional[Union[QuerySet, Collection["Section"]]]: # pylin
284286
"""
285287
return self.properties.schedule
286288

287-
def member_of(self, group: Union[Group, str]) -> bool:
289+
def member_of(self, group: Group | str) -> bool:
288290
"""Returns whether a user is a member of a certain group.
289291
290292
Args:
@@ -402,7 +404,7 @@ def get_short_name(self) -> str:
402404
return self.short_name
403405

404406
@property
405-
def primary_email_address(self) -> Optional[str]:
407+
def primary_email_address(self) -> str | None:
406408
try:
407409
return self.primary_email.address if self.primary_email else None
408410
except Email.DoesNotExist:
@@ -434,7 +436,7 @@ def tj_email(self) -> str:
434436
return "{}@{}".format(self.username, domain)
435437

436438
@property
437-
def non_tj_email(self) -> Optional[str]:
439+
def non_tj_email(self) -> str | None:
438440
"""
439441
Returns the user's first non-TJ email found, or None if none is found.
440442
@@ -476,7 +478,7 @@ def notification_email(self) -> str:
476478
return email.address if email and email.address else self.tj_email
477479

478480
@property
479-
def default_photo(self) -> Optional[bytes]:
481+
def default_photo(self) -> bytes | None:
480482
"""Returns default photo (in binary) that should be used
481483
482484
Returns:
@@ -511,7 +513,7 @@ def grade(self) -> "Grade":
511513
return Grade(self.graduation_year)
512514

513515
@property
514-
def permissions(self) -> Dict[str, bool]:
516+
def permissions(self) -> dict[str, bool]:
515517
"""Dynamically generate dictionary of privacy options.
516518
517519
Returns:
@@ -753,7 +755,7 @@ def is_global_admin(self) -> bool:
753755

754756
return self.member_of("admin_all") and self.is_staff and self.is_superuser
755757

756-
def can_manage_group(self, group: Union[Group, str]) -> bool:
758+
def can_manage_group(self, group: Group | str) -> bool:
757759
"""Checks whether this user has permission to edit/manage the given group (either
758760
a Group or a group name).
759761
@@ -1381,7 +1383,7 @@ def __getattr__(self, name):
13811383
raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name))
13821384

13831385
@cached_property
1384-
def base64(self) -> Optional[bytes]:
1386+
def base64(self) -> bytes | None:
13851387
"""Returns base64 encoded binary data for a user's picture.
13861388
13871389
Returns:
@@ -1447,7 +1449,7 @@ def text(self) -> str:
14471449
return self._name
14481450

14491451
@staticmethod
1450-
def number_from_name(name: str) -> Optional[int]:
1452+
def number_from_name(name: str) -> int | None:
14511453
if name in Grade.names:
14521454
return Grade.names.index(name) + 9
14531455
return None

0 commit comments

Comments
 (0)