v2.6.0: structured errors — translate non-2xx at the engine - #56
Open
karaposu wants to merge 7 commits into
Open
v2.6.0: structured errors — translate non-2xx at the engine#56karaposu wants to merge 7 commits into
karaposu wants to merge 7 commits into
Conversation
BrightDataError gains status_code/url/method/retry_after/retryable/raw (all keyword-only, safe defaults). Adds RateLimitError, reparents DatasetError, exports both levels. No behavior change.
datasets filter/status read the body as text first, so a non-JSON error (429 returns a bare string) surfaces as RateLimitError instead of a content-type parse error. api_client.get_status raises instead of collapsing every non-200 into the status string "error", which made an expired token look like a failed scrape.
Retryability no longer follows from the exception class. An APIError with no status code is raised locally, sometimes after the server already accepted the work, so repeating it can create a duplicate billed job - those are now never retried. Explicit 5xx still is. 429 never is, since those responses consume quota and retrying extends the lockout.
Every request already passes through ResponseContextManager, so classify there instead of leaving each subsystem to improvise: 429 -> RateLimitError with retry_after, other 4xx/5xx -> APIError, all carrying status_code, url, method and a bounded body. 202 deliberately passes through - it means success for scraper_studio.trigger_immediate and drives DataNotReadyError recovery elsewhere. Repairs the two call sites whose contract would otherwise change: crawler crawl() keeps returning CrawlResult on HTTP errors, and the unlocker's async get_status keeps returning a status string for its poll loop.
Results are the shape most callers actually receive, and their error field is a string - so every structured attribute was being discarded at the boundary where an exception became a ScrapeResult or CrawlResult. Adds an optional cause field, populated wherever that conversion happens, so callers can branch on the failure instead of parsing its message. Also resolves retryable from status_code when not given explicitly, so the attribute and is_retryable() can no longer disagree.
The engine raises before the per-endpoint checks run, which silently changed two contracts: dataset filter failures surfaced as APIError rather than DatasetError, and error messages lost the endpoint label. The datasets layer now re-types engine errors while letting RateLimitError through, and engine messages carry a bounded body excerpt so a bare print(exc) stays useful.
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.
Makes SDK failures machine-readable. Today
AsyncEngineconverts only HTTP 401 and 403 into exceptions and hands every other non-2xx back as an ordinary response, so each subsystem improvises its own answer and six of the seven exception types carry nothing but a prose message.Two failures this fixes
A rate-limited request surfaced as a JSON parse error.
/datasets/filteranswers 429 with the bare stringtoo_many_parallel_jobsunder a non-JSON content type, anddatasets/base.pycalledresponse.json()before checking the status. The user saw an aiohttp content-type error, never "you are rate limited".A token expiring mid-poll was reported as
"Job failed with status: error".DatasetAPIClient.get_statuscollapsed every non-200 into the string"error", discarding the status code unread — so an authentication problem was blamed on the user's scrape.What changed
429→ newRateLimitError(withretry_afterparsed from the header), other 4xx/5xx →APIError. Bodies are read as text first, so a non-JSON error degrades to a readable message instead of a parse exception.status_code,url,method,retry_after,retryable, andraw(bounded to 4 KB).ScrapeResult/CrawlResultgainedcause, populated wherever an exception is converted into a result — without this the structure was discarded atpoll_until_ready, so it never reached scraper callers at all:retry_with_backoffno longer retries by exception type. An error with no status code is raised locally — sometimes after the server accepted the work — so repeating it could create a duplicate billed job; those are never retried. Explicit 5xx still is. 429 never is, since those responses consume quota and retrying extends the lockout.Deliberately preserved
scraper_studio.trigger_immediate, and elsewhere drives theDataNotReadyError→poll_until_readyrecovery — the only working recovery path in the SDK. Covered by regression tests.crawler.crawl()still returnsCrawlResulton HTTP errors, and the unlocker's asyncget_statusstill returns a status string for its poll loop — both would otherwise have started raising.except DatasetErrorstill catches dataset failures: the datasets layer re-types engine errors, lettingRateLimitErrorthrough as the more specific type.TimeoutError-before-OSErrorordering and the SSL guidance messages are unchanged.Notes for review
status_codeinstead. A bounded 200-char body excerpt is appended so a bareprint(exc)stays useful.DatasetErrornow subclassesBrightDataError(still catchable as before).RateLimitErrorandDataNotReadyErrorare exported frombrightdatatop level.Tests: 321 → 365. Version → 2.6.0.