python-amazon-sp-api 1.7.5__py3-none-any.whl → 2.0.10__py3-none-any.whl

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.
Files changed (214) hide show
  1. {python_amazon_sp_api-1.7.5.data → python_amazon_sp_api-2.0.10.data}/scripts/make_endpoint +2 -2
  2. {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/METADATA +58 -8
  3. python_amazon_sp_api-2.0.10.dist-info/RECORD +253 -0
  4. {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/WHEEL +1 -1
  5. {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/top_level.txt +0 -1
  6. sp_api/__version__.py +1 -1
  7. sp_api/api/__init__.py +48 -41
  8. sp_api/api/amazon_warehousing_and_distribu/amazon_warehousing_and_distribu.py +82 -76
  9. sp_api/api/aplus_content/aplus_content.py +76 -45
  10. sp_api/api/application_integrations/application_integrations.py +118 -0
  11. sp_api/api/application_management/application_management.py +6 -7
  12. sp_api/api/authorization/authorization.py +5 -5
  13. sp_api/api/catalog/catalog.py +9 -10
  14. sp_api/api/catalog_items/catalog_items.py +10 -13
  15. sp_api/api/customer_feedback/customer_feedback.py +110 -0
  16. sp_api/api/data_kiosk/data_kiosk.py +59 -39
  17. sp_api/api/easy_ship/easy_ship.py +190 -0
  18. sp_api/api/external_fulfillment/external_fulfillment.py +706 -0
  19. sp_api/api/fba_inbound_eligibility/fba_inbound_eligibility.py +23 -13
  20. sp_api/api/fba_small_and_light/fba_small_and_light.py +46 -20
  21. sp_api/api/feeds/feeds.py +60 -39
  22. sp_api/api/finances/finances.py +40 -9
  23. sp_api/api/fulfillment_inbound/fulfillment_inbound.py +844 -619
  24. sp_api/api/fulfillment_outbound/fulfillment_outbound.py +63 -57
  25. sp_api/api/inventories/inventories.py +13 -11
  26. sp_api/api/listings_items/listings_items.py +38 -25
  27. sp_api/api/listings_restrictions/listings_restrictions.py +6 -7
  28. sp_api/api/merchant_fulfillment/merchant_fulfillment.py +49 -36
  29. sp_api/api/messaging/messaging.py +129 -25
  30. sp_api/api/notifications/notifications.py +85 -45
  31. sp_api/api/orders/orders.py +123 -38
  32. sp_api/api/orders/orders_2026_01_01.py +54 -0
  33. sp_api/api/product_fees/product_fees.py +75 -67
  34. sp_api/api/product_type_definitions/product_type_definitions.py +9 -10
  35. sp_api/api/products/products.py +177 -53
  36. sp_api/api/products/products_definitions.py +11 -82
  37. sp_api/api/replenishment/replenishment.py +13 -11
  38. sp_api/api/reports/reports.py +113 -95
  39. sp_api/api/sales/sales.py +23 -13
  40. sp_api/api/sellers/sellers.py +3 -3
  41. sp_api/api/services/services.py +41 -30
  42. sp_api/api/shipping/shipping.py +39 -37
  43. sp_api/api/shipping/shippingV2.py +46 -30
  44. sp_api/api/solicitations/solicitations.py +20 -11
  45. sp_api/api/supply_sources/supply_sources.py +45 -37
  46. sp_api/api/tokens/tokens.py +4 -6
  47. sp_api/api/upload/upload.py +10 -8
  48. sp_api/api/vendor_direct_fulfillment_inventory/vendor_direct_fulfillment_inventory.py +10 -6
  49. sp_api/api/vendor_direct_fulfillment_orders/vendor_direct_fulfillment_orders.py +12 -14
  50. sp_api/api/vendor_direct_fulfillment_payments/vendor_direct_fulfillment_payments.py +4 -6
  51. sp_api/api/vendor_direct_fulfillment_shipping/vendor_direct_fulfillment_shipping.py +42 -37
  52. sp_api/api/vendor_direct_fulfillment_transactions/vendor_direct_fulfillment_transactions.py +8 -6
  53. sp_api/api/vendor_invoices/vendor_invoices.py +6 -4
  54. sp_api/api/vendor_orders/vendor_orders.py +16 -19
  55. sp_api/api/vendor_shipments/vendor_shipments.py +91 -262
  56. sp_api/api/vendor_transaction_status/vendor_transaction_status.py +6 -6
  57. sp_api/asyncio/api/__init__.py +167 -0
  58. sp_api/asyncio/api/amazon_warehousing_and_distribu/__init__.py +9 -0
  59. sp_api/asyncio/api/amazon_warehousing_and_distribu/amazon_warehousing_and_distribu.py +130 -0
  60. sp_api/asyncio/api/aplus_content/__init__.py +5 -0
  61. sp_api/asyncio/api/aplus_content/aplus_content.py +330 -0
  62. sp_api/asyncio/api/application_integrations/__init__.py +5 -0
  63. sp_api/asyncio/api/application_integrations/application_integrations.py +119 -0
  64. sp_api/asyncio/api/application_management/__init__.py +5 -0
  65. sp_api/asyncio/api/application_management/application_management.py +36 -0
  66. sp_api/asyncio/api/authorization/__init__.py +5 -0
  67. sp_api/asyncio/api/authorization/authorization.py +54 -0
  68. sp_api/asyncio/api/catalog/__init__.py +5 -0
  69. sp_api/asyncio/api/catalog/catalog.py +111 -0
  70. sp_api/asyncio/api/catalog_items/__init__.py +6 -0
  71. sp_api/asyncio/api/catalog_items/catalog_items.py +93 -0
  72. sp_api/asyncio/api/clients/__init__.py +1 -0
  73. sp_api/asyncio/api/customer_feedback/__init__.py +5 -0
  74. sp_api/asyncio/api/customer_feedback/customer_feedback.py +111 -0
  75. sp_api/asyncio/api/data_kiosk/__init__.py +5 -0
  76. sp_api/asyncio/api/data_kiosk/data_kiosk.py +236 -0
  77. sp_api/asyncio/api/easy_ship/__init__.py +5 -0
  78. sp_api/asyncio/api/easy_ship/easy_ship.py +191 -0
  79. sp_api/asyncio/api/external_fulfillment/__init__.py +5 -0
  80. sp_api/asyncio/api/external_fulfillment/external_fulfillment.py +706 -0
  81. sp_api/asyncio/api/fba_inbound_eligibility/__init__.py +5 -0
  82. sp_api/asyncio/api/fba_inbound_eligibility/fba_inbound_eligibility.py +96 -0
  83. sp_api/asyncio/api/fba_small_and_light/__init__.py +5 -0
  84. sp_api/asyncio/api/fba_small_and_light/fba_small_and_light.py +213 -0
  85. sp_api/asyncio/api/feeds/feeds.py +260 -0
  86. sp_api/asyncio/api/finances/finances.py +100 -0
  87. sp_api/asyncio/api/fulfillment_inbound/fulfillment_inbound.py +1798 -0
  88. sp_api/asyncio/api/fulfillment_outbound/fulfillment_outbound.py +736 -0
  89. sp_api/asyncio/api/inventories/inventories.py +74 -0
  90. sp_api/asyncio/api/listings_items/__init__.py +0 -0
  91. sp_api/asyncio/api/listings_items/listings_items.py +170 -0
  92. sp_api/asyncio/api/listings_restrictions/__init__.py +0 -0
  93. sp_api/asyncio/api/listings_restrictions/listings_restrictions.py +36 -0
  94. sp_api/asyncio/api/merchant_fulfillment/__init__.py +0 -0
  95. sp_api/asyncio/api/merchant_fulfillment/merchant_fulfillment.py +384 -0
  96. sp_api/asyncio/api/messaging/__init__.py +0 -0
  97. sp_api/asyncio/api/messaging/messaging.py +511 -0
  98. sp_api/asyncio/api/models/__init__.py +4 -0
  99. sp_api/asyncio/api/notifications/__init__.py +0 -0
  100. sp_api/asyncio/api/notifications/notifications.py +295 -0
  101. sp_api/asyncio/api/orders/__init__.py +0 -0
  102. sp_api/asyncio/api/orders/orders.py +412 -0
  103. sp_api/asyncio/api/orders/orders_2026_01_01.py +40 -0
  104. sp_api/asyncio/api/overrides/__init__.py +1 -0
  105. sp_api/asyncio/api/product_fees/__init__.py +0 -0
  106. sp_api/asyncio/api/product_fees/product_fees.py +194 -0
  107. sp_api/asyncio/api/product_type_definitions/__init__.py +0 -0
  108. sp_api/asyncio/api/product_type_definitions/product_type_definitions.py +75 -0
  109. sp_api/asyncio/api/products/__init__.py +0 -0
  110. sp_api/asyncio/api/products/products.py +405 -0
  111. sp_api/asyncio/api/products/products_definitions.py +11 -0
  112. sp_api/asyncio/api/replenishment/__init__.py +0 -0
  113. sp_api/asyncio/api/replenishment/replenishment.py +121 -0
  114. sp_api/asyncio/api/reports/__init__.py +0 -0
  115. sp_api/asyncio/api/reports/reports.py +439 -0
  116. sp_api/asyncio/api/sales/__init__.py +0 -0
  117. sp_api/asyncio/api/sales/sales.py +93 -0
  118. sp_api/asyncio/api/sellers/__init__.py +0 -0
  119. sp_api/asyncio/api/sellers/sellers.py +70 -0
  120. sp_api/asyncio/api/services/__init__.py +0 -0
  121. sp_api/asyncio/api/services/services.py +218 -0
  122. sp_api/asyncio/api/shipping/__init__.py +0 -0
  123. sp_api/asyncio/api/shipping/shipping.py +459 -0
  124. sp_api/asyncio/api/shipping/shippingV2.py +651 -0
  125. sp_api/asyncio/api/solicitations/__init__.py +0 -0
  126. sp_api/asyncio/api/solicitations/solicitations.py +78 -0
  127. sp_api/asyncio/api/supply_sources/__init__.py +0 -0
  128. sp_api/asyncio/api/supply_sources/supply_sources.py +138 -0
  129. sp_api/asyncio/api/tokens/__init__.py +0 -0
  130. sp_api/asyncio/api/tokens/tokens.py +65 -0
  131. sp_api/asyncio/api/upload/__init__.py +0 -0
  132. sp_api/asyncio/api/upload/upload.py +18 -0
  133. sp_api/asyncio/api/vendor_direct_fulfillment_inventory/__init__.py +0 -0
  134. sp_api/asyncio/api/vendor_direct_fulfillment_inventory/vendor_direct_fulfillment_inventory.py +64 -0
  135. sp_api/asyncio/api/vendor_direct_fulfillment_orders/__init__.py +0 -0
  136. sp_api/asyncio/api/vendor_direct_fulfillment_orders/vendor_direct_fulfillment_orders.py +196 -0
  137. sp_api/asyncio/api/vendor_direct_fulfillment_payments/__init__.py +0 -0
  138. sp_api/asyncio/api/vendor_direct_fulfillment_payments/vendor_direct_fulfillment_payments.py +254 -0
  139. sp_api/asyncio/api/vendor_direct_fulfillment_shipping/__init__.py +0 -0
  140. sp_api/asyncio/api/vendor_direct_fulfillment_shipping/vendor_direct_fulfillment_shipping.py +627 -0
  141. sp_api/asyncio/api/vendor_direct_fulfillment_transactions/__init__.py +0 -0
  142. sp_api/asyncio/api/vendor_direct_fulfillment_transactions/vendor_direct_fulfillment_transactions.py +43 -0
  143. sp_api/asyncio/api/vendor_invoices/__init__.py +0 -0
  144. sp_api/asyncio/api/vendor_invoices/vendor_invoices.py +295 -0
  145. sp_api/asyncio/api/vendor_orders/__init__.py +0 -0
  146. sp_api/asyncio/api/vendor_orders/vendor_orders.py +210 -0
  147. sp_api/asyncio/api/vendor_shipments/__init__.py +0 -0
  148. sp_api/asyncio/api/vendor_shipments/vendor_shipments.py +118 -0
  149. sp_api/asyncio/api/vendor_transaction_status/__init__.py +0 -0
  150. sp_api/asyncio/api/vendor_transaction_status/vendor_transaction_status.py +41 -0
  151. sp_api/asyncio/auth/__init__.py +12 -0
  152. sp_api/asyncio/auth/access_token_client.py +145 -0
  153. sp_api/asyncio/auth/exceptions.py +5 -0
  154. sp_api/asyncio/base/__init__.py +53 -0
  155. sp_api/asyncio/base/_transport_httpx.py +50 -0
  156. sp_api/asyncio/base/base_client.py +8 -0
  157. sp_api/asyncio/base/client.py +169 -0
  158. sp_api/asyncio/util/__init__.py +29 -0
  159. sp_api/asyncio/util/key_maker.py +5 -0
  160. sp_api/asyncio/util/load_all_pages.py +55 -0
  161. sp_api/asyncio/util/load_date_bound.py +53 -0
  162. sp_api/asyncio/util/retry.py +88 -0
  163. sp_api/auth/__init__.py +3 -3
  164. sp_api/auth/_core.py +39 -0
  165. sp_api/auth/access_token_client.py +20 -31
  166. sp_api/auth/access_token_response.py +4 -4
  167. sp_api/base/ApiResponse.py +5 -4
  168. sp_api/base/__init__.py +53 -42
  169. sp_api/base/_core.py +110 -0
  170. sp_api/base/_transport_httpx.py +39 -0
  171. sp_api/base/base_client.py +4 -4
  172. sp_api/base/client.py +131 -112
  173. sp_api/base/credential_provider.py +41 -34
  174. sp_api/base/exceptions.py +14 -3
  175. sp_api/base/feedTypes.py +44 -32
  176. sp_api/base/fulfillment_channel.py +2 -2
  177. sp_api/base/helpers.py +17 -16
  178. sp_api/base/identifiersType.py +8 -8
  179. sp_api/base/included_data.py +12 -12
  180. sp_api/base/marketplaces.py +5 -1
  181. sp_api/base/notifications.py +1 -1
  182. sp_api/base/processing_status.py +5 -5
  183. sp_api/base/reportTypes.py +198 -111
  184. sp_api/base/sales_enum.py +11 -13
  185. sp_api/util/__init__.py +42 -6
  186. sp_api/util/key_maker.py +4 -2
  187. sp_api/util/load_all_pages.py +16 -5
  188. sp_api/util/load_date_bound.py +28 -13
  189. sp_api/util/params.py +57 -0
  190. sp_api/util/product_fees.py +40 -0
  191. sp_api/util/products_definitions.py +169 -0
  192. sp_api/util/report_document.py +154 -0
  193. sp_api/util/retry.py +16 -15
  194. python_amazon_sp_api-1.7.5.dist-info/RECORD +0 -144
  195. tests/api/finances/test_finances.py +0 -19
  196. tests/api/notifications/test_notifications.py +0 -26
  197. tests/api/orders/test_orders.py +0 -122
  198. tests/api/product_fees/product_fees.py +0 -49
  199. tests/api/reports/test_reports.py +0 -127
  200. tests/client/test_auth.py +0 -59
  201. tests/client/test_base.py +0 -163
  202. tests/client/test_credential_provider.py +0 -45
  203. tests/client/test_helpers.py +0 -142
  204. {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info/licenses}/LICENSE +0 -0
  205. {tests → sp_api/api/application_integrations}/__init__.py +0 -0
  206. {tests/api → sp_api/api/customer_feedback}/__init__.py +0 -0
  207. {tests/api/finances → sp_api/api/easy_ship}/__init__.py +0 -0
  208. {tests/api/notifications → sp_api/api/external_fulfillment}/__init__.py +0 -0
  209. {tests/api/orders → sp_api/asyncio}/__init__.py +0 -0
  210. {tests/api/product_fees → sp_api/asyncio/api/feeds}/__init__.py +0 -0
  211. {tests/api/reports → sp_api/asyncio/api/finances}/__init__.py +0 -0
  212. {tests/api/sellers → sp_api/asyncio/api/fulfillment_inbound}/__init__.py +0 -0
  213. {tests/client → sp_api/asyncio/api/fulfillment_outbound}/__init__.py +0 -0
  214. /tests/api/sellers/test_sellers.py → /sp_api/asyncio/api/inventories/__init__.py +0 -0
@@ -0,0 +1,88 @@
1
+ import asyncio
2
+
3
+
4
+ def retry(exception_classes=None, tries=10, delay=5, rate=1.3):
5
+ """
6
+ retry(exception_classes=None, tries=10, delay=5, rate=1.3)
7
+
8
+ Retry a call against an endpoint <tries> time
9
+
10
+ Args:
11
+ exception_classes: tuple | The Exceptions to be caught
12
+ tries: int | How often the call should be retried
13
+ delay: float | The delay after an error was caught
14
+ rate: float | The rate to increment delay by
15
+
16
+ Returns:
17
+
18
+ """
19
+ if exception_classes is None:
20
+ exception_classes = (Exception,)
21
+
22
+ tries_counter = {"count": 1, "last_delay": delay}
23
+
24
+ def decorator(function):
25
+ async def wrapper(*args, **kwargs):
26
+ try:
27
+ return await function(*args, **kwargs)
28
+ except exception_classes as e:
29
+ if tries_counter.get("count") + 1 > tries:
30
+ raise e
31
+
32
+ delay_now = (
33
+ delay
34
+ if tries_counter.get("count") == 1
35
+ else tries_counter.get("last_delay") * rate
36
+ )
37
+ tries_counter.update(
38
+ {"count": tries_counter.get("count") + 1, "last_delay": delay_now}
39
+ )
40
+ await asyncio.sleep(delay_now)
41
+ return await wrapper(*args, **kwargs)
42
+ finally:
43
+ tries_counter.update({"count": 1, "last_delay": delay})
44
+
45
+ wrapper.__doc__ = function.__doc__
46
+ return wrapper
47
+
48
+ return decorator
49
+
50
+
51
+ def sp_retry(exception_classes=(), tries=10, delay=5, rate=1.3):
52
+ """
53
+ This is a shorthand for retry that catches all exceptions thrown by this library
54
+
55
+ Retry a call against an endpoint <tries> time
56
+ Args:
57
+ exception_classes:
58
+ tries:
59
+ delay:
60
+ rate:
61
+
62
+ Returns:
63
+
64
+ """
65
+ from sp_api.base import SellingApiException
66
+
67
+ return retry((SellingApiException,) + exception_classes, tries, delay, rate)
68
+
69
+
70
+ def throttle_retry(exception_classes=(), tries=10, delay=5, rate=1.3):
71
+ """
72
+ This is a shorthand for retry that catches SellingApiRequestThrottledException
73
+
74
+ Retry a call against an endpoint <tries> time
75
+ Args:
76
+ exception_classes:
77
+ tries:
78
+ delay:
79
+ rate:
80
+
81
+ Returns:
82
+
83
+ """
84
+ from sp_api.base import SellingApiRequestThrottledException
85
+
86
+ return retry(
87
+ (SellingApiRequestThrottledException,) + exception_classes, tries, delay, rate
88
+ )
sp_api/auth/__init__.py CHANGED
@@ -3,7 +3,7 @@ from .access_token_response import AccessTokenResponse
3
3
  from .credentials import Credentials
4
4
 
5
5
  __all__ = [
6
- 'AccessTokenResponse',
7
- 'AccessTokenClient',
8
- 'Credentials',
6
+ "AccessTokenResponse",
7
+ "AccessTokenClient",
8
+ "Credentials",
9
9
  ]
sp_api/auth/_core.py ADDED
@@ -0,0 +1,39 @@
1
+ import hashlib
2
+
3
+
4
+ def build_cache_key(refresh_token, token_flavor=""):
5
+ return "access_token_" + hashlib.md5(
6
+ (token_flavor + (refresh_token or "__grantless__")).encode("utf-8"),
7
+ usedforsecurity=False,
8
+ ).hexdigest()
9
+
10
+
11
+ def build_refresh_token_data(credentials, grant_type):
12
+ return {
13
+ "grant_type": grant_type,
14
+ "client_id": credentials.client_id,
15
+ "refresh_token": credentials.refresh_token,
16
+ "client_secret": credentials.client_secret,
17
+ }
18
+
19
+
20
+ def build_grantless_data(credentials, scope_value):
21
+ return {
22
+ "grant_type": "client_credentials",
23
+ "client_id": credentials.client_id,
24
+ "scope": scope_value,
25
+ "client_secret": credentials.client_secret,
26
+ }
27
+
28
+
29
+ def build_auth_code_request_body(credentials, auth_code):
30
+ return {
31
+ "grant_type": "authorization_code",
32
+ "code": auth_code,
33
+ "client_id": credentials.client_id,
34
+ "client_secret": credentials.client_secret,
35
+ }
36
+
37
+
38
+ def build_headers(user_agent, content_type):
39
+ return {"User-Agent": user_agent, "content-type": content_type}
@@ -1,17 +1,23 @@
1
1
  import os
2
2
 
3
- import requests
4
- import hashlib
5
3
  import logging
6
4
  from cachetools import TTLCache
7
5
  from sp_api.base import BaseClient
6
+ from sp_api.base._transport_httpx import HttpxTransport
8
7
 
9
8
  from .credentials import Credentials
10
9
  from .access_token_response import AccessTokenResponse
11
10
  from .exceptions import AuthorizationError
11
+ from ._core import (
12
+ build_auth_code_request_body,
13
+ build_cache_key,
14
+ build_grantless_data,
15
+ build_headers,
16
+ build_refresh_token_data,
17
+ )
12
18
 
13
- cache = TTLCache(maxsize=int(os.environ.get('SP_API_AUTH_CACHE_SIZE', 10)), ttl=3200)
14
- grantless_cache = TTLCache(maxsize=int(os.environ.get('SP_API_AUTH_CACHE_SIZE', 10)), ttl=3200)
19
+ cache = TTLCache(maxsize=int(os.environ.get('SP_API_AUTH_CACHE_SIZE', 10)), ttl=int(os.environ.get('SP_API_AUTH_CACHE_TTL', 3200)))
20
+ grantless_cache = TTLCache(maxsize=int(os.environ.get('SP_API_AUTH_CACHE_SIZE', 10)), ttl=int(os.environ.get('SP_API_AUTH_CACHE_TTL', 3200)))
15
21
 
16
22
  logger = logging.getLogger(__name__)
17
23
 
@@ -25,9 +31,13 @@ class AccessTokenClient(BaseClient):
25
31
  self.cred = Credentials(refresh_token, credentials)
26
32
  self.proxies = proxies
27
33
  self.verify = verify
34
+ self._transport = HttpxTransport(
35
+ proxies=proxies,
36
+ verify=verify,
37
+ )
28
38
 
29
39
  def _request(self, url, data, headers):
30
- response = requests.post(url, data=data, headers=headers, proxies=self.proxies, verify=self.verify)
40
+ response = self._transport.request("POST", url, data=data, headers=headers)
31
41
  response_data = response.json()
32
42
  if response.status_code != 200:
33
43
  error_message = response_data.get('error_description')
@@ -93,39 +103,18 @@ class AccessTokenClient(BaseClient):
93
103
  return res
94
104
 
95
105
  def _auth_code_request_body(self, auth_code):
96
- return {
97
- 'grant_type': 'authorization_code',
98
- 'code': auth_code,
99
- 'client_id': self.cred.client_id,
100
- 'client_secret': self.cred.client_secret
101
- }
106
+ return build_auth_code_request_body(self.cred, auth_code)
102
107
 
103
108
  def grantless_data(self, scope_value: str):
104
- return {
105
- 'grant_type': 'client_credentials',
106
- 'client_id': self.cred.client_id,
107
- 'scope': scope_value,
108
- 'client_secret': self.cred.client_secret
109
- }
109
+ return build_grantless_data(self.cred, scope_value)
110
110
 
111
111
  @property
112
112
  def data(self):
113
- return {
114
- 'grant_type': self.grant_type,
115
- 'client_id': self.cred.client_id,
116
- 'refresh_token': self.cred.refresh_token,
117
- 'client_secret': self.cred.client_secret
118
- }
113
+ return build_refresh_token_data(self.cred, self.grant_type)
119
114
 
120
115
  @property
121
116
  def headers(self):
122
- return {
123
- 'User-Agent': self.user_agent,
124
- 'content-type': self.content_type
125
- }
117
+ return build_headers(self.user_agent, self.content_type)
126
118
 
127
119
  def _get_cache_key(self, token_flavor=''):
128
- return 'access_token_' + hashlib.md5(
129
- (token_flavor + (self.cred.refresh_token or '__grantless__')).encode('utf-8')
130
- ).hexdigest()
131
-
120
+ return build_cache_key(self.cred.refresh_token, token_flavor)
@@ -1,6 +1,6 @@
1
1
  class AccessTokenResponse:
2
2
  def __init__(self, **kwargs):
3
- self.access_token = kwargs.get('access_token')
4
- self.refresh_token = kwargs.get('refresh_token')
5
- self.expires_in = kwargs.get('expires_in')
6
- self.token_type = kwargs.get('token_type')
3
+ self.access_token = kwargs.get("access_token")
4
+ self.refresh_token = kwargs.get("refresh_token")
5
+ self.expires_in = kwargs.get("expires_in")
6
+ self.token_type = kwargs.get("token_type")
@@ -44,14 +44,15 @@ class ApiResponse:
44
44
  self.errors = errors
45
45
  self.pagination = pagination
46
46
  self.headers = headers
47
- self.rate_limit = headers.get("x-amzn-RateLimit-Limit")
47
+ self.rate_limit = (headers or {}).get("x-amzn-RateLimit-Limit")
48
48
  try:
49
49
  self.next_token = (
50
50
  nextToken
51
- or self.payload.get('pagination', {}).get("nextToken", None)
51
+ or self.payload.get("pagination", {}).get("nextToken", None)
52
52
  or self.payload.get("NextToken", None)
53
- or self.pagination.get("nextToken", None)
54
- or self.payload.get('nextPageToken', None)
53
+ or (self.pagination or {}).get("nextToken", None)
54
+ or self.payload.get("nextPageToken", None)
55
+ or self.payload.get("nextToken", None)
55
56
  )
56
57
 
57
58
  except AttributeError:
sp_api/base/__init__.py CHANGED
@@ -1,6 +1,13 @@
1
1
  from .base_client import BaseClient
2
2
  from .client import Client
3
- from .helpers import fill_query_params, sp_endpoint, create_md5, nest_dict, _nest_dict_rec, deprecated
3
+ from .helpers import (
4
+ fill_query_params,
5
+ sp_endpoint,
6
+ create_md5,
7
+ nest_dict,
8
+ _nest_dict_rec,
9
+ deprecated,
10
+ )
4
11
  from .marketplaces import Marketplaces
5
12
  from .exceptions import SellingApiException
6
13
  from .exceptions import SellingApiBadRequestException
@@ -16,7 +23,11 @@ from .schedules import Schedules
16
23
  from .report_status import ReportStatus
17
24
  from .sales_enum import FirstDayOfWeek, Granularity, BuyerType
18
25
  from .fulfillment_channel import FulfillmentChannel
19
- from .included_data import IncludedData, ListingItemsIncludedData, CatalogItemsIncludedData
26
+ from .included_data import (
27
+ IncludedData,
28
+ ListingItemsIncludedData,
29
+ CatalogItemsIncludedData,
30
+ )
20
31
  from .notifications import NotificationType
21
32
  from .credential_provider import CredentialProvider, MissingCredentials
22
33
  from .ApiResponse import ApiResponse
@@ -30,44 +41,44 @@ from .marketplaces import AwsEnv
30
41
 
31
42
 
32
43
  __all__ = [
33
- 'Credentials',
34
- 'AuthorizationError',
35
- 'AccessTokenClient',
36
- 'ReportType',
37
- 'FeedType',
38
- 'ProcessingStatus',
39
- 'ApiResponse',
40
- 'Client',
41
- 'BaseClient',
42
- 'Marketplaces',
43
- 'fill_query_params',
44
- 'sp_endpoint',
45
- 'SellingApiException',
46
- 'SellingApiBadRequestException',
47
- 'SellingApiNotFoundException',
48
- 'SellingApiServerException',
49
- 'SellingApiForbiddenException',
50
- 'SellingApiBadRequestException',
51
- 'SellingApiRequestThrottledException',
52
- 'SellingApiTemporarilyUnavailableException',
53
- 'SellingApiTooLargeException',
54
- 'SellingApiStateConflictException',
55
- 'SellingApiUnsupportedFormatException',
56
- 'Schedules',
57
- 'ReportStatus',
58
- 'FirstDayOfWeek',
59
- 'Granularity',
60
- 'BuyerType',
61
- 'FulfillmentChannel',
62
- 'deprecated',
63
- 'NotificationType',
64
- 'CredentialProvider',
65
- 'MissingCredentials',
66
- 'nest_dict',
67
- '_nest_dict_rec',
68
- 'IneligibilityReasonList',
69
- 'IncludedData',
70
- 'ListingItemsIncludedData',
71
- 'CatalogItemsIncludedData',
72
- 'AwsEnv'
44
+ "Credentials",
45
+ "AuthorizationError",
46
+ "AccessTokenClient",
47
+ "ReportType",
48
+ "FeedType",
49
+ "ProcessingStatus",
50
+ "ApiResponse",
51
+ "Client",
52
+ "BaseClient",
53
+ "Marketplaces",
54
+ "fill_query_params",
55
+ "sp_endpoint",
56
+ "SellingApiException",
57
+ "SellingApiBadRequestException",
58
+ "SellingApiNotFoundException",
59
+ "SellingApiServerException",
60
+ "SellingApiForbiddenException",
61
+ "SellingApiBadRequestException",
62
+ "SellingApiRequestThrottledException",
63
+ "SellingApiTemporarilyUnavailableException",
64
+ "SellingApiTooLargeException",
65
+ "SellingApiStateConflictException",
66
+ "SellingApiUnsupportedFormatException",
67
+ "Schedules",
68
+ "ReportStatus",
69
+ "FirstDayOfWeek",
70
+ "Granularity",
71
+ "BuyerType",
72
+ "FulfillmentChannel",
73
+ "deprecated",
74
+ "NotificationType",
75
+ "CredentialProvider",
76
+ "MissingCredentials",
77
+ "nest_dict",
78
+ "_nest_dict_rec",
79
+ "IneligibilityReasonList",
80
+ "IncludedData",
81
+ "ListingItemsIncludedData",
82
+ "CatalogItemsIncludedData",
83
+ "AwsEnv",
73
84
  ]
sp_api/base/_core.py ADDED
@@ -0,0 +1,110 @@
1
+ import json
2
+ from datetime import datetime
3
+ from json import JSONDecodeError
4
+
5
+ from .ApiResponse import ApiResponse
6
+ from .exceptions import get_exception_for_code
7
+
8
+
9
+ def resolve_method(params, data):
10
+ if params is None:
11
+ params = {}
12
+ if data is None:
13
+ data = {}
14
+ method = params.pop(
15
+ "method", data.pop("method", "GET") if isinstance(data, dict) else "GET"
16
+ )
17
+ return method, params, data
18
+
19
+
20
+ def check_version(path, version):
21
+ if "<version>" not in path:
22
+ return path
23
+ return path.replace("<version>", version)
24
+
25
+
26
+ def add_marketplaces(method, marketplace_id, data):
27
+ post_keys = ["marketplaceIds", "MarketplaceIds"]
28
+ get_keys = ["MarketplaceId", "MarketplaceIds", "marketplace_ids", "marketplaceIds"]
29
+
30
+ if method == "POST":
31
+ if any(key in data.keys() for key in post_keys):
32
+ return
33
+ data.update(
34
+ {
35
+ key: marketplace_id if not key.endswith("s") else [marketplace_id]
36
+ for key in post_keys
37
+ }
38
+ )
39
+ return
40
+ if any(key in data.keys() for key in get_keys):
41
+ return
42
+ data.update(
43
+ {
44
+ key: marketplace_id if not key.endswith("s") else [marketplace_id]
45
+ for key in get_keys
46
+ }
47
+ )
48
+
49
+
50
+ def build_headers(endpoint, user_agent, access_token, content_type="application/json"):
51
+ return {
52
+ "host": endpoint[8:],
53
+ "user-agent": user_agent,
54
+ "x-amz-access-token": access_token,
55
+ "x-amz-date": datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"),
56
+ "content-type": content_type,
57
+ }
58
+
59
+
60
+ def prepare_request(
61
+ *,
62
+ method,
63
+ endpoint,
64
+ path,
65
+ params,
66
+ data,
67
+ headers,
68
+ add_marketplace,
69
+ marketplace_id,
70
+ version,
71
+ ):
72
+ if add_marketplace:
73
+ add_marketplaces(method, marketplace_id, data if method in ("POST", "PUT") else params)
74
+ url = endpoint + check_version(path, version)
75
+ content = (
76
+ json.dumps(data) if data and method in ("POST", "PUT", "PATCH") else None
77
+ )
78
+ return {
79
+ "method": method,
80
+ "url": url,
81
+ "params": params,
82
+ "content": content,
83
+ "headers": headers,
84
+ }
85
+
86
+
87
+ def parse_response(res, *, method, res_no_data=False, bulk=False, wrap_list=False):
88
+ if (method == "DELETE" or res_no_data) and 200 <= res.status_code < 300:
89
+ try:
90
+ js = res.json() or {}
91
+ except JSONDecodeError:
92
+ js = {"status_code": res.status_code}
93
+ else:
94
+ try:
95
+ js = res.json() or {}
96
+ except JSONDecodeError:
97
+ js = {}
98
+
99
+ if isinstance(js, list):
100
+ if wrap_list:
101
+ js = dict(payload=js)
102
+ else:
103
+ js = js[0]
104
+
105
+ error = js.get("errors", None)
106
+ if error:
107
+ exception = get_exception_for_code(res.status_code)
108
+ raise exception(error, headers=res.headers)
109
+
110
+ return ApiResponse(**js, headers=res.headers)
@@ -0,0 +1,39 @@
1
+ import httpx
2
+
3
+
4
+ class HttpxTransport:
5
+ def __init__(
6
+ self, *, timeout=None, proxies=None, proxy=None, verify=True, client=None
7
+ ):
8
+ proxy_config = proxy if proxy is not None else proxies
9
+ self._client = client or httpx.Client(
10
+ timeout=timeout,
11
+ proxy=proxy_config,
12
+ verify=verify,
13
+ )
14
+
15
+ def request(self, method, url, *, params=None, data=None, content=None, headers=None):
16
+ return self._client.request(
17
+ method,
18
+ url,
19
+ params=params,
20
+ data=data,
21
+ content=content,
22
+ headers=headers,
23
+ )
24
+
25
+ def stream(
26
+ self, method, url, *, params=None, data=None, content=None, headers=None, timeout=None
27
+ ):
28
+ return self._client.stream(
29
+ method,
30
+ url,
31
+ params=params,
32
+ data=data,
33
+ content=content,
34
+ headers=headers,
35
+ timeout=timeout,
36
+ )
37
+
38
+ def close(self):
39
+ self._client.close()
@@ -2,7 +2,7 @@ from sp_api.__version__ import __version__
2
2
 
3
3
 
4
4
  class BaseClient:
5
- scheme = 'https://'
6
- method = 'GET'
7
- content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
8
- user_agent = f'python-sp-api-{__version__}'
5
+ scheme = "https://"
6
+ method = "GET"
7
+ content_type = "application/x-www-form-urlencoded;charset=UTF-8"
8
+ user_agent = f"python-sp-api-{__version__}"