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
sp_api/base/client.py CHANGED
@@ -1,61 +1,45 @@
1
- import hashlib
2
- import json
3
1
  from datetime import datetime
4
2
  import logging
5
3
  import os
6
4
 
7
- from requests import request
8
- from requests.exceptions import JSONDecodeError
9
-
10
5
  from sp_api.auth import AccessTokenClient, AccessTokenResponse
11
6
  from .ApiResponse import ApiResponse
12
7
  from .base_client import BaseClient
13
- from .exceptions import get_exception_for_code, MissingScopeException
8
+ from .exceptions import MissingScopeException
14
9
  from .marketplaces import Marketplaces
15
10
  from sp_api.base.credential_provider import CredentialProvider
11
+ from ._core import prepare_request, parse_response, resolve_method
12
+ from ._transport_httpx import HttpxTransport
16
13
 
17
14
  log = logging.getLogger(__name__)
15
+ log.setLevel(logging.INFO) # Set default to DEBUG; users can override externally
18
16
 
19
17
 
20
- def show_donation_message():
21
- import os
22
- if os.environ.get('ENV_DISABLE_DONATION_MSG', 0) == '1':
23
- return
24
-
25
- print("🌟 Thank you for using python-amazon-sp-api! 🌟")
26
- print("This tool helps developers and businesses connect seamlessly with Amazon's vast marketplace,")
27
- print("enabling powerful automations and data management.")
28
- print("If you appreciate this project and find it useful, please consider supporting its continued development:")
29
- print(" - 🙌 GitHub Sponsors: https://github.com/sponsors/saleweaver")
30
- print(" - 🌐 BTC Address: bc1q6uqgczasmnvnc5upumarugw2mksnwneg0f65ws")
31
- print(" - 🌐 ETH Address: 0xf59534F7a7F5410DBCD0c779Ac3bB6503bd32Ae5")
32
- print("\nYour support helps keep the project alive and evolving, and is greatly appreciated!")
33
- print("\nTo disable this donation message, set the ENV_DISABLE_DONATION_MSG=1 environment variable.")
34
-
35
18
 
36
19
  class Client(BaseClient):
37
- grantless_scope: str = ''
20
+ grantless_scope: str = ""
38
21
  keep_restricted_data_token: bool = False
39
22
  version = None
40
23
 
41
24
  def __init__(
42
- self,
43
- marketplace: Marketplaces = Marketplaces[
44
- os.environ.get('SP_API_DEFAULT_MARKETPLACE', Marketplaces.US.name)],
45
- *,
46
- refresh_token=None,
47
- account='default',
48
- credentials=None,
49
- restricted_data_token=None,
50
- proxies=None,
51
- verify=True,
52
- timeout=None,
53
- version=None,
54
- credential_providers=None,
55
- auth_token_client_class=AccessTokenClient,
25
+ self,
26
+ marketplace: Marketplaces = Marketplaces[
27
+ os.environ.get("SP_API_DEFAULT_MARKETPLACE", Marketplaces.US.name)
28
+ ],
29
+ *,
30
+ refresh_token=None,
31
+ account="default",
32
+ credentials=None,
33
+ restricted_data_token=None,
34
+ proxies=None,
35
+ verify=True,
36
+ timeout=None,
37
+ version=None,
38
+ credential_providers=None,
39
+ auth_token_client_class=AccessTokenClient,
56
40
  ):
