Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion etsy_python/v3/enums/Listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class State(Enum):
SOLD_OUT = "sold_out"
DRAFT = "draft"
EXPIRED = "expired"
REMOVED = "removed" # Not in OAS spec; kept for backward compatibility, may be removed in next major version
REMOVED = "removed"


class VideoState(Enum):
Expand Down
84 changes: 82 additions & 2 deletions etsy_python/v3/models/Listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,53 @@ def _store_personalization(
)


class CreateDraftListingRequest(_PersonalizationFieldsMixin, Request):
#: EU commercial guarantee / GPSR fields (ECGT), shared by createDraftListing
#: and updateListing. All are optional and nullable. Etsy silently ignores them
#: for sellers who are not eligible EU traders. ``ecgt_software_update_details``
#: is ignored for physical listings; the other six are ignored for digital ones.
#: ``ecgt_garan_brand``, ``ecgt_garan_model``, ``ecgt_garan_years`` and
#: ``ecgt_garan_guarantee_details`` are required *together* -- Etsy enforces
#: that server-side and rejects a partial set.
ECGT_FIELDS = (
"ecgt_garan_brand",
"ecgt_garan_model",
"ecgt_garan_years",
"ecgt_garan_guarantee_details",
"ecgt_other_commercial_guarantee_details",
"ecgt_after_sales_service_info",
"ecgt_software_update_details",
)


class _ECGTFieldsMixin:
"""Storage for the EU commercial guarantee (ECGT) listing fields.

Both listing request models accept the same seven fields, so the
assignment lives here instead of being duplicated. See ``ECGT_FIELDS``.
"""

def _store_ecgt(
self,
ecgt_garan_brand: Optional[str],
ecgt_garan_model: Optional[str],
ecgt_garan_years: Optional[int],
ecgt_garan_guarantee_details: Optional[str],
ecgt_other_commercial_guarantee_details: Optional[str],
ecgt_after_sales_service_info: Optional[str],
ecgt_software_update_details: Optional[str],
) -> None:
self.ecgt_garan_brand = ecgt_garan_brand
self.ecgt_garan_model = ecgt_garan_model
self.ecgt_garan_years = ecgt_garan_years
self.ecgt_garan_guarantee_details = ecgt_garan_guarantee_details
self.ecgt_other_commercial_guarantee_details = (
ecgt_other_commercial_guarantee_details
)
self.ecgt_after_sales_service_info = ecgt_after_sales_service_info
self.ecgt_software_update_details = ecgt_software_update_details


