etsy-python 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {etsy_python-1.2.1/etsy_python.egg-info → etsy_python-1.2.3}/PKG-INFO +3 -3
- {etsy_python-1.2.1 → etsy_python-1.2.3}/README.md +2 -2
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/_version.py +1 -1
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/common/Request.py +2 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Listing.py +35 -2
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingInventory.py +2 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingVideo.py +16 -7
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Session.py +61 -23
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Taxonomy.py +10 -3
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/UserAddress.py +9 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3/etsy_python.egg-info}/PKG-INFO +3 -3
- {etsy_python-1.2.1 → etsy_python-1.2.3}/LICENSE +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/MANIFEST.in +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/auth/OAuth.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/auth/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/common/Env.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/common/Utils.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/common/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/HolidayPreferences.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/Language.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/Listing.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/ListingInventory.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/ProcessingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/ShippingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/ShopReceipt.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/enums/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/exceptions/BaseAPIException.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/exceptions/RequestException.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/exceptions/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/FileRequest.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/HolidayPreferences.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Miscellaneous.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/ProcessingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Product.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Receipt.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Request.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/ShippingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Shop.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/ShopReturnPolicy.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/Utils.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/models/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/HolidayPreferences.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Listing.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingFile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingImage.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingOffering.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingProduct.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingTranslation.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ListingVariationImages.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Miscellaneous.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Payment.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/PaymentLedgerEntry.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ProcessingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Receipt.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ReceiptTransactions.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Response.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Review.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ShippingProfile.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/Shop.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ShopProductionPartner.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ShopReturnPolicy.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/ShopSection.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/User.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/enums/RateLimit.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/enums/Request.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python/v3/resources/enums/__init__.py +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python.egg-info/SOURCES.txt +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python.egg-info/dependency_links.txt +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python.egg-info/requires.txt +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/etsy_python.egg-info/top_level.txt +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/setup.cfg +0 -0
- {etsy_python-1.2.1 → etsy_python-1.2.3}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etsy-python
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Etsy API Client Library for Python
|
|
5
5
|
Author: Amit Ray
|
|
6
6
|
Author-email: mail@amitray.dev
|
|
@@ -181,7 +181,7 @@ token_data = oauth.get_access_token()
|
|
|
181
181
|
### 2. Initialize Client Session
|
|
182
182
|
|
|
183
183
|
```python
|
|
184
|
-
from datetime import datetime
|
|
184
|
+
from datetime import datetime, timedelta, timezone
|
|
185
185
|
from etsy_python.v3.resources.Session import EtsyClient
|
|
186
186
|
|
|
187
187
|
# Create client with tokens
|
|
@@ -189,7 +189,7 @@ client = EtsyClient(
|
|
|
189
189
|
keystring="your_api_key",
|
|
190
190
|
access_token=token_data["access_token"],
|
|
191
191
|
refresh_token=token_data["refresh_token"],
|
|
192
|
-
expiry=datetime.
|
|
192
|
+
expiry=datetime.now(tz=timezone.utc) + timedelta(seconds=token_data["expires_in"])
|
|
193
193
|
)
|
|
194
194
|
```
|
|
195
195
|
|
|
@@ -144,7 +144,7 @@ token_data = oauth.get_access_token()
|
|
|
144
144
|
### 2. Initialize Client Session
|
|
145
145
|
|
|
146
146
|
```python
|
|
147
|
-
from datetime import datetime
|
|
147
|
+
from datetime import datetime, timedelta, timezone
|
|
148
148
|
from etsy_python.v3.resources.Session import EtsyClient
|
|
149
149
|
|
|
150
150
|
# Create client with tokens
|
|
@@ -152,7 +152,7 @@ client = EtsyClient(
|
|
|
152
152
|
keystring="your_api_key",
|
|
153
153
|
access_token=token_data["access_token"],
|
|
154
154
|
refresh_token=token_data["refresh_token"],
|
|
155
|
-
expiry=datetime.
|
|
155
|
+
expiry=datetime.now(tz=timezone.utc) + timedelta(seconds=token_data["expires_in"])
|
|
156
156
|
)
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -57,6 +57,28 @@ def _warn_if_personalization_used(
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
_FEATURED_RANK_DEPRECATION_MSG = (
|
|
61
|
+
"featured_rank was removed from updateListing by the Etsy API and is no "
|
|
62
|
+
"longer sent; it will be dropped from UpdateListingRequest in a future "
|
|
63
|
+
"major version."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _warn_if_featured_rank_used(featured_rank: Optional[int]) -> None:
|
|
68
|
+
"""Emit DeprecationWarning when the removed featured_rank field is set.
|
|
69
|
+
|
|
70
|
+
``0`` does not warn: it is the default a fetched listing reports, so
|
|
71
|
+
callers that round-trip a listing into an update would otherwise warn on
|
|
72
|
+
every call. This matches the personalization fields, which skip falsy
|
|
73
|
+
values. ``stacklevel=3`` points at the caller of the model ``__init__`` or
|
|
74
|
+
the ``featured_rank`` setter.
|
|
75
|
+
"""
|
|
76
|
+
if featured_rank:
|
|
77
|
+
warnings.warn(
|
|
78
|
+
_FEATURED_RANK_DEPRECATION_MSG, DeprecationWarning, stacklevel=3
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
60
82
|
class _PersonalizationFieldsMixin:
|
|
61
83
|
"""Accessors for the personalization fields Etsy removed from listings.
|
|
62
84
|
|
|
@@ -310,7 +332,6 @@ class UpdateListingRequest(_PersonalizationFieldsMixin, _ECGTFieldsMixin, Reques
|
|
|
310
332
|
"item_weight_unit",
|
|
311
333
|
"item_dimensions_unit",
|
|
312
334
|
"tags",
|
|
313
|
-
"featured_rank",
|
|
314
335
|
"production_partner_ids",
|
|
315
336
|
"_type",
|
|
316
337
|
*ECGT_FIELDS,
|
|
@@ -375,7 +396,10 @@ class UpdateListingRequest(_PersonalizationFieldsMixin, _ECGTFieldsMixin, Reques
|
|
|
375
396
|
self.tags = tags
|
|
376
397
|
self.who_made = who_made
|
|
377
398
|
self.when_made = when_made
|
|
378
|
-
|
|
399
|
+
# Removed from the updateListing body by Etsy: stored under an
|
|
400
|
+
# underscore name so todict never sends it.
|
|
401
|
+
self._featured_rank = featured_rank
|
|
402
|
+
_warn_if_featured_rank_used(featured_rank)
|
|
379
403
|
self._store_personalization(
|
|
380
404
|
is_personalizable,
|
|
381
405
|
personalization_is_required,
|
|
@@ -400,6 +424,15 @@ class UpdateListingRequest(_PersonalizationFieldsMixin, _ECGTFieldsMixin, Reques
|
|
|
400
424
|
mandatory=UpdateListingRequest.mandatory,
|
|
401
425
|
)
|
|
402
426
|
|
|
427
|
+
@property
|
|
428
|
+
def featured_rank(self) -> Optional[int]:
|
|
429
|
+
return self._featured_rank
|
|
430
|
+
|
|
431
|
+
@featured_rank.setter
|
|
432
|
+
def featured_rank(self, value: Optional[int]) -> None:
|
|
433
|
+
self._featured_rank = value
|
|
434
|
+
_warn_if_featured_rank_used(value)
|
|
435
|
+
|
|
403
436
|
|
|
404
437
|
class UpdateListingInventoryRequest(Request):
|
|
405
438
|
nullable: List[str] = ["readiness_state_on_property"]
|
|
@@ -35,6 +35,7 @@ class ListingInventoryResource:
|
|
|
35
35
|
self, listing_id: int, listing_inventory: UpdateListingInventoryRequest,
|
|
36
36
|
legacy: Optional[bool] = None,
|
|
37
37
|
max_variations_supported: Optional[MaxVariationsSupported] = None,
|
|
38
|
+
enable_scale_validation: Optional[bool] = None,
|
|
38
39
|
) -> Union[Response, RequestException]:
|
|
39
40
|
if legacy is not None:
|
|
40
41
|
warn_removed_legacy_param("updateListingInventory")
|
|
@@ -43,6 +44,7 @@ class ListingInventoryResource:
|
|
|
43
44
|
"max_variations_supported": max_variations_supported.value
|
|
44
45
|
if max_variations_supported is not None
|
|
45
46
|
else None,
|
|
47
|
+
"enable_scale_validation": enable_scale_validation,
|
|
46
48
|
}
|
|
47
49
|
return self.session.make_request(
|
|
48
50
|
endpoint, method=Method.PUT, payload=listing_inventory,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from typing import Optional, Union
|
|
3
4
|
|
|
@@ -37,14 +38,22 @@ class ListingVideoResource:
|
|
|
37
38
|
) -> Union[Response, RequestException]:
|
|
38
39
|
"""Upload a video to a listing.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
upload replaces
|
|
41
|
+
``is_multi_video`` is deprecated: Etsy removed it from
|
|
42
|
+
uploadListingVideo, so any value is discarded and not sent, and Etsy
|
|
43
|
+
no longer documents whether an upload replaces an existing video. To
|
|
44
|
+
replace one, remove it with ``delete_listing_video`` before uploading
|
|
45
|
+
the new video. Etsy limits uploads to 10 per listing per day.
|
|
43
46
|
"""
|
|
47
|
+
if is_multi_video is not None:
|
|
48
|
+
warnings.warn(
|
|
49
|
+
"'is_multi_video' was removed from uploadListingVideo by Etsy "
|
|
50
|
+
"and is no longer sent; to replace a video, remove it with "
|
|
51
|
+
"delete_listing_video first. The argument will be dropped from "
|
|
52
|
+
"this method in a future major version.",
|
|
53
|
+
DeprecationWarning,
|
|
54
|
+
stacklevel=2,
|
|
55
|
+
)
|
|
44
56
|
endpoint = f"/shops/{shop_id}/listings/{listing_id}/videos"
|
|
45
57
|
return self.session.make_request(
|
|
46
|
-
endpoint,
|
|
47
|
-
method=Method.POST,
|
|
48
|
-
payload=listing_video,
|
|
49
|
-
query_params={"is_multi_video": is_multi_video},
|
|
58
|
+
endpoint, method=Method.POST, payload=listing_video
|
|
50
59
|
)
|
|
@@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, Optional
|
|
|
3
3
|
|
|
4
4
|
from requests import Session
|
|
5
5
|
|
|
6
|
-
from etsy_python.v3.common.Request import
|
|
6
|
+
from etsy_python.v3.common.Request import NO_RESPONSE_CODES
|
|
7
7
|
from etsy_python.v3.common.Env import environment
|
|
8
8
|
from etsy_python.v3.common.Utils import generate_get_uri
|
|
9
9
|
from etsy_python.v3.resources.enums.Request import Method
|
|
@@ -35,22 +35,51 @@ class EtsyClient:
|
|
|
35
35
|
self.session.headers = self._get_resource_headers(keystring, access_token)
|
|
36
36
|
|
|
37
37
|
def update_token(self) -> tuple:
|
|
38
|
-
self._remove_authorization_header()
|
|
39
38
|
refresh_json = self._get_refresh_json(self.keystring, self.refresh_token)
|
|
40
|
-
|
|
39
|
+
# Drop Authorization for this call only. The shared session header is
|
|
40
|
+
# never touched, so a failed refresh cannot leave later requests
|
|
41
|
+
# unauthenticated.
|
|
42
|
+
response = self.session.post(
|
|
43
|
+
environment.token_url, json=refresh_json, headers={"Authorization": None}
|
|
44
|
+
)
|
|
41
45
|
response_json = self._process_request(response).message
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
if not isinstance(response_json, dict):
|
|
47
|
+
raise RequestException(
|
|
48
|
+
code=response.status_code,
|
|
49
|
+
error="Token refresh returned a non-JSON body",
|
|
50
|
+
)
|
|
51
|
+
access_token = response_json.get("access_token")
|
|
52
|
+
refresh_token = response_json.get("refresh_token")
|
|
53
|
+
expires_in = response_json.get("expires_in")
|
|
54
|
+
# expires_in must be a positive int; bool is an int subclass, and
|
|
55
|
+
# floats allow NaN/inf, which timedelta cannot handle.
|
|
56
|
+
invalid = [
|
|
57
|
+
name
|
|
58
|
+
for name, ok in (
|
|
59
|
+
("access_token", bool(access_token)),
|
|
60
|
+
("refresh_token", bool(refresh_token)),
|
|
61
|
+
(
|
|
62
|
+
"expires_in",
|
|
63
|
+
isinstance(expires_in, int)
|
|
64
|
+
and not isinstance(expires_in, bool)
|
|
65
|
+
and 0 < expires_in <= 10**9,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
if not ok
|
|
69
|
+
]
|
|
70
|
+
if invalid:
|
|
46
71
|
raise RequestException(
|
|
47
|
-
code=401,
|
|
72
|
+
code=401,
|
|
73
|
+
error=response_json.get("error", "Something went wrong!"),
|
|
74
|
+
error_description=response_json.get("error_description")
|
|
75
|
+
or "Token refresh response has missing or invalid: "
|
|
76
|
+
+ ", ".join(invalid),
|
|
48
77
|
)
|
|
49
78
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.expiry =
|
|
79
|
+
# Store the new tokens only once the whole response is valid.
|
|
80
|
+
self.access_token = access_token
|
|
81
|
+
self.refresh_token = refresh_token
|
|
82
|
+
self.expiry = datetime.now(tz=timezone.utc) + timedelta(seconds=expires_in)
|
|
54
83
|
|
|
55
84
|
self.session.headers.update(
|
|
56
85
|
self._prepare_authorization_token(self.access_token)
|
|
@@ -70,9 +99,6 @@ class EtsyClient:
|
|
|
70
99
|
def _prepare_authorization_token(access_token: str) -> dict:
|
|
71
100
|
return {"Authorization": f"Bearer {access_token}"}
|
|
72
101
|
|
|
73
|
-
def _remove_authorization_header(self) -> None:
|
|
74
|
-
self.session.headers.pop("Authorization", None)
|
|
75
|
-
|
|
76
102
|
@staticmethod
|
|
77
103
|
def _get_request_headers(keystring: str) -> dict:
|
|
78
104
|
return {"Accept": "application/json", "x-api-key": keystring}
|
|
@@ -132,21 +158,26 @@ class EtsyClient:
|
|
|
132
158
|
return self._process_request(response)
|
|
133
159
|
|
|
134
160
|
def _process_request(self, response: Any) -> Any:
|
|
135
|
-
|
|
161
|
+
# Any 4xx/5xx is an error, including codes Etsy documents later
|
|
162
|
+
# (e.g. 429 for the uploadListingVideo daily limit).
|
|
163
|
+
is_error = response.status_code >= 400
|
|
136
164
|
|
|
137
165
|
rate_limits = None
|
|
138
166
|
if "X-Limit-Per-Day" in response.headers:
|
|
139
167
|
rate_limits = RateLimit(
|
|
140
|
-
response.headers
|
|
141
|
-
response.headers
|
|
142
|
-
response.headers
|
|
143
|
-
response.headers
|
|
168
|
+
response.headers.get("X-Limit-Per-Second"),
|
|
169
|
+
response.headers.get("X-Remaining-This-Second"),
|
|
170
|
+
response.headers.get("X-Limit-Per-Day"),
|
|
171
|
+
response.headers.get("X-Remaining-Today"),
|
|
144
172
|
)
|
|
145
173
|
|
|
146
|
-
response_json = (
|
|
147
|
-
response.json() if response.status_code not in NO_RESPONSE_CODES else None
|
|
148
|
-
)
|
|
149
174
|
if is_error:
|
|
175
|
+
try:
|
|
176
|
+
response_json = response.json()
|
|
177
|
+
except ValueError:
|
|
178
|
+
# Gateway and method errors (405, 502, 504) can return a
|
|
179
|
+
# non-JSON or empty body; keep the text, or the reason phrase.
|
|
180
|
+
response_json = response.text or response.reason
|
|
150
181
|
error_response = (
|
|
151
182
|
response_json
|
|
152
183
|
if isinstance(response_json, dict)
|
|
@@ -160,6 +191,13 @@ class EtsyClient:
|
|
|
160
191
|
error_description,
|
|
161
192
|
rate_limits=rate_limits,
|
|
162
193
|
)
|
|
194
|
+
response_json = None
|
|
195
|
+
if response.status_code not in NO_RESPONSE_CODES:
|
|
196
|
+
try:
|
|
197
|
+
response_json = response.json()
|
|
198
|
+
except ValueError:
|
|
199
|
+
# A success with an empty or non-JSON body (202, 304, ...).
|
|
200
|
+
response_json = response.text
|
|
163
201
|
return Response(
|
|
164
202
|
response.status_code, response_json or "OK", rate_limits=rate_limits
|
|
165
203
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import Any, Dict, Optional, Union
|
|
3
3
|
|
|
4
4
|
from etsy_python.v3.exceptions.RequestException import RequestException
|
|
5
5
|
from etsy_python.v3.resources.Response import Response
|
|
@@ -30,7 +30,14 @@ class SellerTaxonomyResource:
|
|
|
30
30
|
return self.session.make_request(endpoint)
|
|
31
31
|
|
|
32
32
|
def get_properties_by_taxonomy_id(
|
|
33
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
taxonomy_id: int,
|
|
35
|
+
supports_variations: Optional[bool] = None,
|
|
36
|
+
supports_attributes: Optional[bool] = None,
|
|
34
37
|
) -> Union[Response, RequestException]:
|
|
35
38
|
endpoint = f"/seller-taxonomy/nodes/{taxonomy_id}/properties"
|
|
36
|
-
|
|
39
|
+
query_params: Dict[str, Any] = {
|
|
40
|
+
"supports_variations": supports_variations,
|
|
41
|
+
"supports_attributes": supports_attributes,
|
|
42
|
+
}
|
|
43
|
+
return self.session.make_request(endpoint, query_params=query_params)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from typing import Union, Dict, Any
|
|
3
4
|
|
|
@@ -14,6 +15,14 @@ class UserAddressResource:
|
|
|
14
15
|
def delete_user_address(
|
|
15
16
|
self, user_address_id: int
|
|
16
17
|
) -> Union[Response, RequestException]:
|
|
18
|
+
"""Deprecated: Etsy removed deleteUserAddress from the Open API v3 spec."""
|
|
19
|
+
warnings.warn(
|
|
20
|
+
"delete_user_address is deprecated: Etsy removed deleteUserAddress "
|
|
21
|
+
"from the Open API v3 spec, so the request may fail. It will be "
|
|
22
|
+
"removed in a future major version.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
|
17
26
|
endpoint = f"/user/addresses/{user_address_id}"
|
|
18
27
|
return self.session.make_request(endpoint, method=Method.DELETE)
|
|
19
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etsy-python
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Etsy API Client Library for Python
|
|
5
5
|
Author: Amit Ray
|
|
6
6
|
Author-email: mail@amitray.dev
|
|
@@ -181,7 +181,7 @@ token_data = oauth.get_access_token()
|
|
|
181
181
|
### 2. Initialize Client Session
|
|
182
182
|
|
|
183
183
|
```python
|
|
184
|
-
from datetime import datetime
|
|
184
|
+
from datetime import datetime, timedelta, timezone
|
|
185
185
|
from etsy_python.v3.resources.Session import EtsyClient
|
|
186
186
|
|
|
187
187
|
# Create client with tokens
|
|
@@ -189,7 +189,7 @@ client = EtsyClient(
|
|
|
189
189
|
keystring="your_api_key",
|
|
190
190
|
access_token=token_data["access_token"],
|
|
191
191
|
refresh_token=token_data["refresh_token"],
|
|
192
|
-
expiry=datetime.
|
|
192
|
+
expiry=datetime.now(tz=timezone.utc) + timedelta(seconds=token_data["expires_in"])
|
|
193
193
|
)
|
|
194
194
|
```
|
|
195
195
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|