python-amazon-sp-api 1.9.18__py3-none-any.whl → 2.0.7__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 (167) hide show
  1. {python_amazon_sp_api-1.9.18.data → python_amazon_sp_api-2.0.7.data}/scripts/make_endpoint +2 -2
  2. {python_amazon_sp_api-1.9.18.dist-info → python_amazon_sp_api-2.0.7.dist-info}/METADATA +46 -23
  3. python_amazon_sp_api-2.0.7.dist-info/RECORD +251 -0
  4. {python_amazon_sp_api-1.9.18.dist-info → python_amazon_sp_api-2.0.7.dist-info}/WHEEL +1 -1
  5. {python_amazon_sp_api-1.9.18.dist-info → python_amazon_sp_api-2.0.7.dist-info}/top_level.txt +0 -1
  6. sp_api/__version__.py +1 -1
  7. sp_api/api/__init__.py +18 -1
  8. sp_api/api/application_integrations/application_integrations.py +118 -0
  9. sp_api/api/application_management/application_management.py +2 -1
  10. sp_api/api/catalog/catalog.py +3 -4
  11. sp_api/api/catalog_items/catalog_items.py +3 -6
  12. sp_api/api/customer_feedback/customer_feedback.py +110 -0
  13. sp_api/api/data_kiosk/data_kiosk.py +5 -6
  14. sp_api/api/easy_ship/easy_ship.py +190 -0
  15. sp_api/api/external_fulfillment/external_fulfillment.py +706 -0
  16. sp_api/api/feeds/feeds.py +11 -8
  17. sp_api/api/finances/finances.py +30 -4
  18. sp_api/api/fulfillment_inbound/fulfillment_inbound.py +35 -2
  19. sp_api/api/inventories/inventories.py +2 -7
  20. sp_api/api/listings_items/listings_items.py +3 -24
  21. sp_api/api/messaging/messaging.py +42 -0
  22. sp_api/api/orders/orders.py +7 -0
  23. sp_api/api/product_fees/product_fees.py +31 -74
  24. sp_api/api/products/products.py +80 -2
  25. sp_api/api/products/products_definitions.py +11 -85
  26. sp_api/api/reports/reports.py +65 -97
  27. sp_api/api/sales/sales.py +2 -2
  28. sp_api/asyncio/api/__init__.py +164 -0
  29. sp_api/asyncio/api/amazon_warehousing_and_distribu/__init__.py +9 -0
  30. sp_api/asyncio/api/amazon_warehousing_and_distribu/amazon_warehousing_and_distribu.py +130 -0
  31. sp_api/asyncio/api/aplus_content/__init__.py +5 -0
  32. sp_api/asyncio/api/aplus_content/aplus_content.py +330 -0
  33. sp_api/asyncio/api/application_integrations/__init__.py +5 -0
  34. sp_api/asyncio/api/application_integrations/application_integrations.py +119 -0
  35. sp_api/asyncio/api/application_management/__init__.py +5 -0
  36. sp_api/asyncio/api/application_management/application_management.py +36 -0
  37. sp_api/asyncio/api/authorization/__init__.py +5 -0
  38. sp_api/asyncio/api/authorization/authorization.py +54 -0
  39. sp_api/asyncio/api/catalog/__init__.py +5 -0
  40. sp_api/asyncio/api/catalog/catalog.py +111 -0
  41. sp_api/asyncio/api/catalog_items/__init__.py +6 -0
  42. sp_api/asyncio/api/catalog_items/catalog_items.py +93 -0
  43. sp_api/asyncio/api/clients/__init__.py +1 -0
  44. sp_api/asyncio/api/customer_feedback/__init__.py +5 -0
  45. sp_api/asyncio/api/customer_feedback/customer_feedback.py +111 -0
  46. sp_api/asyncio/api/data_kiosk/__init__.py +5 -0
  47. sp_api/asyncio/api/data_kiosk/data_kiosk.py +236 -0
  48. sp_api/asyncio/api/easy_ship/__init__.py +5 -0
  49. sp_api/asyncio/api/easy_ship/easy_ship.py +191 -0
  50. sp_api/asyncio/api/external_fulfillment/__init__.py +5 -0
  51. sp_api/asyncio/api/external_fulfillment/external_fulfillment.py +706 -0
  52. sp_api/asyncio/api/fba_inbound_eligibility/__init__.py +5 -0
  53. sp_api/asyncio/api/fba_inbound_eligibility/fba_inbound_eligibility.py +96 -0
  54. sp_api/asyncio/api/fba_small_and_light/__init__.py +5 -0
  55. sp_api/asyncio/api/fba_small_and_light/fba_small_and_light.py +213 -0
  56. sp_api/asyncio/api/feeds/feeds.py +260 -0
  57. sp_api/asyncio/api/finances/finances.py +100 -0
  58. sp_api/asyncio/api/fulfillment_inbound/fulfillment_inbound.py +1798 -0
  59. sp_api/asyncio/api/fulfillment_outbound/fulfillment_outbound.py +736 -0
  60. sp_api/asyncio/api/inventories/inventories.py +74 -0
  61. sp_api/asyncio/api/listings_items/__init__.py +0 -0
  62. sp_api/asyncio/api/listings_items/listings_items.py +170 -0
  63. sp_api/asyncio/api/listings_restrictions/__init__.py +0 -0
  64. sp_api/asyncio/api/listings_restrictions/listings_restrictions.py +36 -0
  65. sp_api/asyncio/api/merchant_fulfillment/__init__.py +0 -0
  66. sp_api/asyncio/api/merchant_fulfillment/merchant_fulfillment.py +384 -0
  67. sp_api/asyncio/api/messaging/__init__.py +0 -0
  68. sp_api/asyncio/api/messaging/messaging.py +511 -0
  69. sp_api/asyncio/api/models/__init__.py +4 -0
  70. sp_api/asyncio/api/notifications/__init__.py +0 -0
  71. sp_api/asyncio/api/notifications/notifications.py +295 -0
  72. sp_api/asyncio/api/orders/__init__.py +0 -0
  73. sp_api/asyncio/api/orders/orders.py +362 -0
  74. sp_api/asyncio/api/overrides/__init__.py +1 -0
  75. sp_api/asyncio/api/product_fees/__init__.py +0 -0
  76. sp_api/asyncio/api/product_fees/product_fees.py +194 -0
  77. sp_api/asyncio/api/product_type_definitions/__init__.py +0 -0
  78. sp_api/asyncio/api/product_type_definitions/product_type_definitions.py +75 -0
  79. sp_api/asyncio/api/products/__init__.py +0 -0
  80. sp_api/asyncio/api/products/products.py +405 -0
  81. sp_api/asyncio/api/products/products_definitions.py +11 -0
  82. sp_api/asyncio/api/replenishment/__init__.py +0 -0
  83. sp_api/asyncio/api/replenishment/replenishment.py +121 -0
  84. sp_api/asyncio/api/reports/__init__.py +0 -0
  85. sp_api/asyncio/api/reports/reports.py +439 -0
  86. sp_api/asyncio/api/sales/__init__.py +0 -0
  87. sp_api/asyncio/api/sales/sales.py +93 -0
  88. sp_api/asyncio/api/sellers/__init__.py +0 -0
  89. sp_api/asyncio/api/sellers/sellers.py +70 -0
  90. sp_api/asyncio/api/services/__init__.py +0 -0
  91. sp_api/asyncio/api/services/services.py +218 -0
  92. sp_api/asyncio/api/shipping/__init__.py +0 -0
  93. sp_api/asyncio/api/shipping/shipping.py +459 -0
  94. sp_api/asyncio/api/shipping/shippingV2.py +651 -0
  95. sp_api/asyncio/api/solicitations/__init__.py +0 -0
  96. sp_api/asyncio/api/solicitations/solicitations.py +78 -0
  97. sp_api/asyncio/api/supply_sources/__init__.py +0 -0
  98. sp_api/asyncio/api/supply_sources/supply_sources.py +138 -0
  99. sp_api/asyncio/api/tokens/__init__.py +0 -0
  100. sp_api/asyncio/api/tokens/tokens.py +65 -0
  101. sp_api/asyncio/api/upload/__init__.py +0 -0
  102. sp_api/asyncio/api/upload/upload.py +18 -0
  103. sp_api/asyncio/api/vendor_direct_fulfillment_inventory/__init__.py +0 -0
  104. sp_api/asyncio/api/vendor_direct_fulfillment_inventory/vendor_direct_fulfillment_inventory.py +64 -0
  105. sp_api/asyncio/api/vendor_direct_fulfillment_orders/__init__.py +0 -0
  106. sp_api/asyncio/api/vendor_direct_fulfillment_orders/vendor_direct_fulfillment_orders.py +196 -0
  107. sp_api/asyncio/api/vendor_direct_fulfillment_payments/__init__.py +0 -0
  108. sp_api/asyncio/api/vendor_direct_fulfillment_payments/vendor_direct_fulfillment_payments.py +254 -0
  109. sp_api/asyncio/api/vendor_direct_fulfillment_shipping/__init__.py +0 -0
  110. sp_api/asyncio/api/vendor_direct_fulfillment_shipping/vendor_direct_fulfillment_shipping.py +627 -0
  111. sp_api/asyncio/api/vendor_direct_fulfillment_transactions/__init__.py +0 -0
  112. sp_api/asyncio/api/vendor_direct_fulfillment_transactions/vendor_direct_fulfillment_transactions.py +43 -0
  113. sp_api/asyncio/api/vendor_invoices/__init__.py +0 -0
  114. sp_api/asyncio/api/vendor_invoices/vendor_invoices.py +295 -0
  115. sp_api/asyncio/api/vendor_orders/__init__.py +0 -0
  116. sp_api/asyncio/api/vendor_orders/vendor_orders.py +210 -0
  117. sp_api/asyncio/api/vendor_shipments/__init__.py +0 -0
  118. sp_api/asyncio/api/vendor_shipments/vendor_shipments.py +118 -0
  119. sp_api/asyncio/api/vendor_transaction_status/__init__.py +0 -0
  120. sp_api/asyncio/api/vendor_transaction_status/vendor_transaction_status.py +41 -0
  121. sp_api/asyncio/auth/__init__.py +12 -0
  122. sp_api/asyncio/auth/access_token_client.py +145 -0
  123. sp_api/asyncio/auth/exceptions.py +5 -0
  124. sp_api/asyncio/base/__init__.py +53 -0
  125. sp_api/asyncio/base/_transport_httpx.py +50 -0
  126. sp_api/asyncio/base/base_client.py +8 -0
  127. sp_api/asyncio/base/client.py +169 -0
  128. sp_api/asyncio/util/__init__.py +29 -0
  129. sp_api/asyncio/util/key_maker.py +5 -0
  130. sp_api/asyncio/util/load_all_pages.py +55 -0
  131. sp_api/asyncio/util/load_date_bound.py +53 -0
  132. sp_api/asyncio/util/retry.py +88 -0
  133. sp_api/auth/_core.py +39 -0
  134. sp_api/auth/access_token_client.py +18 -29
  135. sp_api/base/ApiResponse.py +3 -2
  136. sp_api/base/_core.py +110 -0
  137. sp_api/base/_transport_httpx.py +39 -0
  138. sp_api/base/client.py +40 -63
  139. sp_api/base/helpers.py +1 -1
  140. sp_api/base/reportTypes.py +3 -2
  141. sp_api/util/__init__.py +36 -0
  142. sp_api/util/load_all_pages.py +2 -1
  143. sp_api/util/params.py +57 -0
  144. sp_api/util/product_fees.py +40 -0
  145. sp_api/util/products_definitions.py +169 -0
  146. sp_api/util/report_document.py +154 -0
  147. python_amazon_sp_api-1.9.18.dist-info/RECORD +0 -144
  148. tests/api/finances/test_finances.py +0 -19
  149. tests/api/notifications/test_notifications.py +0 -26
  150. tests/api/orders/test_orders.py +0 -122
  151. tests/api/product_fees/product_fees.py +0 -49
  152. tests/api/reports/test_reports.py +0 -127
  153. tests/client/test_auth.py +0 -59
  154. tests/client/test_base.py +0 -163
  155. tests/client/test_credential_provider.py +0 -45
  156. tests/client/test_helpers.py +0 -142
  157. {python_amazon_sp_api-1.9.18.dist-info → python_amazon_sp_api-2.0.7.dist-info/licenses}/LICENSE +0 -0
  158. {tests → sp_api/api/application_integrations}/__init__.py +0 -0
  159. {tests/api → sp_api/api/customer_feedback}/__init__.py +0 -0
  160. {tests/api/finances → sp_api/api/easy_ship}/__init__.py +0 -0
  161. {tests/api/notifications → sp_api/api/external_fulfillment}/__init__.py +0 -0
  162. {tests/api/orders → sp_api/asyncio}/__init__.py +0 -0
  163. {tests/api/product_fees → sp_api/asyncio/api/feeds}/__init__.py +0 -0
  164. {tests/api/reports → sp_api/asyncio/api/finances}/__init__.py +0 -0
  165. {tests/api/sellers → sp_api/asyncio/api/fulfillment_inbound}/__init__.py +0 -0
  166. {tests/client → sp_api/asyncio/api/fulfillment_outbound}/__init__.py +0 -0
  167. /tests/api/sellers/test_sellers.py → /sp_api/asyncio/api/inventories/__init__.py +0 -0