class CreateDraftListingRequest(_PersonalizationFieldsMixin, _ECGTFieldsMixin, Request):
nullable = [
"shipping_profile_id",
"return_policy_id",
Expand All @@ -146,6 +192,7 @@ class CreateDraftListingRequest(_PersonalizationFieldsMixin, Request):
"production_partner_ids",
"image_ids",
"readiness_state_id",
*ECGT_FIELDS,
]
mandatory = [
"quantity",
Expand Down Expand Up @@ -192,6 +239,13 @@ def __init__(
is_taxable: Optional[bool] = None,
listing_type: Optional[Type] = None,
readiness_state_id: Optional[int] = None,
ecgt_garan_brand: Optional[str] = None,
ecgt_garan_model: Optional[str] = None,
ecgt_garan_years: Optional[int] = None,
ecgt_garan_guarantee_details: Optional[str] = None,
ecgt_other_commercial_guarantee_details: Optional[str] = None,
ecgt_after_sales_service_info: Optional[str] = None,
ecgt_software_update_details: Optional[str] = None,
):
self.quantity = quantity
self.title = title
Expand Down Expand Up @@ -228,13 +282,22 @@ def __init__(
self.is_taxable = is_taxable
self._type = listing_type
self.readiness_state_id = readiness_state_id
self._store_ecgt(
ecgt_garan_brand,
ecgt_garan_model,
ecgt_garan_years,
ecgt_garan_guarantee_details,
ecgt_other_commercial_guarantee_details,
ecgt_after_sales_service_info,
ecgt_software_update_details,
)
super().__init__(
nullable=CreateDraftListingRequest.nullable,
mandatory=CreateDraftListingRequest.mandatory,
)


class UpdateListingRequest(_PersonalizationFieldsMixin, Request):
class UpdateListingRequest(_PersonalizationFieldsMixin, _ECGTFieldsMixin, Request):
nullable: List[str] = [
"materials",
"shipping_profile_id",
Expand All @@ -250,6 +313,7 @@ class UpdateListingRequest(_PersonalizationFieldsMixin, Request):
"featured_rank",
"production_partner_ids",
"_type",
*ECGT_FIELDS,
]

mandatory: List[str] = []
Expand Down Expand Up @@ -284,6 +348,13 @@ def __init__(
is_supply: Optional[bool] = None,
production_partner_ids: Optional[List[int]] = None,
listing_type: Optional[Type] = None,
ecgt_garan_brand: Optional[str] = None,
ecgt_garan_model: Optional[str] = None,
ecgt_garan_years: Optional[int] = None,
ecgt_garan_guarantee_details: Optional[str] = None,
ecgt_other_commercial_guarantee_details: Optional[str] = None,
ecgt_after_sales_service_info: Optional[str] = None,
ecgt_software_update_details: Optional[str] = None,
):
self.image_ids = image_ids
self.title = title
Expand Down Expand Up @@ -315,6 +386,15 @@ def __init__(
self.is_supply = is_supply
self.production_partner_ids = production_partner_ids
self._type = listing_type
self._store_ecgt(
ecgt_garan_brand,
ecgt_garan_model,
ecgt_garan_years,
ecgt_garan_guarantee_details,
ecgt_other_commercial_guarantee_details,
ecgt_after_sales_service_info,
ecgt_software_update_details,
)
super().__init__(
nullable=UpdateListingRequest.nullable,
mandatory=UpdateListingRequest.mandatory,
Expand Down
19 changes: 16 additions & 3 deletions etsy_python/v3/resources/ListingVideo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Union
from typing import Optional, Union

from etsy_python.v3.exceptions.RequestException import RequestException
from etsy_python.v3.models.Listing import UpdateListingVideoRequest
Expand Down Expand Up @@ -29,9 +29,22 @@ def get_listing_videos(self, listing_id: int) -> Union[Response, RequestExceptio
return self.session.make_request(endpoint)

def upload_listing_video(
self, shop_id: int, listing_id: int, listing_video: UpdateListingVideoRequest
self,
shop_id: int,
listing_id: int,
listing_video: UpdateListingVideoRequest,
is_multi_video: Optional[bool] = None,
) -> Union[Response, RequestException]:
"""Upload a video to a listing.

Pass ``is_multi_video=True`` to keep existing videos on the listing.
Omitting it preserves the former single-video behaviour, where the
upload replaces any video already attached.
"""
endpoint = f"/shops/{shop_id}/listings/{listing_id}/videos"
return self.session.make_request(
endpoint, method=Method.POST, payload=listing_video
endpoint,
method=Method.POST,
payload=listing_video,
query_params={"is_multi_video": is_multi_video},
)
30 changes: 0 additions & 30 deletions specs/audit-ignore.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,6 @@
"reason": "By design: the SDK names US_HOLIDAYS (1-11) and CA_HOLIDAYS (12-23) and documents passing integer IDs directly for other regions (24-105), so the full spec enum is intentionally not enumerated. See enums/HolidayPreferences.py.",
"added": "2026-06-02"
},
{
"type": "enum_staleness",
"key": "ShopListing.state -> State",
"direction": "extra",
"values": [
"removed"
],
"reason": "State.REMOVED is kept for backward compatibility and is not in the OAS response schema. Documented inline in enums/Listing.py; may be removed in the next major version.",
"added": "2026-06-02"
},
{
"type": "enum_staleness",
"key": "ShopListingWithAssociations.state -> State",
"direction": "extra",
"values": [
"removed"
],
"reason": "State.REMOVED is kept for backward compatibility and is not in the OAS response schema. Documented inline in enums/Listing.py; may be removed in the next major version.",
"added": "2026-06-02"
},
{
"type": "enum_staleness",
"key": "getListing.includes -> Includes",
Expand All @@ -155,16 +135,6 @@
"reason": "Etsy removed 'Shipping' and 'Inventory' from the includes enum on getListing and getListingsByListingIds (still valid on getListingsByShop). The SDK Includes enum keeps both for backward compatibility and because they remain valid on getListingsByShop, which shares the enum; removing them would be a breaking change. Documented inline in enums/Listing.py.",
"added": "2026-07-08"
},
{
"type": "enum_staleness",
"key": "getListingsByShop.state -> State",
"direction": "extra",
"values": [
"removed"
],
"reason": "Same State.REMOVED back-compat value as ShopListing.state; surfaced additionally as the state query parameter on getListingsByShop now that parameter-level enums are audited. Documented inline in enums/Listing.py.",
"added": "2026-07-08"
},
{
"type": "enum_staleness",
"key": "updateHolidayPreferences.holiday_id -> CA_HOLIDAYS",
Expand Down
Loading
Loading