Skip to content

fix: stop get_course_users swallowing roster parse errors - #73

Open
dave-doty wants to merge 1 commit into
nyuoss:mainfrom
dave-doty:fix/get-course-users-swallows-parse-errors
Open

dave-doty wants to merge 1 commit into
nyuoss:mainfrom
dave-doty:fix/get-course-users-swallows-parse-errors

Conversation

@dave-doty

Copy link
Copy Markdown

Problem

Account.get_course_users wraps its scrape in:

except Exception:
    return None

Its annotated return type is list[Member], so on any parse failure a caller sees only:

TypeError: 'NoneType' object is not iterable

at their own for member in members loop, with nothing pointing at the real cause. I hit this on a course roster and it took a while to work out that the error was in the scrape, not the loop.

Cause

Three spots in get_course_members assume every roster row is a student. On a course with instructors, TAs or readers, each raises and is then swallowed:

line assumption raises
data_url.split("user_id=") data-url is present whenever the js-rosterName button is AttributeError
int(cells[num_submissions_column].text) submissions cell is always numeric ValueError
id_to_role[...] data-role is always in {0,1,2,3} KeyError

The first is the narrowest: the button is already guarded for None, but the attribute read .get("data-url", None) is not, so a name button without that attribute goes straight into .split().

The second and third are the common ones — a blank submissions cell for anyone who cannot submit takes down the entire roster, not just that row.

Fix

  • get_course_users no longer swallows exceptions, matching its declared return type. Genuine failures (auth, 404, markup changes) now surface where they happen.
  • user_id stays None when there is no data-url, which is what the field's own comment already documents: "only present for 'student' accounts in a course".
  • a non-numeric submissions cell becomes 0.
  • an unrecognised role id passes through as the raw id.

Each now degrades for the single member it concerns instead of discarding every member.

Tests

tests/test_course_members.py parses fixture HTML directly, so unlike the rest of the suite it needs no GRADESCOPE_CI_* credentials and runs in CI as-is. It covers a student row, a staff row with a blank submissions cell, a name button with no data-url, an unrecognised role id, and a mixed roster.

Against main four of the five fail (ValueError, AttributeError, KeyError); all five pass with this change. The student-row case passes both ways, confirming student parsing is unchanged.

I could not run the credentialed suite, having no CI accounts — it reports 33 failed / 3 passed identically on main and on this branch, every failure being ValueError: Invalid credentials.

get_course_users wrapped its scrape in `except Exception: return None`, so any
parsing failure surfaced only as `TypeError: 'NoneType' object is not iterable`
at the caller's loop, with no indication of what actually went wrong. Its
annotated return type is `list[Member]`, so let the exception propagate instead.

Three roster rows could reach that path, all of them non-student members:

- `user_id` is read from the js-rosterName button's `data-url`. The button is
  guarded for None but `data-url` itself is not, so a row carrying the button
  without that attribute raised AttributeError on `data_url.split(...)`.
- the submissions count cell is blank for members who cannot submit
  (instructors, TAs, readers), so `int(cell.text)` raised ValueError.
- `id_to_role[...]` raised KeyError for any role id outside {0,1,2,3}.

Each now degrades for that one member -- `user_id=None`, `num_submissions=0`,
or the raw role id passed through -- rather than taking down the whole roster.

Adds offline parser tests covering student rows, staff rows, a name button with
no data-url, an unrecognised role id, and a mixed roster; these need no
Gradescope credentials.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dave-doty

Copy link
Copy Markdown
Author

For the record, Claude Opus wrote these fixes, and I didn't inspect them carefully, but they do appear to fix the error I was encountering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant