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/sales_enum.py CHANGED
@@ -2,22 +2,20 @@ from enum import Enum
2
2
 
3
3
 
4
4
  class Granularity(str, Enum):
5
- HOUR = 'Hour'
6
- DAY = 'Day'
7
- WEEK = 'Week'
8
- MONTH = 'Month'
9
- YEAR = 'Year'
10
- TOTAL = 'Total'
5
+ HOUR = "Hour"
6
+ DAY = "Day"
7
+ WEEK = "Week"
8
+ MONTH = "Month"
9
+ YEAR = "Year"
10
+ TOTAL = "Total"
11
11
 
12
12
 
13
13
  class BuyerType(str, Enum):
14
- B2B = 'B2B' # doc: Business to business.
15
- B2C = 'B2C' # doc: Business to customer.
16
- ALL = 'All' # doc: Both of above
14
+ B2B = "B2B" # doc: Business to business.
15
+ B2C = "B2C" # doc: Business to customer.
16
+ ALL = "All" # doc: Both of above
17
17
 
18
18
 
19
19
  class FirstDayOfWeek(str, Enum):
20
- MO = 'Monday'
21
- SU = 'Sunday'
22
-
23
-
20
+ MO = "Monday"
21
+ SU = "Sunday"
sp_api/util/__init__.py CHANGED
@@ -2,12 +2,48 @@ from .retry import retry, sp_retry, throttle_retry
2
2
  from .load_all_pages import load_all_pages
3
3
  from .key_maker import KeyMaker
4
4
  from .load_date_bound import load_date_bound
5
+ from .params import (
6
+ normalize_csv_param,
7
+ normalize_included_data,
8
+ normalize_marketplace_ids,
9
+ normalize_datetime_kwargs,
10
+ encode_kwarg,
11
+ should_add_marketplace,
12
+ ensure_csv,
13
+ )
14
+ from .products_definitions import (
15
+ CompetitiveSummaryIncludedData,
16
+ ItemOffersRequest,
17
+ GetItemOffersBatchRequest,
18
+ ListingOffersRequest,
19
+ GetListingOffersBatchRequest,
20
+ FeaturedOfferExpectedPriceRequest,
21
+ GetFeaturedOfferExpectedPriceBatch,
22
+ CompetitiveSummaryRequest,
23
+ GetCompetitiveSummaryBatch,
24
+ )
5
25
 
6
26
  __all__ = [
7
- 'retry',
8
- 'sp_retry',
9
- 'throttle_retry',
10
- 'load_all_pages',
11
- 'KeyMaker',
12
- 'load_date_bound'
27
+ "retry",
28
+ "sp_retry",
29
+ "throttle_retry",
30
+ "load_all_pages",
31
+ "KeyMaker",
32
+ "load_date_bound",
33
+ "normalize_csv_param",
34
+ "normalize_included_data",
35
+ "normalize_marketplace_ids",
36
+ "normalize_datetime_kwargs",
37
+ "encode_kwarg",
38
+ "should_add_marketplace",
39
+ "ensure_csv",
40
+ "CompetitiveSummaryIncludedData",
41
+ "ItemOffersRequest",
42
+ "GetItemOffersBatchRequest",
43
+ "ListingOffersRequest",
44
+ "GetListingOffersBatchRequest",
45
+ "FeaturedOfferExpectedPriceRequest",
46
+ "GetFeaturedOfferExpectedPriceBatch",
47
+ "CompetitiveSummaryRequest",
48
+ "GetCompetitiveSummaryBatch",
13
49
  ]
sp_api/util/key_maker.py CHANGED
@@ -72,5 +72,7 @@ class KeyMaker:
72
72
 
73
73
  @staticmethod
74
74
  def _replace_dash(key):
75
- return key[0].lower() + ''.join(
76
- word.title() if i > 0 else word for i, word in enumerate(re.sub(r'[-\s]', '_', key[1:]).split('_')))
75
+ return key[0].lower() + "".join(
76
+ word.title() if i > 0 else word
77
+ for i, word in enumerate(re.sub(r"[-\s]", "_", key[1:]).split("_"))
78
+ )
@@ -7,9 +7,13 @@ def make_sleep_time(rate_limit, use_rate_limit_header, throttle_by_seconds):
7
7
  return throttle_by_seconds