@@ -0,0 +1,41 @@
1
+ import urllib.parse
2
+
3
+ from sp_api.base import Client, sp_endpoint, fill_query_params, ApiResponse
4
+ from sp_api.asyncio.base import AsyncBaseClient
5
+
6
+
7
+ class VendorTransactionStatus(AsyncBaseClient):
8
+ """
9
+ VendorTransactionStatus SP-API Client
10
+ :link:
11
+
12
+ The Selling Partner API for Retail Procurement Transaction Status provides programmatic access to status information on specific asynchronous POST transactions for vendors.
13
+ """
14
+
15
+ @sp_endpoint("/vendor/transactions/v1/transactions/{}", method="GET")
16
+ async def get_transaction(self, transactionId, **kwargs) -> ApiResponse:
17
+ """
18
+ get_transaction(self, transactionId, **kwargs) -> ApiResponse
19
+
20
+ Returns the status of the transaction that you specify.
21
+
22
+ **Usage Plans:**
23
+
24
+ ====================================== ==============
25
+ Rate (requests per second) Burst
26
+ ====================================== ==============
27
+ 10 10
28
+ ====================================== ==============
29
+
30
+ The x-amzn-RateLimit-Limit response header returns the usage plan rate limits that were applied to the requested operation. Rate limits for some selling partners will vary from the default rate and burst shown in the table above. For more information, see "Usage Plans and Rate Limits" in the Selling Partner API documentation.
31
+
32
+ Args:
33
+ transactionId:string | * REQUIRED The GUID provided by Amazon in the 'transactionId' field in response to the post request of a specific transaction.
34
+
35
+ Returns:
36
+ ApiResponse:
37
+ """
38
+
39
+ return await self._request(
40
+ fill_query_params(kwargs.pop("path"), transactionId), params=kwargs
41
+ )
@@ -0,0 +1,12 @@
1
+ from sp_api.auth.access_token_response import AccessTokenResponse
2
+ from sp_api.auth.credentials import Credentials
3
+ from sp_api.auth.exceptions import AuthorizationError
4
+
5
+ from .access_token_client import AccessTokenClient
6
+
7
+ __all__ = [
8
+ "AccessTokenResponse",
9
+ "AccessTokenClient",
10
+ "AuthorizationError",
11
+ "Credentials",
12
+ ]
@@ -0,0 +1,145 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+
5
+ from cachetools import TTLCache
6
+
7
+ from sp_api.auth.access_token_response import AccessTokenResponse
8
+ from sp_api.auth.credentials import Credentials
9
+ from sp_api.auth.exceptions import AuthorizationError
10
+ from sp_api.auth._core import (
11
+ build_auth_code_request_body,
12
+ build_cache_key,
13
+ build_grantless_data,
14
+ build_headers,
15
+ build_refresh_token_data,
16
+ )
17
+ from sp_api.asyncio.base.base_client import BaseClient
18
+ from sp_api.asyncio.base._transport_httpx import AsyncHttpxTransport
19
+
20
+ cache = TTLCache(
21
+ maxsize=int(os.environ.get("SP_API_AUTH_CACHE_SIZE", 10)),
22
+ ttl=int(os.environ.get("SP_API_AUTH_CACHE_TTL", 3200)),
23
+ )
24
+ grantless_cache = TTLCache(
25
+ maxsize=int(os.environ.get("SP_API_AUTH_CACHE_SIZE", 10)),
26
+ ttl=int(os.environ.get("SP_API_AUTH_CACHE_TTL", 3200)),
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class AccessTokenClient(BaseClient):
33
+ host = "api.amazon.com"
34
+ grant_type = "refresh_token"
35
+ path = "/auth/o2/token"
36
+
37
+ def __init__(self, refresh_token=None, credentials=None, proxies=None, verify=True):
38
+ self.cred = Credentials(refresh_token, credentials)
39
+ self.proxies = proxies
40
+ self.verify = verify
41
+ self._lock = asyncio.Lock()
42
+ self._transport = AsyncHttpxTransport(
43
+ proxies=proxies,
44
+ verify=verify,
45
+ )
46
+
47
+ async def _request(self, url, data, headers):
48
+ response = await self._transport.request(
49
+ "POST",
50
+ url,
51
+ data=data,
52
+ headers=headers,
53
+ )
54
+ response_data = response.json()
55
+ if response.status_code != 200:
56
+ error_message = response_data.get("error_description")
57
+ error_code = response_data.get("error")
58
+ raise AuthorizationError(error_code, error_message, response.status_code)
59
+ return response_data
60
+
61
+ async def get_auth(self) -> AccessTokenResponse:
62
+ """
63
+ Get's the access token
64
+ :return:AccessTokenResponse
65
+ """
66
+ cache_key = self._get_cache_key()
67
+ try:
68
+ access_token = cache[cache_key]
69
+ except KeyError:
70
+ async with self._lock:
71
+ try:
72
+ access_token = cache[cache_key]
73
+ except KeyError:
74
+ request_url = self.scheme + self.host + self.path
75
+ access_token = await self._request(
76
+ request_url,
77
+ self.data,
78
+ self.headers,
79
+ )
80
+ cache[cache_key] = access_token
81
+ return AccessTokenResponse(**access_token)
82
+
83
+ async def get_grantless_auth(self, scope="sellingpartnerapi::notifications"):
84
+ """
85
+ :param scope: One of allowed scope for grantless operations:
86
+ sellingpartnerapi::notifications or sellingpartnerapi::migration
87
+ See: https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#step-3-configure-your-lwa-credentials
88
+
89
+ POST /auth/o2/token HTTP/l.l
90
+ Host: api.amazon.com
91
+ Content-Type: application/x-www-form-urlencoded;charset=UTF-8
92
+ grant_type=client_credentials
93
+ &scope=sellingpartnerapi::notifications
94
+ &client_id=foodev
95
+ &client_secret=Y76SDl2F
96
+ :return: AccessTokenResponse
97
+ """
98
+ cache_key = self._get_cache_key(scope)
99
+ try:
100
+ access_token = grantless_cache[cache_key]
101
+ logger.debug("from_cache. scope: %s", scope)
102
+ except KeyError:
103
+ async with self._lock:
104
+ try:
105
+ access_token = grantless_cache[cache_key]
106
+ except KeyError:
107
+ request_url = self.scheme + self.host + self.path
108
+ access_token = await self._request(
109
+ request_url,
110
+ data=self.grantless_data(scope),
111
+ headers=self.headers,
112
+ )
113
+ logger.debug("token_refreshed")
114
+ grantless_cache.clear()
115
+ grantless_cache[cache_key] = access_token
116
+
117
+ return AccessTokenResponse(**access_token)
118
+
119
+ async def authorize_auth_code(self, auth_code):
120
+ request_url = self.scheme + self.host + self.path
121
+ return await self._request(
122
+ request_url,
123
+ data=self._auth_code_request_body(auth_code),
124
+ headers=self.headers,
125
+ )
126
+
127
+ def _auth_code_request_body(self, auth_code):
128
+ return build_auth_code_request_body(self.cred, auth_code)
129
+
130
+ def grantless_data(self, scope_value: str):
131
+ return build_grantless_data(self.cred, scope_value)
132
+
133
+ @property
134
+ def data(self):
135
+ return build_refresh_token_data(self.cred, self.grant_type)
136
+
137
+ @property
138
+ def headers(self):
139
+ return build_headers(self.user_agent, self.content_type)
140
+
141
+ def _get_cache_key(self, token_flavor=""):
142
+ return build_cache_key(self.cred.refresh_token, token_flavor)
143
+
144
+ async def aclose(self):
145
+ await self._transport.aclose()
@@ -0,0 +1,5 @@
1
+ from sp_api.auth.exceptions import AuthorizationError
2
+
3
+ __all__ = [
4
+ "AuthorizationError",
5
+ ]
@@ -0,0 +1,53 @@
1
+ from .base_client import BaseClient
2
+ from .client import Client
3
+
4
+ AsyncBaseClient = Client
5
+
6
+ from sp_api.base import (
7
+ Marketplaces,
8
+ ReportType,
9
+ FeedType,
10
+ ProcessingStatus,
11
+ Schedules,
12
+ ReportStatus,
13
+ FirstDayOfWeek,
14
+ Granularity,
15
+ BuyerType,
16
+ FulfillmentChannel,
17
+ NotificationType,
18
+ IncludedData,
19
+ ListingItemsIncludedData,
20
+ CatalogItemsIncludedData,
21
+ IneligibilityReasonList,
22
+ AwsEnv,
23
+ )
24
+ from sp_api.base.credential_provider import (
25
+ FromCodeCredentialProvider,
26
+ FromSecretsCredentialProvider,
27
+ FromCachedSecretsCredentialProvider,
28
+ )
29
+
30
+ __all__ = [
31
+ "Marketplaces",
32
+ "ReportType",
33
+ "FeedType",
34
+ "ProcessingStatus",
35
+ "Schedules",
36
+ "ReportStatus",
37
+ "FirstDayOfWeek",
38
+ "Granularity",
39
+ "BuyerType",
40
+ "FulfillmentChannel",
41
+ "NotificationType",
42
+ "IncludedData",
43
+ "ListingItemsIncludedData",
44
+ "CatalogItemsIncludedData",
45
+ "IneligibilityReasonList",
46
+ "AwsEnv",
47
+ "FromCodeCredentialProvider",
48
+ "FromSecretsCredentialProvider",
49
+ "FromCachedSecretsCredentialProvider",
50
+ "BaseClient",
51
+ "Client",
52
+ "AsyncBaseClient",
53
+ ]
@@ -0,0 +1,50 @@
1
+ import httpx
2
+
3
+
4
+ class AsyncHttpxTransport:
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.AsyncClient(
10
+ timeout=timeout,
11
+ proxy=proxy_config,
12
+ verify=verify,
13
+ )
14
+
15
+ async def request(
16
+ self,
17
+ method,
18
+ url,
19
+ *,
20
+ params=None,
21
+ data=None,
22
+ content=None,
23
+ headers=None,
24
+ timeout=None,
25
+ ):
26
+ return await self._client.request(
27
+ method,
28
+ url,
29
+ params=params,
30
+ data=data,
31
+ content=content,
32
+ headers=headers,
33
+ timeout=timeout,
34
+ )
35
+
36
+ def stream(
37
+ self, method, url, *, params=None, data=None, content=None, headers=None, timeout=None
38
+ ):
39
+ return self._client.stream(
40
+ method,
41
+ url,
42
+ params=params,
43
+ data=data,
44
+ content=content,
45
+ headers=headers,
46
+ timeout=timeout,
47
+ )
48
+
49
+ async def aclose(self):
50
+ await self._client.aclose()
@@ -0,0 +1,8 @@
1
+ from sp_api.__version__ import __version__
2
+
3
+
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__}"
@@ -0,0 +1,169 @@
1
+ from datetime import datetime
2
+ import logging
3
+ import os
4
+
5
+ from sp_api.auth import AccessTokenResponse
6
+ from sp_api.base.ApiResponse import ApiResponse
7
+ from sp_api.base.credential_provider import CredentialProvider
8
+ from sp_api.base.marketplaces import Marketplaces
9
+ from sp_api.base._core import parse_response, prepare_request, resolve_method
10
+
11
+ from ._transport_httpx import AsyncHttpxTransport
12
+ from .base_client import BaseClient
13
+ from ..auth import AccessTokenClient
14
+ from sp_api.base.exceptions import MissingScopeException
15
+
16
+ log = logging.getLogger(__name__)
17
+ log.setLevel(logging.INFO) # Set default to DEBUG; users can override externally
18
+
19
+
20
+ class Client(BaseClient):
21
+ grantless_scope: str = ""
22
+ keep_restricted_data_token: bool = False
23
+ version = None
24
+
25
+ def __init__(
26
+ self,
27
+ marketplace: Marketplaces = Marketplaces[
28
+ os.environ.get("SP_API_DEFAULT_MARKETPLACE", Marketplaces.US.name)
29
+ ],
30
+ *,
31
+ refresh_token=None,
32
+ account="default",
33
+ credentials=None,
34
+ restricted_data_token=None,
35
+ proxies=None,
36
+ verify=True,
37
+ timeout=None,
38
+ version=None,
39
+ credential_providers=None,
40
+ auth_token_client_class=AccessTokenClient,
41
+ ):
42
+ if os.environ.get("SP_API_DEFAULT_MARKETPLACE", None):
43
+ marketplace = Marketplaces[os.environ.get("SP_API_DEFAULT_MARKETPLACE")]
44
+ self.credentials = CredentialProvider(
45
+ account,
46
+ credentials,
47
+ credential_providers=credential_providers,
48
+ ).credentials
49
+
50
+ self.endpoint = marketplace.endpoint
51
+ self.marketplace_id = marketplace.marketplace_id
52
+ self.region = marketplace.region
53
+ self.restricted_data_token = restricted_data_token
54
+ self._auth = auth_token_client_class(
55
+ refresh_token=refresh_token,
56
+ credentials=self.credentials,
57
+ proxies=proxies,
58
+ verify=verify,
59
+ )
60
+ self.proxies = proxies
61
+ self.timeout = timeout
62
+ self.version = version
63
+ self.verify = verify
64
+ self._transport = AsyncHttpxTransport(
65
+ timeout=timeout,
66
+ proxies=proxies,
67
+ verify=verify,
68
+ )
69
+
70
+ async def _get_headers(self, access_token=None):
71
+ token = access_token or self.restricted_data_token
72
+ if token is None:
73
+ token = (await self.auth).access_token
74
+ return {
75
+ "host": self.endpoint[8:],
76
+ "user-agent": self.user_agent,
77
+ "x-amz-access-token": token,
78
+ "x-amz-date": datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"),
79
+ "content-type": "application/json",
80
+ }
81
+
82
+ @property
83
+ async def auth(self) -> AccessTokenResponse:
84
+ return await self._auth.get_auth()
85
+
86
+ @property
87
+ async def grantless_auth(self) -> AccessTokenResponse:
88
+ if not self.grantless_scope:
89
+ raise MissingScopeException("Grantless operations require scope")
90
+ return await self._auth.get_grantless_auth(self.grantless_scope)
91
+
92
+ async def _request(
93
+ self,
94
+ path: str,
95
+ *,
96
+ data: dict = None,
97
+ params: dict = None,
98
+ headers=None,
99
+ add_marketplace=True,
100
+ res_no_data: bool = False,
101
+ bulk: bool = False,
102
+ wrap_list: bool = False,
103
+ ) -> ApiResponse:
104
+ method, params, data = resolve_method(params, data)
105
+ self.method = method
106
+
107
+ request_headers = headers or await self._get_headers()
108
+
109
+ log.debug("HTTP Method: %s", method)
110
+ log.debug("Making request to URL: %s", self.endpoint + self._check_version(path))
111
+ log.debug("Request Params: %s", params)
112
+ log.debug(
113
+ "Request Data: %s",
114
+ data if method in ("POST", "PUT", "PATCH") else None,
115
+ )
116
+ log.debug("Request Headers: %s", request_headers)
117
+
118
+ prepared = prepare_request(
119
+ method=method,
120
+ endpoint=self.endpoint,
121
+ path=path,
122
+ params=params,
123
+ data=data,
124
+ headers=request_headers,
125
+ add_marketplace=add_marketplace,
126
+ marketplace_id=self.marketplace_id,
127
+ version=self.version,
128
+ )
129
+ res = await self._transport.request(**prepared)
130
+ return parse_response(
131
+ res,
132
+ method=self.method,
133
+ res_no_data=res_no_data,
134
+ bulk=bulk,
135
+ wrap_list=wrap_list,
136
+ )
137
+
138
+ async def _request_grantless_operation(
139
+ self, path: str, *, data: dict = None, params: dict = None
140
+ ):
141
+ headers = await self._get_headers(
142
+ access_token=(await self.grantless_auth).access_token
143
+ )
144
+ log.debug("HTTP Method: %s", self.method)
145
+ log.debug("Making request to URL: %s", self.endpoint + self._check_version(path))
146
+ log.debug("Request Params: %s", params)
147
+ log.debug(
148
+ "Request Data: %s",
149
+ data if self.method in ("POST", "PUT", "PATCH") else None,
150
+ )
151
+ log.debug("Request Headers: %s", headers)
152
+ return await self._request(path, data=data, params=params, headers=headers)
153
+
154
+ def _check_version(self, path):
155
+ if "<version>" not in path:
156
+ return path
157
+ return path.replace("<version>", self.version)
158
+
159
+ async def __aenter__(self):
160
+ self.keep_restricted_data_token = True
161
+ return self
162
+
163
+ async def __aexit__(self, *args, **kwargs):
164
+ self.restricted_data_token = None
165
+ self.keep_restricted_data_token = False
166
+ await self.aclose()
167
+
168
+ async def aclose(self):
169
+ await self._transport.aclose()
@@ -0,0 +1,29 @@
1
+ from .retry import retry, sp_retry, throttle_retry
2
+ from .load_all_pages import load_all_pages
3
+ from .key_maker import KeyMaker
4
+ from .load_date_bound import load_date_bound
5
+ from sp_api.util 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
+
15
+ __all__ = [
16
+ "retry",
17
+ "sp_retry",
18
+ "throttle_retry",
19
+ "load_all_pages",
20
+ "KeyMaker",
21
+ "load_date_bound",
22
+ "normalize_csv_param",
23
+ "normalize_included_data",
24
+ "normalize_marketplace_ids",
25
+ "normalize_datetime_kwargs",
26
+ "encode_kwarg",
27
+ "should_add_marketplace",
28
+ "ensure_csv",
29
+ ]
@@ -0,0 +1,5 @@
1
+ from sp_api.util.key_maker import KeyMaker
2
+
3
+ __all__ = [
4
+ "KeyMaker",
5
+ ]
@@ -0,0 +1,55 @@
1
+ import asyncio
2
+
3
+
4
+ def make_sleep_time(rate_limit, use_rate_limit_header, throttle_by_seconds):
5
+ if use_rate_limit_header and rate_limit:
6
+ return 1 / float(rate_limit)
7
+ return throttle_by_seconds
8
+
9
+
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
+ ):
17
+ """
18
+ Load all pages if a next token is returned
19
+
20
+ Args:
21
+ throttle_by_seconds: float
22
+ next_token_param: str | The param amazon expects to hold the next token
23
+ use_rate_limit_header: if the function should try to use amazon's rate limit header
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
26
+ Returns:
27
+ Transforms the function in an async generator, returning all pages
28
+ """
29
+ if not extras:
30
+ extras = {}
31
+
32
+ def decorator(function):
33
+ async def wrapper(*args, **kwargs):
34
+ done = False
35
+ while not done:
36
+ res = await function(*args, **kwargs)
37
+
38
+ yield res
39
+ if res.next_token:
40
+ sleep_time = make_sleep_time(
41
+ res.rate_limit, use_rate_limit_header, throttle_by_seconds
42
+ )
43
+ if sleep_time > 0:
44
+ await asyncio.sleep(sleep_time)
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})
49
+ else:
50
+ done = True
51
+
52
+ wrapper.__doc__ = function.__doc__
53
+ return wrapper
54
+
55
+ return decorator
@@ -0,0 +1,53 @@
1
+ import datetime
2
+
3
+
4
+ def load_date_bound(interval_days: int = 30):
5
+ def make_end_date(s: datetime, e: datetime, i: int):
6
+ end_date = s + datetime.timedelta(days=i)
7
+ if end_date > e:
8
+ return e
9
+ return end_date
10
+
11
+ def parse_if_needed(dt: datetime or str):
12
+ if isinstance(dt, datetime.datetime):
13
+ return dt
14
+ return datetime.datetime.fromisoformat(dt)
15
+
16
+ date_range = {}
17
+
18
+ def decorator(function):
19
+ async def wrapper(*args, **kwargs):
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"]:
38
+ yield await function(*args, **kwargs)
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
+ )
49
+
50
+ wrapper.__doc__ = function.__doc__
51
+ return wrapper
52
+
53
+ return decorator