fix(openedx-client): send only the next page's query, and survive a bare 429 - #2527
Open
blarghmatey wants to merge 2 commits into
Open
fix(openedx-client): send only the next page's query, and survive a bare 429#2527blarghmatey wants to merge 2 commits into
blarghmatey wants to merge 2 commits into
Conversation
…are 429
Two bugs on the same request path, one of which caused the other.
Pagination: `get_edx_course_ids` passed the whole absolute URL from
`pagination.next` to `parse_qs`. That returns a single entry whose key
is everything up to the first '=' -- the entire URL -- so the intended
`page` parameter was never sent as such, and every request carried a
URL-shaped junk parameter instead. In production the query string grew
with each round trip until, by page 27, it had the courses endpoint
nested about 25 times. That is what drew the HTTP 429.
Parsing `urlparse(next_page).query` sends `page` as a real parameter
and keeps the query string flat.
Retry-After: the 429 handler defaulted the header to the *int* 60 and
then called `.isdigit()` on it, so a 429 whose response omits
Retry-After raised
AttributeError: 'int' object has no attribute 'isdigit'
from inside the branch that exists to handle exactly that response --
converting a recoverable rate limit into a hard job failure. The
default is now the string "60".
The same `parse_qs(next_page)` pattern appears in `get_edxorg_programs`
and `get_edxorg_mitx_courses`, both of which walk discovery.edx.org.
Left alone here to keep this change to the path with production
evidence behind it.
Fixes DAGSTER-E
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ssj5MFfDB33GBZod1PYst9
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Open edX pagination and rate-limit recovery failures.
Changes:
- Parses only the query component of pagination URLs.
- Handles missing
Retry-Afterheaders safely. - Adds regression tests for both cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
packages/ol-orchestrate-lib/src/ol_orchestrate/resources/openedx.py |
Corrects pagination query parsing. |
packages/ol-orchestrate-lib/src/ol_orchestrate/resources/oauth.py |
Prevents headerless 429 retries from crashing. |
packages/ol-orchestrate-lib/tests/resources/test_openedx_client.py |
Tests multi-page traversal and termination. |
packages/ol-orchestrate-lib/tests/resources/test_oauth.py |
Tests retry behavior without Retry-After. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…s too get_edxorg_programs and get_edxorg_mitx_courses had the identical parse_qs defect as get_edx_course_ids: passing an absolute URL to parse_qs makes the whole URL a parameter *name*, so `page` was never sent and the walk refetched page 1 while the query string grew. Deferred in the first pass as out of scope. Review flagged it as HIGH, and both are reachable from edxorg/assets/edxorg_api.py, so the loop runs against a live API rather than being dead code -- fixing rather than deferring again. These use the plain DRF envelope (top-level `next`/`count`) instead of the nested `pagination.next` the courses API returns, so the new tests cover that shape separately. Raised by Sentry review on #2527. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ssj5MFfDB33GBZod1PYst9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What are the relevant tickets?
Sentry: DAGSTER-E
Description (What does it do?)
Part of a set of PRs working through the Sentry backlog from #2518.
The Sentry issue is an
AttributeErrorin the 429 retry handler, but that turned out to be the second bug on this request path — the first one is what produced the 429.1. Pagination sent the whole URL as a parameter name.
get_edx_course_idspassed the absolute URL frompagination.nextstraight toparse_qs:parse_qson an absolute URL returns a single entry whose key is everything up to the first=:So the intended
pageparameter was never sent aspage, and each request carried a URL-shaped junk parameter instead. Because the API echoes the request back when buildingnext, the query string grew every round trip. The request URL in the Sentry event has the courses endpoint nested roughly 25 times before ending in&page=27. That is what drew the rate limit.Parsing
urlparse(next_page).querysendspageas a real parameter and keeps the query string flat.2. The 429 handler crashed on a 429 with no Retry-After.
The default was the int
60, so.isdigit()raisedAttributeError: 'int' object has no attribute 'isdigit'on exactly the responses this branch exists to handle. A recoverable rate limit became a hard job failure. The default is now the string"60".How can this be tested?
New tests:
tests/resources/test_openedx_client.py— walks three pages against a DRF-shaped mock (nextis absolute and echoes the request). Assertspagearrives aspage, and that no parameter name is a URL, which is the signature of the bug.tests/resources/test_oauth.py::test_rate_limit_retry_survives_a_missing_retry_after_header— 429 with noRetry-After, then 200; asserts a 60s backoff was taken instead of raising.Both were verified to fail against the unfixed code. Worth noting the pagination test doesn't merely fail on the old code — against a conformant mock the old code loops forever, because
pagenever reaches the server so every response is page 1 with anext.pre-commit run --files <changed>is clean (ruff format, ruff check, mypy).Additional Context
The same
parse_qs(next_page)pattern appears twice more in the same class, inget_edxorg_programsandget_edxorg_mitx_courses, both walkingdiscovery.edx.org. I left them alone to keep this PR scoped to the path with production evidence behind it — happy to fix them here instead if you'd prefer them together. They will have the same behaviour.