Skip to content

fix: stop sending 'legacy' param removed from 8 listing endpoints (#34) - #35

Merged
amitray007 merged 4 commits into
masterfrom
fix/sdk-audit-2026-07-30
Jul 30, 2026
Merged

fix: stop sending 'legacy' param removed from 8 listing endpoints (#34)#35
amitray007 merged 4 commits into
masterfrom
fix/sdk-audit-2026-07-30

Conversation

@amitray007

Copy link
Copy Markdown
Owner

Summary

Resolves the spec-drift reported in #34 (Etsy OAS spec, 2026-07-27).

The drift: Etsy removed the legacy query parameter from 8 listing endpoints with no deprecation period — createDraftListing, getListingsByShop, getListing, findAllListingsActive, findAllActiveListingsByShop, updateListing, getListingInventory, updateListingInventory. It remains valid on 14 other operations (receipts, transactions, getListingsByListingIds, …), so this is a targeted removal, not a global one.

Approach (non-breaking): the legacy keyword is kept on the 8 affected methods so no caller breaks, but passing it now emits a DeprecationWarning and the value is no longer forwarded to the API. Any explicit value — including legacy=False, which previously sent ?legacy=false on the wire — is discarded. Plan to drop the kwarg in the next major version. The 14 still-valid sites are untouched.

Also in this PR (found during the work):

  • Audit suppression engine could not suppress parameter drift at all — that report section rendered straight from raw data and never passed through the ignore engine, so the 8 deliberate back-compat kwargs would be flagged as noise on every weekly maintenance run. Routed param drift through the engine as structured findings so suppressions stay value-scoped: a newly drifted param on an already-suppressed operation still surfaces. Added 8 value-scoped param_drift entries to specs/audit-ignore.json.
  • add_on_price was added to the PersonalizationQuestion schema (incl. the updateListingPersonalization request body). No SDK change needed — personalization_questions is typed List[Dict[str, Any]] and passes it through; pinned by a test.
  • Synced .bumpversion.cfg (had drifted to 1.0.19) up to _version.py (1.1.10) so the consistency check passes.

Code review

An independent review pass found two mutants surviving the test suite plus a suppression edge; all fixed in 41c7943:

  • The legacy is not None guard had no legacy=False coverage → parametrized over [True, False].
  • pytest.warns(match=...) prefix-matched sibling operations (getListinggetListingsByShop) → anchored all matches.
  • A valued ignore omitting values silently defaulted to wildcard → now defaults to "suppress nothing / self-report stale".

Test plan

source venv/bin/activate
pytest -q                                            # 380 passed
pytest --cov=etsy_python --cov-report=term-missing   # 100%
python scripts/audit_sdk.py --spec specs/latest.json # no drift, 19 suppressed, no stale ignores
python scripts/check_version_consistency.py          # OK 1.1.10

Closes #34

🤖 Generated with Claude Code

amitray007 and others added 4 commits July 30, 2026 02:17
Etsy's 2026-07-27 spec removed the `legacy` query parameter from 8 listing
endpoints with no deprecation period. It remains valid on 14 other operations
(receipts, transactions, getListingsByListingIds, ...), so this is a targeted
removal, not a global one.

The `legacy` keyword argument is retained on the affected methods so existing
callers don't break, but passing it now emits a DeprecationWarning and the
value is no longer forwarded to the API. Plan to drop the kwarg in the next
major version.

Affected: createDraftListing, getListingsByShop, getListing,
findAllListingsActive, findAllActiveListingsByShop, updateListing,
getListingInventory, updateListingInventory.

Also route parameter drift through the audit suppression engine. It previously
rendered straight from raw data, so param findings could not be suppressed at
all — the 8 deliberate back-compat kwargs would have been flagged as noise on
every weekly run. Findings now carry (type, key, direction, values) like enum
findings, so an ignore suppresses only the reviewed parameter names and newly
drifted params on the same operation still surface (verified by test and by
temporarily introducing a bogus param). Report output is unchanged when no
param_drift ignores are configured.

Note: specs/baseline.json refreshed; `add_on_price` on PersonalizationQuestion
needs no SDK change (response-only field, and questions are typed as
List[Dict[str, Any]]). All 11 pre-existing suppressions re-verified as still
valid. Pre-existing unrelated drift: .bumpversion.cfg (1.0.19) is behind
_version.py (1.1.10).

Closes #34

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correction to the previous commit message: `add_on_price` is NOT
response-only. The 2026-07-27 spec added it to the
updateListingPersonalization *request* body too, as an optional nullable
float per question (only supported for optional text_input questions).

The conclusion is unchanged — no SDK change is required — but for a
different reason than stated: `personalization_questions` is typed
List[Dict[str, Any]], so the field already serializes through to the API.
This test pins that pass-through so the behaviour is deliberate rather
than incidental.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial probing of the extended suppression engine surfaced two sharp
edges. Both are pre-existing properties of the enum design, but adding
param_drift makes them reachable for a new finding type, so pin them:

- A `"*"` wildcard on a param_drift entry would hide unreviewed parameter
  drift on that operation, defeating the self-verifying property.
- partition_findings applies only the FIRST ignore matching a given
  (type, key, direction). A duplicate entry would silently suppress part
  of a finding while reporting itself as stale.

Both guards were mutation-tested: each fails when the condition is
injected into the shipped file and passes once reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Independent code review found two mutants surviving the 370-test suite plus
a suppression-engine sharp edge. All three verified and fixed:

1. `legacy is not None` guard had zero coverage. No test passed legacy=False,
   so mutating the guard to `if legacy:` (which stops warning on False) passed
   all tests. Parametrized every legacy warn-and-drop test over [True, False]
   so explicit False is now pinned. Mutant now fails 8 tests.

2. pytest.warns(match=...) is re.search, and operation ids are prefixes of
   one another ("getListing" ⊂ "getListingsByShop"/"getListingInventory",
   "updateListing" ⊂ "updateListingInventory"). Five of eight warn assertions
   could not tell the operations apart — swapping the op id inside get_listing
   passed all tests. Anchored every match to `from {op} by Etsy`. Mutant now
   fails 2 tests.

3. Warning message reworded to state that any explicit value, incl. False, is
   discarded — legacy=False previously sent ?legacy=false on the wire, so
   dropping it is a request-shape change, not a no-op. Docstring documents the
   deliberate is-not-None guard. (Release notes are generated from commits, so
   this message is the changelog entry for that wire change.)

4. partition_findings defaulted a valued ignore's missing `values` to "*",
   so an entry that OMITTED values silently suppressed everything — reachable
   by omission, defeating the "prefer explicit values" guarantee for the newly
   added param_drift type. Default is now [] (suppress nothing, self-report
   stale); an explicit "*" still works. Guarded by two new tests.

Not changed: the add_on_price test (review agreed it earns its place),
_norm_values on a stray string (fails safe), and the 8x-duplicated ignore
reason (JSON has no shared constant; low value).

380 tests pass, 100% coverage retained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Test Coverage Report

Overall: 100% (1701/1701 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 6 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 150 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 22 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 94 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 23 0 100%
etsy_python/v3/resources/User.py 14 0 100%
etsy_python/v3/resources/UserAddress.py 19 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 force-pushed the fix/sdk-audit-2026-07-30 branch from e3d5b57 to 41c7943 Compare July 30, 2026 08:16
@amitray007
amitray007 merged commit 7b9fda2 into master Jul 30, 2026
6 checks passed
@amitray007
amitray007 deleted the fix/sdk-audit-2026-07-30 branch July 30, 2026 08:25
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.

audit: Spec Drift [2026-07-27] — medium

1 participant