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/sales_enum.py
CHANGED
|
@@ -2,22 +2,20 @@ from enum import Enum
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Granularity(str, Enum):
|
|
5
|
-
HOUR =
|
|
6
|
-
DAY =
|
|
7
|
-
WEEK =
|
|
8
|
-
MONTH =
|
|
9
|
-
YEAR =
|
|
10
|
-
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 =
|
|
15
|
-
B2C =
|
|
16
|
-
ALL =
|
|
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 =
|
|
21
|
-
SU =
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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() +
|
|
76
|
-
word.title() if i > 0 else word
|
|
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
|
+
)
|
sp_api/util/load_all_pages.py
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
sp_api/util/load_date_bound.py
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
30
|
+
if tries_counter.get("count") + 1 > tries:
|
|
34
31
|
raise e
|
|
35
32
|
|
|
36
|
-
delay_now =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
86
|
+
|
|
87
|
+
return retry(
|
|
88
|
+
(SellingApiRequestThrottledException,) + exception_classes, tries, delay, rate
|
|
89
|
+
)
|