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.
- {python_amazon_sp_api-1.7.5.data → python_amazon_sp_api-2.0.10.data}/scripts/make_endpoint +2 -2
- {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/METADATA +58 -8
- python_amazon_sp_api-2.0.10.dist-info/RECORD +253 -0
- {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/WHEEL +1 -1
- {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info}/top_level.txt +0 -1
- sp_api/__version__.py +1 -1
- sp_api/api/__init__.py +48 -41
- sp_api/api/amazon_warehousing_and_distribu/amazon_warehousing_and_distribu.py +82 -76
- sp_api/api/aplus_content/aplus_content.py +76 -45
- sp_api/api/application_integrations/application_integrations.py +118 -0
- sp_api/api/application_management/application_management.py +6 -7
- sp_api/api/authorization/authorization.py +5 -5
- sp_api/api/catalog/catalog.py +9 -10
- sp_api/api/catalog_items/catalog_items.py +10 -13
- sp_api/api/customer_feedback/customer_feedback.py +110 -0
- sp_api/api/data_kiosk/data_kiosk.py +59 -39
- sp_api/api/easy_ship/easy_ship.py +190 -0
- sp_api/api/external_fulfillment/external_fulfillment.py +706 -0
- sp_api/api/fba_inbound_eligibility/fba_inbound_eligibility.py +23 -13
- sp_api/api/fba_small_and_light/fba_small_and_light.py +46 -20
- sp_api/api/feeds/feeds.py +60 -39
- sp_api/api/finances/finances.py +40 -9
- sp_api/api/fulfillment_inbound/fulfillment_inbound.py +844 -619
- sp_api/api/fulfillment_outbound/fulfillment_outbound.py +63 -57
- sp_api/api/inventories/inventories.py +13 -11
- sp_api/api/listings_items/listings_items.py +38 -25
- sp_api/api/listings_restrictions/listings_restrictions.py +6 -7
- sp_api/api/merchant_fulfillment/merchant_fulfillment.py +49 -36
- sp_api/api/messaging/messaging.py +129 -25
- sp_api/api/notifications/notifications.py +85 -45
- sp_api/api/orders/orders.py +123 -38
- sp_api/api/orders/orders_2026_01_01.py +54 -0
- sp_api/api/product_fees/product_fees.py +75 -67
- sp_api/api/product_type_definitions/product_type_definitions.py +9 -10
- sp_api/api/products/products.py +177 -53
- sp_api/api/products/products_definitions.py +11 -82
- sp_api/api/replenishment/replenishment.py +13 -11
- sp_api/api/reports/reports.py +113 -95
- sp_api/api/sales/sales.py +23 -13
- sp_api/api/sellers/sellers.py +3 -3
- sp_api/api/services/services.py +41 -30
- sp_api/api/shipping/shipping.py +39 -37
- sp_api/api/shipping/shippingV2.py +46 -30
- sp_api/api/solicitations/solicitations.py +20 -11
- sp_api/api/supply_sources/supply_sources.py +45 -37
- sp_api/api/tokens/tokens.py +4 -6
- sp_api/api/upload/upload.py +10 -8
- sp_api/api/vendor_direct_fulfillment_inventory/vendor_direct_fulfillment_inventory.py +10 -6
- sp_api/api/vendor_direct_fulfillment_orders/vendor_direct_fulfillment_orders.py +12 -14
- sp_api/api/vendor_direct_fulfillment_payments/vendor_direct_fulfillment_payments.py +4 -6
- sp_api/api/vendor_direct_fulfillment_shipping/vendor_direct_fulfillment_shipping.py +42 -37
- sp_api/api/vendor_direct_fulfillment_transactions/vendor_direct_fulfillment_transactions.py +8 -6
- sp_api/api/vendor_invoices/vendor_invoices.py +6 -4
- sp_api/api/vendor_orders/vendor_orders.py +16 -19
- sp_api/api/vendor_shipments/vendor_shipments.py +91 -262
- sp_api/api/vendor_transaction_status/vendor_transaction_status.py +6 -6
- sp_api/asyncio/api/__init__.py +167 -0
- sp_api/asyncio/api/amazon_warehousing_and_distribu/__init__.py +9 -0
- sp_api/asyncio/api/amazon_warehousing_and_distribu/amazon_warehousing_and_distribu.py +130 -0
- sp_api/asyncio/api/aplus_content/__init__.py +5 -0
- sp_api/asyncio/api/aplus_content/aplus_content.py +330 -0
- sp_api/asyncio/api/application_integrations/__init__.py +5 -0
- sp_api/asyncio/api/application_integrations/application_integrations.py +119 -0
- sp_api/asyncio/api/application_management/__init__.py +5 -0
- sp_api/asyncio/api/application_management/application_management.py +36 -0
- sp_api/asyncio/api/authorization/__init__.py +5 -0
- sp_api/asyncio/api/authorization/authorization.py +54 -0
- sp_api/asyncio/api/catalog/__init__.py +5 -0
- sp_api/asyncio/api/catalog/catalog.py +111 -0
- sp_api/asyncio/api/catalog_items/__init__.py +6 -0
- sp_api/asyncio/api/catalog_items/catalog_items.py +93 -0
- sp_api/asyncio/api/clients/__init__.py +1 -0
- sp_api/asyncio/api/customer_feedback/__init__.py +5 -0
- sp_api/asyncio/api/customer_feedback/customer_feedback.py +111 -0
- sp_api/asyncio/api/data_kiosk/__init__.py +5 -0
- sp_api/asyncio/api/data_kiosk/data_kiosk.py +236 -0
- sp_api/asyncio/api/easy_ship/__init__.py +5 -0
- sp_api/asyncio/api/easy_ship/easy_ship.py +191 -0
- sp_api/asyncio/api/external_fulfillment/__init__.py +5 -0
- sp_api/asyncio/api/external_fulfillment/external_fulfillment.py +706 -0
- sp_api/asyncio/api/fba_inbound_eligibility/__init__.py +5 -0
- sp_api/asyncio/api/fba_inbound_eligibility/fba_inbound_eligibility.py +96 -0
- sp_api/asyncio/api/fba_small_and_light/__init__.py +5 -0
- sp_api/asyncio/api/fba_small_and_light/fba_small_and_light.py +213 -0
- sp_api/asyncio/api/feeds/feeds.py +260 -0
- sp_api/asyncio/api/finances/finances.py +100 -0
- sp_api/asyncio/api/fulfillment_inbound/fulfillment_inbound.py +1798 -0
- sp_api/asyncio/api/fulfillment_outbound/fulfillment_outbound.py +736 -0
- sp_api/asyncio/api/inventories/inventories.py +74 -0
- sp_api/asyncio/api/listings_items/__init__.py +0 -0
- sp_api/asyncio/api/listings_items/listings_items.py +170 -0
- sp_api/asyncio/api/listings_restrictions/__init__.py +0 -0
- sp_api/asyncio/api/listings_restrictions/listings_restrictions.py +36 -0
- sp_api/asyncio/api/merchant_fulfillment/__init__.py +0 -0
- sp_api/asyncio/api/merchant_fulfillment/merchant_fulfillment.py +384 -0
- sp_api/asyncio/api/messaging/__init__.py +0 -0
- sp_api/asyncio/api/messaging/messaging.py +511 -0
- sp_api/asyncio/api/models/__init__.py +4 -0
- sp_api/asyncio/api/notifications/__init__.py +0 -0
- sp_api/asyncio/api/notifications/notifications.py +295 -0
- sp_api/asyncio/api/orders/__init__.py +0 -0
- sp_api/asyncio/api/orders/orders.py +412 -0
- sp_api/asyncio/api/orders/orders_2026_01_01.py +40 -0
- sp_api/asyncio/api/overrides/__init__.py +1 -0
- sp_api/asyncio/api/product_fees/__init__.py +0 -0
- sp_api/asyncio/api/product_fees/product_fees.py +194 -0
- sp_api/asyncio/api/product_type_definitions/__init__.py +0 -0
- sp_api/asyncio/api/product_type_definitions/product_type_definitions.py +75 -0
- sp_api/asyncio/api/products/__init__.py +0 -0
- sp_api/asyncio/api/products/products.py +405 -0
- sp_api/asyncio/api/products/products_definitions.py +11 -0
- sp_api/asyncio/api/replenishment/__init__.py +0 -0
- sp_api/asyncio/api/replenishment/replenishment.py +121 -0
- sp_api/asyncio/api/reports/__init__.py +0 -0
- sp_api/asyncio/api/reports/reports.py +439 -0
- sp_api/asyncio/api/sales/__init__.py +0 -0
- sp_api/asyncio/api/sales/sales.py +93 -0
- sp_api/asyncio/api/sellers/__init__.py +0 -0
- sp_api/asyncio/api/sellers/sellers.py +70 -0
- sp_api/asyncio/api/services/__init__.py +0 -0
- sp_api/asyncio/api/services/services.py +218 -0
- sp_api/asyncio/api/shipping/__init__.py +0 -0
- sp_api/asyncio/api/shipping/shipping.py +459 -0
- sp_api/asyncio/api/shipping/shippingV2.py +651 -0
- sp_api/asyncio/api/solicitations/__init__.py +0 -0
- sp_api/asyncio/api/solicitations/solicitations.py +78 -0
- sp_api/asyncio/api/supply_sources/__init__.py +0 -0
- sp_api/asyncio/api/supply_sources/supply_sources.py +138 -0
- sp_api/asyncio/api/tokens/__init__.py +0 -0
- sp_api/asyncio/api/tokens/tokens.py +65 -0
- sp_api/asyncio/api/upload/__init__.py +0 -0
- sp_api/asyncio/api/upload/upload.py +18 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_inventory/__init__.py +0 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_inventory/vendor_direct_fulfillment_inventory.py +64 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_orders/__init__.py +0 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_orders/vendor_direct_fulfillment_orders.py +196 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_payments/__init__.py +0 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_payments/vendor_direct_fulfillment_payments.py +254 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_shipping/__init__.py +0 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_shipping/vendor_direct_fulfillment_shipping.py +627 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_transactions/__init__.py +0 -0
- sp_api/asyncio/api/vendor_direct_fulfillment_transactions/vendor_direct_fulfillment_transactions.py +43 -0
- sp_api/asyncio/api/vendor_invoices/__init__.py +0 -0
- sp_api/asyncio/api/vendor_invoices/vendor_invoices.py +295 -0
- sp_api/asyncio/api/vendor_orders/__init__.py +0 -0
- sp_api/asyncio/api/vendor_orders/vendor_orders.py +210 -0
- sp_api/asyncio/api/vendor_shipments/__init__.py +0 -0
- sp_api/asyncio/api/vendor_shipments/vendor_shipments.py +118 -0
- sp_api/asyncio/api/vendor_transaction_status/__init__.py +0 -0
- sp_api/asyncio/api/vendor_transaction_status/vendor_transaction_status.py +41 -0
- sp_api/asyncio/auth/__init__.py +12 -0
- sp_api/asyncio/auth/access_token_client.py +145 -0
- sp_api/asyncio/auth/exceptions.py +5 -0
- sp_api/asyncio/base/__init__.py +53 -0
- sp_api/asyncio/base/_transport_httpx.py +50 -0
- sp_api/asyncio/base/base_client.py +8 -0
- sp_api/asyncio/base/client.py +169 -0
- sp_api/asyncio/util/__init__.py +29 -0
- sp_api/asyncio/util/key_maker.py +5 -0
- sp_api/asyncio/util/load_all_pages.py +55 -0
- sp_api/asyncio/util/load_date_bound.py +53 -0
- sp_api/asyncio/util/retry.py +88 -0
- sp_api/auth/__init__.py +3 -3
- sp_api/auth/_core.py +39 -0
- sp_api/auth/access_token_client.py +20 -31
- sp_api/auth/access_token_response.py +4 -4
- sp_api/base/ApiResponse.py +5 -4
- sp_api/base/__init__.py +53 -42
- sp_api/base/_core.py +110 -0
- sp_api/base/_transport_httpx.py +39 -0
- sp_api/base/base_client.py +4 -4
- sp_api/base/client.py +131 -112
- sp_api/base/credential_provider.py +41 -34
- sp_api/base/exceptions.py +14 -3
- sp_api/base/feedTypes.py +44 -32
- sp_api/base/fulfillment_channel.py +2 -2
- sp_api/base/helpers.py +17 -16
- sp_api/base/identifiersType.py +8 -8
- sp_api/base/included_data.py +12 -12
- sp_api/base/marketplaces.py +5 -1
- sp_api/base/notifications.py +1 -1
- sp_api/base/processing_status.py +5 -5
- sp_api/base/reportTypes.py +198 -111
- sp_api/base/sales_enum.py +11 -13
- sp_api/util/__init__.py +42 -6
- sp_api/util/key_maker.py +4 -2
- sp_api/util/load_all_pages.py +16 -5
- sp_api/util/load_date_bound.py +28 -13
- sp_api/util/params.py +57 -0
- sp_api/util/product_fees.py +40 -0
- sp_api/util/products_definitions.py +169 -0
- sp_api/util/report_document.py +154 -0
- sp_api/util/retry.py +16 -15
- python_amazon_sp_api-1.7.5.dist-info/RECORD +0 -144
- tests/api/finances/test_finances.py +0 -19
- tests/api/notifications/test_notifications.py +0 -26
- tests/api/orders/test_orders.py +0 -122
- tests/api/product_fees/product_fees.py +0 -49
- tests/api/reports/test_reports.py +0 -127
- tests/client/test_auth.py +0 -59
- tests/client/test_base.py +0 -163
- tests/client/test_credential_provider.py +0 -45
- tests/client/test_helpers.py +0 -142
- {python_amazon_sp_api-1.7.5.dist-info → python_amazon_sp_api-2.0.10.dist-info/licenses}/LICENSE +0 -0
- {tests → sp_api/api/application_integrations}/__init__.py +0 -0
- {tests/api → sp_api/api/customer_feedback}/__init__.py +0 -0
- {tests/api/finances → sp_api/api/easy_ship}/__init__.py +0 -0
- {tests/api/notifications → sp_api/api/external_fulfillment}/__init__.py +0 -0
- {tests/api/orders → sp_api/asyncio}/__init__.py +0 -0
- {tests/api/product_fees → sp_api/asyncio/api/feeds}/__init__.py +0 -0
- {tests/api/reports → sp_api/asyncio/api/finances}/__init__.py +0 -0
- {tests/api/sellers → sp_api/asyncio/api/fulfillment_inbound}/__init__.py +0 -0
- {tests/client → sp_api/asyncio/api/fulfillment_outbound}/__init__.py +0 -0
- /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
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(
|
|
58
|
-
marketplace = Marketplaces[os.environ.get(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 = [
|
|
153
|
-
GET = [
|
|
147
|
+
POST = ["marketplaceIds", "MarketplaceIds"]
|
|
148
|
+
GET = ["MarketplaceId", "MarketplaceIds", "marketplace_ids", "marketplaceIds"]
|
|
154
149
|
|
|
155
|
-
if self.method ==
|
|
150
|
+
if self.method == "POST":
|
|
156
151
|
if any(x in data.keys() for x in POST):
|
|
157
152
|
return
|
|
158
|
-
return data.update(
|
|
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(
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
193
|
+
if "<version>" not in path:
|
|
176
194
|
return path
|
|
177
|
-
return path.replace(
|
|
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 =
|
|
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 = [
|
|
49
|
-
|
|
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(
|
|
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__(
|
|
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(
|
|
70
|
-
config_filename = os.path.join(config.config_dir(),
|
|
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(
|
|
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(
|
|
88
|
-
lwa_app_id=secret.get(
|
|
89
|
-
lwa_client_secret=secret.get(
|
|
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(
|
|
105
|
+
client = boto3.client("secretsmanager")
|
|
105
106
|
response = client.get_secret_value(SecretId=secret_id)
|
|
106
|
-
return json.loads(response.get(
|
|
107
|
+
return json.loads(response.get("SecretString"))
|
|
107
108
|
except ImportError:
|
|
108
|
-
print(
|
|
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(
|
|
139
|
-
lwa_app_id=self._get_env(
|
|
140
|
-
lwa_client_secret=self._get_env(
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
183
|
-
self.lwa_app_id = kwargs.get(
|
|
184
|
-
self.lwa_client_secret = kwargs.get(
|
|
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(
|
|
17
|
-
self.amzn_code = error[0].get(
|
|
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)
|