8
8
 
9
9
 
10
-
11
- def load_all_pages(throttle_by_seconds: float = 2, next_token_param='NextToken', use_rate_limit_header: bool = False,
12
- extras: dict = None):
10
+ def load_all_pages(
11
+ throttle_by_seconds: float = 2,
12
+ next_token_param="NextToken",
13
+ use_rate_limit_header: bool = False,
14
+ extras: dict = None,
15
+ next_token_only: bool = False
16
+ ):
13
17
  """
14
18
  Load all pages if a next token is returned
15
19
 
@@ -18,6 +22,7 @@ def load_all_pages(throttle_by_seconds: float = 2, next_token_param='NextToken',
18
22
  next_token_param: str | The param amazon expects to hold the next token
19
23
  use_rate_limit_header: if the function should try to use amazon's rate limit header
20
24
  extras: additional data to be sent with NextToken, e.g `dict(QueryType='NEXT_TOKEN')` for `FulfillmentInbound`
25
+ next_token_only: remove all other params from kwargs, required for reports API
21
26
  Returns:
22
27
  Transforms the function in a generator, returning all pages
23
28
  """
@@ -29,12 +34,18 @@ def load_all_pages(throttle_by_seconds: float = 2, next_token_param='NextToken',
29
34
  done = False
30
35
  while not done:
31
36
  res = function(*args, **kwargs)
37
+
32
38
  yield res
33
39
  if res.next_token:
34
- sleep_time = make_sleep_time(res.rate_limit, use_rate_limit_header, throttle_by_seconds)
40
+ sleep_time = make_sleep_time(
41
+ res.rate_limit, use_rate_limit_header, throttle_by_seconds
42
+ )
35
43
  if sleep_time > 0:
36
44
  time.sleep(sleep_time)
37
- kwargs.update({next_token_param: res.next_token, **extras})
45
+ if next_token_only:
46
+ kwargs = {next_token_param: res.next_token}
47
+ else:
48
+ kwargs.update({next_token_param: res.next_token, **extras})
38
49
  else:
39
50
  done = True
40
51
 
@@ -17,20 +17,35 @@ def load_date_bound(interval_days: int = 30):
17
17
 
18
18
  def decorator(function):
19
19
  def wrapper(*args, **kwargs):
20
- date_range.update({
21
- 'dataStartTime': parse_if_needed(kwargs['dataStartTime']),
22
- 'dataEndTime': parse_if_needed(kwargs.get('dataEndTime', datetime.datetime.utcnow()))
23
- })
24
- kwargs.update({
25
- 'dataEndTime': make_end_date(date_range['dataStartTime'], date_range['dataEndTime'], interval_days)
26
- })
27
- while kwargs['dataStartTime'] < kwargs['dataEndTime']:
20
+ date_range.update(
21
+ {
22
+ "dataStartTime": parse_if_needed(kwargs["dataStartTime"]),
23
+ "dataEndTime": parse_if_needed(
24
+ kwargs.get("dataEndTime", datetime.datetime.utcnow())
25
+ ),
26
+ }
27
+ )
28
+ kwargs.update(
29
+ {
30
+ "dataEndTime": make_end_date(
31
+ date_range["dataStartTime"],
32
+ date_range["dataEndTime"],
33
+ interval_days,
34
+ )
35
+ }
36
+ )
37
+ while kwargs["dataStartTime"] < kwargs["dataEndTime"]:
28
38
  yield function(*args, **kwargs)
29
- kwargs.update({
30
- 'dataStartTime': parse_if_needed(kwargs['dataEndTime']),
31
- 'dataEndTime': make_end_date(parse_if_needed(kwargs['dataEndTime']), date_range['dataEndTime'],
32
- interval_days)
33
- })
39
+ kwargs.update(
40
+ {
41
+ "dataStartTime": parse_if_needed(kwargs["dataEndTime"]),
42
+ "dataEndTime": make_end_date(
43
+ parse_if_needed(kwargs["dataEndTime"]),
44
+ date_range["dataEndTime"],
45
+ interval_days,
46
+ ),
47
+ }
48
+ )
34
49
 
35
50
  wrapper.__doc__ = function.__doc__
36
51
  return wrapper
sp_api/util/params.py ADDED
@@ -0,0 +1,57 @@
1
+ from collections import abc
2
+ from datetime import datetime
3
+
4
+
5
+ def to_csv(value, mapper=None):
6
+ if value and isinstance(value, abc.Iterable) and not isinstance(value, str):
7
+ if mapper:
8
+ value = [mapper(item) for item in value]
9
+ return ",".join(value)
10
+ return value
11
+
12
+
13
+ def normalize_csv_param(kwargs, key, mapper=None):
14
+ if key in kwargs:
15
+ value = to_csv(kwargs.get(key), mapper)
16
+ if value is not None:
17
+ kwargs[key] = value
18
+ return kwargs
19
+
20
+
21
+ def normalize_included_data(kwargs, enum_cls=None, key="includedData"):
22
+ def mapper(value):
23
+ if enum_cls and isinstance(value, enum_cls):
24
+ return value.value
25
+ return value
26
+
27
+ return normalize_csv_param(kwargs, key, mapper)
28
+
29
+
30
+ def normalize_marketplace_ids(kwargs, marketplace_cls=None, key="marketplaceIds"):
31
+ def mapper(value):
32
+ if marketplace_cls and isinstance(value, marketplace_cls):
33
+ return value.marketplace_id
34
+ return value
35
+
36
+ return normalize_csv_param(kwargs, key, mapper)
37
+
38
+
39
+ def normalize_datetime_kwargs(kwargs, keys):
40
+ for key in keys:
41
+ if isinstance(kwargs.get(key), datetime):
42
+ kwargs[key] = kwargs.get(key).isoformat()
43
+ return kwargs
44
+
45
+
46
+ def encode_kwarg(kwargs, key, encoder):
47
+ if key in kwargs:
48
+ kwargs[key] = encoder(kwargs.pop(key))
49
+ return kwargs
50
+
51
+
52
+ def should_add_marketplace(kwargs, token_key="nextToken"):
53
+ return token_key not in kwargs
54
+
55
+
56
+ def ensure_csv(value, mapper=None):
57
+ return to_csv(value, mapper)
@@ -0,0 +1,40 @@
1
+ def create_fees_body(
2
+ *,
3
+ price,
4
+ shipping_price=None,
5
+ currency="USD",
6
+ is_fba=False,
7
+ identifier=None,
8
+ points=None,
9
+ marketplace_id=None,
10
+ optional_fulfillment_program=None,
11
+ id_type=None,
12
+ id_value=None,
13
+ ):
14
+ body = {
15
+ "FeesEstimateRequest": {
16
+ "Identifier": identifier or str(price),
17
+ "PriceToEstimateFees": {
18
+ "ListingPrice": {"Amount": price, "CurrencyCode": currency},
19
+ "Shipping": (
20
+ {"Amount": shipping_price, "CurrencyCode": currency}
21
+ if shipping_price
22
+ else None
23
+ ),
24
+ "Points": points or None,
25
+ },
26
+ "IsAmazonFulfilled": is_fba,
27
+ "OptionalFulfillmentProgram": (
28
+ optional_fulfillment_program
29
+ if is_fba is True and optional_fulfillment_program
30
+ else None
31
+ ),
32
+ "MarketplaceId": marketplace_id,
33
+ }
34
+ }
35
+
36
+ if id_type and id_value:
37
+ body["IdType"] = id_type
38
+ body["IdValue"] = id_value
39
+
40
+ return body
@@ -0,0 +1,169 @@
1
+ from enum import Enum
2
+ from typing import Optional, List, Dict, Union
3
+ from dataclasses import dataclass, asdict
4
+
5
+
6
+ class CompetitiveSummaryIncludedData(Enum):
7
+ FEATURED_BUYING_OPTIONS = "featuredBuyingOptions"
8
+ LOWEST_PRICED_OFFERS = "lowestPricedOffers"
9
+ REFERENCE_PRICES = "referencePrices"
10
+
11
+
12
+ @dataclass
13
+ class ItemOffersRequest:
14
+ """Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v0-reference
15
+ #itemoffersrequest"""
16
+
17
+ uri: str
18
+ method: str
19
+ MarketplaceId: str
20
+ ItemCondition: str = None
21
+ CustomerType: str = None
22
+ headers: Dict = None
23
+
24
+
25
+ @dataclass
26
+ class GetItemOffersBatchRequest:
27
+ """Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v0-reference
28
+ #getitemoffersbatchrequest"""
29
+
30
+ requests: Optional[List[Union[ItemOffersRequest, Dict]]] = None
31
+
32
+ def __post_init__(self):
33
+ self.requests = self.parse_requests(self.requests)
34
+
35
+ def to_dict(self):
36
+ return asdict(self)
37
+
38
+ @staticmethod
39
+ def parse_requests(requests) -> List[ItemOffersRequest]:
40
+ parsed_requestes = []
41
+
42
+ for request in requests:
43
+ if isinstance(request, Dict):
44
+ request = ItemOffersRequest(**request)
45
+
46
+ if not isinstance(request, ItemOffersRequest):
47
+ raise TypeError
48
+
49
+ parsed_requestes.append(request)
50
+
51
+ return parsed_requestes
52
+
53
+
54
+ @dataclass
55
+ class ListingOffersRequest:
56
+ """Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v0-reference
57
+ #listingoffersrequest"""
58
+
59
+ uri: str
60
+ MarketplaceId: str
61
+ ItemCondition: str
62
+ method: str = "GET"
63
+ CustomerType: str = "Consumer"
64
+
65
+
66
+ @dataclass
67
+ class GetListingOffersBatchRequest:
68
+ """Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v0-reference
69
+ #getlistingoffersbatchrequest"""
70
+
71
+ requests: Optional[List[Union[ListingOffersRequest, Dict]]] = None
72
+
73
+ def __post_init__(self):
74
+ self.requests = self.parse_requests(self.requests)
75
+
76
+ def to_dict(self):
77
+ return asdict(self)
78
+
79
+ @staticmethod
80
+ def parse_requests(requests) -> List[ListingOffersRequest]:
81
+ parsed_requestes = []
82
+
83
+ for request in requests:
84
+ if isinstance(request, Dict):
85
+ request = ListingOffersRequest(**request)
86
+
87
+ if not isinstance(request, ListingOffersRequest):
88
+ raise TypeError
89
+
90
+ parsed_requestes.append(request)
91
+
92
+ return parsed_requestes
93
+
94
+
95
+ @dataclass
96
+ class FeaturedOfferExpectedPriceRequest:
97
+ """ Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v2022-05-01-reference
98
+ #FeaturedOfferExpectedPriceRequest """
99
+ marketplaceId: str
100
+ sku: str
101
+ uri: str = "/products/pricing/2022-05-01/offer/featuredOfferExpectedPrice"
102
+ method: str = "GET"
103
+
104
+
105
+ @dataclass
106
+ class GetFeaturedOfferExpectedPriceBatch:
107
+ """ Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v2022-05-01-reference
108
+ #getFeaturedOfferExpectedPriceBatch """
109
+ requests: Optional[List[Union[FeaturedOfferExpectedPriceRequest, Dict]]] = None
110
+
111
+ def __post_init__(self):
112
+ self.requests = self.parse_requests(self.requests)
113
+
114
+ def to_dict(self):
115
+ return asdict(self)
116
+
117
+ @staticmethod
118
+ def parse_requests(requests) -> List[FeaturedOfferExpectedPriceRequest]:
119
+ parsed_requests = []
120
+
121
+ for request in requests:
122
+ if isinstance(request, Dict):
123
+ request = FeaturedOfferExpectedPriceRequest(**request)
124
+
125
+ if not isinstance(request, FeaturedOfferExpectedPriceRequest):
126
+ raise TypeError
127
+
128
+ parsed_requests.append(request)
129
+
130
+ return parsed_requests
131
+
132
+
133
+ @dataclass
134
+ class CompetitiveSummaryRequest:
135
+ """ Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v2022-05-01-reference
136
+ #FeaturedOfferExpectedPriceRequest """
137
+ marketplaceId: str
138
+ asin: str
139
+ includedData: List[CompetitiveSummaryIncludedData]
140
+ uri: str = "/products/pricing/2022-05-01/items/competitiveSummary"
141
+ method: str = "GET"
142
+
143
+
144
+ @dataclass
145
+ class GetCompetitiveSummaryBatch:
146
+ """ Implements definition: https://developer-docs.amazon.com/sp-api/docs/product-pricing-api-v2022-05-01-reference
147
+ #getCompetitiveSummary """
148
+ requests: Optional[List[Union[CompetitiveSummaryRequest, Dict]]] = None
149
+
150
+ def __post_init__(self):
151
+ self.requests = self.parse_requests(self.requests)
152
+
153
+ def to_dict(self):
154
+ return asdict(self)
155
+
156
+ @staticmethod
157
+ def parse_requests(requests) -> List[CompetitiveSummaryRequest]:
158
+ parsed_requests = []
159
+
160
+ for request in requests:
161
+ if isinstance(request, Dict):
162
+ request = CompetitiveSummaryRequest(**request)
163
+
164
+ if not isinstance(request, CompetitiveSummaryRequest):
165
+ raise TypeError
166
+
167
+ parsed_requests.append(request)
168
+
169
+ return parsed_requests
@@ -0,0 +1,154 @@
1
+ import codecs
2
+ import zlib
3
+ from io import BytesIO, StringIO
4
+
5
+
6
+ def resolve_character_code(response_encoding, fallback="iso-8859-1"):
7
+ character_code = response_encoding or fallback
8
+ if character_code and character_code.lower() == "windows-31j":
9
+ character_code = "cp932"
10
+ return character_code
11
+
12
+
13
+ def decompress_bytes(document, compression_algorithm):
14
+ if compression_algorithm:
15
+ return zlib.decompress(bytearray(document), 15 + 32)
16
+ return document
17
+
18
+
19
+ def decode_document(document, character_code):
20
+ if character_code:
21
+ try:
22
+ return document.decode(character_code)
23
+ except Exception:
24
+ return document
25
+ return document
26
+
27
+
28
+ def handle_file(file, document, encoding):
29
+ if isinstance(file, str):
30
+ with open(file, "w+", encoding=encoding) as text_file:
31
+ text_file.write(document)
32
+ elif isinstance(file, BytesIO):
33
+ file.write(document.encode(encoding))
34
+ file.seek(0)
35
+ elif isinstance(file, StringIO):
36
+ file.write(document)
37
+ file.seek(0)
38
+ else:
39
+ file.write(document)
40
+
41
+
42
+ def _open_stream_target(file, encoding):
43
+ if isinstance(file, str):
44
+ return open(file, "w+", encoding=encoding), True, True
45
+ if isinstance(file, StringIO):
46
+ return file, True, False
47
+ if isinstance(file, BytesIO):
48
+ return file, False, False
49
+ if hasattr(file, "mode") and "b" in file.mode:
50
+ return file, False, False
51
+ return file, True, False
52
+
53
+
54
+ def _stream_bytes_to_target_sync(
55
+ chunk_iter, file, encoding, compression_algorithm, close_after
56
+ ):
57
+ decompressor = zlib.decompressobj(15 + 32) if compression_algorithm else None
58
+ target, is_text, should_close = file
59
+ decoder = (
60
+ codecs.getincrementaldecoder(encoding)(errors="replace") if is_text else None
61
+ )
62
+ try:
63
+ for chunk in chunk_iter:
64
+ if decompressor:
65
+ chunk = decompressor.decompress(chunk)
66
+ if decoder:
67
+ text = decoder.decode(chunk)
68
+ if text:
69
+ target.write(text)
70
+ else:
71
+ target.write(chunk)
72
+ if decompressor:
73
+ remainder = decompressor.flush()
74
+ if remainder:
75
+ if decoder:
76
+ text = decoder.decode(remainder)
77
+ if text:
78
+ target.write(text)
79
+ else:
80
+ target.write(remainder)
81
+ if decoder:
82
+ tail = decoder.decode(b"", final=True)
83
+ if tail:
84
+ target.write(tail)
85
+ finally:
86
+ if close_after and should_close:
87
+ target.close()
88
+
89
+
90
+ async def _stream_bytes_to_target_async(
91
+ chunk_iter, file, encoding, compression_algorithm, close_after
92
+ ):
93
+ decompressor = zlib.decompressobj(15 + 32) if compression_algorithm else None
94
+ target, is_text, should_close = file
95
+ decoder = (
96
+ codecs.getincrementaldecoder(encoding)(errors="replace") if is_text else None
97
+ )
98
+ try:
99
+ async for chunk in chunk_iter:
100
+ if decompressor:
101
+ chunk = decompressor.decompress(chunk)
102
+ if decoder:
103
+ text = decoder.decode(chunk)
104
+ if text:
105
+ target.write(text)
106
+ else:
107
+ target.write(chunk)
108
+ if decompressor:
109
+ remainder = decompressor.flush()
110
+ if remainder:
111
+ if decoder:
112
+ text = decoder.decode(remainder)
113
+ if text:
114
+ target.write(text)
115
+ else:
116
+ target.write(remainder)
117
+ if decoder:
118
+ tail = decoder.decode(b"", final=True)
119
+ if tail:
120
+ target.write(tail)
121
+ finally:
122
+ if close_after and should_close:
123
+ target.close()
124
+
125
+
126
+ def _iter_response_bytes_sync(response):
127
+ if hasattr(response, "iter_content"):
128
+ return response.iter_content(chunk_size=8192)
129
+ if hasattr(response, "iter_bytes"):
130
+ return response.iter_bytes()
131
+ return response.iter_content(chunk_size=8192)
132
+
133
+
134
+ def stream_to_file_sync(response, file, encoding, compression_algorithm):
135
+ target = _open_stream_target(file, encoding)
136
+ _stream_bytes_to_target_sync(
137
+ _iter_response_bytes_sync(response),
138
+ target,
139
+ encoding,
140
+ compression_algorithm,
141
+ True,
142
+ )
143
+
144
+
145
+ async def stream_to_file_async(response, file, encoding, compression_algorithm):
146
+ target = _open_stream_target(file, encoding)
147
+
148
+ await _stream_bytes_to_target_async(
149
+ response.aiter_bytes(),
150
+ target,
151
+ encoding,
152
+ compression_algorithm,
153
+ True,
154
+ )
sp_api/util/retry.py CHANGED
@@ -20,31 +20,28 @@ def retry(exception_classes=None, tries=10, delay=5, rate=1.3):
20
20
  if exception_classes is None:
21
21
  exception_classes = (Exception,)
22
22
 
23
- tries_counter = {
24
- 'count': 1,
25
- 'last_delay': delay
26
- }
23
+ tries_counter = {"count": 1, "last_delay": delay}
27
24
 
28
25
  def decorator(function):
29
26
  def wrapper(*args, **kwargs):
30
27
  try:
31
28
  return function(*args, **kwargs)
32
29
  except exception_classes as e:
33
- if tries_counter.get('count') + 1 > tries:
30
+ if tries_counter.get("count") + 1 > tries:
34
31
  raise e
35
32
 
36
- delay_now = delay if tries_counter.get('count') == 1 else tries_counter.get('last_delay') * rate
37
- tries_counter.update({
38
- 'count': tries_counter.get('count') + 1,
39
- 'last_delay': delay_now
40
- })
33
+ delay_now = (
34
+ delay
35
+ if tries_counter.get("count") == 1
36
+ else tries_counter.get("last_delay") * rate
37
+ )
38
+ tries_counter.update(
39
+ {"count": tries_counter.get("count") + 1, "last_delay": delay_now}
40
+ )
41
41
  time.sleep(delay_now)
42
42
  return wrapper(*args, **kwargs)
43
43
  finally:
44
- tries_counter.update({
45
- 'count': 1,
46
- 'last_delay': delay
47
- })
44
+ tries_counter.update({"count": 1, "last_delay": delay})
48
45
 
49
46
  wrapper.__doc__ = function.__doc__
50
47
  return wrapper
@@ -67,6 +64,7 @@ def sp_retry(exception_classes=(), tries=10, delay=5, rate=1.3):
67
64
 
68
65
  """
69
66
  from sp_api.base import SellingApiException
67
+
70
68
  return retry((SellingApiException,) + exception_classes, tries, delay, rate)
71
69
 
72
70
 
@@ -85,4 +83,7 @@ def throttle_retry(exception_classes=(), tries=10, delay=5, rate=1.3):
85
83
 
86
84
  """
87
85
  from sp_api.base import SellingApiRequestThrottledException
88
- return retry((SellingApiRequestThrottledException,) + exception_classes, tries, delay, rate)
86
+
87
+ return retry(
88
+ (SellingApiRequestThrottledException,) + exception_classes, tries, delay, rate
89
+ )