|
1 | 1 | import datetime |
2 | 2 | import json |
3 | 3 | import logging |
| 4 | +import threading |
4 | 5 | from concurrent.futures import ThreadPoolExecutor, as_completed |
5 | 6 |
|
6 | 7 | import requests |
|
50 | 51 | class DiningAPIWrapper: |
51 | 52 | def __init__(self): |
52 | 53 | self.token = None |
| 54 | + self.token_lock = threading.Lock() # Only one thread should update the token at a time |
53 | 55 | self.expiration = timezone.localtime() |
54 | 56 | self.openid_endpoint = ( |
55 | 57 | "https://sso.apps.k8s.upenn.edu/auth/realms/master/protocol/openid-connect/token" |
56 | 58 | ) |
57 | 59 |
|
58 | 60 | def update_token(self): |
59 | | - if self.expiration > timezone.localtime(): |
60 | | - return |
61 | | - body = { |
62 | | - "client_id": settings.DINING_ID, |
63 | | - "client_secret": settings.DINING_SECRET, |
64 | | - "grant_type": "client_credentials", |
65 | | - } |
66 | | - response = requests.post(self.openid_endpoint, data=body).json() |
67 | | - if "error" in response: |
68 | | - raise APIError(f"Dining: {response['error']}, {response.get('error_description')}") |
69 | | - self.expiration = timezone.localtime() + datetime.timedelta(seconds=response["expires_in"]) |
70 | | - self.token = response["access_token"] |
| 61 | + with self.token_lock: |
| 62 | + if self.expiration > timezone.localtime(): |
| 63 | + return |
| 64 | + body = { |
| 65 | + "client_id": settings.DINING_ID, |
| 66 | + "client_secret": settings.DINING_SECRET, |
| 67 | + "grant_type": "client_credentials", |
| 68 | + } |
| 69 | + response = requests.post(self.openid_endpoint, data=body).json() |
| 70 | + if "error" in response: |
| 71 | + raise APIError(f"Dining: {response['error']}, {response.get('error_description')}") |
| 72 | + self.expiration = timezone.localtime() + datetime.timedelta( |
| 73 | + seconds=response["expires_in"] |
| 74 | + ) |
| 75 | + self.token = response["access_token"] |
71 | 76 |
|
72 | 77 | def request(self, *args, **kwargs): |
73 | 78 | """Make a signed request to the dining API.""" |
@@ -143,9 +148,8 @@ def fetch_menu(self, venue_id, date): |
143 | 148 | """ |
144 | 149 | Calls API to fetch menu for a given venue and date |
145 | 150 | """ |
146 | | - worker = DiningAPIWrapper() # avoid shared mutable token state across threads |
147 | 151 | menu_base = OPEN_DATA_ENDPOINTS["MENUS"] |
148 | | - response = worker.request("GET", f"{menu_base}?cafe={venue_id}&date={date}") |
| 152 | + response = self.request("GET", f"{menu_base}?cafe={venue_id}&date={date}") |
149 | 153 | if response.status_code != 200: |
150 | 154 | raise APIError("Dining: error connecting to API " + response.text) |
151 | 155 | return ( |
|
0 commit comments