57
- if os.environ.get('SP_API_DEFAULT_MARKETPLACE', None):
58
- marketplace = Marketplaces[os.environ.get('SP_API_DEFAULT_MARKETPLACE')]
41
+ if os.environ.get("SP_API_DEFAULT_MARKETPLACE", None):
42
+ marketplace = Marketplaces[os.environ.get("SP_API_DEFAULT_MARKETPLACE")]
59
43
  self.credentials = CredentialProvider(
60
44
  account,
61
45
  credentials,
@@ -66,21 +50,30 @@ class Client(BaseClient):
66
50
  self.marketplace_id = marketplace.marketplace_id
67
51
  self.region = marketplace.region
68
52
  self.restricted_data_token = restricted_data_token
69
- self._auth = auth_token_client_class(refresh_token=refresh_token, credentials=self.credentials, proxies=proxies, verify=verify)
53
+ self._auth = auth_token_client_class(
54
+ refresh_token=refresh_token,
55
+ credentials=self.credentials,
56
+ proxies=proxies,
57
+ verify=verify,
58
+ )
70
59
  self.proxies = proxies
71
60
  self.timeout = timeout
72
61
  self.version = version
73
62
  self.verify = verify
74
- show_donation_message()
63
+ self._transport = HttpxTransport(
64
+ timeout=timeout,
65
+ proxies=proxies,
66
+ verify=verify,
67
+ )
75
68
 
76
69
  @property
77
70
  def headers(self):
78
71
  return {
79
- 'host': self.endpoint[8:],
80
- 'user-agent': self.user_agent,
81
- 'x-amz-access-token': self.restricted_data_token or self.auth.access_token,
82
- 'x-amz-date': datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'),
83
- 'content-type': 'application/json'
72
+ "host": self.endpoint[8:],
73
+ "user-agent": self.user_agent,
74
+ "x-amz-access-token": self.restricted_data_token or self.auth.access_token,
75
+ "x-amz-date": datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"),
76
+ "content-type": "application/json",
84
77
  }
85
78
 
86
79
  @property
@@ -93,88 +86,113 @@ class Client(BaseClient):
93
86
  raise MissingScopeException("Grantless operations require scope")
94
87
  return self._auth.get_grantless_auth(self.grantless_scope)
95
88
 
96
- def _request(self, path: str, *, data: dict = None, params: dict = None, headers=None,
97
- add_marketplace=True, res_no_data: bool = False, bulk: bool = False,
98
- wrap_list: bool = False) -> ApiResponse:
99
- if params is None:
100
- params = {}
101
- if data is None:
102
- data = {}
103
-
104
- # Note: The use of isinstance here is to support request schemas that are an array at the
105
- # top level, eg get_product_fees_estimate
106
- self.method = params.pop('method', data.pop('method', 'GET') if isinstance(data, dict) else 'GET')
107
-
108
- if add_marketplace:
109
- self._add_marketplaces(data if self.method in ('POST', 'PUT') else params)
110
-
111
- res = request(self.method,
112
- self.endpoint + self._check_version(path),
113
- params=params,
114
- data=json.dumps(data) if data and self.method in ('POST', 'PUT', 'PATCH') else None,
115
- headers=headers or self.headers,
116
- timeout=self.timeout,
117
- proxies=self.proxies,
118
- verify=self.verify)
89
+ def _request(
90
+ self,
91
+ path: str,
92
+ *,
93
+ data: dict = None,
94
+ params: dict = None,
95
+ headers=None,
96
+ add_marketplace=True,
97
+ res_no_data: bool = False,
98
+ bulk: bool = False,
99
+ wrap_list: bool = False,
100
+ ) -> ApiResponse:
101
+ method, params, data = resolve_method(params, data)
102
+ self.method = method
103
+
104
+ request_headers = headers or self.headers
105
+
106
+ log.debug("HTTP Method: %s", method)
107
+ log.debug("Making request to URL: %s", self.endpoint + self._check_version(path))
108
+ log.debug("Request Params: %s", params)
109
+ log.debug(
110
+ "Request Data: %s",
111
+ data if method in ("POST", "PUT", "PATCH") else None,
112
+ )
113
+ log.debug("Request Headers: %s", request_headers)
114
+
115
+ prepared = prepare_request(
116
+ method=method,
117
+ endpoint=self.endpoint,
118
+ path=path,
119
+ params=params,
120
+ data=data,
121
+ headers=request_headers,
122
+ add_marketplace=add_marketplace,
123
+ marketplace_id=self.marketplace_id,
124
+ version=self.version,
125
+ )
126
+ res = self._transport.request(**prepared)
119
127
  return self._check_response(res, res_no_data, bulk, wrap_list)
120
128
 
121
- def _check_response(self, res, res_no_data: bool = False, bulk: bool = False,
122
- wrap_list: bool = False) -> ApiResponse:
123
- if (self.method == 'DELETE' or res_no_data) and 200 <= res.status_code < 300:
124
- try:
125
- js = res.json() or {}
126
- except JSONDecodeError:
127
- js = {'status_code': res.status_code}
128
- else:
129
- try:
130
- js = res.json() or {}
131
- except JSONDecodeError:
132
- js = {}
133
-
134
- if isinstance(js, list):
135
- if wrap_list:
136
- # Support responses that are an array at the top level, eg get_product_fees_estimate
137
- js = dict(payload=js)
138
- else:
139
- js = js[0]
140
-
141
- error = js.get('errors', None)
142
-
143
- if error:
144
- exception = get_exception_for_code(res.status_code)
145
- raise exception(error, headers=res.headers)
146
-
147
- # show_donation_message()
148
-
149
- return ApiResponse(**js, headers=res.headers)
129
+ def _check_response(
130
+ self,
131
+ res,
132
+ res_no_data: bool = False,
133
+ bulk: bool = False,
134
+ wrap_list: bool = False,
135
+ ) -> ApiResponse:
136
+ response = parse_response(
137
+ res,
138
+ method=self.method,
139
+ res_no_data=res_no_data,
140
+ bulk=bulk,
141
+ wrap_list=wrap_list,
142
+ )
143
+ log.debug("Response: %s", response)
144
+ return response
150
145
 
151
146
  def _add_marketplaces(self, data):
152
- POST = ['marketplaceIds', 'MarketplaceIds']
153
- GET = ['MarketplaceId', 'MarketplaceIds', 'marketplace_ids', 'marketplaceIds']
147
+ POST = ["marketplaceIds", "MarketplaceIds"]
148
+ GET = ["MarketplaceId", "MarketplaceIds", "marketplace_ids", "marketplaceIds"]
154
149
 
155
- if self.method == 'POST':
150
+ if self.method == "POST":
156
151
  if any(x in data.keys() for x in POST):
157
152
  return
158
- return data.update({k: self.marketplace_id if not k.endswith('s') else [self.marketplace_id] for k in POST})
153
+ return data.update(
154
+ {
155
+ k: (
156
+ self.marketplace_id
157
+ if not k.endswith("s")
158
+ else [self.marketplace_id]
159
+ )
160
+ for k in POST
161
+ }
162
+ )
159
163
  if any(x in data.keys() for x in GET):
160
164
  return
161
- return data.update({k: self.marketplace_id if not k.endswith('s') else [self.marketplace_id] for k in GET})
162
-
163
- def _request_grantless_operation(self, path: str, *, data: dict = None, params: dict = None):
165
+ return data.update(
166
+ {
167
+ k: self.marketplace_id if not k.endswith("s") else [self.marketplace_id]
168
+ for k in GET
169
+ }
170
+ )
171
+
172
+ def close(self):
173
+ self._transport.close()
174
+
175
+ def _request_grantless_operation(
176
+ self, path: str, *, data: dict = None, params: dict = None
177
+ ):
164
178
  headers = {
165
- 'host': self.endpoint[8:],
166
- 'user-agent': self.user_agent,
167
- 'x-amz-access-token': self.grantless_auth.access_token,
168
- 'x-amz-date': datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'),
169
- 'content-type': 'application/json'
179
+ "host": self.endpoint[8:],
180
+ "user-agent": self.user_agent,
181
+ "x-amz-access-token": self.grantless_auth.access_token,
182
+ "x-amz-date": datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"),
183
+ "content-type": "application/json",
170
184
  }
171
-
185
+ log.debug("HTTP Method: %s", self.method)
186
+ log.debug("Making request to URL: %s", self.endpoint + self._check_version(path))
187
+ log.debug("Request Params: %s", params)
188
+ log.debug("Request Data: %s", data if self.method in ("POST", "PUT", "PATCH") else None)
189
+ log.debug("Request Headers: %s", headers or self.headers)
172
190
  return self._request(path, data=data, params=params, headers=headers)
173
191
 
174
192
  def _check_version(self, path):
175
- if '<version>' not in path:
193
+ if "<version>" not in path:
176
194
  return path
177
- return path.replace('<version>', self.version)
195
+ return path.replace("<version>", self.version)
178
196
 
179
197
  def __enter__(self):
180
198
  self.keep_restricted_data_token = True
@@ -183,3 +201,4 @@ class Client(BaseClient):
183
201
  def __exit__(self, *args, **kwargs):
184
202
  self.restricted_data_token = None
185
203
  self.keep_restricted_data_token = False
204
+ self.close()
@@ -15,16 +15,14 @@ except ImportError:
15
15
  SecretCache = None
16
16
 
17
17
 
18
- required_credentials = [
19
- 'lwa_app_id',
20
- 'lwa_client_secret'
21
- ]
18
+ required_credentials = ["lwa_app_id", "lwa_client_secret"]
22
19
 
23
20
 
24
21
  class MissingCredentials(Exception):
25
22
  """
26
23
  Credentials are missing, see the error output to find possible causes
27
24
  """
25
+
28
26
  pass
29
27
 
30
28
 
@@ -32,7 +30,7 @@ class BaseCredentialProvider(abc.ABC):
32
30
  errors = []
33
31
  credentials = None
34
32
 
35
- def __init__(self, account: str = 'default', *args, **kwargs):
33
+ def __init__(self, account: str = "default", *args, **kwargs):
36
34
  self.account = account
37
35
 
38
36
  def __call__(self, *args, **kwargs):
@@ -40,15 +38,19 @@ class BaseCredentialProvider(abc.ABC):
40
38
  return self.check_credentials()
41
39
 
42
40
  @abc.abstractmethod
43
- def load_credentials(self):
44
- ...
41
+ def load_credentials(self): ...
45
42
 
46
43
  def check_credentials(self):
47
44
  try:
48
- self.errors = [c for c in required_credentials if
49
- c not in self.credentials.keys() or not self.credentials[c]]
45
+ self.errors = [
46
+ c
47
+ for c in required_credentials
48
+ if c not in self.credentials.keys() or not self.credentials[c]
49
+ ]
50
50
  except (AttributeError, TypeError):
51
- raise MissingCredentials(f'Credentials are missing: {", ".join(required_credentials)}')
51
+ raise MissingCredentials(
52
+ f'Credentials are missing: {", ".join(required_credentials)}'
53
+ )
52
54
  if not len(self.errors):
53
55
  return self.credentials
54
56
  raise MissingCredentials(f'Credentials are missing: {", ".join(self.errors)}')
@@ -59,15 +61,15 @@ class FromCodeCredentialProvider(BaseCredentialProvider):
59
61
  return None
60
62
 
61
63
  def __init__(self, credentials: dict, *args, **kwargs):
62
- super(FromCodeCredentialProvider, self).__init__('default', credentials)
64
+ super(FromCodeCredentialProvider, self).__init__("default", credentials)
63
65
  self.credentials = credentials
64
66
 
65
67
 
66
68
  class FromConfigFileCredentialProvider(BaseCredentialProvider):
67
69
  def load_credentials(self):
68
70
  try:
69
- config = confuse.Configuration('python-sp-api')
70
- config_filename = os.path.join(config.config_dir(), 'credentials.yml')
71
+ config = confuse.Configuration("python-sp-api")
72
+ config_filename = os.path.join(config.config_dir(), "credentials.yml")
71
73
  config.set_file(config_filename)
72
74
  account_data = config[self.account].get()
73
75
  self.credentials = account_data
@@ -77,21 +79,20 @@ class FromConfigFileCredentialProvider(BaseCredentialProvider):
77
79
 
78
80
  class BaseFromSecretsCredentialProvider(BaseCredentialProvider):
79
81
  def load_credentials(self):
80
- secret_id = os.environ.get('SP_API_AWS_SECRET_ID')
82
+ secret_id = os.environ.get("SP_API_AWS_SECRET_ID")
81
83
  if not secret_id:
82
84
  return
83
85
  secret = self.get_secret_content(secret_id)
84
86
  if not secret:
85
87
  return
86
88
  self.credentials = dict(
87
- refresh_token=secret.get('SP_API_REFRESH_TOKEN'),
88
- lwa_app_id=secret.get('LWA_APP_ID'),
89
- lwa_client_secret=secret.get('LWA_CLIENT_SECRET'),
89
+ refresh_token=secret.get("SP_API_REFRESH_TOKEN"),
90
+ lwa_app_id=secret.get("LWA_APP_ID"),
91
+ lwa_client_secret=secret.get("LWA_CLIENT_SECRET"),
90
92
  )
91
93
 
92
94
  @abc.abstractmethod
93
- def get_secret_content(self, secret_id: str) -> Dict[str, str]:
94
- ...
95
+ def get_secret_content(self, secret_id: str) -> Dict[str, str]: ...
95
96
 
96
97
 
97
98
  class FromSecretsCredentialProvider(BaseFromSecretsCredentialProvider):
@@ -101,11 +102,11 @@ class FromSecretsCredentialProvider(BaseFromSecretsCredentialProvider):
101
102
  import boto3
102
103
  from botocore.exceptions import ClientError
103
104
 
104
- client = boto3.client('secretsmanager')
105
+ client = boto3.client("secretsmanager")
105
106
  response = client.get_secret_value(SecretId=secret_id)
106
- return json.loads(response.get('SecretString'))
107
+ return json.loads(response.get("SecretString"))
107
108
  except ImportError:
108
- print('boto3 not found')
109
+ print("boto3 not found")
109
110
  return {}
110
111
  except ClientError:
111
112
  return {}
@@ -118,6 +119,7 @@ class FromCachedSecretsCredentialProvider(BaseFromSecretsCredentialProvider):
118
119
  if not secret_cache:
119
120
  return {}
120
121
  from botocore.exceptions import ClientError
122
+
121
123
  try:
122
124
  response = secret_cache.get_secret_string(secret_id=secret_id)
123
125
  return json.loads(response)
@@ -135,15 +137,14 @@ class FromCachedSecretsCredentialProvider(BaseFromSecretsCredentialProvider):
135
137
  class FromEnvironmentVariablesCredentialProvider(BaseCredentialProvider):
136
138
  def load_credentials(self):
137
139
  account_data = dict(
138
- refresh_token=self._get_env('SP_API_REFRESH_TOKEN'),
139
- lwa_app_id=self._get_env('LWA_APP_ID'),
140
- lwa_client_secret=self._get_env('LWA_CLIENT_SECRET'),
140
+ refresh_token=self._get_env("SP_API_REFRESH_TOKEN"),
141
+ lwa_app_id=self._get_env("LWA_APP_ID"),
142
+ lwa_client_secret=self._get_env("LWA_CLIENT_SECRET"),
141
143
  )
142
144
  self.credentials = account_data
143
145
 
144
146
  def _get_env(self, key):
145
- return os.environ.get(f'{key}_{self.account}',
146
- os.environ.get(key))
147
+ return os.environ.get(f"{key}_{self.account}", os.environ.get(key))
147
148
 
148
149
 
149
150
  class CredentialProvider:
@@ -155,17 +156,21 @@ class CredentialProvider:
155
156
  FromEnvironmentVariablesCredentialProvider,
156
157
  FromCachedSecretsCredentialProvider,
157
158
  FromSecretsCredentialProvider,
158
- FromConfigFileCredentialProvider
159
+ FromConfigFileCredentialProvider,
159
160
  )
