Skip to content

Sync SDK with Etsy API spec (Oct 2026), raise on every 4xx/5xx - #47

Merged
amitray007 merged 1 commit into
masterfrom
release/sdk-audit-2026-10-23
Oct 23, 2026
Merged

amitray007 merged 1 commit into
masterfrom
release/sdk-audit-2026-10-23

Conversation

@amitray007

Copy link
Copy Markdown
Owner

Summary

This PR syncs the SDK with the current Etsy OAS spec and fixes how EtsyClient handles error responses and token refresh. Coverage stays at 100% (104/104 operations). Etsy has published no new GitHub releases since 2026-03-24, so the spec diff is the only source of API changes.

Found 3 Must Fix, 1 Should Fix, 4 Informational. Code review added the error-handling fixes below.

Must Fix

  • deleteUserAddress was removed from the spec. delete_user_address still sends the request, but it now emits a DeprecationWarning.
  • featured_rank was removed from the updateListing body. UpdateListingRequest keeps the kwarg and the attribute but no longer sends the value. A non-zero value warns. 0 stays quiet, because fetched listings report it by default.
  • is_multi_video was removed from uploadListingVideo. The kwarg stays, but any value warns and is not sent. The docstring tells callers to remove an old video with delete_listing_video before they upload a replacement.

Each kept kwarg and the kept method has a value-scoped entry in specs/audit-ignore.json.

Should Fix

  • update_listing_inventory accepts the new optional enable_scale_validation query parameter. The default None sends nothing.

Error handling and token refresh (Session.py)

  • Every status of 400 or higher now raises RequestException. Before, only 400, 401, 403, 404, 409, 500, and 503 raised. A 429 from the new upload limit, or a 405, 422, 502, or 504, came back as a normal Response.
  • If an error body is not JSON, the exception keeps the raw text, or the reason phrase when the body is empty. A success body that is not JSON returns a Response instead of crashing.
  • limit_per_second now comes from X-Limit-Per-Second. Before, it held the daily limit. A missing rate-limit header no longer raises KeyError.
  • The token refresh call drops Authorization for that call only. A failed refresh leaves the header, tokens, and expiry unchanged. The client stores new tokens only after access_token, refresh_token, and a positive integer expires_in all validate. A refresh body that is not JSON raises RequestException.
  • The refreshed expiry is now a UTC-aware datetime instead of utcnow(). The README example now uses the same format.

Behavior changes. Code that branched on a Response for 402, 405, 410, 422, 429, 502, or 504 now gets a RequestException. sync_refresh now receives an aware datetime. The commit uses feat:, so it releases as a minor version.

Informational (no code impact)

  • Etsy calls enable_scale_validation temporary. Scale validation will become mandatory for all requests.
  • The ShopListing response still returns featured_rank.
  • The uploadListingVideo spec now documents a limit of 10 uploads per listing per day.
  • I checked all 18 earlier suppressions against the code and the spec, and they still hold. There are no stale ignores.

Test plan

  • pytest: 475 passed.
  • python scripts/audit_sdk.py --spec specs/latest.json: no parameter drift, no body drift, no stale ignores, 0 code issues, 20 verified suppressions.
  • python scripts/check_version_consistency.py: OK at 1.2.2.
  • New tests cover these cases: every 4xx/5xx raises, non-JSON bodies, partial rate-limit headers, a failed refresh that leaves state unchanged (including an invalid expires_in), and requests stripping a header set to None.
  • specs/baseline.json is updated to the audited spec.

🤖 Generated with Claude Code

Spec sync:
- Add the enable_scale_validation query param to update_listing_inventory
- Deprecate delete_user_address: Etsy removed deleteUserAddress from the spec
- Stop sending featured_rank on updateListing; a non-zero value warns
- Stop sending is_multi_video on uploadListingVideo; the kwarg warns and
  points callers to delete_listing_video for replacing a video
- Add audit-ignore entries for the retained deprecated kwargs and method
- Refresh the OAS spec baseline

Error handling:
- Treat every status >= 400 as an error, not a fixed list, so 429 (the new
  uploadListingVideo daily limit), 405, 422, 502 and 504 raise instead of
  returning a success-shaped Response
- Keep the raw text, or the reason phrase, for non-JSON error bodies
- Return a Response for success bodies that are empty or not JSON
- Read limit_per_second from X-Limit-Per-Second, and tolerate missing
  rate-limit headers instead of raising KeyError
- Token refresh drops Authorization for the refresh call only, so a failed
  refresh never leaves the session unauthenticated; new tokens are stored
  only after access_token, refresh_token and a positive integer expires_in
  validate (the error names any bad field), and a non-JSON refresh body
  raises RequestException
- Compute the refreshed expiry as a UTC-aware datetime (was utcnow())
- Update the README client example to a UTC-aware expiry

