From abf27b8d687bd964c6adfad72b6a1d6141aa3a83 Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 20 Sep 2017 11:37:36 -0700 Subject: [PATCH 01/23] EEB-1317 Python 2/3 compatibility --- .gitignore | 2 ++ amazon_advertising_api/advertising_api.py | 3 +-- amazon_advertising_api/requirements.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 amazon_advertising_api/requirements.txt diff --git a/.gitignore b/.gitignore index d1f74f4..772b156 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ target/ localtest.py +# Pycharm +*.idea/ diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index a9f677c..f10e71a 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1,8 +1,7 @@ from amazon_advertising_api.regions import regions from amazon_advertising_api.versions import versions from io import BytesIO -import urllib.request -import urllib.parse +from six.moves import urllib import gzip import json diff --git a/amazon_advertising_api/requirements.txt b/amazon_advertising_api/requirements.txt new file mode 100644 index 0000000..64c56a3 --- /dev/null +++ b/amazon_advertising_api/requirements.txt @@ -0,0 +1 @@ +six \ No newline at end of file From 93e315dd7190a930e4791f0b6f145aeec1a29304 Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 20 Sep 2017 16:34:31 -0700 Subject: [PATCH 02/23] EEB-1317 - Fix reference error --- amazon_advertising_api/advertising_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index f10e71a..df2f076 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -664,11 +664,11 @@ def _download(self, location): 'response': json.loads(data.decode('utf-8'))} else: return {'success': False, - 'code': res.code, + 'code': response.code, 'response': 'Location is empty.'} else: return {'success': False, - 'code': res.code, + 'code': response.code, 'response': 'Location not found.'} except urllib.error.HTTPError as e: return {'success': False, From 254e4924df67240c5c659ab67f7c3d630941404f Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 20 Sep 2017 16:34:59 -0700 Subject: [PATCH 03/23] EEB-1317 - Add setup.py --- setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9ccc660 --- /dev/null +++ b/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup +import amazon_advertising_api.versions as aa_versions + + +setup( + name='amazon_advertising', + packages=['pay_with_amazon'], + version=aa_versions.versions['application_version'], + description='Unofficial Amazon Sponsored Products Python client library.', + url='https://github.com/sguermond/amazon-advertising-api-python') From 6e7ed74ddfd33f7b90b0bd502c88410126da3754 Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 27 Sep 2017 14:04:28 -0700 Subject: [PATCH 04/23] EEB-1317 - Allow refresh when no access token exists. --- amazon_advertising_api/advertising_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index df2f076..e0df9ca 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -69,7 +69,8 @@ def do_refresh_token(self): 'code': 0, 'response': 'refresh_token is empty.'} - self._access_token = urllib.parse.unquote(self._access_token) + if self._access_token: + self._access_token = urllib.parse.unquote(self._access_token) self.refresh_token = urllib.parse.unquote(self.refresh_token) params = { From c97d40dbf1c5f9a6f183ec2c4a5f7b816cea5c20 Mon Sep 17 00:00:00 2001 From: Sarah Date: Thu, 28 Sep 2017 10:12:24 -0700 Subject: [PATCH 05/23] EEB-1317 - Fix error in setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9ccc660..c533c38 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='amazon_advertising', - packages=['pay_with_amazon'], + packages=['amazon_advertising'], version=aa_versions.versions['application_version'], description='Unofficial Amazon Sponsored Products Python client library.', url='https://github.com/sguermond/amazon-advertising-api-python') From 461179fec9ef470b04382ed0ebed2b63d77fea01 Mon Sep 17 00:00:00 2001 From: Sarah Date: Thu, 28 Sep 2017 16:03:15 -0700 Subject: [PATCH 06/23] EEB-1317 - Add state check during token refresh --- amazon_advertising_api/advertising_api.py | 29 ++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index e0df9ca..22c9f1c 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -4,6 +4,8 @@ from six.moves import urllib import gzip import json +import os +import base64 class AdvertisingApi: @@ -63,6 +65,12 @@ def access_token(self, value): """Set access_token""" self._access_token = value + @staticmethod + def generate_state_parameter(): + random = os.urandom(256) + state = base64.b64encode(random) + return state + def do_refresh_token(self): if self.refresh_token is None: return {'success': False, @@ -73,11 +81,13 @@ def do_refresh_token(self): self._access_token = urllib.parse.unquote(self._access_token) self.refresh_token = urllib.parse.unquote(self.refresh_token) + state = self.generate_state_parameter() params = { 'grant_type': 'refresh_token', 'refresh_token': self.refresh_token, 'client_id': self.client_id, - 'client_secret': self.client_secret} + 'client_secret': self.client_secret, + 'state': state} data = urllib.parse.urlencode(params) @@ -90,10 +100,17 @@ def do_refresh_token(self): response = f.read().decode('utf-8') if 'access_token' in response: json_data = json.loads(response) - self._access_token = json_data['access_token'] - return {'success': True, - 'code': f.code, - 'response': self._access_token} + if json_data.get('state') == state: + self._access_token = json_data['access_token'] + return {'success': True, + 'code': f.code, + 'response': self._access_token} + else: + return { + 'success': False, + 'code': '', + 'response': 'response state did not match request state' + } else: return {'success': False, 'code': f.code, @@ -136,7 +153,7 @@ def update_profiles(self, data): profileIds. :PUT: /profiles - :param data: A list of updates containing **proflileId** and the + :param data: A list of updates containing **profileId** and the mutable fields to be modified. Only daily budgets are mutable at this time. :type data: List of **Profile** From 8300866227197ed5a5e025fe67414281a0bac564 Mon Sep 17 00:00:00 2001 From: Sarah Date: Thu, 28 Sep 2017 16:09:22 -0700 Subject: [PATCH 07/23] EEB-1317 - Fix typo in state check --- amazon_advertising_api/advertising_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 22c9f1c..3f7322c 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -108,7 +108,7 @@ def do_refresh_token(self): else: return { 'success': False, - 'code': '', + 'code': 0, 'response': 'response state did not match request state' } else: @@ -705,6 +705,7 @@ def _operation(self, interface, params=None, method='GET'): :type method: string """ if self._access_token is None: + return {'success': False, 'code': 0, 'response': 'access_token is empty.'} From 515d362872d9a253733e9945cd386c00090465e2 Mon Sep 17 00:00:00 2001 From: Sarah Date: Tue, 3 Oct 2017 10:46:48 -0700 Subject: [PATCH 08/23] EEB-1317 - Remove state check since it's not returned in access token refresh --- amazon_advertising_api/advertising_api.py | 25 +++++------------------ 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 3f7322c..190c084 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -65,12 +65,6 @@ def access_token(self, value): """Set access_token""" self._access_token = value - @staticmethod - def generate_state_parameter(): - random = os.urandom(256) - state = base64.b64encode(random) - return state - def do_refresh_token(self): if self.refresh_token is None: return {'success': False, @@ -81,13 +75,11 @@ def do_refresh_token(self): self._access_token = urllib.parse.unquote(self._access_token) self.refresh_token = urllib.parse.unquote(self.refresh_token) - state = self.generate_state_parameter() params = { 'grant_type': 'refresh_token', 'refresh_token': self.refresh_token, 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'state': state} + 'client_secret': self.client_secret} data = urllib.parse.urlencode(params) @@ -100,17 +92,10 @@ def do_refresh_token(self): response = f.read().decode('utf-8') if 'access_token' in response: json_data = json.loads(response) - if json_data.get('state') == state: - self._access_token = json_data['access_token'] - return {'success': True, - 'code': f.code, - 'response': self._access_token} - else: - return { - 'success': False, - 'code': 0, - 'response': 'response state did not match request state' - } + self._access_token = json_data['access_token'] + return {'success': True, + 'code': f.code, + 'response': self._access_token} else: return {'success': False, 'code': f.code, From 81f43c2cbb9d6578f1742fed1e0e55606e12b17a Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 4 Oct 2017 14:25:07 -0700 Subject: [PATCH 09/23] EEB-1317 - Require setting a profile ID before making most calls --- amazon_advertising_api/advertising_api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 190c084..7e096ff 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -690,7 +690,6 @@ def _operation(self, interface, params=None, method='GET'): :type method: string """ if self._access_token is None: - return {'success': False, 'code': 0, 'response': 'access_token is empty.'} @@ -701,6 +700,12 @@ def _operation(self, interface, params=None, method='GET'): if self.profile_id is not None and self.profile_id != '': headers['Amazon-Advertising-API-Scope'] = self.profile_id + print('Profile ID: ', self.profile_id) + elif 'profiles' not in interface: + # Profile ID is required for all calls beyond authentication and getting profile info + return {'success': False, + 'code': 0, + 'response': 'profile_id is empty.'} data = None From 641b1c895509c1273a0b8dc1e09cf2397ca1002d Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 4 Oct 2017 16:48:34 -0700 Subject: [PATCH 10/23] EEB-1317 - Allow passing profile id at init --- amazon_advertising_api/advertising_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 7e096ff..55c872c 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -16,6 +16,7 @@ def __init__(self, client_id, client_secret, region, + profile_id=None, access_token=None, refresh_token=None, sandbox=False): @@ -44,7 +45,7 @@ def __init__(self, self.api_version = versions['api_version'] self.user_agent = 'AdvertisingAPI Python Client Library v{}'.format( versions['application_version']) - self.profile_id = None + self.profile_id = profile_id self.token_url = None if region in regions: @@ -686,7 +687,7 @@ def _operation(self, interface, params=None, method='GET'): :type interface: string :param params: Parameters associated with this call. :type params: GET: string POST: dictionary - :param method: Call method. Should be either 'GET' or 'POST' + :param method: Call method. Should be either 'GET', 'PUT', or 'POST' :type method: string """ if self._access_token is None: @@ -694,13 +695,12 @@ def _operation(self, interface, params=None, method='GET'): 'code': 0, 'response': 'access_token is empty.'} - headers = {'Authorization': 'bearer {}'.format(self._access_token), + headers = {'Authorization': 'Bearer {}'.format(self._access_token), 'Content-Type': 'application/json', 'User-Agent': self.user_agent} if self.profile_id is not None and self.profile_id != '': headers['Amazon-Advertising-API-Scope'] = self.profile_id - print('Profile ID: ', self.profile_id) elif 'profiles' not in interface: # Profile ID is required for all calls beyond authentication and getting profile info return {'success': False, From b01e9b493bcee5198345b7d4cd1c2dbed54840ab Mon Sep 17 00:00:00 2001 From: Sarah Date: Thu, 5 Oct 2017 17:25:00 -0700 Subject: [PATCH 11/23] EEB-1317 - Add error details to HTTPErrors to make them less obscure --- amazon_advertising_api/advertising_api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 55c872c..07804e1 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -104,7 +104,8 @@ def do_refresh_token(self): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg} + 'response': e.msg, + 'details': e.read()} def get_profiles(self): """ @@ -677,7 +678,8 @@ def _download(self, location): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg} + 'response': e.msg, + 'details': e.read()} def _operation(self, interface, params=None, method='GET'): """ @@ -740,7 +742,8 @@ def _operation(self, interface, params=None, method='GET'): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg} + 'response': e.msg, + 'details': e.read()} class NoRedirectHandler(urllib.request.HTTPErrorProcessor): From e2af8a2ce23f411a1fd58b25867e8f5740064c96 Mon Sep 17 00:00:00 2001 From: sguermond Date: Fri, 20 Oct 2017 10:54:56 -0700 Subject: [PATCH 12/23] Clean up response for HTTPErrors while retaining details. --- amazon_advertising_api/advertising_api.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 07804e1..ba38838 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -104,8 +104,7 @@ def do_refresh_token(self): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg, - 'details': e.read()} + 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} def get_profiles(self): """ @@ -678,8 +677,7 @@ def _download(self, location): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg, - 'details': e.read()} + 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} def _operation(self, interface, params=None, method='GET'): """ @@ -742,8 +740,7 @@ def _operation(self, interface, params=None, method='GET'): except urllib.error.HTTPError as e: return {'success': False, 'code': e.code, - 'response': e.msg, - 'details': e.read()} + 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} class NoRedirectHandler(urllib.request.HTTPErrorProcessor): From be3c5f4b8e8e11e2f0a12756f70533e73375550e Mon Sep 17 00:00:00 2001 From: sguermond Date: Fri, 20 Oct 2017 11:16:29 -0700 Subject: [PATCH 13/23] Convert to new style class --- amazon_advertising_api/advertising_api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index ba38838..831a254 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -4,11 +4,9 @@ from six.moves import urllib import gzip import json -import os -import base64 -class AdvertisingApi: +class AdvertisingApi(object): """Lightweight client library for Amazon Sponsored Products API.""" From 7d6e957341480b19c127c880c125ce093e994856 Mon Sep 17 00:00:00 2001 From: sguermond Date: Thu, 26 Oct 2017 10:01:18 -0700 Subject: [PATCH 14/23] Return error on insufficent parameters when requesting snapshots and reports --- amazon_advertising_api/advertising_api.py | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 831a254..2b5cba4 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -603,20 +603,46 @@ def list_product_ads_ex(self, data=None): return self._operation(interface, data) def request_snapshot(self, record_type=None, snapshot_id=None, data=None): + """ + Required data: + * :campaignType: The type of campaign for which snapshot should be generated. Must be sponsoredProducts. + """ + if not data: + data = {'campaignType': 'sponsoredProducts'} + elif not data.get('campaignType'): + data['campaignType'] = 'sponsoredProducts' + if record_type is not None: interface = '{}/snapshot'.format(record_type) return self._operation(interface, data, method='POST') elif snapshot_id is not None: interface = 'snapshots/{}'.format(snapshot_id) return self._operation(interface, data) + else: + return {'success': False, + 'code': 0, + 'response': 'record_type and snapshot_id are both empty.'} def request_report(self, 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' + if record_type is not None: interface = '{}/report'.format(record_type) return self._operation(interface, data, method='POST') elif report_id is not None: interface = 'reports/{}'.format(report_id) return self._operation(interface) + else: + return {'success': False, + 'code': 0, + 'response': 'record_type and report_id are both empty.'} def get_report(self, report_id): interface = 'reports/{}'.format(report_id) From 4e4791ae31d972d5bf2c65e06ce784d924b056e4 Mon Sep 17 00:00:00 2001 From: sguermond Date: Thu, 9 Nov 2017 10:07:47 -0800 Subject: [PATCH 15/23] EEB-1310 - WIP submitter --- 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 2b5cba4..af12628 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -53,7 +53,7 @@ def __init__(self, self.endpoint = regions[region]['prod'] self.token_url = regions[region]['token_url'] else: - raise KeyError('Region {} not found in regions.'.format(regions)) + raise KeyError('Region {} not found in regions.'.format(region)) @property def access_token(self): From a2fa1b521d2ae547ab734b3e5db3ade060302d09 Mon Sep 17 00:00:00 2001 From: sguermond Date: Tue, 7 Nov 2017 15:27:03 -0800 Subject: [PATCH 16/23] Implement register_profile, fix Python 2/3 incompatibility issue due to urllib2 forcing PUT to POST. (cherry picked from commit 6e742bb) --- amazon_advertising_api/advertising_api.py | 52 +++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index af12628..9b078d1 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -1,7 +1,15 @@ from amazon_advertising_api.regions import regions from amazon_advertising_api.versions import versions from io import BytesIO -from six.moves import urllib +try: + # Python 3 + import urllib.request + import urllib.parse + PYTHON = 3 +except ImportError: + # Python 2 + from six.moves import urllib + PYTHON = 2 import gzip import json @@ -104,6 +112,23 @@ def do_refresh_token(self): 'code': e.code, 'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())} + def register_profile(self, country_code): + """ + Registers a sandbox profile. + + :PUT: /profiles/register + :param country_code: The country in which to register the profile. + Country code can be one of the following: + US, CA, UK, DE, FR, ES, IT, IN, CN, JP + :returns: + :200: Success + :401: Unauthorized + """ + interface = 'profiles/register' + params = {"countryCode": country_code} + method = 'PUT' + return self._operation(interface, params, method) + def get_profiles(self): """ Retrieves profiles associated with an auth token. @@ -753,7 +778,10 @@ def _operation(self, interface, params=None, method='GET'): api_version=self.api_version, interface=interface) - req = urllib.request.Request(url=url, headers=headers, data=data) + if PYTHON == 3: + req = urllib.request.Request(url=url, headers=headers, data=data) + else: + req = MethodRequest(url=url, headers=headers, data=data, method=method) req.method = method try: @@ -768,7 +796,6 @@ def _operation(self, interface, params=None, method='GET'): class NoRedirectHandler(urllib.request.HTTPErrorProcessor): - """Handles report and snapshot redirects.""" def http_response(self, request, response): @@ -783,3 +810,22 @@ def http_response(self, request, response): self, request, response) https_response = http_response + + +class MethodRequest(urllib.request.Request): + """ + When not using Python 3 and the requests library. + Source: Ed Marshall, https://gist.github.com/logic/2715756 + """ + def __init__(self, *args, **kwargs): + if 'method' in kwargs: + self._method = kwargs['method'] + del kwargs['method'] + else: + self._method = None + return urllib.request.Request.__init__(self, *args, **kwargs) + + def get_method(self, *args, **kwargs): + if self._method is not None: + return self._method + return urllib.request.Request.get_method(self, *args, **kwargs) From e14cf83af1dc4c47b399b7ed588548b0a25fcc51 Mon Sep 17 00:00:00 2001 From: Sarah Date: Fri, 17 Nov 2017 11:02:21 -0800 Subject: [PATCH 17/23] EEB-1310 - Fix install issue --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c533c38..d2a341d 100644 --- a/setup.py +++ b/setup.py @@ -3,8 +3,8 @@ setup( - name='amazon_advertising', - packages=['amazon_advertising'], + name='amazon_advertising_api', + 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') From 9cf13f90c87a25d6aabf9e92c3386a3bd4bf4635 Mon Sep 17 00:00:00 2001 From: Sarah Date: Wed, 10 Jan 2018 12:24:15 -0800 Subject: [PATCH 18/23] EEB-1554 - Start adding recommendations to API --- amazon_advertising_api/advertising_api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py index 9b078d1..8fb0657 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -689,6 +689,26 @@ def get_snapshot(self, snapshot_id): else: return res + def get_ad_group_bid_recommendations(self, ad_group_id): + """Request bid recommendations for specified ad group.""" + interface = 'adGroups/{}/bidRecommendations'.format(ad_group_id) + return self._operation(interface) + + def get_keyword_bid_recommendations(self, keyword_id=None, keyword_data=None): + """ + Request bid recommendations for: + + * a specified keyword + * a list of up to 100 keywords + + A list of keywords must be in the KeywordBidRecommendationsData format: + + ``` + int adGroupId: [] + ``` + """ + pass + def _download(self, location): headers = {'Authorization': 'Bearer {}'.format(self._access_token), 'Content-Type': 'application/json', From b248308befea29cf39ed0ce5978de3b221a4c554 Mon Sep 17 00:00:00 2001 From: Sarah Date: Fri, 19 Jan 2018 16:49:27 -0800 Subject: [PATCH 19/23] Add FE endpoint to regions --- amazon_advertising_api/regions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/amazon_advertising_api/regions.py b/amazon_advertising_api/regions.py index 0f48470..ded27d9 100644 --- a/amazon_advertising_api/regions.py +++ b/amazon_advertising_api/regions.py @@ -3,4 +3,7 @@ 'token_url': 'api.amazon.com/auth/o2/token'}, 'eu': {'sandbox': 'advertising-api-test.amazon.com', 'prod': 'advertising-api-eu.amazon.com', + 'token_url': 'api.amazon.com/auth/o2/token'}, + 'fe': {'sandbox': 'advertising-api-test.amazon.com', + 'prod': 'advertising-api-fe.amazon.com', 'token_url': 'api.amazon.com/auth/o2/token'}} From 0a763daa8561f78849c3d72c2f0d7dd505eafb79 Mon Sep 17 00:00:00 2001 From: Sarah Date: Fri, 19 Jan 2018 17:14:27 -0800 Subject: [PATCH 20/23] Remove FE endpoint, wishful thinking. --- amazon_advertising_api/regions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/amazon_advertising_api/regions.py b/amazon_advertising_api/regions.py index ded27d9..0f48470 100644 --- a/amazon_advertising_api/regions.py +++ b/amazon_advertising_api/regions.py @@ -3,7 +3,4 @@ 'token_url': 'api.amazon.com/auth/o2/token'}, 'eu': {'sandbox': 'advertising-api-test.amazon.com', 'prod': 'advertising-api-eu.amazon.com', - 'token_url': 'api.amazon.com/auth/o2/token'}, - 'fe': {'sandbox': 'advertising-api-test.amazon.com', - 'prod': 'advertising-api-fe.amazon.com', 'token_url': 'api.amazon.com/auth/o2/token'}} From fc080e1abc2c39f2ee3959f8a662eb98737f5bfb Mon Sep 17 00:00:00 2001 From: Sarah Date: Fri, 10 Aug 2018 11:22:04 -0700 Subject: [PATCH 21/23] Added check for 200 code before trying to download the report --- 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 8fb0657..bf41742 100644 --- a/amazon_advertising_api/advertising_api.py +++ b/amazon_advertising_api/advertising_api.py @@ -672,7 +672,7 @@ 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': + if res['code'] == 200 and json.loads(res['response'])['status'] == 'SUCCESS': res = self._download( location=json.loads(res['response'])['location']) return res From ff583d3b38d780cb439b85ee797bfde386d97ad0 Mon Sep 17 00:00:00 2001 From: Sarah Guermond Date: Tue, 23 Oct 2018 09:48:18 -0700 Subject: [PATCH 22/23] Added link to Amazon documentation Specified supported API version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 159b813..28781d5 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Unofficial Amazon Sponsored Products Python client library. +Unofficial [Amazon Sponsored Products Python client library (v1.0)](https://advertising.amazon.com/API/docs/v1/guides/get_started) From c91dbf79a5ac5fdc08e8263ecc3265cf8ccbfb84 Mon Sep 17 00:00:00 2001 From: Sarah Guermond Date: Sat, 28 Aug 2021 16:03:02 -0700 Subject: [PATCH 23/23] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28781d5..fbe3c4a 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -Unofficial [Amazon Sponsored Products Python client library (v1.0)](https://advertising.amazon.com/API/docs/v1/guides/get_started) + ⚠️ This repository is no longer being maintained. I recommend using [hartjet](https://github.com/hartjet/amazon-advertising-api-python) or [pepsico-ecommerce](https://github.com/pepsico-ecommerce/amazon-advertising-api-python) for more actively maintained versions. ⚠️ + +# Unofficial [Amazon Sponsored Products Python client library (v1.0)](https://advertising.amazon.com/API/docs/v1/guides/get_started)