160
161
 
161
162
  def __init__(
162
163
  self,
163
- account: str = 'default',
164
+ account: str = "default",
164
165
  credentials: Optional[Dict[str, str]] = None,
165
166
  credential_providers: Optional[Iterable[Type[BaseCredentialProvider]]] = None,
166
167
  ):
167
168
  self.account = account
168
- providers = self.CREDENTIAL_PROVIDERS if credential_providers is None else credential_providers
169
+ providers = (
170
+ self.CREDENTIAL_PROVIDERS
171
+ if credential_providers is None
172
+ else credential_providers
173
+ )
169
174
  for cp in providers:
170
175
  try:
171
176
  self.credentials = cp(account=account, credentials=credentials)()
@@ -175,10 +180,12 @@ class CredentialProvider:
175
180
  if self.credentials:
176
181
  self.credentials = self.Config(**self.credentials)
177
182
  else:
178
- raise MissingCredentials(f'Credentials are missing: {", ".join(required_credentials)}')
183
+ raise MissingCredentials(
184
+ f'Credentials are missing: {", ".join(required_credentials)}'
185
+ )
179
186
 
180
187
  class Config:
181
188
  def __init__(self, **kwargs):
182
- self.refresh_token = kwargs.get('refresh_token')
183
- self.lwa_app_id = kwargs.get('lwa_app_id')
184
- self.lwa_client_secret = kwargs.get('lwa_client_secret')
189
+ self.refresh_token = kwargs.get("refresh_token")
190
+ self.lwa_app_id = kwargs.get("lwa_app_id")
191
+ self.lwa_client_secret = kwargs.get("lwa_client_secret")
sp_api/base/exceptions.py CHANGED
@@ -9,12 +9,13 @@ class SellingApiException(Exception):
9
9
  error: list Amazon Error list