Callers that branched on a Response for 402/405/410/422/429/502/504 now get
a RequestException. sync_refresh now receives a UTC-aware expiry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amitray007 amitray007 added the api-drift Auto-created when Etsy API spec changes are detected by weekly maintenance check label Oct 23, 2026
@amitray007 amitray007 self-assigned this Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Test Coverage Report

Overall: 100% (1767/1767 statements covered)

Coverage by file
File Statements Missing Coverage
etsy_python/__init__.py 2 0 100%
etsy_python/_version.py 1 0 100%
etsy_python/v3/auth/OAuth.py 33 0 100%
etsy_python/v3/auth/__init__.py 1 0 100%
etsy_python/v3/common/Env.py 10 0 100%
etsy_python/v3/common/Request.py 3 0 100%
etsy_python/v3/common/Utils.py 34 0 100%
etsy_python/v3/enums/HolidayPreferences.py 29 0 100%
etsy_python/v3/enums/Language.py 12 0 100%
etsy_python/v3/enums/Listing.py 78 0 100%
etsy_python/v3/enums/ListingInventory.py 4 0 100%
etsy_python/v3/enums/ProcessingProfile.py 7 0 100%
etsy_python/v3/enums/ShippingProfile.py 261 0 100%
etsy_python/v3/enums/ShopReceipt.py 12 0 100%
etsy_python/v3/exceptions/BaseAPIException.py 10 0 100%
etsy_python/v3/exceptions/RequestException.py 8 0 100%
etsy_python/v3/exceptions/__init__.py 2 0 100%
etsy_python/v3/models/FileRequest.py 7 0 100%
etsy_python/v3/models/HolidayPreferences.py 8 0 100%
etsy_python/v3/models/Listing.py 201 0 100%
etsy_python/v3/models/Miscellaneous.py 8 0 100%
etsy_python/v3/models/ProcessingProfile.py 21 0 100%
etsy_python/v3/models/Product.py 7 0 100%
etsy_python/v3/models/Receipt.py 39 0 100%
etsy_python/v3/models/Request.py 22 0 100%
etsy_python/v3/models/ShippingProfile.py 85 0 100%
etsy_python/v3/models/Shop.py 24 0 100%
etsy_python/v3/models/ShopReturnPolicy.py 25 0 100%
etsy_python/v3/models/Utils.py 19 0 100%
etsy_python/v3/models/__init__.py 10 0 100%
etsy_python/v3/resources/HolidayPreferences.py 19 0 100%
etsy_python/v3/resources/Listing.py 102 0 100%
etsy_python/v3/resources/ListingFile.py 22 0 100%
etsy_python/v3/resources/ListingImage.py 22 0 100%
etsy_python/v3/resources/ListingInventory.py 25 0 100%
etsy_python/v3/resources/ListingOffering.py 12 0 100%
etsy_python/v3/resources/ListingProduct.py 12 0 100%
etsy_python/v3/resources/ListingTranslation.py 19 0 100%
etsy_python/v3/resources/ListingVariationImages.py 16 0 100%
etsy_python/v3/resources/ListingVideo.py 25 0 100%
etsy_python/v3/resources/Miscellaneous.py 16 0 100%
etsy_python/v3/resources/Payment.py 23 0 100%
etsy_python/v3/resources/PaymentLedgerEntry.py 15 0 100%
etsy_python/v3/resources/ProcessingProfile.py 26 0 100%
etsy_python/v3/resources/Receipt.py 25 0 100%
etsy_python/v3/resources/ReceiptTransactions.py 27 0 100%
etsy_python/v3/resources/Response.py 9 0 100%
etsy_python/v3/resources/Review.py 16 0 100%
etsy_python/v3/resources/Session.py 105 0 100%
etsy_python/v3/resources/ShippingProfile.py 58 0 100%
etsy_python/v3/resources/Shop.py 23 0 100%
etsy_python/v3/resources/ShopProductionPartner.py 11 0 100%
etsy_python/v3/resources/ShopReturnPolicy.py 28 0 100%
etsy_python/v3/resources/ShopSection.py 25 0 100%
etsy_python/v3/resources/Taxonomy.py 24 0 100%
etsy_python/v3/resources/User.py 14 0 100%
etsy_python/v3/resources/UserAddress.py 21 0 100%
etsy_python/v3/resources/__init__.py 29 0 100%
etsy_python/v3/resources/enums/RateLimit.py 8 0 100%
etsy_python/v3/resources/enums/Request.py 7 0 100%

Updated by PR Tests

@amitray007
amitray007 merged commit 77cb6e9 into master Oct 23, 2026
7 checks passed
@amitray007 amitray007 linked an issue Oct 23, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-drift Auto-created when Etsy API spec changes are detected by weekly maintenance check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

audit: Spec Drift [2026-10-19] — medium

1 participant