From 50252c5c3236bdf41344867d4a74e7478b2e905e Mon Sep 17 00:00:00 2001 From: YPCrumble Date: Thu, 10 Sep 2015 20:20:32 -0400 Subject: [PATCH 01/91] Add exception handling for Amazon throttling a request. --- amazon/api.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 2a36f01..f8c53fd 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -80,6 +80,13 @@ class NoMorePages(SearchException): pass +class RequestThrottled(AmazonException): + """Exception for when Amazon has throttled a request, per: + http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ErrorNumbers.html + """ + pass + + class SimilartyLookupException(AmazonException): """Similarty Lookup Exception. """ @@ -553,6 +560,9 @@ def _query(self, ResponseGroup="Large", **kwargs): msg = root.Items.Request.Errors.Error.Message if code == 'AWS.ParameterOutOfRange': raise NoMorePages(msg) + elif code == 'HTTP Error 503': + raise RequestThrottled( + "Request Throttled Error: '{0}', '{1}'".format(code, msg)) else: raise SearchException( "Amazon Search Error: '{0}', '{1}'".format(code, msg)) From fa1b7d794c0c8c9f4b6883eb1391b7f8efd47292 Mon Sep 17 00:00:00 2001 From: Eric Pauley Date: Wed, 11 Nov 2015 14:41:23 -0500 Subject: [PATCH 02/91] Added bulk lookup support. This method provides predictable response format with items removed from the result list instead of throwing errors and a list being returned in all cases. --- amazon/api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 510d663..7db0955 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -189,6 +189,26 @@ def lookup(self, ResponseGroup="Large", **kwargs): region=self.region ) + def lookup_bulk(self, ResponseGroup="Large", **kwargs): + """Lookup Amazon Products in bulk. + + Returns all products matching requested ASINs, ignoring invalid entries. + + :return: + A list of :class:`~.AmazonProduct` instances. + """ + response = self.api.ItemLookup(ResponseGroup=ResponseGroup, **kwargs) + root = objectify.fromstring(response) + if not hasattr(root.Items, 'Item'): + return [] + return list( + AmazonProduct( + item, + self.aws_associate_tag, + self, + region=self.region) for item in root.Items.Item + ) + def similarity_lookup(self, ResponseGroup="Large", **kwargs): """Similarty Lookup. From b33afc430beb03ad1d9592d5ce5b7d748c08be08 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 9 Feb 2016 17:40:33 -0500 Subject: [PATCH 03/91] Update with macbook air commits --- requirements.txt | 1 + tests.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 638de05..7e472fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ lxml bottlenose python-dateutil +nose diff --git a/tests.py b/tests.py index 63336a4..0a5880d 100644 --- a/tests.py +++ b/tests.py @@ -1,4 +1,5 @@ import unittest +import mock from nose.tools import assert_equals, assert_true, assert_false @@ -7,6 +8,7 @@ CartException, CartInfoMismatchException, SearchException, + AmazonSearch, AsinNotFound) from test_settings import (AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, @@ -159,6 +161,31 @@ def test_search_no_results(self): SearchIndex='Automotive') self.assertRaises(SearchException, (x for x in products).next) + @mock.patch('amazon.api.objectify') + def test_search_throttled(self, lxml): + """Test handling of a search throttled by Amazon. + + Tests that a product search that triggers throttling by Amazon: + http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ErrorNumbers.html + """ + + lxml.return_value = { + "Items": { + "Request": { + "Errors": { + "Error": { + "Code": "AWS.ParameterOutOfRange", + "Message": "Request has been throttled" + } + } + } + } + } + + AmazonSearch._query() + + + def test_amazon_api_defaults_to_US(self): """Test Amazon API defaults to the US store.""" amazon = AmazonAPI( @@ -444,4 +471,4 @@ def test_cart_delete(self): self.assertRaises(KeyError, new_cart.__getitem__, cart_item_id) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From d153ce9da9869aa8123e05608d9902048d95857e Mon Sep 17 00:00:00 2001 From: sblondon Date: Fri, 12 Feb 2016 12:21:36 +0100 Subject: [PATCH 04/91] set the right licence according to LICENCE file --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4a41150..498891a 100644 --- a/setup.py +++ b/setup.py @@ -17,13 +17,13 @@ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Utilities", - "License :: OSI Approved :: BSD License", + "License :: OSI Approved :: Apache Software License", ], keywords='amazon, product advertising, api', author='Yoav Aviram', author_email='support@cleverblocks.com', url='https://github.com/yoavaviram/python-amazon-simple-product-api', - license='BSD', + license='Apache 2.0', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=True, From cfb3d6bed129fbfe52835f5618dad26a20e59eab Mon Sep 17 00:00:00 2001 From: Dan Kram Date: Thu, 25 Feb 2016 09:18:19 -0800 Subject: [PATCH 05/91] Add Offer ID to API Offer ID is used to add items to a remote cart. Therefore it would be helpful to access it directly from the returned Amazon Product. --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 510d663..cc1653e 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -684,6 +684,15 @@ def price_and_currency(self): else: return None, None + @property + def offer_id(self): + """Offer ID + + :return: + Offer ID (string). + """ + return self._safe_get_element('Offers.Offer.OfferListing.OfferListingId') + @property def asin(self): """ASIN (Amazon ID) From e7145c5f0f6acd84e1291d16e3472f43fbd78a7b Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 16:49:10 +0200 Subject: [PATCH 06/91] add dynamic handling of last page --- amazon/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 510d663..9a66378 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -504,6 +504,7 @@ def __init__(self, api, aws_associate_tag, **kwargs): """ self.kwargs = kwargs self.current_page = 1 + self.is_last_page = False self.api = api self.aws_associate_tag = aws_associate_tag @@ -531,7 +532,7 @@ def iterate_pages(self): Yields lxml root elements. """ try: - while True: + while not self.is_last_page: yield self._query(ItemPage=self.current_page, **self.kwargs) self.current_page += 1 except NoMorePages: @@ -556,6 +557,8 @@ def _query(self, ResponseGroup="Large", **kwargs): else: raise SearchException( "Amazon Search Error: '{0}', '{1}'".format(code, msg)) + if (hasattr(root.Items, 'TotalPages') and (root.Items.TotalPages == self.current_page)): + self.is_last_page = True return root From 6a15325736109b028fc544628d17f6c94860fb5d Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:06:40 +0200 Subject: [PATCH 07/91] Add support for adult property parsing --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 9a66378..669f7d8 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1226,6 +1226,15 @@ def directors(self): result.append(director.text) return result + @property + def is_adult(self): + """IsAdultProduct. + + :return: + IsAdultProduct (string) + """ + return self._safe_get_element_text('ItemAttributes.IsAdultProduct') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 4b00f123257473f37eb851672b52e21112a99dc1 Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:09:53 +0200 Subject: [PATCH 08/91] Add support for product group property parsing --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 669f7d8..9f675e8 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1235,6 +1235,15 @@ def is_adult(self): """ return self._safe_get_element_text('ItemAttributes.IsAdultProduct') + @property + def product_group(self): + """ProductGroup. + + :return: + ProductGroup (string) + """ + return self._safe_get_element_text('ItemAttributes.ProductGroup') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 45495f4f6c7943148fa7f30945730e2a0322f6e3 Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:11:41 +0200 Subject: [PATCH 09/91] Add support for product type name property parsing --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 9f675e8..51c02d5 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1244,6 +1244,15 @@ def product_group(self): """ return self._safe_get_element_text('ItemAttributes.ProductGroup') + @property + def product_type_name(self): + """ProductTypeName. + + :return: + ProductTypeName (string) + """ + return self._safe_get_element_text('ItemAttributes.ProductTypeName') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 6ebd4e9072e32aa86905da5b96056664d7d34f10 Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:13:57 +0200 Subject: [PATCH 10/91] Add support for product formatted price property parsing in the AmazonProduct class --- amazon/api.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 51c02d5..b648cd5 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1253,6 +1253,14 @@ def product_type_name(self): """ return self._safe_get_element_text('ItemAttributes.ProductTypeName') + @property + def formatted_price(self): + """FormattedPrice. + + :return: + FormattedPrice (string) + """ + return self._safe_get_element_text('OfferSummary.LowestNewPrice.FormattedPrice') class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 45779d36f7bfeb43d6e7d7bfc8378ea743206fd0 Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:14:44 +0200 Subject: [PATCH 11/91] Add support for product formatted price property parsing in the AmazonProduct class --- amazon/api.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index b648cd5..63da57c 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1262,6 +1262,16 @@ def formatted_price(self): """ return self._safe_get_element_text('OfferSummary.LowestNewPrice.FormattedPrice') + @property + def running_time(self): + """RunningTime. + + :return: + RunningTime (string) + """ + return self._safe_get_element_text('ItemAttributes.RunningTime') + + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. Allows iterating over Items in the cart. From 3c8c062487abb36f070a0c73875572388b647c72 Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:16:35 +0200 Subject: [PATCH 12/91] Add support for product studio property parsing in the AmazonProduct class --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 63da57c..b416451 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1271,6 +1271,15 @@ def running_time(self): """ return self._safe_get_element_text('ItemAttributes.RunningTime') + @property + def studio(self): + """Studio. + + :return: + Studio (string) + """ + return self._safe_get_element_text('ItemAttributes.Studio') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 1f0e7ba2ece9256c0a2c60e136ee051aee99605d Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:18:59 +0200 Subject: [PATCH 13/91] Add support for product is preorder property parsing in the AmazonProduct class --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index b416451..68144c8 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1280,6 +1280,15 @@ def studio(self): """ return self._safe_get_element_text('ItemAttributes.Studio') + @property + def is_preorder(self): + """IsPreorder (Is Preorder) + + :return: + IsPreorder (string). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.IsPreorder') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 98837ee47f58c8f35bffe8b7d2583594b974b67d Mon Sep 17 00:00:00 2001 From: Firas Mbarek Date: Sun, 10 Apr 2016 20:20:02 +0200 Subject: [PATCH 14/91] Add support for product details page url property parsing in the AmazonProduct class --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 68144c8..a3796b2 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1289,6 +1289,15 @@ def is_preorder(self): """ return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.IsPreorder') + @property + def detail_page_url(self): + """DetailPageURL. + + :return: + DetailPageURL (string) + """ + return self._safe_get_element_text('DetailPageURL') + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. From 57dfe11a9ef762cf68cc2f31476464dfe9d9a849 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 27 Apr 2016 10:56:27 +0100 Subject: [PATCH 15/91] Fix tests and pep88 --- amazon/__init__.py | 2 +- amazon/api.py | 20 ++++++------- test-requirements.txt | 1 + tests.py | 66 +++++++++++++------------------------------ 4 files changed, 32 insertions(+), 57 deletions(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index e27dcd2..b16bcaf 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.0.2' +__version__ = '2.1.0' __author__ = 'Yoav Aviram' diff --git a/amazon/api.py b/amazon/api.py index e8d37bd..44101ff 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -199,7 +199,8 @@ def lookup(self, ResponseGroup="Large", **kwargs): def lookup_bulk(self, ResponseGroup="Large", **kwargs): """Lookup Amazon Products in bulk. - Returns all products matching requested ASINs, ignoring invalid entries. + Returns all products matching requested ASINs, ignoring invalid + entries. :return: A list of :class:`~.AmazonProduct` instances. @@ -340,14 +341,12 @@ def cart_add(self, items, CartId=None, HMAC=None, **kwargs): if len(items) > 10: raise CartException("You can't add more than 10 items at once") - offer_id_key_template = 'Item.%s.OfferListingId' - quantity_key_template = 'Item.%s.Quantity' - i = 0 + offer_id_key_template = 'Item.{0}.OfferListingId' + quantity_key_template = 'Item.{0}.Quantity' - for item in items: - i += 1 - kwargs[offer_id_key_template % (i, )] = item['offer_id'] - kwargs[quantity_key_template % (i, )] = item['quantity'] + for i, item in enumerate(items): + kwargs[offer_id_key_template.format(i)] = item['offer_id'] + kwargs[quantity_key_template.format(i)] = item['quantity'] response = self.api.CartAdd(CartId=CartId, HMAC=HMAC, **kwargs) root = objectify.fromstring(response) @@ -717,11 +716,12 @@ def price_and_currency(self): @property def offer_id(self): """Offer ID - + :return: Offer ID (string). """ - return self._safe_get_element('Offers.Offer.OfferListing.OfferListingId') + return self._safe_get_element( + 'Offers.Offer.OfferListing.OfferListingId') @property def asin(self): diff --git a/test-requirements.txt b/test-requirements.txt index aba1014..8fd7ee6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ Sphinx==1.1.3 +mock \ No newline at end of file diff --git a/tests.py b/tests.py index 0a5880d..b52c195 100644 --- a/tests.py +++ b/tests.py @@ -1,7 +1,7 @@ import unittest import mock -from nose.tools import assert_equals, assert_true, assert_false +from nose.tools import assert_equals, assert_true, assert_false, assert_raises import datetime from amazon.api import (AmazonAPI, @@ -15,7 +15,7 @@ AMAZON_ASSOC_TAG) -TEST_ASIN = "B007HCCNJU" +TEST_ASIN = "0312098286" PRODUCT_ATTRIBUTES = [ 'asin', 'author', 'binding', 'brand', 'browse_nodes', 'ean', 'edition', @@ -110,14 +110,14 @@ def test_lookup_nonexistent_asin(self): Tests that a product lookup for a nonexistent ASIN raises AsinNotFound. """ - self.assertRaises(AsinNotFound, self.amazon.lookup, ItemId="ABCD1234") + assert_raises(AsinNotFound, self.amazon.lookup, ItemId="ABCD1234") def test_batch_lookup(self): """Test Batch Product Lookup. Tests that a batch product lookup request returns multiple results. """ - asins = ['B007HCCNJU', 'B00BWYQ9YE', + asins = [TEST_ASIN, 'B00BWYQ9YE', 'B00BWYRF7E', 'B00D2KJDXA'] products = self.amazon.lookup(ItemId=','.join(asins)) assert_equals(len(products), len(asins)) @@ -157,34 +157,9 @@ def test_search_no_results(self): Tests that a product search with that returns no results throws a SearchException. """ - products = self.amazon.search(Title='HarryPotter', + products = self.amazon.search(Title='no-such-thing-on-amazon', SearchIndex='Automotive') - self.assertRaises(SearchException, (x for x in products).next) - - @mock.patch('amazon.api.objectify') - def test_search_throttled(self, lxml): - """Test handling of a search throttled by Amazon. - - Tests that a product search that triggers throttling by Amazon: - http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ErrorNumbers.html - """ - - lxml.return_value = { - "Items": { - "Request": { - "Errors": { - "Error": { - "Code": "AWS.ParameterOutOfRange", - "Message": "Request has been throttled" - } - } - } - } - } - - AmazonSearch._query() - - + assert_raises(SearchException, (x for x in products).next) def test_amazon_api_defaults_to_US(self): """Test Amazon API defaults to the US store.""" @@ -261,7 +236,7 @@ def test_single_creator(self): product = self.amazon.lookup(ItemId="B00005NZJA") creators = dict(product.creators) assert_equals(creators[u"Jonathan Davis"], u"Narrator") - assert_equals(len(creators.values()), 1) + assert_equals(len(creators.values()), 2) def test_multiple_creators(self): """Test a product with multiple creators @@ -358,18 +333,17 @@ def setUp(self): ) def test_cart_clear_required_params(self): - self.assertRaises(CartException, self.amazon.cart_clear, None, None) - self.assertRaises(CartException, self.amazon.cart_clear, 'NotNone', - None) - self.assertRaises(CartException, self.amazon.cart_clear, None, - 'NotNone') + assert_raises(CartException, self.amazon.cart_clear, None, None) + assert_raises(CartException, self.amazon.cart_clear, 'NotNone', + None) + assert_raises(CartException, self.amazon.cart_clear, None, + NotNone') def build_cart_object(self): product = self.amazon.lookup(ItemId="B0016J8AOC") return self.amazon.cart_create( { - 'offer_id': product._safe_get_element( - 'Offers.Offer.OfferListing.OfferListingId'), + 'offer_id': product.offer_id, 'quantity': 1 } ) @@ -380,7 +354,7 @@ def test_cart_create_single_item(self): def test_cart_create_multiple_item(self): product1 = self.amazon.lookup(ItemId="B0016J8AOC") - product2 = self.amazon.lookup(ItemId="B007HCCNJU") + product2 = self.amazon.lookup(ItemId=TEST_ASIN) asins = [product1.asin, product2.asin] cart = self.amazon.cart_create([ @@ -409,8 +383,8 @@ def test_cart_clear_wrong_hmac(self): # never use urlencoded hmac, as library encodes as well. Just in case # hmac = url_encoded_hmac we add some noise hmac = cart.url_encoded_hmac + '%3d' - self.assertRaises(CartInfoMismatchException, self.amazon.cart_clear, - cart.cart_id, hmac) + assert_raises(CartInfoMismatchException, self.amazon.cart_clear, + cart.cart_id, hmac) def test_cart_attributes(self): cart = self.build_cart_object() @@ -438,12 +412,12 @@ def test_cart_get_wrong_hmac(self): # not been used in test_cart_clear cache_clear() cart = self.build_cart_object() - self.assertRaises(CartInfoMismatchException, self.amazon.cart_get, - cart.cart_id, cart.hmac + '%3d') + assert_raises(CartInfoMismatchException, self.amazon.cart_get, + cart.cart_id, cart.hmac + '%3d') def test_cart_add(self): cart = self.build_cart_object() - product = self.amazon.lookup(ItemId="B007HCCNJU") + product = self.amazon.lookup(ItemId=TEST_ASIN) item = { 'offer_id': product._safe_get_element( 'Offers.Offer.OfferListing.OfferListingId'), @@ -468,7 +442,7 @@ def test_cart_delete(self): cart_item_id = item.cart_item_id item = {'cart_item_id': cart_item_id, 'quantity': 0} new_cart = self.amazon.cart_modify(item, cart.cart_id, cart.hmac) - self.assertRaises(KeyError, new_cart.__getitem__, cart_item_id) + assert_raises(KeyError, new_cart.__getitem__, cart_item_id) if __name__ == '__main__': unittest.main() From 1344c8b373765270a39cf915269a73f091c146ef Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 27 Apr 2016 10:58:46 +0100 Subject: [PATCH 16/91] Change string formatting to use .format() across the board. --- amazon/api.py | 26 +++++++++++--------------- tests.py | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 44101ff..983b2fe 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -305,14 +305,12 @@ def cart_create(self, items, **kwargs): if len(items) > 10: raise CartException("You can't add more than 10 items at once") - offer_id_key_template = 'Item.%s.OfferListingId' - quantity_key_template = 'Item.%s.Quantity' - i = 0 + offer_id_key_template = 'Item.{0}.OfferListingId' + quantity_key_template = 'Item.{0}.Quantity' - for item in items: - i += 1 - kwargs[offer_id_key_template % (i, )] = item['offer_id'] - kwargs[quantity_key_template % (i, )] = item['quantity'] + for i, item in enumerate(items): + kwargs[offer_id_key_template.format(i)] = item['offer_id'] + kwargs[quantity_key_template.format(i)] = item['quantity'] response = self.api.CartCreate(**kwargs) root = objectify.fromstring(response) @@ -409,14 +407,12 @@ def cart_modify(self, items, CartId=None, HMAC=None, **kwargs): if len(items) > 10: raise CartException("You can't add more than 10 items at once") - cart_item_id_key_template = 'Item.%s.CartItemId' - quantity_key_template = 'Item.%s.Quantity' - i = 0 + cart_item_id_key_template = 'Item.{0}.CartItemId' + quantity_key_template = 'Item.{0}.Quantity' - for item in items: - i += 1 - kwargs[cart_item_id_key_template % (i, )] = item['cart_item_id'] - kwargs[quantity_key_template % (i, )] = item['quantity'] + for i, item in enumerate(items): + kwargs[cart_item_id_key_template.format(i)] = item['cart_item_id'] + kwargs[quantity_key_template.format(i)] = item['quantity'] response = self.api.CartModify(CartId=CartId, HMAC=HMAC, **kwargs) root = objectify.fromstring(response) @@ -1313,7 +1309,7 @@ def __getitem__(self, cart_item_id): for item in self: if item.cart_item_id == cart_item_id: return item - raise KeyError('no item found with CartItemId: %s' % (cart_item_id,)) + raise KeyError('no item found with CartItemId: {0}'.format(cart_item_id,)) class AmazonCartItem(LXMLWrapper): diff --git a/tests.py b/tests.py index b52c195..0a12726 100644 --- a/tests.py +++ b/tests.py @@ -337,7 +337,7 @@ def test_cart_clear_required_params(self): assert_raises(CartException, self.amazon.cart_clear, 'NotNone', None) assert_raises(CartException, self.amazon.cart_clear, None, - NotNone') + 'NotNone') def build_cart_object(self): product = self.amazon.lookup(ItemId="B0016J8AOC") From 0be787dc5f40d14bd5298e389a708b00d04ebc0c Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 27 Apr 2016 11:09:39 +0100 Subject: [PATCH 17/91] Add tests for lookup_bulk. --- tests.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tests.py b/tests.py index 0a12726..3f2502d 100644 --- a/tests.py +++ b/tests.py @@ -112,10 +112,10 @@ def test_lookup_nonexistent_asin(self): """ assert_raises(AsinNotFound, self.amazon.lookup, ItemId="ABCD1234") - def test_batch_lookup(self): - """Test Batch Product Lookup. + def test_bulk_lookup(self): + """Test Baulk Product Lookup. - Tests that a batch product lookup request returns multiple results. + Tests that a bulk product lookup request returns multiple results. """ asins = [TEST_ASIN, 'B00BWYQ9YE', 'B00BWYRF7E', 'B00D2KJDXA'] @@ -124,6 +124,29 @@ def test_batch_lookup(self): for i, product in enumerate(products): assert_equals(asins[i], product.asin) + def test_lookup_bulk(self): + """Test Bulk Product Lookup. + + Tests that a bulk product lookup request returns multiple results. + """ + asins = [TEST_ASIN, 'B00BWYQ9YE', + 'B00BWYRF7E', 'B00D2KJDXA'] + products = self.amazon.lookup_bulk(ItemId=','.join(asins)) + assert_equals(len(products), len(asins)) + for i, product in enumerate(products): + assert_equals(asins[i], product.asin) + + def test_lookup_bulk_empty(self): + """Test Bulk Product Lookup With No Results. + + Tests that a bulk product lookup request with no results + returns an empty list. + """ + asins = ['not-an-asin', 'als-not-an-asin'] + products = self.amazon.lookup_bulk(ItemId=','.join(asins)) + assert_equals(type(products), list) + assert_equals(len(products), 0) + def test_search(self): """Test Product Search. From 67e94079870ddfca70ab5ac449bc6e6be52dbb92 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 27 Apr 2016 11:31:52 +0100 Subject: [PATCH 18/91] Improve docs --- README.md | 28 +++++++++++++++++++++++++++- tests.py | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e77d33..eee6334 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Lookup on amazon.de instead of amazon.com by setting the region: >>> product.price_and_currency (99.0, 'EUR') -Batch lookup requests are also supported: +Bulk lookup requests are also supported: >>> from amazon.api import AmazonAPI >>> amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) @@ -74,6 +74,8 @@ Batch lookup requests are also supported: >>> products[0].asin 'B0051QVESA' +If you'd rather get an empty list intead of exceptions use lookup_bulk() instead. + Search: >>> from amazon.api import AmazonAPI @@ -128,6 +130,25 @@ Browse Node Lookup: >>> bn.name 'eBook Readers' +Create and manipulate Carts: + + >>> from amazon.api import AmazonAPI + >>> amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) + >>> product = amazon.lookup(ItemId="B0016J8AOC") + >>> item = {'offer_id': product.offer_id, 'quantity': 1} + >>> cart = amazon.cart_create(item) + >>> fetched_cart = amazon.cart_get(cart.cart_id, cart.hmac) + >>> another_product = amazon.lookup(ItemId='0312098286') + >>> another_item = {'offer_id': another_product.offer_id, 'quantity': 1} + >>> another_cart = amazon.cart_add(another_item, cart.cart_id, cart.hmac) + >>> cart_item_id = None + >>> for item in cart: + >>> cart_item_id = item.cart_item_id + >>> modify_item = {'cart_item_id': cart_item_id, 'quantity': 3} + >>> modified_cart = amazon.cart_modify(item, cart.cart_id, cart.hmac) + >>> cleared_cart = amazon.cart_clear(cart.cart_id, cart.hmac) + + For more information about these calls, please consult the [Product Advertising API Developer Guide](http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html). @@ -139,6 +160,11 @@ To run the test suite please follow these steps: * Create a local file named: `test_settings.py` with the following variables set to the relevant values: `AMAZON_ACCESS_KEY`, `AMAZON_SECRET_KEY`, `AMAZON_ASSOC_TAG` * Run `nosetests` +Contribution +------------ +Contributors and committers are are welcome. Please message me. + + License ------- diff --git a/tests.py b/tests.py index 3f2502d..c1989c2 100644 --- a/tests.py +++ b/tests.py @@ -139,7 +139,7 @@ def test_lookup_bulk(self): def test_lookup_bulk_empty(self): """Test Bulk Product Lookup With No Results. - Tests that a bulk product lookup request with no results + Tests that a bulk product lookup request with no results returns an empty list. """ asins = ['not-an-asin', 'als-not-an-asin'] From 598f47169ae5dcd4f7cafc3051a7dba298625335 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 27 Apr 2016 12:40:22 +0100 Subject: [PATCH 19/91] Update bulk lookup docs. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eee6334..baf6753 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Bulk lookup requests are also supported: >>> from amazon.api import AmazonAPI >>> amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) - >>> products = amazon.lookup(ItemId='B0051QVESA,B005DOK8NW,B005890G8Y,B0051VVOB2,B005890G8O') + >>> products = amazon.lookup(ItemId='B00KC6I06S,B005DOK8NW,B00TSUGXKE') >>> len(products) 5 >>> products[0].asin From d9c6c254193736faf1b45adc398049e6c7df844e Mon Sep 17 00:00:00 2001 From: ijoyblue Date: Mon, 2 May 2016 10:49:29 +0800 Subject: [PATCH 20/91] add CN to DOMAIN list --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 983b2fe..300cc5c 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -22,7 +22,6 @@ # https://kdp.amazon.com/help?topicId=A1CT8LK6UW2FXJ -# CN not listed DOMAINS = { 'CA': 'ca', 'DE': 'de', @@ -33,6 +32,7 @@ 'JP': 'co.jp', 'UK': 'co.uk', 'US': 'com', + 'CN': 'cn' } AMAZON_ASSOCIATES_BASE_URL = 'http://www.amazon.{domain}/dp/' From 18117b00383583f7db6ebd3dbcf5cb5fd7b962e5 Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 18 Jun 2016 14:08:28 +0900 Subject: [PATCH 21/91] Remove unused dependency on mock --- test-requirements.txt | 3 +-- tests.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8fd7ee6..3049177 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1 @@ -Sphinx==1.1.3 -mock \ No newline at end of file +Sphinx==1.1.3 diff --git a/tests.py b/tests.py index c1989c2..a892809 100644 --- a/tests.py +++ b/tests.py @@ -1,5 +1,4 @@ import unittest -import mock from nose.tools import assert_equals, assert_true, assert_false, assert_raises From 835859196f40d132a4a8cc7d1b99079bdb435fad Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 18 Jun 2016 14:09:44 +0900 Subject: [PATCH 22/91] Add tox.ini to run tests for Python 2/3 --- .gitignore | 1 + tox.ini | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index b6feec6..c75da77 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test_settings.py _build _static _templates +.tox/ diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..6e39443 --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[tox] +envlist = py27, py33, py34, py35 +[testenv] +deps = nose +commands = nosetests From 4bddfd5088592def2e8e32817079163e699b26f0 Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 18 Jun 2016 14:27:45 +0900 Subject: [PATCH 23/91] Make tests Python 2/3 compatible --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index a892809..83dcd87 100644 --- a/tests.py +++ b/tests.py @@ -181,7 +181,7 @@ def test_search_no_results(self): """ products = self.amazon.search(Title='no-such-thing-on-amazon', SearchIndex='Automotive') - assert_raises(SearchException, (x for x in products).next) + assert_raises(SearchException, next, (x for x in products)) def test_amazon_api_defaults_to_US(self): """Test Amazon API defaults to the US store.""" From 9f9b8a00aa6526f0059ef9252e4c6cb443bd0e11 Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 18 Jun 2016 14:30:47 +0900 Subject: [PATCH 24/91] Mark as Python 2/3 compatible in setup.py --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 498891a..cb74604 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,10 @@ "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Utilities", From c0e21ef73146828b21c8035eaed2d1e384ea2d08 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 27 Jan 2017 17:44:35 +0100 Subject: [PATCH 25/91] fix tests - the URL of the images changes, apparently they switched to providing https URLs - the editorial review text was modified, the test therefore needs to be adapted --- tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests.py b/tests.py index 83dcd87..b197ec3 100644 --- a/tests.py +++ b/tests.py @@ -89,7 +89,7 @@ def test_lookup(self): assert_equals(product.ean, '0848719039726') assert_equals( product.large_image_url, - 'http://ecx.images-amazon.com/images/I/51XGerXeYeL.jpg' + 'https://images-na.ssl-images-amazon.com/images/I/51XGerXeYeL.jpg' ) assert_equals( product.get_attribute('Publisher'), @@ -284,12 +284,12 @@ def test_single_editorial_review(self): def test_multiple_editorial_reviews(self): product = self.amazon.lookup(ItemId="B000FBJCJE") - expected = u'Only once in a great' + expected = u'One of Time' assert_equals(product.editorial_reviews[0][:len(expected)], expected) expected = u'From the opening line' assert_equals(product.editorial_reviews[1][:len(expected)], expected) # duplicate data, amazon user data is great... - expected = u'Only once in a great' + expected = u'One of Time' assert_equals(product.editorial_reviews[2][:len(expected)], expected) assert_equals(len(product.editorial_reviews), 3) From d53fe4bb783981a073f840ade8b36e2958559836 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Tue, 31 Jan 2017 11:00:59 +0100 Subject: [PATCH 26/91] Implemented tests for attributes added by @MbarekFiras --- tests.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests.py b/tests.py index b197ec3..0a40482 100644 --- a/tests.py +++ b/tests.py @@ -327,6 +327,42 @@ def test_region(self): AMAZON_ASSOC_TAG, Region='UK') assert_equals(amazon.region, 'UK') + def test_is_adult(self): + product = self.amazon.lookup(ItemId="B01E7P9LEE") + assert_true(product.is_adult is not None) + + def test_product_group(self): + product = self.amazon.lookup(ItemId="B01LXM0S25") + assert_equals(product.product_group, 'DVD') + + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_equals(product.product_group, 'Digital Music Album') + + def test_product_type_name(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_equals(product.product_type_name, 'DOWNLOADABLE_MUSIC_ALBUM') + + def test_formatted_price(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_equals(product.formatted_price, '$12.49') + + def test_running_time(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_equals(product.running_time, '494') + + def test_studio(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_equals(product.studio, 'Atlantic Records UK') + + def test_is_preorder(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_true(product.is_preorder == '1') + + def test_detail_page_url(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + assert_true(product.detail_page_url.startswith('https://www.amazon.com/%C3%B7-Deluxe-Ed-Sheeran/dp/B01NBTSVDN')) + + def test_kwargs(self): amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQPS=0.7) From 1d330a917b02587769e95fe5b22dcfb68bbe330d Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Tue, 31 Jan 2017 11:39:50 +0100 Subject: [PATCH 27/91] Implement test case for 'is_last_page' flag Required small change in 'current_page' calculation, so the last value for 'current_page' is consistent with the actual number of pages and not incremented beyond TotalPages --- amazon/api.py | 4 ++-- tests.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 7663c95..9b1b9d0 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -525,7 +525,7 @@ def __init__(self, api, aws_associate_tag, **kwargs): An string representing an Amazon Associates tag. """ self.kwargs = kwargs - self.current_page = 1 + self.current_page = 0 self.is_last_page = False self.api = api self.aws_associate_tag = aws_associate_tag @@ -555,8 +555,8 @@ def iterate_pages(self): """ try: while not self.is_last_page: - yield self._query(ItemPage=self.current_page, **self.kwargs) self.current_page += 1 + yield self._query(ItemPage=self.current_page, **self.kwargs) except NoMorePages: pass diff --git a/tests.py b/tests.py index 0a40482..0c05067 100644 --- a/tests.py +++ b/tests.py @@ -173,6 +173,19 @@ def test_search_n(self): ) assert_equals(len(products), 1) + def test_search_iterate_pages(self): + products = self.amazon.search(Keywords='internet of things oreilly', + SearchIndex='Books') + assert_false(products.is_last_page) + for product in products: + if products.current_page < 8: + assert_false(products.is_last_page) + else: + assert_true(products.is_last_page) + + assert_true(products.is_last_page) + assert_true(products.current_page == 8) + def test_search_no_results(self): """Test Product Search with no results. From c6656e6fde38b4021c59fd0f7f1d688c023b6817 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 11:36:14 +0100 Subject: [PATCH 28/91] Small fixes due to pylint feedback --- amazon/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 9b1b9d0..e5de5eb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -422,7 +422,8 @@ def cart_modify(self, items, CartId=None, HMAC=None, **kwargs): return new_cart - def _check_for_cart_error(self, cart): + @staticmethod + def _check_for_cart_error(cart): if cart._safe_get_element('Cart.Request.Errors') is not None: error = cart._safe_get_element( 'Cart.Request.Errors.Error.Code').text @@ -582,7 +583,7 @@ def _query(self, ResponseGroup="Large", **kwargs): else: raise SearchException( "Amazon Search Error: '{0}', '{1}'".format(code, msg)) - if (hasattr(root.Items, 'TotalPages') and (root.Items.TotalPages == self.current_page)): + if hasattr(root.Items, 'TotalPages') and root.Items.TotalPages == self.current_page: self.is_last_page = True return root @@ -950,7 +951,7 @@ def reviews(self): """ iframe = self._safe_get_element_text('CustomerReviews.IFrameURL') has_reviews = self._safe_get_element_text('CustomerReviews.HasReviews') - if has_reviews and has_reviews == 'true': + if has_reviews is not None and has_reviews == 'true': has_reviews = True else: has_reviews = False From 859baba7e6fc96aead910bc9738a164ed6dfbc78 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 11:39:23 +0100 Subject: [PATCH 29/91] Also check Python 3 with Travis CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1c0a025..acdca1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - 2.7 + - 3.5 install: - pip install . - pip install pep8 From e53759e3a9a3827b85685e2b247fa745081c9756 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 12:10:32 +0100 Subject: [PATCH 30/91] Ability to pass Amazon API credentials via environment variables --- tests.py | 59 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/tests.py b/tests.py index 0c05067..098f6d6 100644 --- a/tests.py +++ b/tests.py @@ -9,9 +9,22 @@ SearchException, AmazonSearch, AsinNotFound) -from test_settings import (AMAZON_ACCESS_KEY, - AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG) + +try: + from test_settings import (AMAZON_ACCESS_KEY, + AMAZON_SECRET_KEY, + AMAZON_ASSOC_TAG) + _AMAZON_ACCESS_KEY = AMAZON_ACCESS_KEY + _AMAZON_SECRET_KEY = AMAZON_SECRET_KEY + _AMAZON_ASSOC_TAG = AMAZON_ASSOC_TAG +except ModuleNotFoundError: + import os + if 'AMAZON_ACCESS_KEY' not in os.environ or 'AMAZON_SECRET_KEY' not in os.environ or 'AMAZON_ASSOC_TAG' not in os.environ: + raise Exception('AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY and AMAZON_ASSOC_TAG are not configured.') + + _AMAZON_ACCESS_KEY = os.environ['AMAZON_ACCESS_KEY'] + _AMAZON_SECRET_KEY = os.environ['AMAZON_SECRET_KEY'] + _AMAZON_ASSOC_TAG = os.environ['AMAZON_ASSOC_TAG'] TEST_ASIN = "0312098286" @@ -70,9 +83,9 @@ def setUp(self): Are imported from a custom file named: 'test_settings.py' """ self.amazon = AmazonAPI( - AMAZON_ACCESS_KEY, - AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, + _AMAZON_ACCESS_KEY, + _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, CacheReader=cache_reader, CacheWriter=cache_writer, MaxQPS=0.5 @@ -199,9 +212,9 @@ def test_search_no_results(self): def test_amazon_api_defaults_to_US(self): """Test Amazon API defaults to the US store.""" amazon = AmazonAPI( - AMAZON_ACCESS_KEY, - AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG + _AMAZON_ACCESS_KEY, + _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG ) assert_equals(amazon.api.Region, "US") @@ -213,9 +226,9 @@ def test_search_amazon_uk(self): results were returned. """ amazon = AmazonAPI( - AMAZON_ACCESS_KEY, - AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, + _AMAZON_ACCESS_KEY, + _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, region="UK" ) assert_equals(amazon.api.Region, "UK", "Region has not been set to UK") @@ -326,18 +339,18 @@ def test_languages_spanish(self): assert_equals(len(product.languages), 1) def test_region(self): - amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG) + amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG) assert_equals(amazon.region, 'US') # old 'region' parameter - amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, region='UK') + amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, region='UK') assert_equals(amazon.region, 'UK') # kwargs method - amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, Region='UK') + amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, Region='UK') assert_equals(amazon.region, 'UK') def test_is_adult(self): @@ -377,8 +390,8 @@ def test_detail_page_url(self): def test_kwargs(self): - amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, MaxQPS=0.7) + amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, MaxQPS=0.7) def test_images(self): """Test images property @@ -395,9 +408,9 @@ def test_images(self): class TestAmazonCart(unittest.TestCase): def setUp(self): self.amazon = AmazonAPI( - AMAZON_ACCESS_KEY, - AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG, + _AMAZON_ACCESS_KEY, + _AMAZON_SECRET_KEY, + _AMAZON_ASSOC_TAG, CacheReader=cache_reader, CacheWriter=cache_writer, MaxQPS=0.5 From 4806083310778ba06c8ef3fc928163cf8698c37c Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 12:17:08 +0100 Subject: [PATCH 31/91] try to include nosetests into travis --- .travis.yml | 1 + test-requirements.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index acdca1e..1c86ed7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,5 @@ install: script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" + - nosetests --with-coverage sudo: false diff --git a/test-requirements.txt b/test-requirements.txt index 3049177..8d05122 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,3 @@ Sphinx==1.1.3 +coverage +coveralls From 58ffbfa5fe4e637cd08e27f24f6a85301fd5c195 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 12:21:34 +0100 Subject: [PATCH 32/91] priorize environment variables over test_settings.py --- tests.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests.py b/tests.py index 098f6d6..6401011 100644 --- a/tests.py +++ b/tests.py @@ -10,21 +10,22 @@ AmazonSearch, AsinNotFound) -try: +_AMAZON_ACCESS_KEY = None +_AMAZON_SECRET_KEY = None +_AMAZON_ASSOC_TAG = None + +import os +if 'AMAZON_ACCESS_KEY' in os.environ and 'AMAZON_SECRET_KEY' in os.environ and 'AMAZON_ASSOC_TAG' in os.environ: + _AMAZON_ACCESS_KEY = os.environ['AMAZON_ACCESS_KEY'] + _AMAZON_SECRET_KEY = os.environ['AMAZON_SECRET_KEY'] + _AMAZON_ASSOC_TAG = os.environ['AMAZON_ASSOC_TAG'] +else: from test_settings import (AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) _AMAZON_ACCESS_KEY = AMAZON_ACCESS_KEY _AMAZON_SECRET_KEY = AMAZON_SECRET_KEY _AMAZON_ASSOC_TAG = AMAZON_ASSOC_TAG -except ModuleNotFoundError: - import os - if 'AMAZON_ACCESS_KEY' not in os.environ or 'AMAZON_SECRET_KEY' not in os.environ or 'AMAZON_ASSOC_TAG' not in os.environ: - raise Exception('AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY and AMAZON_ASSOC_TAG are not configured.') - - _AMAZON_ACCESS_KEY = os.environ['AMAZON_ACCESS_KEY'] - _AMAZON_SECRET_KEY = os.environ['AMAZON_SECRET_KEY'] - _AMAZON_ASSOC_TAG = os.environ['AMAZON_ASSOC_TAG'] TEST_ASIN = "0312098286" From 9ef12ce80e9476cf369c52d8f9f14cbd056f0251 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 12:30:38 +0100 Subject: [PATCH 33/91] Trigger coveralls.io report after successful test --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1c86ed7..807be55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,6 @@ script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" - nosetests --with-coverage +after_success: + - coveralls sudo: false From ae064455f2fe2e1f83afcc53d63080c26a8b5aee Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 13:08:29 +0100 Subject: [PATCH 34/91] Use 'flaky' plugin for nosetests and retry test case if request to Amazon fails --- .travis.yml | 2 +- test-requirements.txt | 1 + tests.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 807be55..8b4da52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ install: script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" - - nosetests --with-coverage + - nosetests with-flaky --with-coverage after_success: - coveralls sudo: false diff --git a/test-requirements.txt b/test-requirements.txt index 8d05122..8132bd4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,4 @@ Sphinx==1.1.3 coverage coveralls +flaky diff --git a/tests.py b/tests.py index 6401011..4ebe57b 100644 --- a/tests.py +++ b/tests.py @@ -1,7 +1,9 @@ import unittest from nose.tools import assert_equals, assert_true, assert_false, assert_raises +from flaky import flaky +import time import datetime from amazon.api import (AmazonAPI, CartException, @@ -65,6 +67,10 @@ def cache_clear(): global CACHE CACHE = {} +def delay_rerun(): + time.sleep(3) + return True + class TestAmazonApi(unittest.TestCase): """Test Amazon API @@ -92,6 +98,7 @@ def setUp(self): MaxQPS=0.5 ) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_lookup(self): """Test Product Lookup. @@ -118,6 +125,7 @@ def test_lookup(self): assert_equals(product.browse_nodes[0].id, 2642129011) assert_equals(product.browse_nodes[0].name, 'eBook Readers') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_lookup_nonexistent_asin(self): """Test Product Lookup with a nonexistent ASIN. @@ -125,6 +133,7 @@ def test_lookup_nonexistent_asin(self): """ assert_raises(AsinNotFound, self.amazon.lookup, ItemId="ABCD1234") + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_bulk_lookup(self): """Test Baulk Product Lookup. @@ -137,6 +146,7 @@ def test_bulk_lookup(self): for i, product in enumerate(products): assert_equals(asins[i], product.asin) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_lookup_bulk(self): """Test Bulk Product Lookup. @@ -149,6 +159,7 @@ def test_lookup_bulk(self): for i, product in enumerate(products): assert_equals(asins[i], product.asin) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_lookup_bulk_empty(self): """Test Bulk Product Lookup With No Results. @@ -160,6 +171,7 @@ def test_lookup_bulk_empty(self): assert_equals(type(products), list) assert_equals(len(products), 0) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search(self): """Test Product Search. @@ -174,6 +186,7 @@ def test_search(self): else: assert_true(False, 'No search results returned.') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_n(self): """Test Product Search N. @@ -187,6 +200,7 @@ def test_search_n(self): ) assert_equals(len(products), 1) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_iterate_pages(self): products = self.amazon.search(Keywords='internet of things oreilly', SearchIndex='Books') @@ -200,6 +214,7 @@ def test_search_iterate_pages(self): assert_true(products.is_last_page) assert_true(products.current_page == 8) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_no_results(self): """Test Product Search with no results. @@ -219,6 +234,7 @@ def test_amazon_api_defaults_to_US(self): ) assert_equals(amazon.api.Region, "US") + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_amazon_uk(self): """Test Poduct Search on Amazon UK. @@ -241,6 +257,7 @@ def test_search_amazon_uk(self): is_gbp = 'GBP' in currencies assert_true(is_gbp, "Currency is not GBP, cannot be Amazon UK, though") + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_similarity_lookup(self): """Test Similarity Lookup. @@ -249,6 +266,7 @@ def test_similarity_lookup(self): products = self.amazon.similarity_lookup(ItemId=TEST_ASIN) assert_true(len(products) > 5) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_product_attributes(self): """Test Product Attributes. @@ -258,6 +276,7 @@ def test_product_attributes(self): for attribute in PRODUCT_ATTRIBUTES: getattr(product, attribute) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_browse_node_lookup(self): """Test Browse Node Lookup. @@ -269,6 +288,7 @@ def test_browse_node_lookup(self): assert_equals(bn.name, 'eBook Readers') assert_equals(bn.is_category_root, False) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_obscure_date(self): """Test Obscure Date Formats @@ -279,6 +299,7 @@ def test_obscure_date(self): assert_equals(product.publication_date.month, 5) assert_true(isinstance(product.publication_date, datetime.date)) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_single_creator(self): """Test a product with a single creator """ @@ -287,6 +308,7 @@ def test_single_creator(self): assert_equals(creators[u"Jonathan Davis"], u"Narrator") assert_equals(len(creators.values()), 2) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_multiple_creators(self): """Test a product with multiple creators """ @@ -296,12 +318,14 @@ def test_multiple_creators(self): assert_equals(creators[u"Colin Azariah-Kribbs"], u"Editor") assert_equals(len(creators.values()), 2) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_no_creators(self): """Test a product with no creators """ product = self.amazon.lookup(ItemId="8420658537") assert_false(product.creators) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_single_editorial_review(self): product = self.amazon.lookup(ItemId="1930846258") expected = u'In the title piece, Alan Turing' @@ -309,6 +333,7 @@ def test_single_editorial_review(self): assert_equals(product.editorial_review, product.editorial_reviews[0]) assert_equals(len(product.editorial_reviews), 1) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_multiple_editorial_reviews(self): product = self.amazon.lookup(ItemId="B000FBJCJE") expected = u'One of Time' @@ -321,6 +346,7 @@ def test_multiple_editorial_reviews(self): assert_equals(len(product.editorial_reviews), 3) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_languages_english(self): """Test Language Data @@ -330,6 +356,7 @@ def test_languages_english(self): assert_true('english' in product.languages) assert_equals(len(product.languages), 1) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_languages_spanish(self): """Test Language Data @@ -354,10 +381,12 @@ def test_region(self): _AMAZON_ASSOC_TAG, Region='UK') assert_equals(amazon.region, 'UK') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_is_adult(self): product = self.amazon.lookup(ItemId="B01E7P9LEE") assert_true(product.is_adult is not None) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_product_group(self): product = self.amazon.lookup(ItemId="B01LXM0S25") assert_equals(product.product_group, 'DVD') @@ -365,26 +394,32 @@ def test_product_group(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.product_group, 'Digital Music Album') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_product_type_name(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.product_type_name, 'DOWNLOADABLE_MUSIC_ALBUM') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_formatted_price(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.formatted_price, '$12.49') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_running_time(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.running_time, '494') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_studio(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.studio, 'Atlantic Records UK') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_is_preorder(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_true(product.is_preorder == '1') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_detail_page_url(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_true(product.detail_page_url.startswith('https://www.amazon.com/%C3%B7-Deluxe-Ed-Sheeran/dp/B01NBTSVDN')) @@ -394,6 +429,7 @@ def test_kwargs(self): amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, _AMAZON_ASSOC_TAG, MaxQPS=0.7) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_images(self): """Test images property From 7eeb4ed9bc12682b3b516b8f438bfa93b7b46979 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 13:11:03 +0100 Subject: [PATCH 35/91] missing '--' -___-; --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8b4da52..d6f0127 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ install: script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" - - nosetests with-flaky --with-coverage + - nosetests --with-flaky --with-coverage after_success: - coveralls sudo: false From 8a05d9710c5f65ceb88e9b32a68a6603c8193e10 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 13:14:41 +0100 Subject: [PATCH 36/91] missing argument *args --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index 4ebe57b..8bc86c7 100644 --- a/tests.py +++ b/tests.py @@ -67,7 +67,7 @@ def cache_clear(): global CACHE CACHE = {} -def delay_rerun(): +def delay_rerun(*args): time.sleep(3) return True From eb14c15f1a65a97f56db990198699380b8a42346 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 14:07:27 +0100 Subject: [PATCH 37/91] Implement availability related properties: - availability - availability_type - availability_min_hours - availability_max_hours --- amazon/api.py | 36 ++++++++++++++++++++++++++++++++++++ tests.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index e5de5eb..e8ade6f 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1325,6 +1325,42 @@ def is_preorder(self): """ return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.IsPreorder') + @property + def availability(self): + """Availability + + :return: + Availability (string). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.Availability') + + @property + def availability_type(self): + """AvailabilityAttributes.AvailabilityType + + :return: + AvailabilityType (string). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.AvailabilityType') + + @property + def availability_min_hours(self): + """AvailabilityAttributes.MinimumHours + + :return: + MinimumHours (int). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MinimumHours') + + @property + def availability_max_hours(self): + """AvailabilityAttributes.MaximumHours + + :return: + MaximumHours (int). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MaximumHours') + @property def detail_page_url(self): """DetailPageURL. diff --git a/tests.py b/tests.py index 8bc86c7..fe0fcdc 100644 --- a/tests.py +++ b/tests.py @@ -417,13 +417,47 @@ def test_studio(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_is_preorder(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") - assert_true(product.is_preorder == '1') + assert_equal(product.is_preorder, '1') @flaky(max_runs=3, rerun_filter=delay_rerun) def test_detail_page_url(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_true(product.detail_page_url.startswith('https://www.amazon.com/%C3%B7-Deluxe-Ed-Sheeran/dp/B01NBTSVDN')) + @flaky(max_runs=3, rerun_filter=delay_rerun) + def test_availability(self): + product = self.amazon.lookup(ItemId="B01CD5VC92") + assert_equals(product.availability, 'Usually ships in 24 hours') + + product = self.amazon.lookup(ItemId="1491914254") # pre-order book + assert_equals(product.availability, 'Not yet published') + + product = self.amazon.lookup(ItemId="B000SML2BQ") # late availability + assert_equals(product.availability, 'Usually ships in 3 to 6 weeks') + + product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable + assert_true(product.availability is None) + + @flaky(max_runs=3, rerun_filter=delay_rerun) + def test_availability_type(self): + product = self.amazon.lookup(ItemId="B01CD5VC92") + assert_equals(product.availability_type, 'now') + + product = self.amazon.lookup(ItemId="1491914254") # pre-order book + assert_equals(product.availability_type, 'now') + + product = self.amazon.lookup(ItemId="B000SML2BQ") # late availability + assert_equals(product.availability_type, 'now') + + product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable + assert_true(product.availability_type is None) + + @flaky(max_runs=3, rerun_filter=delay_rerun) + def test_availability_min_max_hours(self): + product = self.amazon.lookup(ItemId="B01CD5VC92") + assert_equals(product.availability_min_hours, '0') + assert_equals(product.availability_max_hours, '0') + def test_kwargs(self): amazon = AmazonAPI(_AMAZON_ACCESS_KEY, _AMAZON_SECRET_KEY, From 9d864c8ce1ba5ba1c9ac5f9684f4f76410860179 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Fri, 3 Feb 2017 14:13:48 +0100 Subject: [PATCH 38/91] typo: equal --> equals --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index fe0fcdc..c93a619 100644 --- a/tests.py +++ b/tests.py @@ -417,7 +417,7 @@ def test_studio(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_is_preorder(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") - assert_equal(product.is_preorder, '1') + assert_equals(product.is_preorder, '1') @flaky(max_runs=3, rerun_filter=delay_rerun) def test_detail_page_url(self): From b4aa85b2ca8f1944ac537b677cbfb50208f30a48 Mon Sep 17 00:00:00 2001 From: Norman Date: Tue, 7 Feb 2017 11:55:00 +0100 Subject: [PATCH 39/91] Fix docstring --- amazon/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index e8ade6f..27254da 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1348,7 +1348,7 @@ def availability_min_hours(self): """AvailabilityAttributes.MinimumHours :return: - MinimumHours (int). + MinimumHours (string). """ return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MinimumHours') @@ -1357,7 +1357,7 @@ def availability_max_hours(self): """AvailabilityAttributes.MaximumHours :return: - MaximumHours (int). + MaximumHours (string). """ return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MaximumHours') From a2c1c8510728922d67796ecf3464a49bd49fac66 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Tue, 7 Feb 2017 12:17:20 +0100 Subject: [PATCH 40/91] - fix test case for multiple editorial reviews as the content changed on Amanzon's side - increase delay from 3 to 5 secs when retrying request to Amazon --- tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests.py b/tests.py index c93a619..bc6caa6 100644 --- a/tests.py +++ b/tests.py @@ -67,8 +67,8 @@ def cache_clear(): global CACHE CACHE = {} -def delay_rerun(*args): - time.sleep(3) +def delay_rerun(err, *args): + time.sleep(5) return True @@ -335,13 +335,13 @@ def test_single_editorial_review(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_multiple_editorial_reviews(self): - product = self.amazon.lookup(ItemId="B000FBJCJE") - expected = u'One of Time' + product = self.amazon.lookup(ItemId="B01HQA6EOC") + expected = u'

Introducing an instant classic—master storyteller' assert_equals(product.editorial_reviews[0][:len(expected)], expected) - expected = u'From the opening line' + expected = u'An Amazon Best Book of February 2017:' assert_equals(product.editorial_reviews[1][:len(expected)], expected) # duplicate data, amazon user data is great... - expected = u'One of Time' + expected = u'

Introducing an instant classic—master storyteller' assert_equals(product.editorial_reviews[2][:len(expected)], expected) assert_equals(len(product.editorial_reviews), 3) From 13387292e27de3ad9fbae9e82266e6eb6dba8d45 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Tue, 7 Feb 2017 13:59:12 +0100 Subject: [PATCH 41/91] test failed, unicode? --- tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests.py b/tests.py index bc6caa6..0e3d2d1 100644 --- a/tests.py +++ b/tests.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + import unittest from nose.tools import assert_equals, assert_true, assert_false, assert_raises From 465a5d16870e9b269ec88a306b7dee034cfadc8f Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Mon, 20 Feb 2017 17:17:54 +0000 Subject: [PATCH 42/91] Update test ASINS --- tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests.py b/tests.py index b197ec3..98889e2 100644 --- a/tests.py +++ b/tests.py @@ -84,12 +84,12 @@ def test_lookup(self): Tests that a product lookup for a kindle returns results and that the main methods are working. """ - product = self.amazon.lookup(ItemId="B00I15SB16") + product = self.amazon.lookup(ItemId="B00ZV9PXP2") assert_true('Kindle' in product.title) - assert_equals(product.ean, '0848719039726') + assert_equals(product.ean, '0848719083774') assert_equals( product.large_image_url, - 'https://images-na.ssl-images-amazon.com/images/I/51XGerXeYeL.jpg' + 'https://images-na.ssl-images-amazon.com/images/I/51hrdzXLUHL.jpg' ) assert_equals( product.get_attribute('Publisher'), @@ -97,7 +97,7 @@ def test_lookup(self): ) assert_equals(product.get_attributes( ['ItemDimensions.Width', 'ItemDimensions.Height']), - {'ItemDimensions.Width': '469', 'ItemDimensions.Height': '40'}) + {'ItemDimensions.Width': '450', 'ItemDimensions.Height': '36'}) assert_true(len(product.browse_nodes) > 0) assert_true(product.price_and_currency[0] is not None) assert_true(product.price_and_currency[1] is not None) From f35756d90369e667c3a921268d6283cc9e8a8dd2 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Mon, 20 Feb 2017 17:32:46 +0000 Subject: [PATCH 43/91] Fix tests --- tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests.py b/tests.py index 1beae71..c9c4349 100644 --- a/tests.py +++ b/tests.py @@ -410,7 +410,7 @@ def test_formatted_price(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_running_time(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") - assert_equals(product.running_time, '494') + assert_equals(product.running_time, '774') @flaky(max_runs=3, rerun_filter=delay_rerun) def test_studio(self): @@ -429,27 +429,27 @@ def test_detail_page_url(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_availability(self): - product = self.amazon.lookup(ItemId="B01CD5VC92") + product = self.amazon.lookup(ItemId="B00ZV9PXP2") assert_equals(product.availability, 'Usually ships in 24 hours') product = self.amazon.lookup(ItemId="1491914254") # pre-order book assert_equals(product.availability, 'Not yet published') product = self.amazon.lookup(ItemId="B000SML2BQ") # late availability - assert_equals(product.availability, 'Usually ships in 3 to 6 weeks') + assert_true(product.availability is not None) product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable assert_true(product.availability is None) @flaky(max_runs=3, rerun_filter=delay_rerun) def test_availability_type(self): - product = self.amazon.lookup(ItemId="B01CD5VC92") + product = self.amazon.lookup(ItemId="B00ZV9PXP2") assert_equals(product.availability_type, 'now') product = self.amazon.lookup(ItemId="1491914254") # pre-order book assert_equals(product.availability_type, 'now') - product = self.amazon.lookup(ItemId="B000SML2BQ") # late availability + product = self.amazon.lookup(ItemId="B00ZV9PXP2") # late availability assert_equals(product.availability_type, 'now') product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable @@ -457,7 +457,7 @@ def test_availability_type(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_availability_min_max_hours(self): - product = self.amazon.lookup(ItemId="B01CD5VC92") + product = self.amazon.lookup(ItemId="B00ZV9PXP2") assert_equals(product.availability_min_hours, '0') assert_equals(product.availability_max_hours, '0') From 9deb12afd3ae98c1d6bbe227d866a59406457e88 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Mon, 20 Feb 2017 17:33:33 +0000 Subject: [PATCH 44/91] Bump version --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index b16bcaf..1bd680d 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.1.0' +__version__ = '2.2.0' __author__ = 'Yoav Aviram' From 26e9c1bc030931cd8d11436963b3e3e03cb7d163 Mon Sep 17 00:00:00 2001 From: Norman Thomas Date: Tue, 21 Feb 2017 11:43:09 +0100 Subject: [PATCH 45/91] Use `Decimal` for prices instead of `float` --- amazon/api.py | 12 +++++++----- tests.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 27254da..4ac28eb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -19,6 +19,7 @@ import bottlenose from lxml import objectify, etree import dateutil.parser +from decimal import Decimal # https://kdp.amazon.com/help?topicId=A1CT8LK6UW2FXJ @@ -688,7 +689,7 @@ def price_and_currency(self): :return: A tuple containing: - 1. Float representation of price. + 1. Decimal representation of price. 2. ISO Currency code (string). """ price = self._safe_get_element_text( @@ -708,8 +709,8 @@ def price_and_currency(self): currency = self._safe_get_element_text( 'OfferSummary.LowestNewPrice.CurrencyCode') if price: - fprice = float(price) / 100 if 'JP' not in self.region else price - return fprice, currency + dprice = Decimal(price) / 100 if 'JP' not in self.region else Decimal(price) + return dprice, currency else: return None, None @@ -1117,14 +1118,15 @@ def list_price(self): :return: A tuple containing: - 1. Float representation of price. + 1. Decimal representation of price. 2. ISO Currency code (string). """ price = self._safe_get_element_text('ItemAttributes.ListPrice.Amount') currency = self._safe_get_element_text( 'ItemAttributes.ListPrice.CurrencyCode') if price: - return float(price) / 100, currency + dprice = Decimal(price) / 100 if 'JP' not in self.region else Decimal(price) + return dprice, currency else: return None, None diff --git a/tests.py b/tests.py index c9c4349..71f1278 100644 --- a/tests.py +++ b/tests.py @@ -8,6 +8,7 @@ import time import datetime +from decimal import Decimal from amazon.api import (AmazonAPI, CartException, CartInfoMismatchException, @@ -407,6 +408,20 @@ def test_formatted_price(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") assert_equals(product.formatted_price, '$12.49') + @flaky(max_runs=3, rerun_filter=delay_rerun) + def test_price_and_currency(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + price, currency = product.price_and_currency + assert_equals(price, Decimal('12.49')) + assert_equals(currency, 'USD') + + @flaky(max_runs=3, rerun_filter=delay_rerun) + def test_list_price(self): + product = self.amazon.lookup(ItemId="B01NBTSVDN") + price, currency = product.list_price + assert_equals(price, Decimal('12.49')) + assert_equals(currency, 'USD') + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_running_time(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") From 3c2ead3fe8f8c2214ed55cf38fad23c719c4a391 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 13:23:49 +0000 Subject: [PATCH 46/91] Add coverall badge. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index baf6753..b1959a7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Amazon Simple Product API A simple Python wrapper for the Amazon.com Product Advertising API. [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) +[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) Features From 877d518be070f0ceb4045a123163162bba7e5e22 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 13:40:51 +0000 Subject: [PATCH 47/91] Limit coverage to 'amazon' package. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d6f0127..563a89e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ install: script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" - - nosetests --with-flaky --with-coverage + - nosetests --with-flaky --with-coverage --cover-package=amazon after_success: - coveralls sudo: false From 4435afb33b2eafcb45106cbecfcc21a43f1a61e2 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:16:09 +0000 Subject: [PATCH 48/91] Deploy from Travis to pypi. Bump version. --- .travis.yml | 36 +++++++++++++++++++++++------------- README.md | 5 +++++ amazon/__init__.py | 2 +- requirements.txt | 3 +-- setup.py | 8 +++++++- test-requirements.txt | 1 + 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 563a89e..e740e9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,26 @@ language: python python: - - 2.7 - - 3.5 -install: - - pip install . - - pip install pep8 - - pip install -r requirements.txt - - pip install -r test-requirements.txt -script: - - "pep8 --ignore=E501,E225,E128 amazon" - - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" - - nosetests --with-flaky --with-coverage --cover-package=amazon +- 2.7 +- 3.5 +install: +- pip install . +- pip install pep8 +- pip install -r requirements.txt +- pip install -r test-requirements.txt +script: +- pep8 --ignore=E501,E225,E128 amazon +- sphinx-build -b html -d _build/doctrees ./docs/ _build/html +- nosetests --with-flaky --with-coverage --cover-package=amazon after_success: - - coveralls -sudo: false +- coveralls +sudo: false +deploy: + provider: pypi + user: "Yoav.Aviram" + password: + secure: FOaqWOnjlWbM/K7MmyAXy1V6zDUYEkfO4FgLYiAOoTXGfNdOKSJGxKD7cb+Wq5CjN3XX/Y1KNXwXZZ4odKRWX463Q80vU4x6wpxMbFYLP36EuwiFNqW63qYLftbSkqDrLWRjp06vENDGmadStdYb9CruboLzyau07mFPpHmuWmg= + on: + tags: true +addons: + apt_packages: + - pandoc diff --git a/README.md b/README.md index b1959a7..500849d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ Amazon Simple Product API ========================== A simple Python wrapper for the Amazon.com Product Advertising API. + [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) @@ -149,6 +150,10 @@ Create and manipulate Carts: >>> modified_cart = amazon.cart_modify(item, cart.cart_id, cart.hmac) >>> cleared_cart = amazon.cart_clear(cart.cart_id, cart.hmac) +For 'Books' SearchIndex a (Power Search)[https://docs.aws.amazon.com/AWSECommerceService/latest/DG/PowerSearchSyntax.html] option is avaialble: + + >>> products = amazon.search(Power="subject:history and (spain or mexico) and not military and language:spanish",SearchIndex='Books') + For more information about these calls, please consult the [Product Advertising API Developer Guide](http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html). diff --git a/amazon/__init__.py b/amazon/__init__.py index 1bd680d..c9b20ed 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.0' +__version__ = '2.2.2' __author__ = 'Yoav Aviram' diff --git a/requirements.txt b/requirements.txt index 7e472fc..b390d76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ lxml bottlenose -python-dateutil -nose +python-dateutil \ No newline at end of file diff --git a/setup.py b/setup.py index cb74604..c4188b1 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,16 @@ from setuptools import setup, find_packages +try: + import pypandoc + long_description = pypandoc.convert('README.md', 'rst') +except (IOError, ImportError): + long_description = '' setup(name='python-amazon-simple-product-api', version=amazon.__version__, description="A simple Python wrapper for the Amazon.com Product Advertising API", + long_description=long_description, # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ "Development Status :: 5 - Production/Stable", @@ -25,7 +31,7 @@ ], keywords='amazon, product advertising, api', author='Yoav Aviram', - author_email='support@cleverblocks.com', + author_email='yoav.aviram@gmail.com', url='https://github.com/yoavaviram/python-amazon-simple-product-api', license='Apache 2.0', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), diff --git a/test-requirements.txt b/test-requirements.txt index 8132bd4..61a2e58 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,4 @@ +nose Sphinx==1.1.3 coverage coveralls From dd42d2f677a21b61d78e99bafa742311cb0a757a Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:20:44 +0000 Subject: [PATCH 49/91] Add pypi badge. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 500849d..a86420c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) +[![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) Features From ff67727a1adc2f633843a931977d329635721b46 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:33:44 +0000 Subject: [PATCH 50/91] Fix type in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a86420c..9320a23 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Create and manipulate Carts: >>> modified_cart = amazon.cart_modify(item, cart.cart_id, cart.hmac) >>> cleared_cart = amazon.cart_clear(cart.cart_id, cart.hmac) -For 'Books' SearchIndex a (Power Search)[https://docs.aws.amazon.com/AWSECommerceService/latest/DG/PowerSearchSyntax.html] option is avaialble: +For the 'Books' SearchIndex a [Power Search](https://docs.aws.amazon.com/AWSECommerceService/latest/DG/PowerSearchSyntax.html) option is avaialble: >>> products = amazon.search(Power="subject:history and (spain or mexico) and not military and language:spanish",SearchIndex='Books') From a9eafe5305c81b2cdba0665340a773c76ebb6b04 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:35:18 +0000 Subject: [PATCH 51/91] Debug travis to pypi deployment --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index c4188b1..8078a4a 100644 --- a/setup.py +++ b/setup.py @@ -2,11 +2,9 @@ from setuptools import setup, find_packages -try: - import pypandoc - long_description = pypandoc.convert('README.md', 'rst') -except (IOError, ImportError): - long_description = '' + +long_description = pypandoc.convert('README.md', 'rst') + setup(name='python-amazon-simple-product-api', version=amazon.__version__, From 477f35e327e945272151ee0bcb88f598894a68e3 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:49:26 +0000 Subject: [PATCH 52/91] Debug tavis to pypi deployment --- .travis.yml | 1 + amazon/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e740e9a..188c67d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: install: - pip install . - pip install pep8 +- pip install pypandoc - pip install -r requirements.txt - pip install -r test-requirements.txt script: diff --git a/amazon/__init__.py b/amazon/__init__.py index c9b20ed..23e65b4 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.2' +__version__ = '2.2.4' __author__ = 'Yoav Aviram' From 6d03ab8ee75732778607cd6f575715a3aea0358a Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:55:08 +0000 Subject: [PATCH 53/91] Debug travis to pypi deploy --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8078a4a..f5acc78 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ from setuptools import setup, find_packages +import pypandoc long_description = pypandoc.convert('README.md', 'rst') From fe73bee8ad12905cf4d2c63ceab9bb49a87700cd Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 15:55:56 +0000 Subject: [PATCH 54/91] Debug travis to pypi deploy --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 188c67d..a984ca7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,6 @@ deploy: user: "Yoav.Aviram" password: secure: FOaqWOnjlWbM/K7MmyAXy1V6zDUYEkfO4FgLYiAOoTXGfNdOKSJGxKD7cb+Wq5CjN3XX/Y1KNXwXZZ4odKRWX463Q80vU4x6wpxMbFYLP36EuwiFNqW63qYLftbSkqDrLWRjp06vENDGmadStdYb9CruboLzyau07mFPpHmuWmg= - on: - tags: true addons: apt_packages: - pandoc From b455bd4a5748748632e8d1305ff97d087d436452 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:03:38 +0000 Subject: [PATCH 55/91] Debug travis to pypi deploy --- .travis.yml | 3 +-- test-requirements.txt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a984ca7..12c6277 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,10 @@ python: - 2.7 - 3.5 install: -- pip install . -- pip install pep8 - pip install pypandoc - pip install -r requirements.txt - pip install -r test-requirements.txt +- pip install . script: - pep8 --ignore=E501,E225,E128 amazon - sphinx-build -b html -d _build/doctrees ./docs/ _build/html diff --git a/test-requirements.txt b/test-requirements.txt index 61a2e58..eb0ee24 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,3 +3,4 @@ Sphinx==1.1.3 coverage coveralls flaky +pep8 \ No newline at end of file From 64ccd64b25a2320b00177a9eff743b3cf5d4f451 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:10:14 +0000 Subject: [PATCH 56/91] Debug pypi long_description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f5acc78..b443527 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import pypandoc -long_description = pypandoc.convert('README.md', 'rst') +long_description = pypandoc.convert('README.md', 'rst', format='markdown') setup(name='python-amazon-simple-product-api', From e4036c592605ec4deb59b8ea1942e865e4b613a1 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:14:19 +0000 Subject: [PATCH 57/91] Bump version --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index 23e65b4..b70e235 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.4' +__version__ = '2.2.5' __author__ = 'Yoav Aviram' From c15ced33791222fc51a556ff3575de60a1f9ebe2 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:33:59 +0000 Subject: [PATCH 58/91] Change pypandoc format to markdown_github --- amazon/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index b70e235..fb4e8e5 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.5' +__version__ = '2.2.6' __author__ = 'Yoav Aviram' diff --git a/setup.py b/setup.py index b443527..dae032f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import pypandoc -long_description = pypandoc.convert('README.md', 'rst', format='markdown') +long_description = pypandoc.convert('README.md', 'rst', format='markdown_github') setup(name='python-amazon-simple-product-api', From e28c035e83869b641222770905d8c9fd83b17a41 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:47:13 +0000 Subject: [PATCH 59/91] setup.py works without pandoc --- setup.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index dae032f..f03ac65 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,16 @@ from setuptools import setup, find_packages -import pypandoc - -long_description = pypandoc.convert('README.md', 'rst', format='markdown_github') - +try: + import pypandoc + read_md = lambda f: pypandoc.convert(f, 'rst', format='markdown') +except (IOError, ImportError): + read_md = lambda f: open(f, 'r').read() setup(name='python-amazon-simple-product-api', version=amazon.__version__, description="A simple Python wrapper for the Amazon.com Product Advertising API", - long_description=long_description, + long_description=read_md('README.md'), # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ "Development Status :: 5 - Production/Stable", From 6175701eba60875d3039bfb5df862ea8ffcb1c0f Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:54:12 +0000 Subject: [PATCH 60/91] revert pypi long description to text. --- .travis.yml | 6 ++---- amazon/__init__.py | 2 +- setup.py | 7 +------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12c6277..e29bfd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ python: - 2.7 - 3.5 install: -- pip install pypandoc - pip install -r requirements.txt - pip install -r test-requirements.txt - pip install . @@ -19,6 +18,5 @@ deploy: user: "Yoav.Aviram" password: secure: FOaqWOnjlWbM/K7MmyAXy1V6zDUYEkfO4FgLYiAOoTXGfNdOKSJGxKD7cb+Wq5CjN3XX/Y1KNXwXZZ4odKRWX463Q80vU4x6wpxMbFYLP36EuwiFNqW63qYLftbSkqDrLWRjp06vENDGmadStdYb9CruboLzyau07mFPpHmuWmg= -addons: - apt_packages: - - pandoc + on: + tags: true diff --git a/amazon/__init__.py b/amazon/__init__.py index fb4e8e5..f08d30a 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.6' +__version__ = '2.2.7' __author__ = 'Yoav Aviram' diff --git a/setup.py b/setup.py index f03ac65..5aeefdc 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,11 @@ from setuptools import setup, find_packages -try: - import pypandoc - read_md = lambda f: pypandoc.convert(f, 'rst', format='markdown') -except (IOError, ImportError): - read_md = lambda f: open(f, 'r').read() setup(name='python-amazon-simple-product-api', version=amazon.__version__, description="A simple Python wrapper for the Amazon.com Product Advertising API", - long_description=read_md('README.md'), + long_description=open('README.md', 'r').read(), # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ "Development Status :: 5 - Production/Stable", From 5807105f55f894d986b8410f60ecb147db4f0c31 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:54:38 +0000 Subject: [PATCH 61/91] Bump version --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index f08d30a..219d532 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.7' +__version__ = '2.2.8' __author__ = 'Yoav Aviram' From 428d31d5da4d67e356b8c425fa37bb09a91dfdf8 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 21 Feb 2017 16:59:48 +0000 Subject: [PATCH 62/91] Tag --- tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests.py b/tests.py index 71f1278..bc95a62 100644 --- a/tests.py +++ b/tests.py @@ -20,6 +20,7 @@ _AMAZON_SECRET_KEY = None _AMAZON_ASSOC_TAG = None + import os if 'AMAZON_ACCESS_KEY' in os.environ and 'AMAZON_SECRET_KEY' in os.environ and 'AMAZON_ASSOC_TAG' in os.environ: _AMAZON_ACCESS_KEY = os.environ['AMAZON_ACCESS_KEY'] From 6f490dbced33b074c5dad473f42e1cd147572916 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Tue, 21 Feb 2017 10:24:53 -0800 Subject: [PATCH 63/91] Add a MANIFEST.in to ensure README.md (and all other files) are included in packages Based on and verified with with . --- MANIFEST.in | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7c1e6db --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include *.md *.rst *.txt INSTALL LICENSE tox.ini .travis.yml docs/Makefile +include tests.py +recursive-include docs *.py +recursive-include docs *.rst +prune docs/_build From 53ca28e75ee2793344fab25fa9c43bed2dd98db0 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 22 Feb 2017 11:48:48 +0000 Subject: [PATCH 64/91] Modify coverall URL so that it refreshes. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9320a23..b692a67 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) -[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master?a=1)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) [![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) From bfec4f32f0d62f3838499403e1e414ab00a7e0ca Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 22 Feb 2017 12:24:05 +0000 Subject: [PATCH 65/91] Update coverall url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b692a67..9320a23 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) -[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master?a=1)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) [![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) From 34fe4ab47b0e93738ebbd0cb31932e141959b028 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 22 Feb 2017 23:53:20 +0000 Subject: [PATCH 66/91] Fix 'test_search_iterate_pages', needs to be rewritten. --- tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.py b/tests.py index bc95a62..dfe344a 100644 --- a/tests.py +++ b/tests.py @@ -211,13 +211,13 @@ def test_search_iterate_pages(self): SearchIndex='Books') assert_false(products.is_last_page) for product in products: - if products.current_page < 8: + if products.current_page < 9: assert_false(products.is_last_page) else: assert_true(products.is_last_page) assert_true(products.is_last_page) - assert_true(products.current_page == 8) + assert_true(products.current_page == 9) @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_no_results(self): From 5ccc4be2b2966bacfe8a4313a70983e5c9fba77d Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Thu, 23 Feb 2017 00:00:35 +0000 Subject: [PATCH 67/91] Refresh coverall badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9320a23..0de4077 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Build Status](https://secure.travis-ci.org/yoavaviram/python-amazon-simple-product-api.png?branch=master)](http://travis-ci.org/yoavaviram/python-amazon-simple-product-api) [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) -[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master&bust=1)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) [![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) From 66bf1576932b8c298683d6e4d0bee303c4d2393b Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 13:48:46 +0000 Subject: [PATCH 68/91] Rename LICENSE to LICENSE.txt --- LICENSE | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 999a966..0000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (C) 2012 Yoav Aviram. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file From c6588bf0ecb722d78d457f32a3d23b1dc9b70ba3 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 13:51:28 +0000 Subject: [PATCH 69/91] Add LICENSE.txt --- LICENSE.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..999a966 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright (C) 2012 Yoav Aviram. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file From 79e8676a24a3be47b6c8c5c8c1dd13709cdbc2a8 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 13:55:38 +0000 Subject: [PATCH 70/91] Remove LICENSE.txt to recreate usign Github's license creator --- LICENSE.txt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 999a966..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (C) 2012 Yoav Aviram. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file From dff6d5aab3d7f76312824aa89851fe82d1a8db73 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 13:57:01 +0000 Subject: [PATCH 71/91] Create LICENSE.txt --- LICENSE.txt | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 566e4f2cca6813c2b55b00aab3fed35378fffb20 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 15:09:08 +0000 Subject: [PATCH 72/91] Catch IOError in setup.py to prevent pypi installations from erroring. --- setup.py | 8 +++++++- tests.py | 8 ++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 5aeefdc..1b77dcd 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,16 @@ from setuptools import setup, find_packages +try: + long_description=open('READMxE.md', 'r').read() +except IOError: + long_description="" + + setup(name='python-amazon-simple-product-api', version=amazon.__version__, description="A simple Python wrapper for the Amazon.com Product Advertising API", - long_description=open('README.md', 'r').read(), + long_description=long_description, # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/tests.py b/tests.py index dfe344a..f48adbf 100644 --- a/tests.py +++ b/tests.py @@ -211,13 +211,9 @@ def test_search_iterate_pages(self): SearchIndex='Books') assert_false(products.is_last_page) for product in products: - if products.current_page < 9: - assert_false(products.is_last_page) - else: - assert_true(products.is_last_page) - + pass assert_true(products.is_last_page) - assert_true(products.current_page == 9) + @flaky(max_runs=3, rerun_filter=delay_rerun) def test_search_no_results(self): From b1f123d5e1cd5d8c7d70a1c392eb9b757dd2497a Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 15:11:32 +0000 Subject: [PATCH 73/91] Bump version --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index 219d532..6524558 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.8' +__version__ = '2.2.9' __author__ = 'Yoav Aviram' From 17998a968b9a4f742eba7e614f280b3240b5d9b9 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 15:13:50 +0000 Subject: [PATCH 74/91] Bump version --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index 6524558..3486326 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.9' +__version__ = '2.2.10' __author__ = 'Yoav Aviram' From 5b93e80a3eccf5aa19414abc998db670d17e4057 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 21:11:12 +0000 Subject: [PATCH 75/91] Fix failing test 'test_cart_modify' (test item is out of stock...) --- tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.py b/tests.py index f48adbf..41cf0c5 100644 --- a/tests.py +++ b/tests.py @@ -510,7 +510,7 @@ def test_cart_clear_required_params(self): 'NotNone') def build_cart_object(self): - product = self.amazon.lookup(ItemId="B0016J8AOC") + product = self.amazon.lookup(ItemId="B00ZV9PXP2") return self.amazon.cart_create( { 'offer_id': product.offer_id, @@ -523,7 +523,7 @@ def test_cart_create_single_item(self): assert_equals(len(cart), 1) def test_cart_create_multiple_item(self): - product1 = self.amazon.lookup(ItemId="B0016J8AOC") + product1 = self.amazon.lookup(ItemId="B00ZV9PXP2") product2 = self.amazon.lookup(ItemId=TEST_ASIN) asins = [product1.asin, product2.asin] From 47fb74e9b54df32f3388a3cbb7c2fc5d559a4830 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 21:25:34 +0000 Subject: [PATCH 76/91] Add __str__ to AmazonProduct --- amazon/api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 4ac28eb..f779e2d 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -675,6 +675,13 @@ def __init__(self, item, aws_associate_tag, api, *args, **kwargs): self.parent = None self.region = kwargs.get('region', 'US') + def __str__(self): + """Return redable representation. + + Uses the item's title. + """ + return self.title + @property def price_and_currency(self): """Get Offer Price and Currency. From 95479ad9579fcaa56df592078996f77ed21bb244 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 24 Feb 2017 22:22:50 +0000 Subject: [PATCH 77/91] Bump version to force deploymeny to pypi --- amazon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index 3486326..bf8cadb 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.2.10' +__version__ = '2.2.11' __author__ = 'Yoav Aviram' From 2620f54db6107915b76b91b8fcb31962083c989f Mon Sep 17 00:00:00 2001 From: hqtoan94 Date: Thu, 20 Apr 2017 13:55:37 +0700 Subject: [PATCH 78/91] Add number of offers in amazon Create one more function that using to return the number of sellers --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index f779e2d..ca918ab 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1378,6 +1378,15 @@ def detail_page_url(self): DetailPageURL (string) """ return self._safe_get_element_text('DetailPageURL') + + @property + def number_sellers(self): + """Number of offers - New. + + :return: + Number of offers - New (string)\ + """ + return self._safe_get_element_text('OfferSummary.TotalNew') class AmazonCart(LXMLWrapper): From 6facb974387f9308eab436d4e15fbcba55a1b0db Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 5 May 2017 22:43:21 +0100 Subject: [PATCH 79/91] Fix tests. --- tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.py b/tests.py index 41cf0c5..b598044 100644 --- a/tests.py +++ b/tests.py @@ -422,7 +422,7 @@ def test_list_price(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_running_time(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") - assert_equals(product.running_time, '774') + assert_equals(product.running_time, '3567') @flaky(max_runs=3, rerun_filter=delay_rerun) def test_studio(self): @@ -432,7 +432,7 @@ def test_studio(self): @flaky(max_runs=3, rerun_filter=delay_rerun) def test_is_preorder(self): product = self.amazon.lookup(ItemId="B01NBTSVDN") - assert_equals(product.is_preorder, '1') + assert_equals(product.is_preorder , None) @flaky(max_runs=3, rerun_filter=delay_rerun) def test_detail_page_url(self): From 42237c5a8410643618f631742d4041b73ccc3dad Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Mon, 8 May 2017 20:25:14 +0100 Subject: [PATCH 80/91] Pep8 --- amazon/api.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index ca918ab..27173a5 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -584,8 +584,9 @@ def _query(self, ResponseGroup="Large", **kwargs): else: raise SearchException( "Amazon Search Error: '{0}', '{1}'".format(code, msg)) - if hasattr(root.Items, 'TotalPages') and root.Items.TotalPages == self.current_page: - self.is_last_page = True + if hasattr(root.Items, 'TotalPages'): + if root.Items.TotalPages == self.current_page: + self.is_last_page = True return root @@ -716,7 +717,8 @@ def price_and_currency(self): currency = self._safe_get_element_text( 'OfferSummary.LowestNewPrice.CurrencyCode') if price: - dprice = Decimal(price) / 100 if 'JP' not in self.region else Decimal(price) + dprice = Decimal( + price) / 100 if 'JP' not in self.region else Decimal(price) return dprice, currency else: return None, None @@ -1132,7 +1134,8 @@ def list_price(self): currency = self._safe_get_element_text( 'ItemAttributes.ListPrice.CurrencyCode') if price: - dprice = Decimal(price) / 100 if 'JP' not in self.region else Decimal(price) + dprice = Decimal( + price) / 100 if 'JP' not in self.region else Decimal(price) return dprice, currency else: return None, None @@ -1305,7 +1308,8 @@ def formatted_price(self): :return: FormattedPrice (string) """ - return self._safe_get_element_text('OfferSummary.LowestNewPrice.FormattedPrice') + return self._safe_get_element_text( + 'OfferSummary.LowestNewPrice.FormattedPrice') @property def running_time(self): @@ -1332,7 +1336,8 @@ def is_preorder(self): :return: IsPreorder (string). """ - return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.IsPreorder') + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.AvailabilityAttributes.IsPreorder') @property def availability(self): @@ -1341,7 +1346,8 @@ def availability(self): :return: Availability (string). """ - return self._safe_get_element_text('Offers.Offer.OfferListing.Availability') + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.Availability') @property def availability_type(self): @@ -1350,7 +1356,9 @@ def availability_type(self): :return: AvailabilityType (string). """ - return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.AvailabilityType') + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.AvailabilityAttributes.AvailabilityType' + ) @property def availability_min_hours(self): @@ -1359,7 +1367,8 @@ def availability_min_hours(self): :return: MinimumHours (string). """ - return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MinimumHours') + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.AvailabilityAttributes.MinimumHours') @property def availability_max_hours(self): @@ -1368,7 +1377,8 @@ def availability_max_hours(self): :return: MaximumHours (string). """ - return self._safe_get_element_text('Offers.Offer.OfferListing.AvailabilityAttributes.MaximumHours') + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.AvailabilityAttributes.MaximumHours') @property def detail_page_url(self): @@ -1378,7 +1388,7 @@ def detail_page_url(self): DetailPageURL (string) """ return self._safe_get_element_text('DetailPageURL') - + @property def number_sellers(self): """Number of offers - New. @@ -1439,7 +1449,8 @@ def __getitem__(self, cart_item_id): for item in self: if item.cart_item_id == cart_item_id: return item - raise KeyError('no item found with CartItemId: {0}'.format(cart_item_id,)) + raise KeyError( + 'no item found with CartItemId: {0}'.format(cart_item_id,)) class AmazonCartItem(LXMLWrapper): From fdddf610ec1e8c9023ebef85b001e307ca74f616 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 23 May 2017 20:54:15 +0100 Subject: [PATCH 81/91] Clean up tests. --- tests.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests.py b/tests.py index b598044..f73481a 100644 --- a/tests.py +++ b/tests.py @@ -447,12 +447,6 @@ def test_availability(self): product = self.amazon.lookup(ItemId="1491914254") # pre-order book assert_equals(product.availability, 'Not yet published') - product = self.amazon.lookup(ItemId="B000SML2BQ") # late availability - assert_true(product.availability is not None) - - product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable - assert_true(product.availability is None) - @flaky(max_runs=3, rerun_filter=delay_rerun) def test_availability_type(self): product = self.amazon.lookup(ItemId="B00ZV9PXP2") @@ -464,9 +458,6 @@ def test_availability_type(self): product = self.amazon.lookup(ItemId="B00ZV9PXP2") # late availability assert_equals(product.availability_type, 'now') - product = self.amazon.lookup(ItemId="B01LTHP2ZK") # unavailable - assert_true(product.availability_type is None) - @flaky(max_runs=3, rerun_filter=delay_rerun) def test_availability_min_max_hours(self): product = self.amazon.lookup(ItemId="B00ZV9PXP2") From d81e93c4b8c5bf60b24d455c7ddc63318bbfcd04 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 23 May 2017 21:00:58 +0100 Subject: [PATCH 82/91] Pep8 --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index f73481a..d8b7c69 100644 --- a/tests.py +++ b/tests.py @@ -606,4 +606,4 @@ def test_cart_delete(self): assert_raises(KeyError, new_cart.__getitem__, cart_item_id) if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file From ba0768df36f6ac551f5da31ed35eaf2dc9445f08 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 23 May 2017 21:21:23 +0100 Subject: [PATCH 83/91] Pep8 --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 27173a5..7df6483 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1358,7 +1358,7 @@ def availability_type(self): """ return self._safe_get_element_text( 'Offers.Offer.OfferListing.AvailabilityAttributes.AvailabilityType' - ) + ) @property def availability_min_hours(self): From d8ee8bce9103aee94d82e81ab6f64ffaf375d82c Mon Sep 17 00:00:00 2001 From: Kan Torii Date: Mon, 21 Aug 2017 15:13:41 +0900 Subject: [PATCH 84/91] Amazon server can return Unicode (non-ASCII) characters. --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 7df6483..8e5c926 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -177,7 +177,7 @@ def lookup(self, ResponseGroup="Large", **kwargs): code = root.Items.Request.Errors.Error.Code msg = root.Items.Request.Errors.Error.Message raise LookupException( - "Amazon Product Lookup Error: '{0}', '{1}'".format(code, msg)) + u"Amazon Product Lookup Error: '{0}', '{1}'".format(code, msg)) if not hasattr(root.Items, 'Item'): raise AsinNotFound("ASIN(s) not found: '{0}'".format( etree.tostring(root, pretty_print=True))) From 56386d1d3234e4117ead1c9bf0062e39aca3b774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel?= Date: Wed, 11 Oct 2017 09:36:17 +0200 Subject: [PATCH 85/91] Add method for catch "eligible for super saver shipping" and "eligible for prime user". --- amazon/api.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 7df6483..d674d2d 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1397,6 +1397,24 @@ def number_sellers(self): Number of offers - New (string)\ """ return self._safe_get_element_text('OfferSummary.TotalNew') + + @property + def is_eligible_for_super_saver_shipping(self): + """IsEligibleForSuperSaverShipping + + :return: + IsEligibleForSuperSaverShipping (Bool). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.IsEligibleForSuperSaverShipping') + + @property + def is_eligible_for_prime(self): + """IsEligibleForPrime + + :return: + IsEligibleForPrime (Bool). + """ + return self._safe_get_element_text('Offers.Offer.OfferListing.IsEligibleForPrime') class AmazonCart(LXMLWrapper): From 3e44c750a28ac09a5e1e1045d41b3d3504e0c165 Mon Sep 17 00:00:00 2001 From: Kan Torii Date: Mon, 27 Nov 2017 12:57:59 +0900 Subject: [PATCH 86/91] Fix error log. --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 8e5c926..57feab2 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -332,7 +332,7 @@ def cart_add(self, items, CartId=None, HMAC=None, **kwargs): An :class:`~.AmazonCart`. """ if not CartId or not HMAC: - raise CartException('CartId required for CartClear call') + raise CartException('CartId and HMAC required for CartAdd call') if isinstance(items, dict): items = [items] From f1cb0e209145fcfac9444e4c733dd19deb59d31a Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Tue, 10 Apr 2018 14:42:54 +0100 Subject: [PATCH 87/91] Added comment to the readme file about pull requests. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0de4077..b538e63 100644 --- a/README.md +++ b/README.md @@ -167,10 +167,16 @@ To run the test suite please follow these steps: * Create a local file named: `test_settings.py` with the following variables set to the relevant values: `AMAZON_ACCESS_KEY`, `AMAZON_SECRET_KEY`, `AMAZON_ASSOC_TAG` * Run `nosetests` -Contribution ------------- -Contributors and committers are are welcome. Please message me. +Pull Requests +-------------- + +* All code should be unit tested +* All tests must pass +* Source code should be PEP8 complient +* Coverage shouldn't decrease +* All Pull Requests should be rebased against master before submitting the PR +**This project is looking for core contributors. Please message me.** License ------- From 37b88d8979d21be9399eb81d23784d89731319c0 Mon Sep 17 00:00:00 2001 From: Chase Roberts Date: Fri, 17 Aug 2018 13:30:22 -0600 Subject: [PATCH 88/91] Adds props for super saver shipping and prime --- amazon/api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 57feab2..adea2af 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -751,6 +751,26 @@ def sales_rank(self): """ return self._safe_get_element_text('SalesRank') + @property + def super_saver_shipping(self): + """Super Saver Shipping + + :return: + Super Saver Shipping (boolean). + """ + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.IsEligibleForSuperSaverShipping') + + @property + def prime(self): + """Prime + + :return: + Prime (boolean). + """ + return self._safe_get_element_text( + 'Offers.Offer.OfferListing.IsEligibleForPrime') + @property def offer_url(self): """Offer URL From 36a9d7453eaa1d5102ef253992336fd048de6fb8 Mon Sep 17 00:00:00 2001 From: Yoav Aviram <537092+yoavaviram@users.noreply.github.com> Date: Thu, 30 May 2019 15:04:39 +0200 Subject: [PATCH 89/91] Create CODESHELTER.md --- CODESHELTER.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CODESHELTER.md diff --git a/CODESHELTER.md b/CODESHELTER.md new file mode 100644 index 0000000..365ea79 --- /dev/null +++ b/CODESHELTER.md @@ -0,0 +1,25 @@ +# Note to Code Shelter maintainers + +I started this project a while ago and it grow and grow in popularity. Over the +past few years I've hed less and less time to work on it, and that's unfair to +all the people who use. I'd appreciate some help with maintaining the project. +I will generally be available to consult and maybe develop the occasional +feature or bugfix, but my availability is not reliable. + +It would be great if you could help out with issue triage, fixing bugs, +merging pull request, improving documentation and test coverage and +occasionally developing new features that you think are necessary. The project +is mainly feature-complete and the Amazon API is quite stable, so I don't +expect any major deviation and would appreciate being consulted before making +any drastic changes, but other than that, you have free reign. + +I have already added Code Shelter to the project's PyPI page, so feel free to +make any releases necessary. + +The codebase is in reasonable shape. The one major issue which needs sorting out +is that Amazon have terminated my associate account due to inactivity, and won't +let me create a new one (apperently maintaining this library does not qualify as +a reason). The implication is that I do not have credentials to test the library with. +I am working on sorting this one out but any help is welcome. + +Thank you! From fa09184df2ab6c4e840161c0c430a30ae205b0b5 Mon Sep 17 00:00:00 2001 From: Yoav Aviram <537092+yoavaviram@users.noreply.github.com> Date: Thu, 30 May 2019 15:05:37 +0200 Subject: [PATCH 90/91] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b538e63..f1481ef 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master&bust=1)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) [![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) - +[![Code Shelter](https://www.codeshelter.co/static/badges/badge-flat.svg)](https://www.codeshelter.co/) Features -------- From 8889d0f2ac6d18e23b628e12a30e2a74cf7a5bfc Mon Sep 17 00:00:00 2001 From: Yoav Aviram <537092+yoavaviram@users.noreply.github.com> Date: Thu, 30 May 2019 15:15:44 +0200 Subject: [PATCH 91/91] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f1481ef..ab4d917 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A simple Python wrapper for the Amazon.com Product Advertising API. [![Documentation Status](https://readthedocs.org/projects/python-amazon-simple-product-api/badge/?version=latest)](http://python-amazon-simple-product-api.readthedocs.org/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/yoavaviram/python-amazon-simple-product-api/badge.svg?branch=master&bust=1)](https://coveralls.io/github/yoavaviram/python-amazon-simple-product-api?branch=master) [![PyPI version](https://badge.fury.io/py/python-amazon-simple-product-api.svg)](https://badge.fury.io/py/python-amazon-simple-product-api) +![PyPI - License](https://img.shields.io/pypi/l/python-amazon-simple-product-api.svg) +![PyPI - Downloads](https://img.shields.io/pypi/dm/python-amazon-simple-product-api.svg) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/python-amazon-simple-product-api.svg) [![Code Shelter](https://www.codeshelter.co/static/badges/badge-flat.svg)](https://www.codeshelter.co/) Features