10
10
 
11
11
  """
12
+
12
13
  code = 999
13
14
 
14
15
  def __init__(self, error, headers):
15
16
  try:
16
- self.message = error[0].get('message')
17
- self.amzn_code = error[0].get('code')
17
+ self.message = error[0].get("message")
18
+ self.amzn_code = error[0].get("code")
18
19
  except IndexError:
19
20
  pass
20
21
  self.error = error
@@ -25,6 +26,7 @@ class SellingApiBadRequestException(SellingApiException):
25
26
  """
26
27
  400 Request has missing or invalid parameters and cannot be parsed.
27
28
  """
29
+
28
30
  code = 400
29
31
 
30
32
  def __init__(self, error, headers=None):
@@ -35,6 +37,7 @@ class SellingApiForbiddenException(SellingApiException):
35
37
  """
36
38
  403 Indicates access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.
37
39
  """
40
+
38
41
  code = 403
39
42
 
40
43
  def __init__(self, error, headers=None):
@@ -45,6 +48,7 @@ class SellingApiNotFoundException(SellingApiException):
45
48
  """
46
49
  404 The resource specified does not exist.
47
50
  """
51
+
48
52
  code = 404
49
53
 
50
54
  def __init__(self, error, headers=None):
@@ -55,6 +59,7 @@ class SellingApiStateConflictException(SellingApiException):
55
59
  """
