Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type safety and type hinting #317

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ repos:
args: []
- id: flake8
args: [--config, backend/setup.cfg]
- id: detect-private-key
- id: detect-private-key
19 changes: 11 additions & 8 deletions backend/dining/api_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import json
from typing import Any

import requests
from django.conf import settings
Expand All @@ -16,14 +17,14 @@


class DiningAPIWrapper:
def __init__(self):
def __init__(self) -> None:
self.token = None
self.expiration = timezone.localtime()
self.openid_endpoint = (
"https://sso.apps.k8s.upenn.edu/auth/realms/master/protocol/openid-connect/token"
)

def update_token(self):
def update_token(self) -> None:
if self.expiration > timezone.localtime():
return
body = {
Expand All @@ -33,11 +34,11 @@ def update_token(self):
}
response = requests.post(self.openid_endpoint, data=body).json()
if "error" in response:
raise APIError(f"Dining: {response['error']}, {response.get('error_description')}")
raise APIError(f"Dining: {response['error']}, {response.get('error_description', '')}")
self.expiration = timezone.localtime() + datetime.timedelta(seconds=response["expires_in"])
self.token = response["access_token"]

def request(self, *args, **kwargs):
def request(self, *args: Any, **kwargs: Any) -> requests.Response:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we would want schemas here? Ideally there are built in ones since these are methods we just override?

"""Make a signed request to the dining API."""
self.update_token()

Expand All @@ -54,7 +55,7 @@ def request(self, *args, **kwargs):
except (ConnectTimeout, ReadTimeout, ConnectionError):
raise APIError("Dining: Connection timeout")

def get_venues(self):
def get_venues(self) -> list[dict[str, Any]]:
results = []
venues_route = OPEN_DATA_ENDPOINTS["VENUES"]
response = self.request("GET", venues_route)
Expand Down Expand Up @@ -107,7 +108,7 @@ def get_venues(self):
results.append(value)
return results

def load_menu(self, date=timezone.now().date()):
def load_menu(self, date: datetime.date = timezone.now().date()) -> None:
"""
Loads the weeks menu starting from today
NOTE: This method should only be used in load_next_menu.py, which is
Expand Down Expand Up @@ -147,7 +148,9 @@ def load_menu(self, date=timezone.now().date()):
# Append stations to dining menu
self.load_stations(daypart["stations"], dining_menu)

def load_stations(self, station_response, dining_menu):
def load_stations(
self, station_response: list[dict[str, Any]], dining_menu: DiningMenu
) -> None:
for station_data in station_response:
# TODO: This is inefficient for venues such as Houston Market
station = DiningStation.objects.create(name=station_data["label"], menu=dining_menu)
Expand All @@ -158,7 +161,7 @@ def load_stations(self, station_response, dining_menu):
station.items.add(*items)
station.save()

def load_items(self, item_response):
def load_items(self, item_response: dict[str, Any]) -> None:
item_list = [
DiningItem(
item_id=key,
Expand Down
3 changes: 2 additions & 1 deletion backend/dining/management/commands/load_next_menu.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
from typing import Any

from django.core.management.base import BaseCommand
from django.utils import timezone
Expand All @@ -13,7 +14,7 @@ class Command(BaseCommand):
the next 7 days, including the original date.
"""

def handle(self, *args, **kwargs):
def handle(self, *args: Any, **kwargs: Any) -> None:
d = DiningAPIWrapper()
d.load_menu(timezone.now().date() + datetime.timedelta(days=6))
self.stdout.write("Loaded new Dining Menu!")
3 changes: 2 additions & 1 deletion backend/dining/management/commands/load_venues.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import csv
from typing import Any

from django.core.management.base import BaseCommand

Expand All @@ -10,7 +11,7 @@ class Command(BaseCommand):
Loads Venues based on CSV
"""

def handle(self, *args, **kwargs):
def handle(self, *args: Any, **kwargs: Any) -> None:

with open("dining/data/dining_venues.csv") as data:
reader = csv.reader(data)
Expand Down
3 changes: 2 additions & 1 deletion backend/dining/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Any

from rest_framework import serializers

Expand All @@ -18,7 +19,7 @@ class Meta:
model = DiningItem
fields = "__all__"

def get_nutrition_info(self, obj):
def get_nutrition_info(self, obj: DiningItem) -> dict[str, Any]:
try:
return json.loads(obj.nutrition_info)
except json.JSONDecodeError:
Expand Down
11 changes: 6 additions & 5 deletions backend/dining/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import datetime

from django.core.cache import cache
from django.db.models import Count
from django.db.models import Count, QuerySet
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.timezone import make_aware
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

Expand All @@ -24,7 +25,7 @@ class Venues(APIView):
GET: returns list of venue data provided by Penn API, as well as an image of the venue
"""

def get(self, request):
def get(self, request: Request) -> Response:
try:
return Response(d.get_venues())
except APIError as e:
Expand All @@ -39,7 +40,7 @@ class Menus(generics.ListAPIView):

serializer_class = DiningMenuSerializer

def get_queryset(self):
def get_queryset(self) -> QuerySet[DiningMenu]:
# TODO: We only have data for the next week, so we should 404
# if date_param is out of bounds
if date_param := self.kwargs.get("date"):
Expand All @@ -61,7 +62,7 @@ class Preferences(APIView):
permission_classes = [IsAuthenticated]
key = "dining_preferences:{user_id}"

def get(self, request):
def get(self, request: Request) -> Response:
key = self.key.format(user_id=request.user.id)
cached_preferences = cache.get(key)
if cached_preferences is None:
Expand All @@ -71,7 +72,7 @@ def get(self, request):
cache.set(key, cached_preferences, Cache.MONTH)
return Response({"preferences": cached_preferences})

def post(self, request):
def post(self, request: Request) -> Response:
key = self.key.format(user_id=request.user.id)
profile = request.user.profile
preferences = profile.dining_preferences
Expand Down
8 changes: 5 additions & 3 deletions backend/gsr_booking/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib import admin
from django.db.models import QuerySet
from rest_framework.request import Request

from gsr_booking.models import GSR, Group, GroupMembership, GSRBooking, Reservation

Expand All @@ -9,10 +11,10 @@ class GroupMembershipInline(admin.TabularInline):

readonly_fields = ["name"]

def name(self, obj):
def name(self, obj: GroupMembership) -> str:
return obj.user.get_full_name()

def get_fields(self, request, obj=None):
def get_fields(self, request, obj=None) -> list[str]:
fields = super().get_fields(request, obj)
to_remove = ["user", "name"]
return ["name"] + [f for f in fields if f not in to_remove]
Expand All @@ -31,7 +33,7 @@ class GroupMembershipAdmin(admin.ModelAdmin):


class GSRAdmin(admin.ModelAdmin):
def get_queryset(self, request):
def get_queryset(self, request: Request) -> QuerySet[GSR]:
return GSR.all_objects.all()

list_display = ["name", "kind", "lid", "gid", "in_use"]
Expand Down
Loading
Loading