From 0350d3f0ee84bc8ca5e3bd9e77e751581a2a0b88 Mon Sep 17 00:00:00 2001 From: Jason Fertel Date: Thu, 9 Aug 2018 12:12:40 -0400 Subject: [PATCH 01/20] report request errors + setup.py fix update report request errors when getting a 429 or a 500 setup.py for integration with requirements.txt --- amazon_advertising_api/advertising_api.py | 37 +++++++++++------------ setup.py | 2 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 8fb0657..d8a208c 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -271,21 +271,21 @@ def list_campaigns(self, data=None): data may contain the following optional parameters: - :param startIndex: 0-indexed record offset for the result set. + :param startIndex: 0-indexed record offset for the result set. Defaults to 0. :type startIndex: Integer - :param count: Number of records to include in the paged response. + :param count: Number of records to include in the paged response. Defaults to max page size. :type count: Integer - :param campaignType: Restricts results to campaigns of a single + :param campaignType: Restricts results to campaigns of a single campaign type. Must be **sponsoredProducts**. :type campaignType: String - :param stateFilter: Restricts results to campaigns with state within - the specified comma-separatedlist. Must be one of **enabled**, + :param stateFilter: Restricts results to campaigns with state within + the specified comma-separatedlist. Must be one of **enabled**, **paused**, **archived**. Default behavior is to include all. :param name: Restricts results to campaigns with the specified name. :type name: String - :param campaignFilterId: Restricts results to campaigns specified in + :param campaignFilterId: Restricts results to campaigns specified in comma-separated list. :type campaignFilterId: String :returns: @@ -477,7 +477,7 @@ def list_ad_groups_ex(self, data=None): def get_biddable_keyword(self, keyword_id): """ - Retrieves a keyword by ID. Note that this call returns the minimal set + Retrieves a keyword by ID. Note that this call returns the minimal set of keyword fields, but is more efficient than getBiddableKeywordEx. :GET: /keywords/{keywordId} @@ -494,9 +494,9 @@ def get_biddable_keyword(self, keyword_id): def get_biddable_keyword_ex(self, keyword_id): """ - Retrieves a keyword and its extended fields by ID. Note that this call - returns the complete set of keyword fields (including serving status - and other read-only fields), but is less efficient than + Retrieves a keyword and its extended fields by ID. Note that this call + returns the complete set of keyword fields (including serving status + and other read-only fields), but is less efficient than getBiddableKeyword. :GET: /keywords/extended/{keywordId} @@ -513,12 +513,12 @@ def get_biddable_keyword_ex(self, keyword_id): def create_biddable_keywords(self, data): """ - Creates one or more keywords. Successfully created keywords will be + Creates one or more keywords. Successfully created keywords will be assigned unique keywordIds. :POST: /keywords - :param data: A list of up to 1000 keywords to be created. Required - fields for keyword creation are campaignId, adGroupId, keywordText, + :param data: A list of up to 1000 keywords to be created. Required + fields for keyword creation are campaignId, adGroupId, keywordText, matchType and state. :type data: List of **Keyword** """ @@ -672,12 +672,11 @@ def request_report(self, record_type=None, report_id=None, data=None): def get_report(self, report_id): interface = 'reports/{}'.format(report_id) res = self._operation(interface) - if json.loads(res['response'])['status'] == 'SUCCESS': - res = self._download( - location=json.loads(res['response'])['location']) - return res - else: - return res + if res['success']: + body = json.loads(res['response']) + if body.get('status') == 'SUCCESS': + res = self._download(location=body['location']) + return res def get_snapshot(self, snapshot_id): interface = 'snapshots/{}'.format(snapshot_id) diff --git a/setup.py b/setup.py index d2a341d..1552951 100644 --- a/setup.py +++ b/setup.py @@ -7,4 +7,4 @@ packages=['amazon_advertising_api'], version=aa_versions.versions['application_version'], description='Unofficial Amazon Sponsored Products Python client library.', - url='https://github.com/sguermond/amazon-advertising-api-python') + url='https://github.com/pepsico-ecommerce/amazon-advertising-api-python') From f66cdc488f3b57a6107fafeff4aa8d74406354b1 Mon Sep 17 00:00:00 2001 From: David Antaramian Date: Thu, 8 Nov 2018 12:16:05 -0500 Subject: [PATCH 02/20] Update use of the advertising API to v2 This updates the version of the advertising API to v2. Only `GET` operations have been updated along with the `POST` operations for the snapshotting and reporting endpoints. The remaining `POST`, `PUT`, and `DELETE` operations have _not_ been updated and may fail due to version inconsistencies. --- amazon_advertising_api/advertising_api.py | 114 +++++++++++++--------- amazon_advertising_api/versions.py | 2 +- 2 files changed, 67 insertions(+), 49 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index d8a208c..644174d 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -174,39 +174,45 @@ def update_profiles(self, data): interface = 'profiles' return self._operation(interface, data, method='PUT') - def get_campaign(self, campaign_id): + def get_campaign(self, campaign_id, campaign_type='sp'): """ Retrieves a campaign by Id. Note that this call returns the minimal set of campaign fields, but is more efficient than **getCampaignEx**. - :GET: /campaigns/{campaignId} + :GET: {campaignType}/campaigns/{campaignId} :param campaign_id: The Id of the requested campaign. :type campaign_id: string + :param campaign_type: The campaignType of the requested campaign ('sp' or 'hsa') + Defaults to 'sp' + :type campaign_type: string :returns: :200: Campaign :401: Unauthorized :404: Campaign not found """ - interface = 'campaigns/{}'. format(campaign_id) + interface = '{}/campaigns/{}'. format(campaign_type, campaign_id) return self._operation(interface) - def get_campaign_ex(self, campaign_id): + def get_campaign_ex(self, campaign_id, campaign_type='sp'): """ Retrieves a campaign and its extended fields by ID. Note that this call returns the complete set of campaign fields (including serving status and other read-only fields), but is less efficient than **getCampaign**. - :GET: /campaigns/extended/{campaignId} + :GET: {campaignType}/campaigns/extended/{campaignId} :param campaign_id: The Id of the requested campaign. :type campaign_id: string + :param campaign_type: The campaignType of the requested campaign ('sp' or 'hsa') + Defaults to 'sp' + :type campaign_type: string :returns: :200: Campaign :401: Unauthorized :404: Campaign not found """ - interface = 'campaigns/extended/{}'. format(campaign_id) + interface = '{}/campaigns/extended/{}'. format(campaign_type, campaign_id) return self._operation(interface) def create_campaigns(self, data): @@ -261,11 +267,14 @@ def archive_campaign(self, campaign_id): interface = 'campaigns/{}'.format(campaign_id) return self._operation(interface, method='DELETE') - def list_campaigns(self, data=None): + def list_campaigns(self, campaign_type='sp', data=None): """ Retrieves a list of campaigns satisfying optional criteria. - :GET: /campaigns + :GET: /{campaignType}/campaigns + :param campaign_type: The campaignType to retrieve campaigns for ('sp' or 'hsa') + Defaults to 'sp' + :type campaign_type: string :param data: Optional, search criteria containing the following parameters. @@ -292,20 +301,23 @@ def list_campaigns(self, data=None): :200: Success. list of campaign :401: Unauthorized """ - interface = 'campaigns' + interface = '{}/campaigns' .format(campaign_type) return self._operation(interface, data) - def list_campaigns_ex(self, data=None): + def list_campaigns_ex(self, campaign_type='sp', data=None): """ Retrieves a list of campaigns with extended fields satisfying optional filtering criteria. - :GET: /campaigns/extended + :GET: /{campaignType}/campaigns/extended + :param campaign_type: campaignType of the requested campaigns ('sp' or 'hsa') + Defaults to 'sp' + :type campaign_type: string :param data: Optional, search criteria containing the following parameters. :type data: JSON string """ - interface = 'campaigns/extended' + interface = '{}/campaigns/extended' .format(campaign_type) return self._operation(interface, data) def get_ad_group(self, ad_group_id): @@ -313,7 +325,7 @@ def get_ad_group(self, ad_group_id): Retrieves an ad group by Id. Note that this call returns the minimal set of ad group fields, but is more efficient than getAdGroupEx. - :GET: /adGroups/{adGroupId} + :GET: /sp/adGroups/{adGroupId} :param ad_group_id: The Id of the requested ad group. :type ad_group_id: string @@ -322,7 +334,7 @@ def get_ad_group(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'adGroups/{}'.format(ad_group_id) + interface = 'sp/adGroups/{}'.format(ad_group_id) return self._operation(interface) def get_ad_group_ex(self, ad_group_id): @@ -332,7 +344,7 @@ def get_ad_group_ex(self, ad_group_id): status and other read-only fields), but is less efficient than getAdGroup. - :GET: /adGroups/extended/{adGroupId} + :GET: /sp/adGroups/extended/{adGroupId} :param ad_group_id: The Id of the requested ad group. :type ad_group_id: string @@ -341,7 +353,7 @@ def get_ad_group_ex(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'adGroups/extended/{}'.format(ad_group_id) + interface = 'sp/adGroups/extended/{}'.format(ad_group_id) return self._operation(interface) def create_ad_groups(self, data): @@ -402,7 +414,7 @@ def list_ad_groups(self, data=None): """ Retrieves a list of ad groups satisfying optional criteria. - :GET: /adGroups + :GET: /sp/adGroups :param data: Parameter list of criteria. data may contain the following optional parameters: @@ -434,14 +446,14 @@ def list_ad_groups(self, data=None): :401: Unauthorized. """ - interface = 'adGroups' + interface = 'sp/adGroups' return self._operation(interface, data) def list_ad_groups_ex(self, data=None): """ Retrieves a list of ad groups satisfying optional criteria. - :GET: /adGroups/extended + :GET: /sp/adGroups/extended :param data: Parameter list of criteria. data may contain the following optional parameters: @@ -472,24 +484,27 @@ def list_ad_groups_ex(self, data=None): :200: Success. List of adGroup. :401: Unauthorized. """ - interface = 'adGroups/extended' + interface = 'sp/adGroups/extended' return self._operation(interface, data) - def get_biddable_keyword(self, keyword_id): + def get_biddable_keyword(self, keyword_id, campaign_type='sp'): """ Retrieves a keyword by ID. Note that this call returns the minimal set of keyword fields, but is more efficient than getBiddableKeywordEx. - :GET: /keywords/{keywordId} + :GET: /{campaignType}/keywords/{keywordId} :param keyword_id: The Id of the requested keyword. :type keyword_id: string + :param campaign_type: The campaignType for the requested keyword + Defaults to 'sp' + :type campaign_type: string :returns: :200: Success. Keyword. :401: Unauthorized. :404: Keyword not found. """ - interface = 'keywords/{}'.format(keyword_id) + interface = '{}/keywords/{}'.format(campaign_type, keyword_id) return self._operation(interface) def get_biddable_keyword_ex(self, keyword_id): @@ -508,7 +523,7 @@ def get_biddable_keyword_ex(self, keyword_id): :401: Unauthorized. :404: Keyword not found. """ - interface = 'keywords/extended/{}'.format(keyword_id) + interface = 'sp/keywords/extended/{}'.format(keyword_id) return self._operation(interface) def create_biddable_keywords(self, data): @@ -534,19 +549,19 @@ def archive_biddable_keyword(self, keyword_id): return self._operation(interface, method='DELETE') def list_biddable_keywords(self, data=None): - interface = 'keywords' + interface = 'sp/keywords' return self._operation(interface, data) def list_biddable_keywords_ex(self, data=None): - interface = 'keywords/extended' + interface = 'sp/keywords/extended' return self._operation(interface, data) def get_negative_keyword(self, negative_keyword_id): - interface = 'negativeKeywords/{}'.format(negative_keyword_id) + interface = 'sp/negativeKeywords/{}'.format(negative_keyword_id) return self._operation(interface) def get_negative_keyword_ex(self, negative_keyword_id): - interface = 'negativeKeywords/extended/{}'.format(negative_keyword_id) + interface = 'sp/negativeKeywords/extended/{}'.format(negative_keyword_id) return self._operation(interface) def create_negative_keywords(self, data): @@ -562,20 +577,20 @@ def archive_negative_keyword(self, negative_keyword_id): return self._operation(interface, method='DELETE') def list_negative_keywords(self, data=None): - interface = 'negativeKeywords' + interface = 'sp/negativeKeywords' return self._operation(interface, data) def list_negative_keywords_ex(self, data=None): - interface = 'negativeKeywords/extended' + interface = 'sp/negativeKeywords/extended' return self._operation(interface, data) def get_campaign_negative_keyword(self, campaign_negative_keyword_id): - interface = 'campaignNegativeKeywords/{}'.format( + interface = 'sp/campaignNegativeKeywords/{}'.format( campaign_negative_keyword_id) return self._operation(interface) def get_campaign_negative_keyword_ex(self, campaign_negative_keyword_id): - interface = 'campaignNegativeKeywords/extended/{}'.format( + interface = 'sp/campaignNegativeKeywords/extended/{}'.format( campaign_negative_keyword_id) return self._operation(interface) @@ -593,19 +608,19 @@ def remove_campaign_negative_keyword(self, campaign_negative_keyword_id): return self._operation(interface, method='DELETE') def list_campaign_negative_keywords(self, data=None): - interface = 'campaignNegativeKeywords' + interface = 'sp/campaignNegativeKeywords' return self._operation(interface, data) def list_campaign_negative_keywords_ex(self, data=None): - interface = 'campaignNegativeKeywords/extended' + interface = 'sp/campaignNegativeKeywords/extended' return self._operation(interface, data) def get_product_ad(self, product_ad_id): - interface = 'productAds/{}'.format(product_ad_id) + interface = 'sp/productAds/{}'.format(product_ad_id) return self._operation(interface) def get_product_ad_ex(self, product_ad_id): - interface = 'productAds/extended/{}'.format(product_ad_id) + interface = 'sp/productAds/extended/{}'.format(product_ad_id) return self._operation(interface) def create_product_ads(self, data): @@ -620,17 +635,21 @@ def archive_product_ads(self): pass def list_product_ads(self, data=None): - interface = 'productAds' + interface = 'sp/productAds' return self._operation(interface, data) def list_product_ads_ex(self, data=None): - interface = 'productAds/extended' + interface = 'sp/productAds/extended' return self._operation(interface, data) def request_snapshot(self, record_type=None, snapshot_id=None, data=None): """ + :POST: /snapshots + Required data: - * :campaignType: The type of campaign for which snapshot should be generated. Must be sponsoredProducts. + * :campaignType: The type of campaign for which snapshot should be + generated. Must be one of 'sponsoredProducts' or 'headlineSearch' + Defaults to 'sponsoredProducts. """ if not data: data = {'campaignType': 'sponsoredProducts'} @@ -648,18 +667,16 @@ def request_snapshot(self, record_type=None, snapshot_id=None, data=None): 'code': 0, 'response': 'record_type and snapshot_id are both empty.'} - def request_report(self, record_type=None, report_id=None, data=None): + def request_report(self, campaign_type='sp', record_type=None, report_id=None, data=None): """ - Required data: - * :campaignType: The type of campaign for which report should be generated. Must be sponsoredProducts. - """ - if not data: - data = {'campaignType': 'sponsoredProducts'} - elif not data.get('campaignType'): - data['campaignType'] = 'sponsoredProducts' + :POST: /{campaignType}/reports + :param campaign_type: The campaignType to request the report for ('sp' or 'hsa') + Defaults to 'sp' + :type data: string + """ if record_type is not None: - interface = '{}/report'.format(record_type) + interface = '{}/{}/report'.format(campaign_type, record_type) return self._operation(interface, data, method='POST') elif report_id is not None: interface = 'reports/{}'.format(report_id) @@ -764,6 +781,7 @@ def _operation(self, interface, params=None, method='GET'): 'response': 'access_token is empty.'} headers = {'Authorization': 'Bearer {}'.format(self._access_token), + 'Amazon-Advertising-API-ClientId': self.client_id, 'Content-Type': 'application/json', 'User-Agent': self.user_agent} diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index e1dccf0..f4c32fb 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,3 +1,3 @@ versions = { - 'api_version': 'v1', + 'api_version': 'v2', 'application_version': '1.0'} From ecda101895d41da3fc1e32426a583ecd18757830 Mon Sep 17 00:00:00 2001 From: David Antaramian Date: Thu, 8 Nov 2018 14:57:06 -0500 Subject: [PATCH 03/20] Fix ordering of parameters This commit makes sure that the new `campaign_type` parameters are added to the end of the parameters list. This accounts for users that were using positional arguments and not keyword arguments. --- amazon_advertising_api/advertising_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 644174d..cbd4988 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -267,7 +267,7 @@ def archive_campaign(self, campaign_id): interface = 'campaigns/{}'.format(campaign_id) return self._operation(interface, method='DELETE') - def list_campaigns(self, campaign_type='sp', data=None): + def list_campaigns(self, data=None, campaign_type='sp'): """ Retrieves a list of campaigns satisfying optional criteria. @@ -304,7 +304,7 @@ def list_campaigns(self, campaign_type='sp', data=None): interface = '{}/campaigns' .format(campaign_type) return self._operation(interface, data) - def list_campaigns_ex(self, campaign_type='sp', data=None): + def list_campaigns_ex(self, data=None, campaign_type='sp'): """ Retrieves a list of campaigns with extended fields satisfying optional filtering criteria. @@ -667,7 +667,7 @@ def request_snapshot(self, record_type=None, snapshot_id=None, data=None): 'code': 0, 'response': 'record_type and snapshot_id are both empty.'} - def request_report(self, campaign_type='sp', record_type=None, report_id=None, data=None): + def request_report(self, record_type=None, report_id=None, data=None, campaign_type='sp'): """ :POST: /{campaignType}/reports From fff06f44550bae0c512a8ca19c5ddceebfc73475 Mon Sep 17 00:00:00 2001 From: Jason Fertel Date: Mon, 7 Jan 2019 17:56:16 -0500 Subject: [PATCH 04/20] add support for sp/targets endpoints for sponsored products --- .gitignore | 2 + README.md | 4 +- amazon_advertising_api/advertising_api.py | 165 ++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 772b156..ab7fb31 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ localtest.py # Pycharm *.idea/ + +venv/* diff --git a/README.md b/README.md index 159b813..3318beb 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -Unofficial Amazon Sponsored Products Python client library. +# Amazon Advertising API Client + +This library provides support for Amazon's Advertising API. It is a fork of [Amazon Advertising API Python](https://github.com/dbrent-amazon/amazon-advertising-api-python). diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index cbd4988..d3cc1ac 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -487,6 +487,171 @@ def list_ad_groups_ex(self, data=None): interface = 'sp/adGroups/extended' return self._operation(interface, data) + def get_target(self, target_id): + """ + Retrieves an ad group by Id. Note that this call returns the minimal + set of ad group fields, but is more efficient than getAdGroupEx. + + :GET: /sp/targets/{targetId} + :param target_id: The Id of the requested ad group. + :type target_id: string + + :returns: + :200: Success, Target response + :401: Unauthorized + :404: Ad group not found + """ + interface = 'sp/targets/{}'.format(ad_group_id) + return self._operation(interface) + + def get_target_ex(self, target_id): + """ + Retrieves a target and its extended fields by ID. Note that this + call returns the complete set of target fields (including serving + status and other read-only fields), but is less efficient than + getTarget. + + :GET: /sp/targets/extended/{adGroupId} + :param target_id: The Id of the requested target. + :type target_id: string + + :returns: + :200: Success, Target response + :401: Unauthorized + :404: Target not found + """ + interface = 'sp/targets/extended/{}'.format(ad_group_id) + return self._operation(interface) + + def create_targets(self, data): + """ + Creates one or more ad groups. Successfully created ad groups will + be assigned unique adGroupIds. + + :POST: /targets + :param data: A list of up to 100 targets to be created. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of AdGroupResponse reflecting the same + order as the input + :401: Unauthorized + """ + interface = 'targets' + return self._operation(interface, data, method='POST') + + def update_ad_groups(self, data): + """ + Updates one or more targets. Targets are identified using their + targetId. + + :PUT: /targets + :param data: A list of up to 100 updates containing targetIds and the + mutable fields to be modified. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of Targets reflecting the same + order as the input + :401: Unauthorized + """ + interface = 'targets' + return self._operation(interface, data, method='PUT') + + def archive_target(self, ad_group_id): + """ + Sets the ad group status to archived. This same operation can be + performed via an update, but is included for completeness. + + :DELETE: /targets/{targetId} + :param target_id: The Id of the ad group to be archived. + :type target_id: string + + :returns: + :200: Success. TargetResponse + :401: Unauthorized + :404: Ad group not found + """ + interface = 'targets/{}'.format(target_id) + return self._operation(interface, method='DELETE') + + def list_targets(self, data=None): + """ + Retrieves a list of targets satisfying optional criteria. + + :GET: /sp/targets + :param data: Parameter list of criteria. + + data may contain the following optional parameters: + + :param startIndex: 0-indexed record offset for the result + set. Defaults to 0. + :type startIndex: integer + :param count: Number of records to include in the paged response. + Defaults to max page size. + :type count: integer + :param expressionTypeFilter: Restricts results to targets + with expression types within the specified comma-separated list. + Possible filter types are: auto and manual + :type expressionTypeFilter: string + :param expressionTextFilter: Content of the targeting expression + :type expressionTextFilter: string + :param campaignIdFilter: Restricts results to ad groups within + campaigns specified in comma-separated list. + :type campaignIdFilter: string + :param adGroupIdFilter: Restricts results to ad groups specified in + comma-separated list. + :type adGroupIdFilter: string + :param stateFilter: Restricts results to targets with state within the + specified comma-separatedlist. Must be one of enabled, paused, + archived. Default behavior is to include all. + :type stateFilter: string + :returns: + :200: Success. List of Targets. + :401: Unauthorized. + """ + interface = 'sp/targets' + return self._operation(interface, data) + + def list_targets_ex(self, data=None): + """ + Retrieves a list of targets satisfying optional criteria. + + :GET: /sp/targets/extended + :param data: Parameter list of criteria. + + data may contain the following optional parameters: + + :param startIndex: 0-indexed record offset for the result + set. Defaults to 0. + :type startIndex: integer + :param count: Number of records to include in the paged response. + Defaults to max page size. + :type count: integer + :param expressionTypeFilter: Restricts results to targets + with expression types within the specified comma-separated list. + Possible filter types are: auto and manual + :type expressionTypeFilter: string + :param expressionTextFilter: Content of the targeting expression + :type expressionTextFilter: string + :param campaignIdFilter: Restricts results to ad groups within + campaigns specified in comma-separated list. + :type campaignIdFilter: string + :param adGroupIdFilter: Restricts results to ad groups specified in + comma-separated list. + :type adGroupIdFilter: string + :param stateFilter: Restricts results to targets with state within the + specified comma-separatedlist. Must be one of enabled, paused, + archived. Default behavior is to include all. + :type stateFilter: string + :returns: + :200: Success. List of Targets. + :401: Unauthorized. + """ + interface = 'sp/targets/extended' + return self._operation(interface, data) + + def get_biddable_keyword(self, keyword_id, campaign_type='sp'): """ Retrieves a keyword by ID. Note that this call returns the minimal set From 2fe62ceca8804ba661a696cd9ed33ef5d436150d Mon Sep 17 00:00:00 2001 From: Jason Fertel Date: Wed, 9 Jan 2019 10:12:48 -0500 Subject: [PATCH 05/20] add negative targets endpoints --- amazon_advertising_api/advertising_api.py | 165 +++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index d3cc1ac..dffa932 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -540,7 +540,7 @@ def create_targets(self, data): interface = 'targets' return self._operation(interface, data, method='POST') - def update_ad_groups(self, data): + def update_targets(self, data): """ Updates one or more targets. Targets are identified using their targetId. @@ -651,6 +651,169 @@ def list_targets_ex(self, data=None): interface = 'sp/targets/extended' return self._operation(interface, data) + def get_negative_target(self, target_id): + """ + Retrieves an ad group by Id. Note that this call returns the minimal + set of ad group fields, but is more efficient than getAdGroupEx. + + :GET: /sp/negativeTargets/{targetId} + :param target_id: The Id of the requested ad group. + :type target_id: string + + :returns: + :200: Success, Target response + :401: Unauthorized + :404: Ad group not found + """ + interface = 'sp/negativeTargets/{}'.format(ad_group_id) + return self._operation(interface) + + def get_negative_target_ex(self, target_id): + """ + Retrieves a target and its extended fields by ID. Note that this + call returns the complete set of target fields (including serving + status and other read-only fields), but is less efficient than + getTarget. + + :GET: /sp/negativeTargets/extended/{adGroupId} + :param target_id: The Id of the requested target. + :type target_id: string + + :returns: + :200: Success, Target response + :401: Unauthorized + :404: Target not found + """ + interface = 'sp/negativeTargets/extended/{}'.format(ad_group_id) + return self._operation(interface) + + def create_negative_targets(self, data): + """ + Creates one or more ad groups. Successfully created ad groups will + be assigned unique adGroupIds. + + :POST: /negativeTargets + :param data: A list of up to 100 negativeTargets to be created. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of AdGroupResponse reflecting the same + order as the input + :401: Unauthorized + """ + interface = 'negativeTargets' + return self._operation(interface, data, method='POST') + + def update_negative_targets(self, data): + """ + Updates one or more negativeTargets. negativeTargets are identified using their + targetId. + + :PUT: /negativeTargets + :param data: A list of up to 100 updates containing targetIds and the + mutable fields to be modified. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of negativeTargets reflecting the same + order as the input + :401: Unauthorized + """ + interface = 'negativeTargets' + return self._operation(interface, data, method='PUT') + + def archive_negative_target(self, ad_group_id): + """ + Sets the ad group status to archived. This same operation can be + performed via an update, but is included for completeness. + + :DELETE: /negativeTargets/{targetId} + :param target_id: The Id of the ad group to be archived. + :type target_id: string + + :returns: + :200: Success. TargetResponse + :401: Unauthorized + :404: Ad group not found + """ + interface = 'negativeTargets/{}'.format(target_id) + return self._operation(interface, method='DELETE') + + def list_negative_targets(self, data=None): + """ + Retrieves a list of negativeTargets satisfying optional criteria. + + :GET: /sp/negativeTargets + :param data: Parameter list of criteria. + + data may contain the following optional parameters: + + :param startIndex: 0-indexed record offset for the result + set. Defaults to 0. + :type startIndex: integer + :param count: Number of records to include in the paged response. + Defaults to max page size. + :type count: integer + :param expressionTypeFilter: Restricts results to negativeTargets + with expression types within the specified comma-separated list. + Possible filter types are: auto and manual + :type expressionTypeFilter: string + :param expressionTextFilter: Content of the targeting expression + :type expressionTextFilter: string + :param campaignIdFilter: Restricts results to ad groups within + campaigns specified in comma-separated list. + :type campaignIdFilter: string + :param adGroupIdFilter: Restricts results to ad groups specified in + comma-separated list. + :type adGroupIdFilter: string + :param stateFilter: Restricts results to negativeTargets with state within the + specified comma-separatedlist. Must be one of enabled, paused, + archived. Default behavior is to include all. + :type stateFilter: string + :returns: + :200: Success. List of negativeTargets. + :401: Unauthorized. + """ + interface = 'sp/negativeTargets' + return self._operation(interface, data) + + def list_negative_targets_ex(self, data=None): + """ + Retrieves a list of negativeTargets satisfying optional criteria. + + :GET: /sp/negativeTargets/extended + :param data: Parameter list of criteria. + + data may contain the following optional parameters: + + :param startIndex: 0-indexed record offset for the result + set. Defaults to 0. + :type startIndex: integer + :param count: Number of records to include in the paged response. + Defaults to max page size. + :type count: integer + :param expressionTypeFilter: Restricts results to negativeTargets + with expression types within the specified comma-separated list. + Possible filter types are: auto and manual + :type expressionTypeFilter: string + :param expressionTextFilter: Content of the targeting expression + :type expressionTextFilter: string + :param campaignIdFilter: Restricts results to ad groups within + campaigns specified in comma-separated list. + :type campaignIdFilter: string + :param adGroupIdFilter: Restricts results to ad groups specified in + comma-separated list. + :type adGroupIdFilter: string + :param stateFilter: Restricts results to negativeTargets with state within the + specified comma-separatedlist. Must be one of enabled, paused, + archived. Default behavior is to include all. + :type stateFilter: string + :returns: + :200: Success. List of negativeTargets. + :401: Unauthorized. + """ + interface = 'sp/negativeTargets/extended' + return self._operation(interface, data) def get_biddable_keyword(self, keyword_id, campaign_type='sp'): """ From 81960c035875fded4c144041f316116a00affa1d Mon Sep 17 00:00:00 2001 From: Jason Fertel Date: Thu, 19 Sep 2019 17:08:18 -0400 Subject: [PATCH 06/20] Update advertising_api.py add campaign_type support to missing entities --- amazon_advertising_api/advertising_api.py | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index dffa932..4da9fdf 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -320,7 +320,7 @@ def list_campaigns_ex(self, data=None, campaign_type='sp'): interface = '{}/campaigns/extended' .format(campaign_type) return self._operation(interface, data) - def get_ad_group(self, ad_group_id): + def get_ad_group(self, ad_group_id, campaign_type='sp'): """ Retrieves an ad group by Id. Note that this call returns the minimal set of ad group fields, but is more efficient than getAdGroupEx. @@ -334,10 +334,10 @@ def get_ad_group(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'sp/adGroups/{}'.format(ad_group_id) + interface = '{}/adGroups/{}'.format(campaign_type, ad_group_id) return self._operation(interface) - def get_ad_group_ex(self, ad_group_id): + def get_ad_group_ex(self, ad_group_id, campaign_type='sp'): """ Retrieves an ad group and its extended fields by ID. Note that this call returns the complete set of ad group fields (including serving @@ -353,10 +353,10 @@ def get_ad_group_ex(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'sp/adGroups/extended/{}'.format(ad_group_id) + interface = '{}/adGroups/extended/{}'.format(campaign_type, ad_group_id) return self._operation(interface) - def create_ad_groups(self, data): + def create_ad_groups(self, data, campaign_type='sp'): """ Creates one or more ad groups. Successfully created ad groups will be assigned unique adGroupIds. @@ -372,10 +372,10 @@ def create_ad_groups(self, data): order as the input :401: Unauthorized """ - interface = 'adGroups' + interface = '{}/adGroups'.format(campaign_type) return self._operation(interface, data, method='POST') - def update_ad_groups(self, data): + def update_ad_groups(self, data, campaign_type='sp'): """ Updates one or more ad groups. Ad groups are identified using their adGroupIds. @@ -390,10 +390,10 @@ def update_ad_groups(self, data): order as the input :401: Unauthorized """ - interface = 'adGroups' + interface = '{}/adGroups'.format(campaign_type) return self._operation(interface, data, method='PUT') - def archive_ad_group(self, ad_group_id): + def archive_ad_group(self, ad_group_id, campaign_type="sp"): """ Sets the ad group status to archived. This same operation can be performed via an update, but is included for completeness. @@ -407,10 +407,10 @@ def archive_ad_group(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'adGroups/{}'.format(ad_group_id) + interface = '{}/adGroups/{}'.format(campaign_type, ad_group_id) return self._operation(interface, method='DELETE') - def list_ad_groups(self, data=None): + def list_ad_groups(self, data=None, campaign_type="sp"): """ Retrieves a list of ad groups satisfying optional criteria. @@ -446,10 +446,10 @@ def list_ad_groups(self, data=None): :401: Unauthorized. """ - interface = 'sp/adGroups' + interface = '{}/adGroups'.format(campaign_type) return self._operation(interface, data) - def list_ad_groups_ex(self, data=None): + def list_ad_groups_ex(self, data=None, campaign_type="sp"): """ Retrieves a list of ad groups satisfying optional criteria. @@ -484,7 +484,7 @@ def list_ad_groups_ex(self, data=None): :200: Success. List of adGroup. :401: Unauthorized. """ - interface = 'sp/adGroups/extended' + interface = '{}/adGroups/extended'.format(campaign_type) return self._operation(interface, data) def get_target(self, target_id): @@ -537,7 +537,7 @@ def create_targets(self, data): order as the input :401: Unauthorized """ - interface = 'targets' + interface = 'sp/targets' return self._operation(interface, data, method='POST') def update_targets(self, data): @@ -555,7 +555,7 @@ def update_targets(self, data): order as the input :401: Unauthorized """ - interface = 'targets' + interface = 'sp/targets' return self._operation(interface, data, method='PUT') def archive_target(self, ad_group_id): @@ -701,7 +701,7 @@ def create_negative_targets(self, data): order as the input :401: Unauthorized """ - interface = 'negativeTargets' + interface = 'sp/negativeTargets' return self._operation(interface, data, method='POST') def update_negative_targets(self, data): @@ -854,7 +854,7 @@ def get_biddable_keyword_ex(self, keyword_id): interface = 'sp/keywords/extended/{}'.format(keyword_id) return self._operation(interface) - def create_biddable_keywords(self, data): + def create_biddable_keywords(self, data, campaign_type='sp'): """ Creates one or more keywords. Successfully created keywords will be assigned unique keywordIds. @@ -865,7 +865,7 @@ def create_biddable_keywords(self, data): matchType and state. :type data: List of **Keyword** """ - interface = 'keywords' + interface = '{}/keywords'.format(campaign_type) return self._operation(interface, data, method='POST') def update_biddable_keywords(self, data): @@ -892,8 +892,8 @@ def get_negative_keyword_ex(self, negative_keyword_id): interface = 'sp/negativeKeywords/extended/{}'.format(negative_keyword_id) return self._operation(interface) - def create_negative_keywords(self, data): - interface = 'negativeKeywords' + def create_negative_keywords(self, data, campaign_type="sp"): + interface = '{}/negativeKeywords'.format(campaign_type) return self._operation(interface, data, method='POST') def update_negative_keywords(self, data): @@ -962,12 +962,12 @@ def update_product_ads(self, data): def archive_product_ads(self): pass - def list_product_ads(self, data=None): - interface = 'sp/productAds' + def list_product_ads(self, data=None, campaign_type="sp"): + interface = '{}/productAds'.format(campaign_type) return self._operation(interface, data) - def list_product_ads_ex(self, data=None): - interface = 'sp/productAds/extended' + def list_product_ads_ex(self, data=None, campaign_type="sp"): + interface = '{}/productAds/extended'.format(campaign_type) return self._operation(interface, data) def request_snapshot(self, record_type=None, snapshot_id=None, data=None): From bc76ab769822fe3d132d05f5d421d7531d2c717e Mon Sep 17 00:00:00 2001 From: Kuz Le Date: Thu, 15 Oct 2020 13:18:38 +0300 Subject: [PATCH 07/20] (AUTO-1642) Add sb v3 api (#3) * Fix wrong params naming * Add api v3 for sb * Update snapshot endpoints * Fix docs * Delete v3 if's * Now requests return api_version * Request response format output * Refactor * Return v3 in json * Fix --- amazon_advertising_api/advertising_api.py | 178 +++++++++++++++------- amazon_advertising_api/versions.py | 1 + 2 files changed, 122 insertions(+), 57 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 4da9fdf..3ca39bf 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -182,7 +182,7 @@ def get_campaign(self, campaign_id, campaign_type='sp'): :GET: {campaignType}/campaigns/{campaignId} :param campaign_id: The Id of the requested campaign. :type campaign_id: string - :param campaign_type: The campaignType of the requested campaign ('sp' or 'hsa') + :param campaign_type: The campaignType of the requested campaign ('sp' or 'sb') Defaults to 'sp' :type campaign_type: string :returns: @@ -191,6 +191,7 @@ def get_campaign(self, campaign_id, campaign_type='sp'): :404: Campaign not found """ interface = '{}/campaigns/{}'. format(campaign_type, campaign_id) + return self._operation(interface) def get_campaign_ex(self, campaign_id, campaign_type='sp'): @@ -203,7 +204,7 @@ def get_campaign_ex(self, campaign_id, campaign_type='sp'): :GET: {campaignType}/campaigns/extended/{campaignId} :param campaign_id: The Id of the requested campaign. :type campaign_id: string - :param campaign_type: The campaignType of the requested campaign ('sp' or 'hsa') + :param campaign_type: The campaignType of the requested campaign ('sp' or 'sb') Defaults to 'sp' :type campaign_type: string :returns: @@ -213,9 +214,10 @@ def get_campaign_ex(self, campaign_id, campaign_type='sp'): """ interface = '{}/campaigns/extended/{}'. format(campaign_type, campaign_id) + return self._operation(interface) - def create_campaigns(self, data): + def create_campaigns(self, data, campaign_type='sp'): """ Creates one or more campaigns. Successfully created campaigns will be assigned unique **campaignIds**. @@ -230,10 +232,10 @@ def create_campaigns(self, data): input. :401: Unauthorized """ - interface = 'campaigns' + interface = '{}/campaigns'.format(campaign_type) return self._operation(interface, data, method='POST') - def update_campaigns(self, data): + def update_campaigns(self, data, campaign_type='sp'): """ Updates one or more campaigns. Campaigns are identified using their **campaignIds**. @@ -248,10 +250,22 @@ def update_campaigns(self, data): input :401: Unauthorized """ - interface = 'campaigns' + interface = '{}/campaigns'.format(campaign_type) return self._operation(interface, data, method='PUT') - def archive_campaign(self, campaign_id): + def get_campaigns(self, campaign_type='sp'): + """ + Gets campaigns + + :GET: /campaigns + :returns: + :207: List of **CampaignResponse** + :401: Unauthorized + """ + interface = '{}/campaigns'.format(campaign_type) + return self._operation(interface) + + def archive_campaign(self, campaign_id, campaign_type='sp'): """ Sets the campaign status to archived. This same operation can be performed via an update, but is included for completeness. @@ -264,7 +278,7 @@ def archive_campaign(self, campaign_id): :401: Unauthorized :404: Campaign not found """ - interface = 'campaigns/{}'.format(campaign_id) + interface = '{}/campaigns/{}'.format(campaign_type, campaign_id) return self._operation(interface, method='DELETE') def list_campaigns(self, data=None, campaign_type='sp'): @@ -302,6 +316,7 @@ def list_campaigns(self, data=None, campaign_type='sp'): :401: Unauthorized """ interface = '{}/campaigns' .format(campaign_type) + return self._operation(interface, data) def list_campaigns_ex(self, data=None, campaign_type='sp'): @@ -335,6 +350,7 @@ def get_ad_group(self, ad_group_id, campaign_type='sp'): :404: Ad group not found """ interface = '{}/adGroups/{}'.format(campaign_type, ad_group_id) + return self._operation(interface) def get_ad_group_ex(self, ad_group_id, campaign_type='sp'): @@ -447,6 +463,7 @@ def list_ad_groups(self, data=None, campaign_type="sp"): """ interface = '{}/adGroups'.format(campaign_type) + return self._operation(interface, data) def list_ad_groups_ex(self, data=None, campaign_type="sp"): @@ -487,7 +504,7 @@ def list_ad_groups_ex(self, data=None, campaign_type="sp"): interface = '{}/adGroups/extended'.format(campaign_type) return self._operation(interface, data) - def get_target(self, target_id): + def get_target(self, target_id, campaign_type='sp'): """ Retrieves an ad group by Id. Note that this call returns the minimal set of ad group fields, but is more efficient than getAdGroupEx. @@ -501,7 +518,8 @@ def get_target(self, target_id): :401: Unauthorized :404: Ad group not found """ - interface = 'sp/targets/{}'.format(ad_group_id) + interface = '{}/targets/{}'.format(campaign_type, target_id) + return self._operation(interface) def get_target_ex(self, target_id): @@ -520,10 +538,10 @@ def get_target_ex(self, target_id): :401: Unauthorized :404: Target not found """ - interface = 'sp/targets/extended/{}'.format(ad_group_id) + interface = 'sp/targets/extended/{}'.format(target_id) return self._operation(interface) - def create_targets(self, data): + def create_targets(self, data, campaign_type='sp'): """ Creates one or more ad groups. Successfully created ad groups will be assigned unique adGroupIds. @@ -537,10 +555,29 @@ def create_targets(self, data): order as the input :401: Unauthorized """ - interface = 'sp/targets' + interface = '{}/targets'.format(campaign_type) + + return self._operation(interface, data, method='POST') + + def create_targets_list(self, data, campaign_type='sp'): + """ + Creates many targets + + :POST: /targets + :param data: A list of up to 100 targets to be created. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of AdGroupResponse reflecting the same + order as the input + :401: Unauthorized + """ + interface = '{}/targets/list'.format(campaign_type) + + # not tested return self._operation(interface, data, method='POST') - def update_targets(self, data): + def update_targets(self, data, campaign_type='sp'): """ Updates one or more targets. Targets are identified using their targetId. @@ -555,10 +592,11 @@ def update_targets(self, data): order as the input :401: Unauthorized """ - interface = 'sp/targets' + interface = '{}/targets'.format(campaign_type) + return self._operation(interface, data, method='PUT') - def archive_target(self, ad_group_id): + def archive_target(self, ad_group_id, campaign_type='sp'): """ Sets the ad group status to archived. This same operation can be performed via an update, but is included for completeness. @@ -572,7 +610,7 @@ def archive_target(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'targets/{}'.format(target_id) + interface = '{}/targets/{}'.format(campaign_type, ad_group_id) return self._operation(interface, method='DELETE') def list_targets(self, data=None): @@ -651,7 +689,7 @@ def list_targets_ex(self, data=None): interface = 'sp/targets/extended' return self._operation(interface, data) - def get_negative_target(self, target_id): + def get_negative_target(self, target_id, campaign_type='sb'): """ Retrieves an ad group by Id. Note that this call returns the minimal set of ad group fields, but is more efficient than getAdGroupEx. @@ -665,7 +703,8 @@ def get_negative_target(self, target_id): :401: Unauthorized :404: Ad group not found """ - interface = 'sp/negativeTargets/{}'.format(ad_group_id) + interface = '{}/negativeTargets/{}'.format(campaign_type, target_id) + return self._operation(interface) def get_negative_target_ex(self, target_id): @@ -684,10 +723,10 @@ def get_negative_target_ex(self, target_id): :401: Unauthorized :404: Target not found """ - interface = 'sp/negativeTargets/extended/{}'.format(ad_group_id) + interface = 'sp/negativeTargets/extended/{}'.format(target_id) return self._operation(interface) - def create_negative_targets(self, data): + def create_negative_targets(self, data, campaign_type='sp'): """ Creates one or more ad groups. Successfully created ad groups will be assigned unique adGroupIds. @@ -701,10 +740,28 @@ def create_negative_targets(self, data): order as the input :401: Unauthorized """ - interface = 'sp/negativeTargets' + interface = '{}/negativeTargets'.format(campaign_type) + + return self._operation(interface, data, method='POST') + + def create_negative_targets_list(self, data, campaign_type='sb'): + """ + Creates list of targets + + :POST: /negativeTargets/list + :param data: A list of negativeTargets to be created. + :type data: List of **Target** + + :returns: + :207: Multi-status. List of AdGroupResponse reflecting the same + order as the input + :401: Unauthorized + """ + interface = '{}/negativeTargets/list'.format(campaign_type) + return self._operation(interface, data, method='POST') - def update_negative_targets(self, data): + def update_negative_targets(self, data, campaign_type='sp'): """ Updates one or more negativeTargets. negativeTargets are identified using their targetId. @@ -719,10 +776,10 @@ def update_negative_targets(self, data): order as the input :401: Unauthorized """ - interface = 'negativeTargets' + interface = '{}/negativeTargets'.format(campaign_type) return self._operation(interface, data, method='PUT') - def archive_negative_target(self, ad_group_id): + def archive_negative_target(self, ad_group_id, campaign_type='sp'): """ Sets the ad group status to archived. This same operation can be performed via an update, but is included for completeness. @@ -736,7 +793,7 @@ def archive_negative_target(self, ad_group_id): :401: Unauthorized :404: Ad group not found """ - interface = 'negativeTargets/{}'.format(target_id) + interface = '{}/negativeTargets/{}'.format(campaign_type, ad_group_id) return self._operation(interface, method='DELETE') def list_negative_targets(self, data=None): @@ -833,6 +890,7 @@ def get_biddable_keyword(self, keyword_id, campaign_type='sp'): :404: Keyword not found. """ interface = '{}/keywords/{}'.format(campaign_type, keyword_id) + return self._operation(interface) def get_biddable_keyword_ex(self, keyword_id): @@ -866,26 +924,27 @@ def create_biddable_keywords(self, data, campaign_type='sp'): :type data: List of **Keyword** """ interface = '{}/keywords'.format(campaign_type) - return self._operation(interface, data, method='POST') - def update_biddable_keywords(self, data): - interface = 'keywords' + return self._operation(interface, data) + + def update_biddable_keywords(self, data, campaign_type='sp'): + interface = '{}/keywords'.format(campaign_type) return self._operation(interface, data, method='PUT') - def archive_biddable_keyword(self, keyword_id): - interface = 'keywords/{}'.format(keyword_id) + def archive_biddable_keyword(self, keyword_id, campaign_type='sp'): + interface = '{}/keywords/{}'.format(campaign_type, keyword_id) return self._operation(interface, method='DELETE') - def list_biddable_keywords(self, data=None): - interface = 'sp/keywords' + def list_biddable_keywords(self, data=None, campaign_type='sp'): + interface = '{}/keywords'.format(campaign_type) return self._operation(interface, data) def list_biddable_keywords_ex(self, data=None): interface = 'sp/keywords/extended' return self._operation(interface, data) - def get_negative_keyword(self, negative_keyword_id): - interface = 'sp/negativeKeywords/{}'.format(negative_keyword_id) + def get_negative_keyword(self, negative_keyword_id, campaign_type='sp'): + interface = '{}/negativeKeywords/{}'.format(campaign_type, negative_keyword_id) return self._operation(interface) def get_negative_keyword_ex(self, negative_keyword_id): @@ -894,18 +953,19 @@ def get_negative_keyword_ex(self, negative_keyword_id): def create_negative_keywords(self, data, campaign_type="sp"): interface = '{}/negativeKeywords'.format(campaign_type) + return self._operation(interface, data, method='POST') - def update_negative_keywords(self, data): - interface = 'negativeKeywords' + def update_negative_keywords(self, data, campaign_type='sp'): + interface = '{}/negativeKeywords'.format(campaign_type) return self._operation(interface, data, method='PUT') - def archive_negative_keyword(self, negative_keyword_id): - interface = 'negativeKeywords/{}'.format(negative_keyword_id) + def archive_negative_keyword(self, negative_keyword_id, campaign_type='sp'): + interface = '{}/negativeKeywords/{}'.format(campaign_type, negative_keyword_id) return self._operation(interface, method='DELETE') - def list_negative_keywords(self, data=None): - interface = 'sp/negativeKeywords' + def list_negative_keywords(self, data=None, campaign_type='sp'): + interface = '{}/negativeKeywords'.format(campaign_type) return self._operation(interface, data) def list_negative_keywords_ex(self, data=None): @@ -970,7 +1030,12 @@ def list_product_ads_ex(self, data=None, campaign_type="sp"): interface = '{}/productAds/extended'.format(campaign_type) return self._operation(interface, data) - def request_snapshot(self, record_type=None, snapshot_id=None, data=None): + def create_keyword_recommendations(self, data, campaign_type='sp'): + interface = '{}/recommendations/keyword'.format(campaign_type) + + return self._operation(interface, data, method='POST') + + def request_snapshot(self, record_type=None, snapshot_id=None, data=None, campaign_type='sp'): """ :POST: /snapshots @@ -978,6 +1043,7 @@ def request_snapshot(self, record_type=None, snapshot_id=None, data=None): * :campaignType: The type of campaign for which snapshot should be generated. Must be one of 'sponsoredProducts' or 'headlineSearch' Defaults to 'sponsoredProducts. + :campaign_type: Should be 'hsa' or 'sp' """ if not data: data = {'campaignType': 'sponsoredProducts'} @@ -985,10 +1051,10 @@ def request_snapshot(self, record_type=None, snapshot_id=None, data=None): data['campaignType'] = 'sponsoredProducts' if record_type is not None: - interface = '{}/snapshot'.format(record_type) + interface = '{}/{}/snapshot'.format(campaign_type, record_type) return self._operation(interface, data, method='POST') elif snapshot_id is not None: - interface = 'snapshots/{}'.format(snapshot_id) + interface = '{}/snapshots/{}'.format(campaign_type, snapshot_id) return self._operation(interface, data) else: return {'success': False, @@ -1103,6 +1169,8 @@ def _operation(self, interface, params=None, method='GET'): :param method: Call method. Should be either 'GET', 'PUT', or 'POST' :type method: string """ + api_v3 = interface.startswith('sb') + if self._access_token is None: return {'success': False, 'code': 0, @@ -1123,26 +1191,19 @@ def _operation(self, interface, params=None, method='GET'): data = None + url = f"https://{self.endpoint}/" + (f"{self.api_version}/" if api_v3 else "") + f"{interface}" + if method == 'GET': if params is not None: p = '?{}'.format(urllib.parse.urlencode(params)) else: p = '' - url = 'https://{host}/{api_version}/{interface}{params}'.format( - host=self.endpoint, - api_version=self.api_version, - interface=interface, - params=p) + url += '{params}'.format(params=p) else: if params is not None: data = json.dumps(params).encode('utf-8') - url = 'https://{host}/{api_version}/{interface}'.format( - host=self.endpoint, - api_version=self.api_version, - interface=interface) - if PYTHON == 3: req = urllib.request.Request(url=url, headers=headers, data=data) else: @@ -1151,9 +1212,12 @@ def _operation(self, interface, params=None, method='GET'): try: f = urllib.request.urlopen(req) - return {'success': True, - 'code': f.code, - 'response': f.read().decode('utf-8')} + return { + 'success': True, + 'api_version': self.api_version if not api_v3 else versions['api_version_sb'], + 'code': f.code, + 'data': f.read().decode('utf-8')} + except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index f4c32fb..89cb392 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,3 +1,4 @@ versions = { 'api_version': 'v2', + 'api_version_sb': 'v3', 'application_version': '1.0'} From c00d982e0a67089da37a0053c5c4a4d04e88f725 Mon Sep 17 00:00:00 2001 From: Daria Koryukova <33223711+dkoryukova@users.noreply.github.com> Date: Thu, 15 Oct 2020 19:42:37 +0300 Subject: [PATCH 08/20] add argo ci workflows. (#4) * add argo ci workflows --- .argo-ci/master.yaml | 255 +++++++++++++++++++++++++++++ .argo-ci/pr.yaml | 254 ++++++++++++++++++++++++++++ .argo-ci/release.yaml | 154 +++++++++++++++++ .bumpversion.cfg | 18 ++ amazon_advertising_api/versions.py | 3 +- setup.py | 3 +- 6 files changed, 683 insertions(+), 4 deletions(-) create mode 100644 .argo-ci/master.yaml create mode 100644 .argo-ci/pr.yaml create mode 100644 .argo-ci/release.yaml create mode 100644 .bumpversion.cfg diff --git a/.argo-ci/master.yaml b/.argo-ci/master.yaml new file mode 100644 index 0000000..4d36d7a --- /dev/null +++ b/.argo-ci/master.yaml @@ -0,0 +1,255 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: amazon-advertising-api-python-master- +spec: + # entrypoint is the name of the template used as the starting point of the workflow + entrypoint: run-check + onExit: exit-handler + arguments: + parameters: + - name: git_url + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: repo_name + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: commit_message + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: sha + value: HAS_TO_BE_PASSED_FROM_SENSOR + # a temporary volume, named workdir, will be used as a working directory + # for this workflow. This volume is passed around from step to step. + volumeClaimTemplates: + - metadata: + name: workdir + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi + volumes: + - name: sshkey + secret: + secretName: argo-ci-git-ssh-key + items: + - key: id_rsa + mode: 0400 + path: id_rsa + - name: known-hosts + secret: + secretName: known-github + - name: pypirc + secret: + secretName: nexus-pypi + items: + - key: .pypirc + path: .pypirc + + + + templates: + - name: run-check + steps: + - - name: check-commit-messsage + template: check-message-script + + - - name: run-build + template: run-build + when: "{{steps.check-commit-messsage.outputs.result}} != skip" + + + - name: run-build + steps: + - - name: set-pending + template: status-update + arguments: + parameters: + - name: status + value: pending + - name: message + value: Argo CI tests has been started + + - - name: get-package-name + template: generate-name + + - - name: checkout + template: git + arguments: + parameters: + - name: cmd + value: > + cd /workdir && + git clone {{workflow.parameters.git_url}} && + cd /workdir/{{workflow.parameters.repo_name}} && + git checkout master + + - - name: alpha-version-bump + template: python + arguments: + parameters: + - name: cmd + value: > + pip install bump2version && + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + bump2version patch + --verbose + --allow-dirty + --no-commit + + - - name: pack-and-push + template: python + arguments: + parameters: + - name: cmd + value: > + pip install twine && + python3 setup.py sdist bdist_wheel && + twine upload --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-dev dist/* + + - - name: version-bump + template: python + arguments: + parameters: + - name: cmd + value: > + pip install bump2version && + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + bump2version release + --serialize {major}.{minor}.{patch} + --verbose + --allow-dirty + + - - name: git-push + template: git + arguments: + parameters: + - name: cmd + value: > + cd /workdir/{{workflow.parameters.repo_name}} && + git push origin master + + + + - name: exit-handler + steps: + - - name: success + template: status-update + arguments: + parameters: + - name: status + value: success + - name: message + value: Version bumped and package deployed. + when: "{{workflow.status}} == Succeeded" + - name: failure + template: status-update + arguments: + parameters: + - name: status + value: failure + - name: message + value: Something went wrong, check logs in details. + when: "{{workflow.status}} != Succeeded" + + - name: git + inputs: + parameters: + - name: cmd + container: + image: "alpine/git" + command: ["sh", "-c"] + args: + - > + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: sshkey + mountPath: /root/.ssh + - mountPath: /etc/ssh + name: known-hosts + + - name: python + inputs: + parameters: + - name: cmd + container: + image: "python:3.7-buster" + command: ["sh", "-c"] + args: + - > + cd /workdir/{{workflow.parameters.repo_name}} && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: pypirc + mountPath: /etc/pypirc/ + env: + - name: NEXUS_PYPI_PASSWORD + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: password + - name: NEXUS_PYPI_USERNAME + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: username + + - name: status-update + inputs: + parameters: + - name: status + - name: message + container: + image: cloudposse/github-status-updater + env: + - name: GITHUB_ACTION + value: update_state + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: github-access-token + key: token + - name: GITHUB_OWNER + value: pepsico-ecommerce + - name: GITHUB_REPO + value: "{{workflow.parameters.repo_name}}" + - name: GITHUB_REF + value: "{{workflow.parameters.sha}}" + - name: GITHUB_CONTEXT + value: Dev package published + - name: GITHUB_STATE + value: "{{inputs.parameters.status}}" + - name: GITHUB_DESCRIPTION + value: "{{inputs.parameters.message}}" + - name: GITHUB_TARGET_URL + value: "https://argo-wf.pepstaging.com/workflows/argo-events/{{workflow.name}}" + + - name: check-message-script + script: + image: python:alpine3.7 + command: [python] + source: | + message = """ + {{workflow.parameters.commit_message}} + """ + if "Bump version:" in message: + print("skip") + else: + print("Building") + + - name: generate-name + script: + image: python:alpine3.7 + command: [python] + source: | + repo_name = """ + {{workflow.parameters.repo_name}} + """ + package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_') + print(package_name) diff --git a/.argo-ci/pr.yaml b/.argo-ci/pr.yaml new file mode 100644 index 0000000..60a510f --- /dev/null +++ b/.argo-ci/pr.yaml @@ -0,0 +1,254 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: amazon-advertising-api-python-pr- +spec: + # entrypoint is the name of the template used as the starting point of the workflow + entrypoint: run-tests + onExit: exit-handler + arguments: + parameters: + - name: git_url + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: source_branch + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: target_branch + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: repo_name + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: source_sha + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: pr_number + value: HAS_TO_BE_PASSED_FROM_SENSOR + # a temporary volume, named workdir, will be used as a working directory + # for this workflow. This volume is passed around from step to step. + volumeClaimTemplates: + - metadata: + name: workdir + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi + volumes: + - name: sshkey + secret: + secretName: argo-ci-git-ssh-key + items: + - key: id_rsa + mode: 0400 + path: id_rsa + - name: known-hosts + secret: + secretName: known-github + - name: pypirc + secret: + secretName: nexus-pypi + items: + - key: .pypirc + path: .pypirc + + + + templates: + - name: run-tests + steps: + - - name: set-pending + template: status-update + arguments: + parameters: + - name: status + value: pending + - name: message + value: Argo CI tests has been started + + - - name: checkout + template: git + arguments: + parameters: + - name: cmd + value: > + cd /workdir && + git clone {{workflow.parameters.git_url}} && + cd {{workflow.parameters.repo_name}} && + git fetch && + git merge origin/{{workflow.parameters.source_branch}} + + - - name: get-package-name + template: generate-name + + - - name: version-bump + template: python + arguments: + parameters: + - name: cmd + value: > + pip install bump2version && + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + bump2version release --allow-dirty --verbose + --serialize 0.0.0-dev{{workflow.parameters.source_sha}} && + python setup.py -V > /version + + - - name: pack-and-push + template: python + arguments: + parameters: + - name: cmd + value: > + pip install twine && + python3 setup.py sdist bdist_wheel && + twine upload --verbose --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-dev dist/* + + - - name: comment-pr + template: PR-comment + arguments: + parameters: + - name: message + value: "Looks good to me! and your package is here \ + {{steps.version-bump.outputs.parameters.version}}" + + + + - name: exit-handler + steps: + - - name: success + template: status-update + arguments: + parameters: + - name: status + value: success + - name: message + value: All tests has passed in argo ci + when: "{{workflow.status}} == Succeeded" + - name: failure + template: status-update + arguments: + parameters: + - name: status + value: failure + - name: message + value: Some tests has failed, check details for logs + when: "{{workflow.status}} != Succeeded" + + - name: git + inputs: + parameters: + - name: cmd + container: + image: "alpine/git" + command: ["sh", "-c"] + args: + - > + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: sshkey + mountPath: /root/.ssh + - mountPath: /etc/ssh + name: known-hosts + + - name: python + inputs: + parameters: + - name: cmd + outputs: + parameters: + - name: version # name of output parameter + valueFrom: + path: /version + container: + image: "python:3.7-buster" + command: ["sh", "-c"] + args: + - > + touch /version && + cd /workdir/{{workflow.parameters.repo_name}} && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: pypirc + mountPath: /etc/pypirc/ + env: + - name: NEXUS_PYPI_PASSWORD + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: password + - name: NEXUS_PYPI_USERNAME + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: username + + - name: status-update + inputs: + parameters: + - name: status + - name: message + container: + image: cloudposse/github-status-updater + env: + - name: GITHUB_ACTION + value: update_state + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: github-access-token + key: token + - name: GITHUB_OWNER + value: pepsico-ecommerce + - name: GITHUB_REPO + value: "{{workflow.parameters.repo_name}}" + - name: GITHUB_REF + value: "{{workflow.parameters.source_sha}}" + - name: GITHUB_CONTEXT + value: Argo CI Workflow + - name: GITHUB_STATE + value: "{{inputs.parameters.status}}" + - name: GITHUB_DESCRIPTION + value: "{{inputs.parameters.message}}" + - name: GITHUB_TARGET_URL + value: "https://argo-wf.pepstaging.com/workflows/argo-events/{{workflow.name}}" + + - name: PR-comment + inputs: + parameters: + - name: message + container: + image: cloudposse/github-commenter + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: github-access-token + key: token + - name: GITHUB_OWNER + value: pepsico-ecommerce + - name: GITHUB_REPO + value: "{{workflow.parameters.repo_name}}" + - name: GITHUB_COMMENT_TYPE + value: pr + - name: GITHUB_PR_ISSUE_NUMBER + value: "{{workflow.parameters.pr_number}}" + - name: GITHUB_COMMENT_FORMAT + value: "Argo CI comment: {{.}}" + - name: GITHUB_COMMENT + value: "{{inputs.parameters.message}}" + - name: GITHUB_DELETE_COMMENT_REGEX + value: "^Argo CI comment" + + - name: generate-name + script: + image: python:alpine3.7 + command: [python] + source: | + repo_name = """ + {{workflow.parameters.repo_name}} + """ + package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_') + print(package_name) diff --git a/.argo-ci/release.yaml b/.argo-ci/release.yaml new file mode 100644 index 0000000..76a660b --- /dev/null +++ b/.argo-ci/release.yaml @@ -0,0 +1,154 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: amazon-advertising-api-python-release- +spec: + # entrypoint is the name of the template used as the starting point of the workflow + entrypoint: run-build + # onExit: exit-handler + arguments: + parameters: + - name: git_url + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: tag_name + value: HAS_TO_BE_PASSED_FROM_SENSOR + - name: repo_name + value: HAS_TO_BE_PASSED_FROM_SENSOR + + # a temporary volume, named workdir, will be used as a working directory + # for this workflow. This volume is passed around from step to step. + volumeClaimTemplates: + - metadata: + name: workdir + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi + volumes: + - name: sshkey + secret: + secretName: argo-ci-git-ssh-key + items: + - key: id_rsa + mode: 0400 + path: id_rsa + - name: known-hosts + secret: + secretName: known-github + - name: pypirc + secret: + secretName: nexus-pypi + items: + - key: .pypirc + path: .pypirc + + + + templates: + - name: run-build + steps: + - - name: checkout + template: git + arguments: + parameters: + - name: cmd + value: > + cd /workdir && + git clone {{workflow.parameters.git_url}} && + cd /workdir/{{workflow.parameters.repo_name}} && + git checkout tags/{{workflow.parameters.tag_name}} + + - - name: compare-version + template: python + arguments: + parameters: + - name: cmd + value: > + if [ $(python setup.py --version) = {{workflow.parameters.tag_name}} ]; + then + echo OK; + else + exit 1; + fi +### add notification to slack if fail + + - - name: get-package-name + template: generate-name + + - - name: pack-and-push + template: python + arguments: + parameters: + - name: cmd + value: > + pip install twine && + python3 setup.py sdist bdist_wheel && + twine upload --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-release dist/* + +### slack notification about new version + + + + - name: git + retryStrategy: + limit: 3 + inputs: + parameters: + - name: cmd + container: + image: "alpine/git" + command: ["sh", "-c"] + args: + - > + git config --global user.email 'argo-ci@pepsi.co' && + git config --global user.name 'Argo CI' && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: sshkey + mountPath: /root/.ssh + - mountPath: /etc/ssh + name: known-hosts + + - name: python + retryStrategy: + limit: 3 + inputs: + parameters: + - name: cmd + container: + image: "python:3.7-buster" + command: ["sh", "-c"] + args: + - > + cd /workdir/{{workflow.parameters.repo_name}} && + {{inputs.parameters.cmd}} + volumeMounts: + - name: workdir + mountPath: /workdir + - name: pypirc + mountPath: /etc/pypirc/ + env: + - name: NEXUS_PYPI_PASSWORD + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: password + - name: NEXUS_PYPI_USERNAME + valueFrom: + secretKeyRef: + name: nexus-pypi-ro-creds + key: username + + - name: generate-name + script: + image: python:alpine3.7 + command: [python] + source: | + repo_name = """ + {{workflow.parameters.repo_name}} + """ + package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_') + print(package_name) diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..ca0c43d --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,18 @@ +[bumpversion] +current_version = 0.0.1 +commit = True +tag = False +message = "Bump version: {current_version} → {new_version}" +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z0-9]{1,10}))? +serialize = + {major}.{minor}.{patch}-{release} + {major}.{minor}.{patch} + +[bumpversion:part:release] +optional_value = dev +first_value = alpha +values = + dev + alpha + +[bumpversion:file:setup.py] diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index 89cb392..efdc64b 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,3 @@ versions = { 'api_version': 'v2', - 'api_version_sb': 'v3', - 'application_version': '1.0'} + 'api_version_sb': 'v3'} diff --git a/setup.py b/setup.py index 1552951..0d177f2 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,9 @@ from setuptools import setup -import amazon_advertising_api.versions as aa_versions setup( name='amazon_advertising_api', packages=['amazon_advertising_api'], - version=aa_versions.versions['application_version'], + version='0.0.1', description='Unofficial Amazon Sponsored Products Python client library.', url='https://github.com/pepsico-ecommerce/amazon-advertising-api-python') From 8cdb22e9d3773f8c1ad7fd6923b0befcf0a23a4f Mon Sep 17 00:00:00 2001 From: Argo CI Date: Thu, 15 Oct 2020 16:44:05 +0000 Subject: [PATCH 09/20] =?UTF-8?q?"Bump=20version:=200.0.2-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.2"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 6 +++--- setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ca0c43d..777ea8a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,17 +1,17 @@ [bumpversion] -current_version = 0.0.1 +current_version = 0.0.2 commit = True tag = False message = "Bump version: {current_version} → {new_version}" parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z0-9]{1,10}))? -serialize = +serialize = {major}.{minor}.{patch}-{release} {major}.{minor}.{patch} [bumpversion:part:release] optional_value = dev first_value = alpha -values = +values = dev alpha diff --git a/setup.py b/setup.py index 0d177f2..36a02b2 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,6 @@ setup( name='amazon_advertising_api', packages=['amazon_advertising_api'], - version='0.0.1', + version='0.0.2', description='Unofficial Amazon Sponsored Products Python client library.', url='https://github.com/pepsico-ecommerce/amazon-advertising-api-python') From e1dca8981ccc6e49e61b39647e71e901aedc746e Mon Sep 17 00:00:00 2001 From: Daria Koryukova <33223711+dkoryukova@users.noreply.github.com> Date: Wed, 21 Oct 2020 10:17:01 -0500 Subject: [PATCH 10/20] fix application version reference for bumpversion and runtime (#5) * fix application version reference --- .bumpversion.cfg | 2 +- amazon_advertising_api/advertising_api.py | 7 +++++-- amazon_advertising_api/versions.py | 2 ++ setup.py | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 777ea8a..5e40fa4 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -15,4 +15,4 @@ values = dev alpha -[bumpversion:file:setup.py] +[bumpversion:file:amazon_advertising_api/versions.py] diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 3ca39bf..7197a4c 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1,5 +1,6 @@ -from amazon_advertising_api.regions import regions +from amazon_advertising_api import versions as v from amazon_advertising_api.versions import versions +from amazon_advertising_api.regions import regions from io import BytesIO try: # Python 3 @@ -49,8 +50,10 @@ def __init__(self, self.refresh_token = refresh_token self.api_version = versions['api_version'] + self.user_agent = 'AdvertisingAPI Python Client Library v{}'.format( - versions['application_version']) + v.__version__) + self.profile_id = profile_id self.token_url = None diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index efdc64b..80cbe8a 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,3 +1,5 @@ +__version__ = "0.0.2" + versions = { 'api_version': 'v2', 'api_version_sb': 'v3'} diff --git a/setup.py b/setup.py index 36a02b2..27ceb13 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,10 @@ from setuptools import setup +from amazon_advertising_api import versions setup( name='amazon_advertising_api', packages=['amazon_advertising_api'], - version='0.0.2', + version=versions.__version__, description='Unofficial Amazon Sponsored Products Python client library.', url='https://github.com/pepsico-ecommerce/amazon-advertising-api-python') From 1f278ca0e0bb46e547eb3b846deaeba5ea7be005 Mon Sep 17 00:00:00 2001 From: Argo CI Date: Wed, 21 Oct 2020 15:18:10 +0000 Subject: [PATCH 11/20] =?UTF-8?q?"Bump=20version:=200.0.3-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- amazon_advertising_api/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5e40fa4..bf2d377 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.2 +current_version = 0.0.3 commit = True tag = False message = "Bump version: {current_version} → {new_version}" diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index 80cbe8a..c1a5a99 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,4 @@ -__version__ = "0.0.2" +__version__ = "0.0.3" versions = { 'api_version': 'v2', From a221227e7ba9026d43c9802b235d540e73eac36b Mon Sep 17 00:00:00 2001 From: Kuz Le Date: Wed, 21 Oct 2020 19:28:59 +0300 Subject: [PATCH 12/20] Fix url (#6) --- amazon_advertising_api/advertising_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 7197a4c..b02eae7 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1194,7 +1194,7 @@ def _operation(self, interface, params=None, method='GET'): data = None - url = f"https://{self.endpoint}/" + (f"{self.api_version}/" if api_v3 else "") + f"{interface}" + url = f"https://{self.endpoint}/" + ("" if api_v3 else f"{self.api_version}/") + f"{interface}" if method == 'GET': if params is not None: From 0df723ffb865638c293efcf130ce649348d102f4 Mon Sep 17 00:00:00 2001 From: Argo CI Date: Wed, 21 Oct 2020 16:30:07 +0000 Subject: [PATCH 13/20] =?UTF-8?q?"Bump=20version:=200.0.4-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.4"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- amazon_advertising_api/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index bf2d377..74f703b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.3 +current_version = 0.0.4 commit = True tag = False message = "Bump version: {current_version} → {new_version}" diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index c1a5a99..a976d86 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,4 @@ -__version__ = "0.0.3" +__version__ = "0.0.4" versions = { 'api_version': 'v2', From 6e798fcca077886d3cbaa2e56ffdf7632c13b70f Mon Sep 17 00:00:00 2001 From: Daria Koryukova <33223711+dkoryukova@users.noreply.github.com> Date: Thu, 29 Oct 2020 15:56:55 -0600 Subject: [PATCH 14/20] revert response param rename and fix snapshot endpoint (#9) * revert response param rename and fix snapshot endpoint --- amazon_advertising_api/advertising_api.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index b02eae7..1c145a8 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1043,16 +1043,8 @@ def request_snapshot(self, record_type=None, snapshot_id=None, data=None, campai :POST: /snapshots Required data: - * :campaignType: The type of campaign for which snapshot should be - generated. Must be one of 'sponsoredProducts' or 'headlineSearch' - Defaults to 'sponsoredProducts. :campaign_type: Should be 'hsa' or 'sp' """ - if not data: - data = {'campaignType': 'sponsoredProducts'} - elif not data.get('campaignType'): - data['campaignType'] = 'sponsoredProducts' - if record_type is not None: interface = '{}/{}/snapshot'.format(campaign_type, record_type) return self._operation(interface, data, method='POST') @@ -1147,6 +1139,7 @@ def _download(self, location): data = f.read() return {'success': True, 'code': res.code, + 'api_version': versions["api_version"], 'response': json.loads(data.decode('utf-8'))} else: return {'success': False, @@ -1219,10 +1212,11 @@ def _operation(self, interface, params=None, method='GET'): 'success': True, 'api_version': self.api_version if not api_v3 else versions['api_version_sb'], 'code': f.code, - 'data': f.read().decode('utf-8')} + 'response': f.read().decode('utf-8')} except urllib.error.HTTPError as e: return {'success': False, + 'api_version': self.api_version if not api_v3 else versions['api_version_sb'], 'code': e.code, 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} From be78a06bc94ca364ea279f51c8b93be72a35e945 Mon Sep 17 00:00:00 2001 From: Argo CI Date: Thu, 29 Oct 2020 21:58:10 +0000 Subject: [PATCH 15/20] =?UTF-8?q?"Bump=20version:=200.0.5-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.5"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- amazon_advertising_api/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 74f703b..8fae2f7 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.4 +current_version = 0.0.5 commit = True tag = False message = "Bump version: {current_version} → {new_version}" diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index a976d86..86b6659 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,4 @@ -__version__ = "0.0.4" +__version__ = "0.0.5" versions = { 'api_version': 'v2', From a134c8bb280ff1e8abc9f1df74014325ec4f7183 Mon Sep 17 00:00:00 2001 From: FanJiangCa Date: Thu, 1 Jul 2021 14:29:06 -0700 Subject: [PATCH 16/20] Adding handling of dsp reports endpoints --- amazon_advertising_api/advertising_api.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 1c145a8..d9f27ab 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -14,6 +14,8 @@ import gzip import json +DSP_REPORT = "dsp_report" + class AdvertisingApi(object): @@ -1065,7 +1067,10 @@ def request_report(self, record_type=None, report_id=None, data=None, campaign_t :type data: string """ if record_type is not None: - interface = '{}/{}/report'.format(campaign_type, record_type) + if record_type == DSP_REPORT: + interface = 'dsp/reports' + else: + interface = '{}/{}/report'.format(campaign_type, record_type) return self._operation(interface, data, method='POST') elif report_id is not None: interface = 'reports/{}'.format(report_id) @@ -1075,8 +1080,10 @@ def request_report(self, record_type=None, report_id=None, data=None, campaign_t 'code': 0, 'response': 'record_type and report_id are both empty.'} - def get_report(self, report_id): + def get_report(self, report_id, report_type=None): interface = 'reports/{}'.format(report_id) + if report_type is not None and report_type == DSP_REPORT: + interface = 'dsp/' + interface res = self._operation(interface) if res['success']: body = json.loads(res['response']) @@ -1166,6 +1173,7 @@ def _operation(self, interface, params=None, method='GET'): :type method: string """ api_v3 = interface.startswith('sb') + dsp_report = interface.startswith('dsp') if self._access_token is None: return {'success': False, @@ -1187,7 +1195,10 @@ def _operation(self, interface, params=None, method='GET'): data = None - url = f"https://{self.endpoint}/" + ("" if api_v3 else f"{self.api_version}/") + f"{interface}" + if api_v3 or dsp_report: + url = f"https://{self.endpoint}/{interface}" + else: + url = f"https://{self.endpoint}/{self.api_version}/{interface}" if method == 'GET': if params is not None: From 4b634563f4022ed3b5bd7ed3091cf9e7a914e886 Mon Sep 17 00:00:00 2001 From: Argo CI Date: Thu, 1 Jul 2021 21:47:44 +0000 Subject: [PATCH 17/20] =?UTF-8?q?"Bump=20version:=200.0.6-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- amazon_advertising_api/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8fae2f7..19f2dac 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.5 +current_version = 0.0.6 commit = True tag = False message = "Bump version: {current_version} → {new_version}" diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index 86b6659..f865a6a 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,4 @@ -__version__ = "0.0.5" +__version__ = "0.0.6" versions = { 'api_version': 'v2', From 2470c8d7e17cb14dfdd801a017774b3a659d860e Mon Sep 17 00:00:00 2001 From: FanJiangCa Date: Wed, 7 Jul 2021 11:35:49 -0700 Subject: [PATCH 18/20] Getting api to return url for dsp report --- amazon_advertising_api/advertising_api.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index d9f27ab..a3e95ed 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1084,10 +1084,13 @@ def get_report(self, report_id, report_type=None): interface = 'reports/{}'.format(report_id) if report_type is not None and report_type == DSP_REPORT: interface = 'dsp/' + interface - res = self._operation(interface) + res = self._operation(interface, report_id=report_id) if res['success']: body = json.loads(res['response']) if body.get('status') == 'SUCCESS': + # for dsp report, return response with url for the report instead of the huge report data itself + if report_type == DSP_REPORT: + return res res = self._download(location=body['location']) return res @@ -1161,7 +1164,7 @@ def _download(self, location): 'code': e.code, 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} - def _operation(self, interface, params=None, method='GET'): + def _operation(self, interface, params=None, method='GET', report_id=None): """ Makes that actual API call. @@ -1184,6 +1187,8 @@ def _operation(self, interface, params=None, method='GET'): 'Amazon-Advertising-API-ClientId': self.client_id, 'Content-Type': 'application/json', 'User-Agent': self.user_agent} + if report_id is not None: + headers['reportId'] = report_id if self.profile_id is not None and self.profile_id != '': headers['Amazon-Advertising-API-Scope'] = self.profile_id From a2c90531487693c06dc2a8e8699669f790266ef8 Mon Sep 17 00:00:00 2001 From: Argo CI Date: Wed, 7 Jul 2021 19:08:59 +0000 Subject: [PATCH 19/20] =?UTF-8?q?"Bump=20version:=200.0.7-alpha=20?= =?UTF-8?q?=E2=86=92=200.0.7"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- amazon_advertising_api/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 19f2dac..bc5e17b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.6 +current_version = 0.0.7 commit = True tag = False message = "Bump version: {current_version} → {new_version}" diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py index f865a6a..b3940da 100644 --- a/amazon_advertising_api/versions.py +++ b/amazon_advertising_api/versions.py @@ -1,4 +1,4 @@ -__version__ = "0.0.6" +__version__ = "0.0.7" versions = { 'api_version': 'v2', From ab01d2b801fa27919044237854957cd8844d101d Mon Sep 17 00:00:00 2001 From: FanJiangCa Date: Thu, 4 Nov 2021 16:52:53 -0700 Subject: [PATCH 20/20] Add handling of dsp entity GET request --- amazon_advertising_api/advertising_api.py | 27 +++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index a3e95ed..0809454 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1094,6 +1094,23 @@ def get_report(self, report_id, report_type=None): res = self._download(location=body['location']) return res + def get_dsp_entity(self, interface, media_type): + """ + :GET: dsp/interface + : + :interface consists of entity_type and query_param, where + : entity_type: advertisers, orders, lineItems, creatives, lineItemCreativeAssociations + : query_param: startIndex=&count=[&=] + : filter: advertiserIdFilter, orderIdFilter, lineItemIdFilter + :media_type example: 'application/vnd.dspbasiclineitems.v3+json' + """ + if interface is None or media_type is None: + return {'success': False, + 'code': 0, + 'response': 'interface and media_type are both empty.'} + else: + return self._operation(interface, method='GET', media_type=media_type) + def get_snapshot(self, snapshot_id): interface = 'snapshots/{}'.format(snapshot_id) res = self._operation(interface) @@ -1164,7 +1181,7 @@ def _download(self, location): 'code': e.code, 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} - def _operation(self, interface, params=None, method='GET', report_id=None): + def _operation(self, interface, params=None, method='GET', report_id=None, media_type=None): """ Makes that actual API call. @@ -1172,8 +1189,12 @@ def _operation(self, interface, params=None, method='GET', report_id=None): :type interface: string :param params: Parameters associated with this call. :type params: GET: string POST: dictionary - :param method: Call method. Should be either 'GET', 'PUT', or 'POST' + :param method: Call method. Should be either 'GET', 'PUT', or 'POST'. :type method: string + :param report_id: report_id obtained in previous 'POST' request. + :type report_id: string + :param media_type: Used for dsp entity 'GET' calls, e.g., 'application/vnd.dsporders.v2.2+json' + :type media_type: string """ api_v3 = interface.startswith('sb') dsp_report = interface.startswith('dsp') @@ -1189,6 +1210,8 @@ def _operation(self, interface, params=None, method='GET', report_id=None): 'User-Agent': self.user_agent} if report_id is not None: headers['reportId'] = report_id + if media_type is not None: + headers['Accept'] = media_type if self.profile_id is not None and self.profile_id != '': headers['Amazon-Advertising-API-Scope'] = self.profile_id