56
60
  409 The resource specified conflicts with the current state.
57
61
  """
62
+
58
63
  code = 409
59
64
 
60
65
  def __init__(self, error, headers=None):
@@ -65,6 +70,7 @@ class SellingApiTooLargeException(SellingApiException):
65
70
  """
66
71
  413 The request size exceeded the maximum accepted size.
67
72
  """
73
+
68
74
  code = 413
69
75
 
70
76
  def __init__(self, error, headers=None):
@@ -75,6 +81,7 @@ class SellingApiUnsupportedFormatException(SellingApiException):
75
81
  """
76
82
  415 The request payload is in an unsupported format.
77
83
  """
84
+
78
85
  code = 415
79
86
 
80
87
  def __init__(self, error, headers=None):
@@ -85,6 +92,7 @@ class SellingApiRequestThrottledException(SellingApiException):
85
92
  """
86
93
  429 The frequency of requests was greater than allowed.
87
94
  """
95
+
88
96
  code = 429
89
97
 
90
98
  def __init__(self, error, headers=None):
@@ -95,6 +103,7 @@ class SellingApiServerException(SellingApiException):
95
103
  """
96
104
  500 An unexpected condition occurred that prevented the server from fulfilling the request.
97
105
  """
106
+
98
107
  code = 500
99
108
 
100
109
  def __init__(self, error, headers=None):
@@ -105,6 +114,7 @@ class SellingApiTemporarilyUnavailableException(SellingApiException):
105
114
  """
106
115
  503 Temporary overloading or maintenance of the server.
107
116
  """
117
+
108
118
  code = 503
109
119
 
110
120
  def __init__(self, error, headers=None):
@@ -115,6 +125,7 @@ class SellingApiGatewayTimeoutException(SellingApiException):
115
125
  """
116
126
  503 Temporary overloading or maintenance of the server.
117
127
  """
128
+
118
129
  code = 504
119
130
 
120
131
  def __init__(self, error, headers=None):
@@ -136,5 +147,5 @@ def get_exception_for_code(code: int):
136
147
  429: SellingApiRequestThrottledException,
137
148
  500: SellingApiServerException,
138
149
  503: SellingApiTemporarilyUnavailableException,
139
- 504: SellingApiGatewayTimeoutException
150
+ 504: SellingApiGatewayTimeoutException,
140
151
  }.get(code, SellingApiException)