Skip to content

Commit b7a7173

Browse files
committed
Add async to Wharton/SEAS management
1 parent e2d268e commit b7a7173

2 files changed

Lines changed: 114 additions & 36 deletions

File tree

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import asyncio
2+
13
from django.contrib.auth import get_user_model
24
from django.core.management.base import BaseCommand
35

@@ -8,22 +10,59 @@
810
class Command(BaseCommand):
911
help = "Updates SEAS status for all users."
1012

13+
def add_arguments(self, parser):
14+
parser.add_argument(
15+
"--concurrency",
16+
type=int,
17+
default=20,
18+
help="Max concurrent API calls (default: 20).",
19+
)
20+
1121
def handle(self, *args, **kwargs):
12-
users = GroupMembership.objects.values_list("user__username", flat=True).distinct()
13-
print(f"Checking {len(users)} users...")
14-
penngroups_wrapper = PennGroupsBookingWrapper()
15-
updated = 0
16-
17-
for username in users:
18-
user = get_user_model().objects.get(username=username)
19-
is_seas = penngroups_wrapper.is_seas(user)
20-
memberships = GroupMembership.objects.filter(user__username=user)
21-
for membership in memberships:
22-
if membership.is_seas != is_seas:
23-
membership.is_seas = is_seas
24-
membership.save()
25-
status = "now" if is_seas else "no longer"
26-
print(f"User {user} is {status} a SEAS user.")
27-
updated += 1
28-
29-
print(f"Done updating SEAS statuses. Updated: {updated} users.")
22+
asyncio.run(self.ahandle(**kwargs))
23+
24+
async def ahandle(self, **kwargs):
25+
max_concurrency = kwargs["concurrency"]
26+
usernames = await asyncio.to_thread(
27+
lambda: list(
28+
GroupMembership.objects.values_list("user__username", flat=True).distinct()
29+
)
30+
)
31+
32+
self.stdout.write(f"Checking {len(usernames)} users (concurrency={max_concurrency})...")
33+
34+
semaphore = asyncio.Semaphore(max_concurrency)
35+
updated_count = 0
36+
37+
async def process_user(username: str) -> int:
38+
async with semaphore:
39+
wrapper = PennGroupsBookingWrapper()
40+
user = await asyncio.to_thread(get_user_model().objects.get, username=username)
41+
is_seas = await asyncio.to_thread(wrapper.is_seas, user)
42+
43+
memberships = await asyncio.to_thread(
44+
lambda: list(GroupMembership.objects.filter(user__username=username))
45+
)
46+
47+
count = 0
48+
for membership in memberships:
49+
if membership.is_seas != is_seas:
50+
membership.is_seas = is_seas
51+
await asyncio.to_thread(membership.save)
52+
status = "now" if is_seas else "no longer"
53+
self.stdout.write(f"User {user} is {status} a SEAS user.")
54+
count += 1
55+
return count
56+
57+
results = await asyncio.gather(
58+
*(process_user(u) for u in usernames),
59+
return_exceptions=True,
60+
)
61+
62+
for username, result in zip(usernames, results):
63+
if isinstance(result, Exception):
64+
self.stderr.write(f"Error processing {username}: {result}")
65+
else:
66+
updated_count += result
67+
68+
self.stdout.write(f"Done updating SEAS statuses. Updated: {updated_count} users.")
Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import asyncio
2+
13
from django.contrib.auth import get_user_model
24
from django.core.management.base import BaseCommand
35

@@ -8,22 +10,59 @@
810
class Command(BaseCommand):
911
help = "Updates Wharton privilege status for all users."
1012

13+
def add_arguments(self, parser):
14+
parser.add_argument(
15+
"--concurrency",
16+
type=int,
17+
default=20,
18+
help="Max concurrent API calls (default: 20).",
19+
)
20+
1121
def handle(self, *args, **kwargs):
12-
users = GroupMembership.objects.values_list("user__username", flat=True).distinct()
13-
print(f"Checking {len(users)} users...")
14-
wharton_wrapper = WhartonBookingWrapper()
15-
updated = 0
16-
17-
for username in users:
18-
user = get_user_model().objects.get(username=username)
19-
is_wharton = wharton_wrapper.is_wharton(user)
20-
memberships = GroupMembership.objects.filter(user__username=user)
21-
for membership in memberships:
22-
if membership.is_wharton != is_wharton:
23-
membership.is_wharton = is_wharton
24-
membership.save()
25-
status = "now" if is_wharton else "no longer"
26-
print(f"User {user} is {status} a Wharton user.")
27-
updated += 1
28-
29-
print(f"Done updating Wharton statuses. Updated: {updated} users.")
22+
asyncio.run(self.ahandle(**kwargs))
23+
24+
async def ahandle(self, **kwargs):
25+
max_concurrency = kwargs["concurrency"]
26+
usernames = await asyncio.to_thread(
27+
lambda: list(
28+
GroupMembership.objects.values_list("user__username", flat=True).distinct()
29+
)
30+
)
31+
32+
self.stdout.write(f"Checking {len(usernames)} users (concurrency={max_concurrency})...")
33+
34+
semaphore = asyncio.Semaphore(max_concurrency)
35+
updated_count = 0
36+
37+
async def process_user(username: str) -> int:
38+
async with semaphore:
39+
wrapper = WhartonBookingWrapper()
40+
user = await asyncio.to_thread(get_user_model().objects.get, username=username)
41+
is_wharton = await asyncio.to_thread(wrapper.is_wharton, user)
42+
43+
memberships = await asyncio.to_thread(
44+
lambda: list(GroupMembership.objects.filter(user__username=username))
45+
)
46+
47+
count = 0
48+
for membership in memberships:
49+
if membership.is_wharton != is_wharton:
50+
membership.is_wharton = is_wharton
51+
await asyncio.to_thread(membership.save)
52+
status = "now" if is_wharton else "no longer"
53+
self.stdout.write(f"User {user} is {status} a Wharton user.")
54+
count += 1
55+
return count
56+
57+
results = await asyncio.gather(
58+
*(process_user(u) for u in usernames),
59+
return_exceptions=True,
60+
)
61+
62+
for username, result in zip(usernames, results):
63+
if isinstance(result, Exception):
64+
self.stderr.write(f"Error processing {username}: {result}")
65+
else:
66+
updated_count += result
67+
68+
self.stdout.write(f"Done updating Wharton statuses. Updated: {updated_count} users.")

0 commit comments

Comments
 (0)