diff --git a/src/gradescopeapi/classes/_helpers/_course_helpers.py b/src/gradescopeapi/classes/_helpers/_course_helpers.py index 23ab416..f2e6a7b 100644 --- a/src/gradescopeapi/classes/_helpers/_course_helpers.py +++ b/src/gradescopeapi/classes/_helpers/_course_helpers.py @@ -155,7 +155,9 @@ def get_course_members(soup: BeautifulSoup, course_id: str) -> list[Member]: # fetch other attributes: email, role, and section # from data attributes in button email = data_button.get("data-email") - role = id_to_role[data_button.get("data-role")] + # an unrecognised role id passes through as-is rather than sinking the whole roster + role_id = data_button.get("data-role") + role = id_to_role.get(role_id, role_id) sections = data_button.get("data-sections") # TODO: check if this is correct # fetch user_id, only available on 'student' accounts @@ -164,11 +166,16 @@ def get_course_members(soup: BeautifulSoup, course_id: str) -> list[Member]: user_id = None if rosterName_button is not None: # data-url="/courses/753413/gradebook.json?user_id=6515875" - data_url: str = rosterName_button.get("data-url", None) - user_id = data_url.split("user_id=")[-1] - - # fetch number of submissions from table cell - num_submissions = int(cells[num_submissions_column].text) + data_url: str | None = rosterName_button.get("data-url", None) + if data_url is not None and "user_id=" in data_url: + user_id = data_url.split("user_id=")[-1] + + # fetch number of submissions from table cell; + # the cell is blank for members who cannot submit (e.g. instructors, TAs, readers) + num_submissions_text = cells[num_submissions_column].text.strip() + num_submissions = ( + int(num_submissions_text) if num_submissions_text.isdigit() else 0 + ) # create Member object with all relevant info member_list.append( diff --git a/src/gradescopeapi/classes/account.py b/src/gradescopeapi/classes/account.py index 93467a4..c260647 100644 --- a/src/gradescopeapi/classes/account.py +++ b/src/gradescopeapi/classes/account.py @@ -88,17 +88,12 @@ def get_course_users(self, course_id: str) -> list[Member]: session = self.session - try: - # scrape page - membership_resp = check_page_auth(session, membership_endpoint) - membership_soup = BeautifulSoup(membership_resp.text, "html.parser") - - # get all users in the course - users = get_course_members(membership_soup, course_id) + # scrape page + membership_resp = check_page_auth(session, membership_endpoint) + membership_soup = BeautifulSoup(membership_resp.text, "html.parser") - return users - except Exception: - return None + # get all users in the course + return get_course_members(membership_soup, course_id) def get_assignments(self, course_id: str) -> list[Assignment]: """ diff --git a/tests/test_course_members.py b/tests/test_course_members.py new file mode 100644 index 0000000..d9b704d --- /dev/null +++ b/tests/test_course_members.py @@ -0,0 +1,97 @@ +"""Tests for roster parsing in get_course_members. + +These are offline tests against fixture HTML, so they need no Gradescope credentials. +""" + +from bs4 import BeautifulSoup + +from gradescopeapi.classes._helpers._course_helpers import get_course_members + +COURSE_ID = "753413" + + +def _roster(rows: str) -> BeautifulSoup: + html = f""" +
| Name | Role | Sections | +Submissions | Edit | Remove |
|---|