Skip to content

Commit 48628ed

Browse files
committed
fix(loadtest,cache): make BasePodiumUser abstract and silence GenericCache pk shadow warning
- Mark BasePodiumUser as abstract to prevent Locust from instantiating a taskless base\n- Consolidate project browsing into _browse_projects and pull per-event max_votes on attend\n- Add Pydantic alias for GenericCache pk to avoid shadowing JsonModel.pk warning
1 parent 971067e commit 48628ed

File tree

2 files changed

+43
-46
lines changed

2 files changed

+43
-46
lines changed

backend/loadtest/locustfile.py

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def get_bearer_token(email: str, client) -> str:
176176
class BasePodiumUser(HttpUser):
177177
"""Base user with common functionality."""
178178
wait_time = between(0.5, 2.0)
179+
abstract = True
179180

180181
def __init__(self, *args, **kwargs):
181182
super().__init__(*args, **kwargs)
@@ -216,6 +217,15 @@ def on_start(self):
216217
else:
217218
print(f"Failed to attend event {self.event_id}: {response.status_code}")
218219

220+
if attended:
221+
with self.get(f"/events/{self.event_id}", name="/events/:id") as response:
222+
if response.status_code == 200:
223+
try:
224+
event_data = response.json()
225+
self.max_votes = event_data.get("max_votes_per_user", self.max_votes)
226+
except Exception:
227+
pass
228+
219229
self.initialized = attended # Only initialize if successfully attended
220230

221231
def _make_request(self, method, path, name=None, params=None, json=None):
@@ -238,15 +248,31 @@ def post(self, path, json=None, name=None):
238248
"""Make POST request."""
239249
return self._make_request("POST", path, name, json=json)
240250

241-
def fetch_event_projects(self):
242-
"""Fetch all projects for the user's event."""
243-
with self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects") as response:
244-
if response.status_code == 200:
251+
def _browse_projects(self, *, leaderboard=False, update_cache=None, label=None):
252+
"""Fetch event projects with optional leaderboard view and caching."""
253+
if not self.initialized:
254+
return False
255+
256+
query = "true" if leaderboard else "false"
257+
request_name = label or ("/events/:id/projects (leaderboard)" if leaderboard else "/events/:id/projects")
258+
should_update_cache = update_cache if update_cache is not None else not leaderboard
259+
260+
with self.get(
261+
f"/events/{self.event_id}/projects?leaderboard={query}",
262+
name=request_name
263+
) as response:
264+
if response.status_code == 200 and should_update_cache:
245265
try:
246266
data = response.json()
247-
self.all_event_projects = data if isinstance(data, list) else []
248-
except:
249-
self.all_event_projects = []
267+
if isinstance(data, list):
268+
self.all_event_projects = data
269+
except Exception:
270+
pass
271+
return response.status_code == 200
272+
273+
def fetch_event_projects(self):
274+
"""Fetch all projects for the user's event."""
275+
self._browse_projects()
250276

251277
def get_random_project_id(self) -> Optional[str]:
252278
"""Get a random project ID from the event."""
@@ -261,9 +287,7 @@ def get_random_project_id(self) -> Optional[str]:
261287

262288
def check_leaderboard(self):
263289
"""Check event leaderboard (sorted projects)."""
264-
if not self.initialized:
265-
return
266-
self.get(f"/events/{self.event_id}/projects?leaderboard=true", name="/events/:id/projects (leaderboard)")
290+
self._browse_projects(leaderboard=True, update_cache=False)
267291

268292

269293
class Attendee(BasePodiumUser):
@@ -279,16 +303,7 @@ class Attendee(BasePodiumUser):
279303
@task(5)
280304
def browse_projects(self):
281305
"""Browse event projects."""
282-
if not self.initialized:
283-
return
284-
285-
with self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects") as response:
286-
if response.status_code == 200:
287-
try:
288-
data = response.json()
289-
self.all_event_projects = data if isinstance(data, list) else []
290-
except:
291-
pass
306+
self._browse_projects()
292307

293308
@task(3)
294309
def view_project_detail(self):
@@ -325,7 +340,7 @@ def create_or_join_project(self):
325340
project_id = response.json().get("id")
326341
if project_id:
327342
self.own_projects.add(project_id)
328-
except:
343+
except Exception:
329344
pass
330345

331346
@task(8)
@@ -377,16 +392,7 @@ class Lurker(BasePodiumUser):
377392
@task(6)
378393
def browse_projects(self):
379394
"""Browse event projects."""
380-
if not self.initialized:
381-
return
382-
383-
with self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects") as response:
384-
if response.status_code == 200:
385-
try:
386-
data = response.json()
387-
self.all_event_projects = data if isinstance(data, list) else []
388-
except:
389-
pass
395+
self._browse_projects()
390396

391397
@task(4)
392398
def view_project_detail(self):
@@ -401,10 +407,7 @@ def view_project_detail(self):
401407
@task(3)
402408
def browse_more_projects(self):
403409
"""Browse projects more frequently."""
404-
if not self.initialized:
405-
return
406-
407-
self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects")
410+
self._browse_projects()
408411

409412

410413
class Organizer(BasePodiumUser):
@@ -419,22 +422,16 @@ class Organizer(BasePodiumUser):
419422
@task(8)
420423
def browse_projects_during_voting(self):
421424
"""Browse projects frequently, especially during voting."""
422-
if not self.initialized:
423-
return
424-
425425
# Especially during vote window - use leaderboard=true to simulate organizer checking results
426426
if in_vote_window():
427-
self.get(f"/events/{self.event_id}/projects?leaderboard=true", name="/events/:id/projects (leaderboard)")
427+
self._browse_projects(leaderboard=True, update_cache=False, label="/events/:id/projects (leaderboard)")
428428
else:
429-
self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects")
429+
self._browse_projects(label="/events/:id/projects")
430430

431431
@task(2)
432432
def browse_projects(self):
433433
"""Occasionally browse projects."""
434-
if not self.initialized:
435-
return
436-
437-
self.get(f"/events/{self.event_id}/projects?leaderboard=false", name="/events/:id/projects")
434+
self._browse_projects()
438435

439436
@task(1)
440437
def check_admin_stats(self):

backend/podium/cache/operations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from enum import Enum
1010
from typing import Iterable, List, Optional, Type, TypeVar
1111

12-
from pydantic import BaseModel
12+
from pydantic import BaseModel, Field
1313
from pyairtable.formulas import match
1414
from redis_om import JsonModel, get_redis_connection
1515

@@ -64,7 +64,7 @@ def _get_ttl() -> int:
6464
class GenericCache(JsonModel):
6565
"""Single generic cache model storing raw dicts."""
6666

67-
pk: str # Format: "{entity}:{record_id}"
67+
primary_key: str = Field(alias="pk") # Format: "{entity}:{record_id}"
6868
entity: str
6969
data: dict
7070

0 commit comments

Comments
 (0)