diff --git a/setup.py b/setup.py index 7af40cd6a..32310ec03 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='python-amazon-sp-api', - version='0.2.0', + version='0.2.1', install_requires=[ "requests", "six~=1.15.0", diff --git a/sp_api/api/reports/reports.py b/sp_api/api/reports/reports.py index 88c4d8fe4..718529ecd 100644 --- a/sp_api/api/reports/reports.py +++ b/sp_api/api/reports/reports.py @@ -1,8 +1,10 @@ +from collections import abc +from datetime import datetime import zlib import requests -from sp_api.base import sp_endpoint, fill_query_params, SellingApiException, ApiResponse +from sp_api.base import sp_endpoint, fill_query_params, SellingApiException, ApiResponse, ProcessingStatus, Marketplaces from sp_api.base import Client from sp_api.base.helpers import decrypt_aes @@ -182,6 +184,52 @@ def get_report_schedule(self, schedule_id, **kwargs) -> ApiResponse: """ return self._request(fill_query_params(kwargs.pop('path'), schedule_id), params=kwargs) + @sp_endpoint('/reports/2020-09-04/reports') + def get_reports(self, **kwargs) -> ApiResponse: + """ + get_reports(self, **kwargs) -> ApiResponse + + Returns report details for the reports that match the filters that you specify. + + **Usage Plan:** + + ====================================== ============== + Rate (requests per second) Burst + ====================================== ============== + 0.0222 10 + ====================================== ============== + + For more information, see "Usage Plans and Rate Limits" in the Selling Partner API documentation. + + + Args: + key reportTypes: str[] or ReportType[] | optional A list of report types used to filter reports. When reportTypes is provided, the other filter parameters (processingStatuses, marketplaceIds, createdSince, createdUntil) and pageSize may also be provided. Either reportTypes or nextToken is required. + key processingStatuses: str[] or ProcessingStatus[] optional A list of processing statuses used to filter reports. + key marketplaceIds: str[] or Marketplaces[] optional A list of marketplace identifiers used to filter reports. The reports returned will match at least one of the marketplaces that you specify. + key pageSize: int optional The maximum number of reports to return in a single call. + key createdSince: str or datetime optional The earliest report creation date and time for reports to include in the response, in ISO 8601 date time format. The default is 90 days ago. Reports are retained for a maximum of 90 days. string (date-time) - + key createdUntil: str or datetime optional The latest report creation date and time for reports to include in the response, in ISO 8601 date time format. The default is now. string (date-time) - + key nextToken: str optional A string token returned in the response to your previous request. nextToken is returned when the number of results exceeds the specified pageSize value. To get the next page of results, call the getReports operation and include this token as the only parameter. Specifying nextToken with any other parameters will cause the request to fail. string - + + + Returns: + ApiResponse + """ + if kwargs.get('reportTypes', None) and isinstance(kwargs.get('reportTypes'), abc.Iterable): + kwargs.update({'reportTypes': ','.join(kwargs.get('reportTypes'))}) + if kwargs.get('processingStatuses', None) and isinstance(kwargs.get('processingStatuses'), abc.Iterable): + kwargs.update({'processingStatuses': ','.join(kwargs.get('processingStatuses'))}) + if kwargs.get('marketplaceIds', None) and isinstance(kwargs.get('marketplaceIds'), abc.Iterable): + marketplaces = kwargs.get('marketplaceIds') + if not isinstance(marketplaces, abc.Iterable): + marketplaces = [marketplaces] + kwargs.update({'marketplaceIds': ','.join([m.marketplace_id if isinstance(m, Marketplaces) else m for m in marketplaces])}) + for k in ['createdSince', 'createdUntil']: + if kwargs.get(k, None) and isinstance(kwargs.get(k), datetime): + kwargs.update({k: kwargs.get(k).isoformat()}) + + return self._request(kwargs.pop('path'), params=kwargs, add_marketplace=False) + @staticmethod def decrypt_report_document(url, initialization_vector, key, encryption_standard, payload): """ diff --git a/sp_api/base/ApiResponse.py b/sp_api/base/ApiResponse.py index 6b5fd7331..a91c7e3d2 100644 --- a/sp_api/base/ApiResponse.py +++ b/sp_api/base/ApiResponse.py @@ -2,11 +2,13 @@ class ApiResponse: - def __init__(self, payload=None, errors=None, pagination=None, headers=None): + def __init__(self, payload=None, errors=None, pagination=None, headers=None, nextToken=None, **kwargs): self.payload = payload self.errors = errors self.pagination = pagination self.headers = headers + self.next_token = nextToken + self.kwargs = kwargs def __str__(self): return pprint.pformat(self.__dict__) diff --git a/sp_api/base/__init__.py b/sp_api/base/__init__.py index a72563666..11ea5325c 100644 --- a/sp_api/base/__init__.py +++ b/sp_api/base/__init__.py @@ -18,8 +18,12 @@ from .notifications import NotificationType from .config import CredentialProvider, MissingCredentials from .ApiResponse import ApiResponse +from .processing_status import ProcessingStatus +from .reportTypes import ReportType __all__ = [ + 'ReportType', + 'ProcessingStatus', 'ApiResponse', 'Client', 'BaseClient', diff --git a/sp_api/base/fulfillment_channel.py b/sp_api/base/fulfillment_channel.py index 05972fe67..3ad956796 100644 --- a/sp_api/base/fulfillment_channel.py +++ b/sp_api/base/fulfillment_channel.py @@ -1,5 +1,6 @@ from enum import Enum + class FulfillmentChannel(Enum): AFN = 'AFN' MFN = 'MFN' diff --git a/sp_api/base/notifications.py b/sp_api/base/notifications.py index 74fa610ca..9ed20ba16 100644 --- a/sp_api/base/notifications.py +++ b/sp_api/base/notifications.py @@ -1,7 +1,7 @@ from enum import Enum -class NotificationType(Enum): +class NotificationType(str, Enum): ANY_OFFER_CHANGED = 'ANY_OFFER_CHANGED' FEED_PROCESSING_FINISHED = 'FEED_PROCESSING_FINISHED' FBA_OUTBOUND_SHIPMENT_STATUS = 'FBA_OUTBOUND_SHIPMENT_STATUS' diff --git a/sp_api/base/processing_status.py b/sp_api/base/processing_status.py new file mode 100644 index 000000000..2a48eaf3f --- /dev/null +++ b/sp_api/base/processing_status.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class ProcessingStatus(str, Enum): + CANCELLED = 'CANCELLED' + DONE = 'DONE' + FATAL = 'FATAL' + IN_PROGRESS = 'IN_PROGRESS' + IN_QUEUE = 'IN_QUEUE' diff --git a/sp_api/base/reportTypes.py b/sp_api/base/reportTypes.py new file mode 100644 index 000000000..ba515245d --- /dev/null +++ b/sp_api/base/reportTypes.py @@ -0,0 +1,59 @@ +from enum import Enum + + +class ReportType(str, Enum): + GET_FLAT_FILE_OPEN_LISTINGS_DATA = 'GET_FLAT_FILE_OPEN_LISTINGS_DATA' + GET_MERCHANT_LISTINGS_DATA = 'GET_MERCHANT_LISTINGS_DATA' + GET_MERCHANT_LISTINGS_DATA_BACK_COMPAT = 'GET_MERCHANT_LISTINGS_DATA_BACK_COMPAT' + GET_MERCHANT_LISTINGS_DATA_LITE = 'GET_MERCHANT_LISTINGS_DATA_LITE' + GET_MERCHANT_LISTINGS_DATA_LITER = 'GET_MERCHANT_LISTINGS_DATA_LITER' + GET_MERCHANT_CANCELLED_LISTINGS_DATA = 'GET_MERCHANT_CANCELLED_LISTINGS_DATA' + GET_CONVERGED_FLAT_FILE_SOLD_LISTINGS_DATA = 'GET_CONVERGED_FLAT_FILE_SOLD_LISTINGS_DATA' + GET_MERCHANT_LISTINGS_DEFECT_DATA = 'GET_MERCHANT_LISTINGS_DEFECT_DATA' + GET_FLAT_FILE_ACTIONABLE_ORDER_DATA = 'GET_FLAT_FILE_ACTIONABLE_ORDER_DATA' + GET_ORDERS_DATA = 'GET_ORDERS_DATA' + GET_FLAT_FILE_ORDERS_DATA = 'GET_FLAT_FILE_ORDERS_DATA' + GET_CONVERGED_FLAT_FILE_ORDER_REPORT_DATA = 'GET_CONVERGED_FLAT_FILE_ORDER_REPORT_DATA' + GET_FLAT_FILE_PENDING_ORDERS_DATA = 'GET_FLAT_FILE_PENDING_ORDERS_DATA' + GET_PENDING_ORDERS_DATA = 'GET_PENDING_ORDERS_DATA' + GET_CONVERGED_FLAT_FILE_PENDING_ORDERS_DATA = 'GET_CONVERGED_FLAT_FILE_PENDING_ORDERS_DATA' + GET_SELLER_FEEDBACK_DATA = 'GET_SELLER_FEEDBACK_DATA' + GET_V1_SELLER_PERFORMANCE_REPORT = 'GET_V1_SELLER_PERFORMANCE_REPORT' + GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE = 'GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE' + GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE_V2 = 'GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE_V2' + GET_FLAT_FILE_SALES_TAX_DATA = 'GET_FLAT_FILE_SALES_TAX_DATA' + SC_VAT_TAX_REPORT = 'SC_VAT_TAX_REPORT' + GET_VAT_TRANSACTION_DATA = 'GET_VAT_TRANSACTION_DATA' + GET_XML_BROWSE_TREE_DATA = 'GET_XML_BROWSE_TREE_DATA' + GET_AMAZON_FULFILLED_SHIPMENTS_DATA = 'GET_AMAZON_FULFILLED_SHIPMENTS_DATA' + FEE_DISCOUNTS_REPORT = 'FEE_DISCOUNTS_REPORT' + GET_FLAT_FILE_ALL_ORDERS_DATA_BY_LAST_UPDATE = 'GET_FLAT_FILE_ALL_ORDERS_DATA_BY_LAST_UPDATE' + GET_FLAT_FILE_ALL_ORDERS_DATA_BY_ORDER_DATE = 'GET_FLAT_FILE_ALL_ORDERS_DATA_BY_ORDER_DATE' + GET_XML_ALL_ORDERS_DATA_BY_LAST_UPDATE = 'GET_XML_ALL_ORDERS_DATA_BY_LAST_UPDATE' + GET_XML_ALL_ORDERS_DATA_BY_ORDER_DATE = 'GET_XML_ALL_ORDERS_DATA_BY_ORDER_DATE' + GET_FBA_FULFILLMENT_CUSTOMER_SHIPMENT_SALES_DATA = 'GET_FBA_FULFILLMENT_CUSTOMER_SHIPMENT_SALES_DATA' + GET_FBA_FULFILLMENT_CUSTOMER_SHIPMENT_PROMOTION_DATA = 'GET_FBA_FULFILLMENT_CUSTOMER_SHIPMENT_PROMOTION_DATA' + GET_FBA_FULFILLMENT_CUSTOMER_TAXES_DATA = 'GET_FBA_FULFILLMENT_CUSTOMER_TAXES_DATA' + GET_AFN_INVENTORY_DATA = 'GET_AFN_INVENTORY_DATA' + GET_FBA_FULFILLMENT_CURRENT_INVENTORY_DATA = 'GET_FBA_FULFILLMENT_CURRENT_INVENTORY_DATA' + GET_FBA_FULFILLMENT_MONTHLY_INVENTORY_DATA = 'GET_FBA_FULFILLMENT_MONTHLY_INVENTORY_DATA' + GET_FBA_FULFILLMENT_INVENTORY_RECEIPTS_DATA = 'GET_FBA_FULFILLMENT_INVENTORY_RECEIPTS_DATA' + GET_RESERVED_INVENTORY_DATA = 'GET_RESERVED_INVENTORY_DATA' + GET_FBA_FULFILLMENT_INVENTORY_SUMMARY_DATA = 'GET_FBA_FULFILLMENT_INVENTORY_SUMMARY_DATA' + GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA = 'GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA' + GET_FBA_FULFILLMENT_INVENTORY_HEALTH_DATA = 'GET_FBA_FULFILLMENT_INVENTORY_HEALTH_DATA' + GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA = 'GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA' + GET_FBA_MYI_ALL_INVENTORY_DATA = 'GET_FBA_MYI_ALL_INVENTORY_DATA' + GET_FBA_FULFILLMENT_CROSS_BORDER_INVENTORY_MOVEMENT_DATA = 'GET_FBA_FULFILLMENT_CROSS_BORDER_INVENTORY_MOVEMENT_DATA' + GET_FBA_FULFILLMENT_INBOUND_NONCOMPLIANCE_DATA = 'GET_FBA_FULFILLMENT_INBOUND_NONCOMPLIANCE_DATA' + GET_STRANDED_INVENTORY_UI_DATA = 'GET_STRANDED_INVENTORY_UI_DATA' + GET_STRANDED_INVENTORY_LOADER_DATA = 'GET_STRANDED_INVENTORY_LOADER_DATA' + GET_FBA_INVENTORY_AGED_DATA = 'GET_FBA_INVENTORY_AGED_DATA' + GET_EXCESS_INVENTORY_DATA = 'GET_EXCESS_INVENTORY_DATA' + GET_FBA_ESTIMATED_FBA_FEES_TXT_DATA = 'GET_FBA_ESTIMATED_FBA_FEES_TXT_DATA' + GET_FBA_REIMBURSEMENTS_DATA = 'GET_FBA_REIMBURSEMENTS_DATA' + GET_FBA_FULFILLMENT_CUSTOMER_RETURNS_DATA = 'GET_FBA_FULFILLMENT_CUSTOMER_RETURNS_DATA' + GET_FBA_RECOMMENDED_REMOVAL_DATA = 'GET_FBA_RECOMMENDED_REMOVAL_DATA' + GET_FBA_FULFILLMENT_REMOVAL_ORDER_DETAIL_DATA = 'GET_FBA_FULFILLMENT_REMOVAL_ORDER_DETAIL_DATA' + GET_FBA_FULFILLMENT_REMOVAL_SHIPMENT_DETAIL_DATA = 'GET_FBA_FULFILLMENT_REMOVAL_SHIPMENT_DETAIL_DATA' + diff --git a/tests/api/reports/test_reports.py b/tests/api/reports/test_reports.py index 9f5b76db3..80f696400 100644 --- a/tests/api/reports/test_reports.py +++ b/tests/api/reports/test_reports.py @@ -1,5 +1,7 @@ from sp_api.api import Reports -from sp_api.base import Marketplaces, Schedules, SellingApiBadRequestException, SellingApiServerException +from sp_api.base import Marketplaces, Schedules, SellingApiBadRequestException, SellingApiServerException, \ + ProcessingStatus +from sp_api.base.reportTypes import ReportType def test_create_report(): @@ -70,3 +72,43 @@ def test_get_schedule_by_id(): assert res.errors is None assert 'period' in res.payload assert res.payload.get('reportType') == 'FEE_DISCOUNTS_REPORT' + + +def test_get_reports_1(): + report_types = [ + "FEE_DISCOUNTS_REPORT", + "GET_AFN_INVENTORY_DATA" + ] + processing_status = [ + "IN_QUEUE", + "IN_PROGRESS" + ] + res = Reports().get_reports(reportTypes=report_types, processingStatuses=processing_status) + assert res.errors is None + + +def test_get_reports_2(): + report_types = [ + "FEE_DISCOUNTS_REPORT", + "GET_AFN_INVENTORY_DATA" + ] + processing_status = [ + ProcessingStatus.IN_QUEUE, + ProcessingStatus.IN_PROGRESS + ] + res = Reports().get_reports(reportTypes=report_types, processingStatuses=processing_status) + assert res.errors is None + + +def test_get_reports_3(): + report_types = [ + ReportType.FEE_DISCOUNTS_REPORT, + ReportType.GET_AFN_INVENTORY_DATA + ] + processing_status = [ + ProcessingStatus.IN_QUEUE, + ProcessingStatus.IN_PROGRESS + ] + res = Reports().get_reports(reportTypes=report_types, processingStatuses=processing_status) + assert res.errors is None +