Skip to content

feat: sync SDK with Etsy API spec (Nov 2026 GPSR economic operators) - #50

Merged
amitray007 merged 6 commits into
masterfrom
release/sdk-audit-2026-11-10
Nov 10, 2026
Merged

amitray007 merged 6 commits into
masterfrom
release/sdk-audit-2026-11-10

Conversation

@amitray007

@amitray007 amitray007 commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Etsy's 2026-11-10 spec adds GPSR (EU General Product Safety Regulation) support and removes one endpoint. The SDK now covers all 107 operations again (it was at 96.3%).

The audit found 1 Must Fix and 2 Should Fix items:

  • Must Fix: Etsy removed getListingOffering. ListingOfferingResource and get_listing_offering stay so existing callers don't break, but both emit a DeprecationWarning. The method has an extra_method entry in specs/audit-ignore.json.
  • Should Fix: a new ShopEconomicOperatorResource covers create, list, get, and update (PATCH) for economic operators. Etsy has no delete endpoint for them.
  • Should Fix: CreateDraftListingRequest and UpdateListingRequest accept the six new gpsr_* fields, with a GPSROperatorType enum (seller, economic_operator).

Three code review rounds added these changes:

  • A CLEAR sentinel (etsy_python.v3.common.Utils.CLEAR) sends null for any nullable field. "", [] and 0 still clear values as before. CLEAR survives copy, deepcopy, and pickle. A CLEAR on a non-nullable field raises ValueError.
  • Request._validate() runs on construction and again in get_dict(). It checks CLEAR placement, GPSR type/id pairs, and blank economic operator fields.
  • Fixed an existing bug: clearing listing_type sent "_type": null. It now sends "type": null.

Informational:

  • check_releases.py hit GitHub's rate limit, and the script can't take a token. gh api shows no Etsy releases since 2026-03-24.
  • All 20 earlier suppressions still hold. None are stale.

Merge note: squash-merge with this title. The publish workflow reads the head commit subject, so feat: gives a minor bump.

Test plan

  • pytest: 561 passed
  • python scripts/audit_sdk.py --spec specs/latest.json: 100% coverage, no drift, no stale ignores
  • python scripts/check_version_consistency.py
  • Try create_shop_economic_operator and a GPSR listing update against a real EU shop

🤖 Generated with Claude Code

amitray007 and others added 5 commits November 10, 2026 23:39
- Add ShopEconomicOperatorResource: create, list, get, and update
  (PATCH) GPSR economic operator records, with request models.
- Add the six gpsr_* fields to CreateDraftListingRequest and
  UpdateListingRequest, plus a GPSROperatorType enum. All are nullable,
  so "" or 0 sends null to clear a value.
- Deprecate get_listing_offering: Etsy removed getListingOffering. It
  now emits a DeprecationWarning and has an audit-ignore entry.
- Update specs/baseline.json to the 2026-11-10 spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add a CLEAR sentinel for sending null on nullable fields, and share one
  is_null_value rule between Request.get_nulled and todict. Request
  rejects CLEAR on a non-nullable field. "", [] and 0 still clear.
- Type the GPSR listing fields and the nullable economic operator fields
  to accept CLEAR.
- Assign the GPSR and economic operator fields by name through
  Request._assign_fields, so a misordered argument fails instead of
  landing in the wrong field.
- Reject blank required economic operator fields locally, which Etsy
  would reject with a 400.
- Warn when ListingOfferingResource is constructed, not only when
  get_listing_offering is called.
- Give the listing model tests a module-level create helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Keep CLEAR a singleton across copy, deepcopy and pickle, and serialize
  any Clear as null in todict, so a copied request no longer sends {}.
- Re-check for CLEAR on non-nullable fields in get_dict, so a field set
  to CLEAR after construction raises instead of sending {}.
- Reject blank or cleared fields on createShopEconomicOperator. A new
  record has nothing to clear.
- Reject GPSR type/id pairs Etsy rejects: an id without type
  economic_operator, and type economic_operator without an id on create.
- Test that the create mandatory list matches NON_BLANK_FIELDS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Send "type": null, not "_type": null, when the listing type is cleared.
  todict now nulls exactly the keys get_nulled selected, with no second
  null check.
- Add Request.validate(), run on construction and in get_dict, so checks
  also catch fields reassigned after construction. The GPSR type/id check
  and the economic operator checks now live there.
- Move the GPSR fields back into a _GPSRFieldsMixin, and require an id
  whenever type economic_operator is sent, on update too.
- Economic operator requests reject any blank or whitespace-only string
  (clear with CLEAR), and an update with no fields.
- Name the field without its storage underscore in the CLEAR error, and
  correct the CLEAR docs about type hints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- On update, a GPSR type of economic_operator without an id is no longer
  rejected locally. The listing may already store the id, so Etsy
  decides. Create still requires the id.
- Rename Request.validate() to _validate(), so it cannot collide with a
  validate method on a user's own Request subclass.

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 Nov 10, 2026
@amitray007 amitray007 self-assigned this Nov 10, 2026
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Coverage Report

Overall: 100% (1889/1889 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 55 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 81 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 227 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 35 0 100%
etsy_python/v3/models/ShippingProfile.py 85 0 100%
etsy_python/v3/models/Shop.py 24 0 100%
etsy_python/v3/models/ShopEconomicOperator.py 29 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 12 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 16 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 24 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/ShopEconomicOperator.py 22 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 30 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

- todict nulls a nullable key only when its value is a null value again.
  External callers that pass a model's full nullable list no longer wipe
  real values. The "_type" to "type" key fix stays.
- Reject type economic_operator sent with a cleared id (CLEAR or 0), on
  create and update. Etsy always rejects that pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amitray007
amitray007 merged commit c4a38be into master Nov 10, 2026
6 checks passed
@amitray007 amitray007 linked an issue Nov 10, 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-11-09] — high

1 participant