From 79c3698efd5b23e46d7271ef79eb6a0044ce4181 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 25 Aug 2013 14:26:36 +0100 Subject: [PATCH 001/152] update tests --- tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests.py b/tests.py index fe5c2d5..34d47a5 100644 --- a/tests.py +++ b/tests.py @@ -44,24 +44,23 @@ 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="B0051QVF7A") - assert_equals( - product.title, - 'Kindle, Wi-Fi, 6" E Ink Display - for international shipment' - ) - assert_equals(product.ean, '0814916014354') + product = self.amazon.lookup(ItemId="B007HCCNJU") + assert_true('Kindle' in product.title) + assert_equals(product.ean, '0814916017775') assert_equals( product.large_image_url, - 'http://ecx.images-amazon.com/images/I/411H%2B731ZzL.jpg' + 'http://ecx.images-amazon.com/images/I/41VZlVs8agL.jpg' ) assert_equals( product.get_attribute('Publisher'), - 'Amazon Digital Services, Inc' + 'Amazon' ) assert_equals(product.get_attributes( ['ItemDimensions.Width', 'ItemDimensions.Height']), - {'ItemDimensions.Width': '450', 'ItemDimensions.Height': '34'}) + {'ItemDimensions.Width': '650', 'ItemDimensions.Height': '130'}) 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) assert_equals(product.browse_nodes[0].id, 2642129011) assert_equals(product.browse_nodes[0].name, 'eBook Readers') From 8a6814825041e69a40c2a86533a240825c82c340 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 25 Aug 2013 14:28:20 +0100 Subject: [PATCH 002/152] pep8 --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index 34d47a5..44956d6 100644 --- a/tests.py +++ b/tests.py @@ -45,7 +45,7 @@ def test_lookup(self): main methods are working. """ product = self.amazon.lookup(ItemId="B007HCCNJU") - assert_true('Kindle' in product.title) + assert_true('Kindle' in product.title) assert_equals(product.ean, '0814916017775') assert_equals( product.large_image_url, From f49e1e492a951b857d02ee61d93dea8ac3e40f2c Mon Sep 17 00:00:00 2001 From: Patrick Socha Date: Mon, 9 Dec 2013 21:54:57 +0000 Subject: [PATCH 003/152] Fixed UK TLD from amazon.uk/ to amazon.co.uk/ --- amazon/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 70e9257..c2fab3b 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -331,7 +331,9 @@ def __init__(self, item, aws_associate_tag, api, *args, **kwargs): self.api = api self.parent = None if 'region' in kwargs: - if kwargs['region'] != "US": + if kwargs['region'] == "UK": + self.region = "co.uk" + elif kwargs['region'] != "US": self.region = kwargs['region'] else: self.region = "com" From 2b32ec4e37d265185895a36029ba2ede6cbc9f1e Mon Sep 17 00:00:00 2001 From: lechatpito Date: Mon, 17 Feb 2014 13:14:00 -0500 Subject: [PATCH 004/152] added the children property in class AmazonBrowseNode --- amazon/api.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index c2fab3b..f484a87 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -315,6 +315,19 @@ def ancestors(self): node = node.ancestor return ancestors + @property + def children(self): + """This browse node's children in the browse node tree. + + :return: + A list of this browse node's children in the browse node tree. + """ + children = [] + child_nodes = getattr(self.element, 'Children') + for child in getattr(child_nodes, 'BrowseNode', []): + children.append(AmazonBrowseNode(child)) + return children + class AmazonProduct(object): """A wrapper class for an Amazon product. From 81566983f9bf298a8adcef7caa98ded41b8e65d0 Mon Sep 17 00:00:00 2001 From: Reuben Cummings Date: Mon, 3 Mar 2014 00:53:45 +0300 Subject: [PATCH 005/152] Add SalesRank (Fixes #26) --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index f484a87..bd0c782 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -466,6 +466,15 @@ def asin(self): """ return self._safe_get_element_text('ASIN') + @property + def sales_rank(self): + """Sales Rank + + :return: + Sales Rank (integer). + """ + return self._safe_get_element_text('SalesRank') + @property def offer_url(self): """Offer URL From c099d5cef6f7be6f81e1d775aee0391db6e522b5 Mon Sep 17 00:00:00 2001 From: Reuben Cummings Date: Sat, 8 Mar 2014 21:55:09 +0300 Subject: [PATCH 006/152] Pass kwargs to AmazonProduct --- amazon/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index bd0c782..ee3537d 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -216,7 +216,8 @@ def __iter__(self): """ for page in self.iterate_pages(): for item in getattr(page.Items, 'Item', []): - yield AmazonProduct(item, self.aws_associate_tag, self.api) + yield AmazonProduct( + item, self.aws_associate_tag, self.api, **self.kwargs) def iterate_pages(self): """Iterate Pages. From 58cbf1e02a3b8fd02217775e422a3e74c0883d84 Mon Sep 17 00:00:00 2001 From: Reuben Cummings Date: Sat, 8 Mar 2014 22:28:12 +0300 Subject: [PATCH 007/152] Fix offer_url --- amazon/api.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index bd0c782..43e4b98 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -20,7 +20,20 @@ from lxml import objectify, etree -AMAZON_ASSOCIATES_BASE_URL = 'http://www.amazon.{region}/dp/' +# https://kdp.amazon.com/help?topicId=A1CT8LK6UW2FXJ +# CN not listed +DOMAINS = { + 'CA': 'ca', + 'DE': 'de', + 'ES': 'es', + 'FR': 'fr', + 'IT': 'it', + 'JP': 'co.jp', + 'UK': 'co.uk', + 'US': 'com', +} + +AMAZON_ASSOCIATES_BASE_URL = 'http://www.amazon.{domain}/dp/' class AmazonException(Exception): @@ -343,15 +356,7 @@ def __init__(self, item, aws_associate_tag, api, *args, **kwargs): self.aws_associate_tag = aws_associate_tag self.api = api self.parent = None - if 'region' in kwargs: - if kwargs['region'] == "UK": - self.region = "co.uk" - elif kwargs['region'] != "US": - self.region = kwargs['region'] - else: - self.region = "com" - else: - self.region = "com" + self.region = kwargs.get('region', 'US') def to_string(self): """Convert Item XML to string. @@ -483,7 +488,7 @@ def offer_url(self): Offer URL (string). """ return "{0}{1}/?tag={2}".format( - AMAZON_ASSOCIATES_BASE_URL.format(region=self.region.lower()), + AMAZON_ASSOCIATES_BASE_URL.format(domain=DOMAINS[self.region]), self.asin, self.aws_associate_tag) From 4099a1d7c10ba89629200c2c2f44b456274f92d7 Mon Sep 17 00:00:00 2001 From: Reuben Cummings Date: Sat, 8 Mar 2014 23:27:19 +0300 Subject: [PATCH 008/152] Make searching behave more like lookup Pass self.region to the AmazonSearch class (just like the lookup method does). --- amazon/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index bd0c782..c0289c3 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -171,6 +171,8 @@ def search(self, **kwargs): :return: An :class:`~.AmazonSearch` iterable. """ + region = kwargs.get('region', self.region) + kwargs.update({'region': region}) return AmazonSearch(self.api, self.aws_associate_tag, **kwargs) def search_n(self, n, **kwargs): @@ -181,6 +183,8 @@ def search_n(self, n, **kwargs): :return: A list of :class:`~.AmazonProduct`. """ + region = kwargs.get('region', self.region) + kwargs.update({'region': region}) items = AmazonSearch(self.api, self.aws_associate_tag, **kwargs) return list(islice(items, n)) From 3d05f1b941e5904c9d673069bff7d8a93542e080 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 25 Apr 2014 11:34:20 +0100 Subject: [PATCH 009/152] Fix lookup test. --- amazon/api.py | 2 +- setup.py | 2 +- tests.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 074e1ff..5823c08 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -105,7 +105,7 @@ def lookup(self, ResponseGroup="Large", **kwargs): An instance of :class:`~.AmazonProduct` if one item was returned, or a list of :class:`~.AmazonProduct` instances if multiple items where returned. - """ + """ response = self.api.ItemLookup(ResponseGroup=ResponseGroup, **kwargs) root = objectify.fromstring(response) if root.Items.Request.IsValid == 'False': diff --git a/setup.py b/setup.py index 3b093b2..4cda5b0 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages -version = '1.4.0' +version = '1.5.0' setup(name='python-amazon-simple-product-api', diff --git a/tests.py b/tests.py index 44956d6..af3a404 100644 --- a/tests.py +++ b/tests.py @@ -69,8 +69,8 @@ def test_batch_lookup(self): Tests that a batch product lookup request returns multiple results. """ - asins = ['B0051QVESA', 'B005DOK8NW', 'B005890G8Y', - 'B0051VVOB2', 'B005890G8O'] + asins = ['B00AWH595M', 'B007HCCNJU', 'B00BWYQ9YE', + 'B00BWYRF7E', 'B00D2KJDXA'] products = self.amazon.lookup(ItemId=','.join(asins)) assert_equals(len(products), 5) for i, product in enumerate(products): From 14ee453b04ecbb398aaad3aa88c587e4363ed396 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 25 Apr 2014 12:06:14 +0100 Subject: [PATCH 010/152] pep8 --- amazon/api.py | 2 +- tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 5823c08..074e1ff 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -105,7 +105,7 @@ def lookup(self, ResponseGroup="Large", **kwargs): An instance of :class:`~.AmazonProduct` if one item was returned, or a list of :class:`~.AmazonProduct` instances if multiple items where returned. - """ + """ response = self.api.ItemLookup(ResponseGroup=ResponseGroup, **kwargs) root = objectify.fromstring(response) if root.Items.Request.IsValid == 'False': diff --git a/tests.py b/tests.py index af3a404..d1a88b9 100644 --- a/tests.py +++ b/tests.py @@ -69,7 +69,7 @@ def test_batch_lookup(self): Tests that a batch product lookup request returns multiple results. """ - asins = ['B00AWH595M', 'B007HCCNJU', 'B00BWYQ9YE', + asins = ['B00AWH595M', 'B007HCCNJU', 'B00BWYQ9YE', 'B00BWYRF7E', 'B00D2KJDXA'] products = self.amazon.lookup(ItemId=','.join(asins)) assert_equals(len(products), 5) From dbee6b440458f21e3b4d59fe5ee5ce19780b8415 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Thu, 15 May 2014 17:55:54 +1000 Subject: [PATCH 011/152] Add dateutil and use it to parse dates. Update _safe_get_element_date to use dateutil instead of the builtin parsers which aren't as good. Add test for products with obscure dates. Update requirements. Update README to include new dependency. --- README.md | 1 + amazon/api.py | 3 ++- requirements.txt | 1 + tests.py | 9 +++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 98121b5..4c8a7df 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Before you get started, make sure you have: * Installed [Bottlenose](https://github.com/lionheart/bottlenose) (`pip install bottlenose`) * Installed lxml (`pip install lxml`) +* Installed [dateutil](http://labix.org/python-dateutil) (`pip install dateutil`) * An Amazon Product Advertising account * An AWS account diff --git a/amazon/api.py b/amazon/api.py index 074e1ff..c90fabc 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -18,6 +18,7 @@ import bottlenose from lxml import objectify, etree +import dateutil.parser # https://kdp.amazon.com/help?topicId=A1CT8LK6UW2FXJ @@ -423,7 +424,7 @@ def _safe_get_element_date(self, path, root=None): value = self._safe_get_element_text(path=path, root=root) if value is not None: try: - value = datetime.datetime.strptime(value, '%Y-%m-%d').date() + value = dateutil.parser.parse(value) except ValueError: value = None diff --git a/requirements.txt b/requirements.txt index ee08ee9..adfa3c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ lxml bottlenose +dateutil diff --git a/tests.py b/tests.py index d1a88b9..6011a81 100644 --- a/tests.py +++ b/tests.py @@ -2,6 +2,7 @@ from nose.tools import assert_equals, assert_true +import datetime from amazon.api import AmazonAPI from test_settings import (AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, @@ -161,3 +162,11 @@ def test_browse_node_lookup(self): assert_equals(bn.id, bnid) assert_equals(bn.name, 'eBook Readers') assert_equals(bn.is_category_root, False) + + def test_obscure_date(self): + """Test Obscure Date Formats + + Test a product with an obscure date format + """ + product = self.amazon.lookup(ItemId="0933635869") + assert_equals(product.publication_date, datetime.datetime(1992, 5, 15, 0, 0)) From 7885ff784f2b72239ee66b6456993c786e977491 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Thu, 15 May 2014 22:41:52 +1000 Subject: [PATCH 012/152] Fix dateutil dependency. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index adfa3c6..638de05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ lxml bottlenose -dateutil +python-dateutil From 0e29787cf12131e84973f89db25a927ee327d711 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Fri, 16 May 2014 17:01:38 +1000 Subject: [PATCH 013/152] Return a date, not a datetime. --- amazon/api.py | 2 ++ tests.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index c90fabc..83f5244 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -425,6 +425,8 @@ def _safe_get_element_date(self, path, root=None): if value is not None: try: value = dateutil.parser.parse(value) + if value: + value = value.date() except ValueError: value = None diff --git a/tests.py b/tests.py index 6011a81..2da753f 100644 --- a/tests.py +++ b/tests.py @@ -169,4 +169,6 @@ def test_obscure_date(self): Test a product with an obscure date format """ product = self.amazon.lookup(ItemId="0933635869") - assert_equals(product.publication_date, datetime.datetime(1992, 5, 15, 0, 0)) + assert_equals(product.publication_date.year, 1992) + assert_equals(product.publication_date.month, 5) + assert_true(isinstance(product.publication_date, datetime.date)) From f3948d5cbf3c10bd20a4d31eb68471611daae77a Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:24:40 +1000 Subject: [PATCH 014/152] Fix possible warnings with node checks. --- amazon/api.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 83f5244..9e02949 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -522,9 +522,10 @@ def authors(self): Returns of list of authors """ result = [] - authors = self._safe_get_element('ItemAttributes.Author') or [] - for author in authors: - result.append(author.text) + authors = self._safe_get_element('ItemAttributes.Author') + if authors is not None: + for author in authors: + result.append(author.text) return result @property @@ -774,9 +775,10 @@ def features(self): Returns a list of 'ItemAttributes.Feature' elements (strings). """ result = [] - features = self._safe_get_element('ItemAttributes.Feature') or [] - for feature in features: - result.append(feature.text) + features = self._safe_get_element('ItemAttributes.Feature') + if features is not None: + for feature in features: + result.append(feature.text) return result @property From 28d61749d815d9364121a9d9c25d31ba4af07bf4 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:27:08 +1000 Subject: [PATCH 015/152] Fix possible warnings --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 9e02949..238a123 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -405,7 +405,7 @@ def _safe_get_element_text(self, path, root=None): String or None. """ element = self._safe_get_element(path, root) - if element: + if element is not None: return element.text else: return None From 0aaf3ef6825ec434377b1b5a7749a22b7de95f3a Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 11:10:59 +1000 Subject: [PATCH 016/152] Add creators property. Iterates through the creators node and returns tuples of (name, role) pairs. --- amazon/api.py | 10 ++++++++++ tests.py | 25 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 83f5244..69e6cfb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -527,6 +527,16 @@ def authors(self): result.append(author.text) return result + @property + def creators(self): + # return tuples of name and role + result = [] + creators = self._safe_get_element('ItemAttributes.Creator') or [] + for creator in creators: + role = creator.attrib['Role'] if 'Role' in creator.attrib else None + result.append((creator.text, role)) + return result + @property def publisher(self): """Publisher. diff --git a/tests.py b/tests.py index 2da753f..3c3f313 100644 --- a/tests.py +++ b/tests.py @@ -1,6 +1,6 @@ from unittest import TestCase -from nose.tools import assert_equals, assert_true +from nose.tools import assert_equals, assert_true, assert_false import datetime from amazon.api import AmazonAPI @@ -172,3 +172,26 @@ def test_obscure_date(self): assert_equals(product.publication_date.year, 1992) assert_equals(product.publication_date.month, 5) assert_true(isinstance(product.publication_date, datetime.date)) + + def test_single_creator(self): + """Test a product with a single creator + """ + product = self.amazon.lookup(ItemId="B00005NZJA") + creators = dict(product.creators) + assert_equals(creators[u"Jonathan Davis"], u"Narrator") + assert_equals(len(creators.values()), 1) + + def test_multiple_creators(self): + """Test a product with multiple creators + """ + product = self.amazon.lookup(ItemId="B007V8RQC4") + creators = dict(product.creators) + assert_equals(creators[u"John Gregory Betancourt"], u"Editor") + assert_equals(creators[u"Colin Azariah-Kribbs"], u"Editor") + assert_equals(len(creators.values()), 2) + + def test_no_creators(self): + """Test a product with no creators + """ + product = self.amazon.lookup(ItemId="8420658537") + assert_false(product.creators) From 5b937855bf0810bf554d0da893cb931878169bd8 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:22:48 +1000 Subject: [PATCH 017/152] Fix possible warning from if check. --- amazon/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 69e6cfb..d9d5e89 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -531,10 +531,11 @@ def authors(self): def creators(self): # return tuples of name and role result = [] - creators = self._safe_get_element('ItemAttributes.Creator') or [] - for creator in creators: - role = creator.attrib['Role'] if 'Role' in creator.attrib else None - result.append((creator.text, role)) + creators = self._safe_get_element('ItemAttributes.Creator') + if creators is not None: + for creator in creators: + role = creator.attrib['Role'] if 'Role' in creator.attrib else None + result.append((creator.text, role)) return result @property From 52b8371f9c081830de1f1577a42e995c7c203540 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:03:33 +1000 Subject: [PATCH 018/152] Add editorial_reviews which returns a list of editorials. editorial_review returns the first review. Add test for single and multiple editorials. --- amazon/api.py | 26 ++++++++++++++++++++++++-- tests.py | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 83f5244..851795e 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -762,8 +762,30 @@ def editorial_review(self): :return: Editorial Review (string) """ - return self._safe_get_element_text( - 'EditorialReviews.EditorialReview.Content') + reviews = self.editorial_reviews + if reviews: + return reviews[0] + return '' + + @property + def editorial_reviews(self): + """Editorial Review. + + Returns a list of all editorial reviews. + + :return: + A list containing: + + Editorial Review (string) + """ + result = [] + reviews_node = self._safe_get_element('EditorialReviews') + + for review_node in reviews_node.iterchildren(): + content_node = getattr(review_node, 'Content') + if content_node: + result.append(content_node.text) + return result @property def features(self): diff --git a/tests.py b/tests.py index 2da753f..507e971 100644 --- a/tests.py +++ b/tests.py @@ -172,3 +172,22 @@ def test_obscure_date(self): assert_equals(product.publication_date.year, 1992) assert_equals(product.publication_date.month, 5) assert_true(isinstance(product.publication_date, datetime.date)) + + def test_single_editorial_review(self): + product = self.amazon.lookup(ItemId="1930846258") + expected = u'In the title piece, Alan Turing' + assert_equals(product.editorial_reviews[0][:len(expected)], expected) + assert_equals(product.editorial_review, product.editorial_reviews[0]) + assert_equals(len(product.editorial_reviews), 1) + + def test_multiple_editorial_reviews(self): + product = self.amazon.lookup(ItemId="B000FBJCJE") + expected = u'Only once in a great' + 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' + assert_equals(product.editorial_reviews[2][:len(expected)], expected) + + assert_equals(len(product.editorial_reviews), 3) From a3b422c57d38b4f7b9d0bb7967a8e04f90aecc57 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:23:47 +1000 Subject: [PATCH 019/152] Add check for None and fix possible warning. --- amazon/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 851795e..cae2a84 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -781,10 +781,11 @@ def editorial_reviews(self): result = [] reviews_node = self._safe_get_element('EditorialReviews') - for review_node in reviews_node.iterchildren(): - content_node = getattr(review_node, 'Content') - if content_node: - result.append(content_node.text) + if reviews_node is not None: + for review_node in reviews_node.iterchildren(): + content_node = getattr(review_node, 'Content') + if content_node is not None: + result.append(content_node.text) return result @property From 365170235c846abd3c40a293ca7373eeb6188b12 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 12:20:42 +1000 Subject: [PATCH 020/152] Add languages property. Iterates through ItemAttributes.Languages and pulls out the Language.Name text values. Add tests for english and spanish books. --- amazon/api.py | 17 +++++++++++++++++ tests.py | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 83f5244..5544a66 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -765,6 +765,23 @@ def editorial_review(self): return self._safe_get_element_text( 'EditorialReviews.EditorialReview.Content') + @property + def languages(self): + """Languages. + + Returns a set of languages in lower-case. + :return: + Returns a set of languages in lower-case (strings). + """ + result = set() + languages = self._safe_get_element('ItemAttributes.Languages') + if languages is not None: + for language in languages.iterchildren(): + text = self._safe_get_element_text('Name', language) + if text: + result.add(text.lower()) + return result + @property def features(self): """Features. diff --git a/tests.py b/tests.py index 2da753f..90c1f87 100644 --- a/tests.py +++ b/tests.py @@ -172,3 +172,23 @@ def test_obscure_date(self): assert_equals(product.publication_date.year, 1992) assert_equals(product.publication_date.month, 5) assert_true(isinstance(product.publication_date, datetime.date)) + + def test_languages_english(self): + """Test Language Data + + Test an English product + """ + product = self.amazon.lookup(ItemId="1930846258") + assert_true('english' in product.languages) + assert_equals(len(product.languages), 1) + + def test_languages_spanish(self): + """Test Language Data + + Test an English product + """ + product = self.amazon.lookup(ItemId="8420658537") + assert_true('spanish' in product.languages) + assert_equals(len(product.languages), 1) + + From 5f05ef7aa0aa9943def556022e6ca18a8cf551e2 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Tue, 20 May 2014 13:08:36 +1000 Subject: [PATCH 021/152] Add a docstring for creators property. --- amazon/api.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index d9d5e89..aa709e4 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -529,6 +529,18 @@ def authors(self): @property def creators(self): + """Creators. + + Creators are not the authors. These are usually editors, translators, + narrators, etc. + + :return: + Returns a list of creators where each is a tuple containing: + + 1. The creators name (string). + 2. The creators role (string). + + """ # return tuples of name and role result = [] creators = self._safe_get_element('ItemAttributes.Creator') From 890eb8ca0bb767d4b53a009f56242d937271fd45 Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Wed, 4 Jun 2014 22:12:10 +1000 Subject: [PATCH 022/152] Add kwargs, redirect 'region' to 'Region'. Add kwargs to AmazonAPI constructor. kwargs are passed directly to bottlenose.Amazon(...). This allows access to MaxQPS and other niceties. Provide a 'region'->'Region' redirect so old code won't break. New code should use the bottlenose Region property. Add tests for region, Region and MaxQPS. --- amazon/api.py | 49 +++++++++++++++++++++++++++++++++++++++++++------ tests.py | 15 +++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 9b355e0..f441100 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -80,24 +80,61 @@ class BrowseNodeLookupException(AmazonException): class AmazonAPI(object): - def __init__(self, aws_key, aws_secret, aws_associate_tag, region="US"): + def __init__(self, aws_key, aws_secret, aws_associate_tag, **kwargs): """Initialize an Amazon API Proxy. + kwargs values are passed directly to Bottlenose. Check the Bottlenose + API for valid values (some are provided below). + For legacy support, the older 'region' value is still supported. + Code should be updated to use the Bottlenose 'Region' value + instead. + :param aws_key: A string representing an AWS authentication key. :param aws_secret: A string representing an AWS authentication secret. :param aws_associate_tag: A string representing an AWS associate tag. - :param region: - A string representing the region, defaulting to "US" (amazon.com) + + Important Bottlenose arguments: + :param Region: + ccTLD you want to search for products on (e.g. 'UK' + for amazon.co.uk). See keys of bottlenose.api.SERVICE_DOMAINS for options, which were CA, CN, DE, ES, FR, IT, JP, UK, US at the time of writing. - """ + Must be uppercase. Default is 'US' (amazon.com). + :param MaxQPS: + Optional maximum queries per second. If we've made an API call + on this object more recently that 1/MaxQPS, we'll wait + before making the call. Useful for making batches of queries. + You generally want to set this a little lower than the + max (so 0.9, not 1.0). + Amazon limits the number of calls per hour, so for long running + tasks this should be set to 0.9 to ensure you don't hit the maximum. + Defaults to None (unlimited). + :param Timeout: + Optional timeout for queries. + Defaults to None. + :param CacheReader: + Called before attempting to make an API call. + A function that takes a single argument, the URL that + would be passed to the API, minus auth information, + and returns a cached version of the (unparsed) response, + or None. + Defaults to None. + :param CacheWriter: + Called after a successful API call. A function that + takes two arguments, the same URL passed to + CacheReader, and the (unparsed) API response. + Defaults to None. + """ + # support older style calls + if 'region' in kwargs: + kwargs['Region'] = kwargs['region'] self.api = bottlenose.Amazon( - aws_key, aws_secret, aws_associate_tag, Region=region) + aws_key, aws_secret, aws_associate_tag, **kwargs) self.aws_associate_tag = aws_associate_tag - self.region = region + self.region = kwargs.get('Region', 'US') def lookup(self, ResponseGroup="Large", **kwargs): """Lookup an Amazon Product. diff --git a/tests.py b/tests.py index 1188436..81e860e 100644 --- a/tests.py +++ b/tests.py @@ -232,3 +232,18 @@ def test_languages_spanish(self): product = self.amazon.lookup(ItemId="8420658537") assert_true('spanish' in product.languages) assert_equals(len(product.languages), 1) + + def test_region(self): + 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') + assert_equals(amazon.region, 'UK') + + # kwargs method + amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, Region='UK') + assert_equals(amazon.region, 'UK') + + def test_kwargs(self): + amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQS=0.7) From 2a71d2adf4ee655901374e7f501a70a64c6c090a Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Wed, 4 Jun 2014 22:21:35 +1000 Subject: [PATCH 023/152] Update README to use Region= not region= --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c8a7df..ac36d92 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Lookup on amazon.de instead of amazon.com by setting the region: >>> region_options = bottlenose.api.SERVICE_DOMAINS.keys() >>> region_options ['US', 'FR', 'CN', 'UK', 'CA', 'DE', 'JP', 'IT', 'ES'] - >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, region="DE") + >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, Region="DE") >>> product = amazon_de.lookup(ItemId='B0051QVF7A') >>> product.title u'Kindle, WLAN, 15 cm (6 Zoll) E Ink Display, deutsches Men\xfc' From b193b0441f8060566d4fd57368c19c5cde52051f Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Thu, 5 Jun 2014 00:47:01 +1000 Subject: [PATCH 024/152] Fix unknown arg and MaxQS parameter in test. MaxQS should be MaxQPS. When we redirect 'region' we need to delete the old kwarg. --- amazon/api.py | 1 + tests.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index f441100..d6e912c 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -131,6 +131,7 @@ def __init__(self, aws_key, aws_secret, aws_associate_tag, **kwargs): # support older style calls if 'region' in kwargs: kwargs['Region'] = kwargs['region'] + del kwargs['region'] self.api = bottlenose.Amazon( aws_key, aws_secret, aws_associate_tag, **kwargs) self.aws_associate_tag = aws_associate_tag diff --git a/tests.py b/tests.py index 81e860e..b37f0ca 100644 --- a/tests.py +++ b/tests.py @@ -246,4 +246,4 @@ def test_region(self): assert_equals(amazon.region, 'UK') def test_kwargs(self): - amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQS=0.7) + amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQPS=0.7) From 547e369c14b3af3a1706fa756bf6bbd8815fec50 Mon Sep 17 00:00:00 2001 From: avinassh Date: Fri, 4 Jul 2014 16:28:11 +0530 Subject: [PATCH 025/152] added installation instructions. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ac36d92..9ce4b2e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ Before you get started, make sure you have: * An Amazon Product Advertising account * An AWS account +Installation +------------- + pip install python-amazon-simple-product-api Usage ----- From b037ed7680fe3758ac2ef18e77aee177daf6c9af Mon Sep 17 00:00:00 2001 From: avinassh Date: Fri, 4 Jul 2014 16:37:52 +0530 Subject: [PATCH 026/152] added 'IN' domain to work with Amazon India --- amazon/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/amazon/api.py b/amazon/api.py index d6e912c..a4c81f7 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -28,6 +28,7 @@ 'DE': 'de', 'ES': 'es', 'FR': 'fr', + 'IN': 'in', 'IT': 'it', 'JP': 'co.jp', 'UK': 'co.uk', From 85ffc1f6c0faede1fbfc3a8549d31f8084b48ec3 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Mon, 7 Jul 2014 16:35:41 +0100 Subject: [PATCH 027/152] Change test ASIN to a valid Kindle. --- tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests.py b/tests.py index b37f0ca..1d29ee2 100644 --- a/tests.py +++ b/tests.py @@ -8,6 +8,7 @@ AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) +TEST_ASIN = "B007HCCNJU" PRODUCT_ATTRIBUTES = [ 'asin', 'author', 'binding', 'brand', 'browse_nodes', 'ean', 'edition', @@ -140,7 +141,7 @@ def test_similarity_lookup(self): Tests that a similarity lookup for a kindle returns 10 results. """ - products = self.amazon.similarity_lookup(ItemId="B0051QVF7A") + products = self.amazon.similarity_lookup(ItemId=TEST_ASIN) assert_true(len(products) > 5) def test_product_attributes(self): @@ -148,7 +149,7 @@ def test_product_attributes(self): Tests that all product that are supposed to be accessible are. """ - product = self.amazon.lookup(ItemId="B0051QVF7A") + product = self.amazon.lookup(ItemId=TEST_ASIN) for attribute in PRODUCT_ATTRIBUTES: getattr(product, attribute) From f56407af7bca97182100a9a268d718f344bc8b03 Mon Sep 17 00:00:00 2001 From: avinassh Date: Mon, 7 Jul 2014 22:38:48 +0530 Subject: [PATCH 028/152] fixed a typo. it should be `region` instead of `Region` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ce4b2e..3553cc7 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Lookup on amazon.de instead of amazon.com by setting the region: >>> region_options = bottlenose.api.SERVICE_DOMAINS.keys() >>> region_options ['US', 'FR', 'CN', 'UK', 'CA', 'DE', 'JP', 'IT', 'ES'] - >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, Region="DE") + >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, region="DE") >>> product = amazon_de.lookup(ItemId='B0051QVF7A') >>> product.title u'Kindle, WLAN, 15 cm (6 Zoll) E Ink Display, deutsches Men\xfc' From e80b85cc9f589ceaf7a989b12f53ab8c26395842 Mon Sep 17 00:00:00 2001 From: avinassh Date: Mon, 7 Jul 2014 22:44:39 +0530 Subject: [PATCH 029/152] updated first example to fetch details of Fire instead of Kindle --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9ce4b2e..d303340 100644 --- a/README.md +++ b/README.md @@ -33,19 +33,19 @@ Lookup: >>> from amazon.api import AmazonAPI >>> amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) - >>> product = amazon.lookup(ItemId='B0051QVF7A') + >>> product = amazon.lookup(ItemId='B00EOE0WKQ') >>> product.title - 'Kindle, Wi-Fi, 6" E Ink Display - for international shipment' + 'Amazon Fire Phone, 32GB (AT&T)' >>> product.price_and_currency - (89.0, 'USD') + (199.0, 'USD') >>> product.ean - '0814916014354' + '0848719035209' >>> product.large_image_url - 'http://ecx.images-amazon.com/images/I/411H%2B731ZzL.jpg' + 'http://ecx.images-amazon.com/images/I/51BrZzpkWrL.jpg' >>> product.get_attribute('Publisher') - 'Amazon Digital Services, Inc' + 'Amazon' >>> product.get_attributes(['ItemDimensions.Width', 'ItemDimensions.Height']) - {'ItemDimensions.Width': '450', 'ItemDimensions.Height': '34'} + {'ItemDimensions.Width': '262', 'ItemDimensions.Height': '35'} (the API wrapper also supports many other product attributes) From 373f41ed141b58eceab90e9e9ec200578deb137e Mon Sep 17 00:00:00 2001 From: avinassh Date: Mon, 7 Jul 2014 22:51:15 +0530 Subject: [PATCH 030/152] bottlenose now supports Amazon India updated the example to reflect the same. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ce4b2e..6a52ea6 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Lookup on amazon.de instead of amazon.com by setting the region: >>> import bottlenose.api >>> region_options = bottlenose.api.SERVICE_DOMAINS.keys() >>> region_options - ['US', 'FR', 'CN', 'UK', 'CA', 'DE', 'JP', 'IT', 'ES'] + ['US', 'FR', 'CN', 'UK', 'IN', 'CA', 'DE', 'JP', 'IT', 'ES'] >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, Region="DE") >>> product = amazon_de.lookup(ItemId='B0051QVF7A') >>> product.title From e36fe9538e7375a68fbdb03731ab6e01e735cd60 Mon Sep 17 00:00:00 2001 From: Daniel Basedow Date: Wed, 23 Jul 2014 22:17:03 +0200 Subject: [PATCH 031/152] all cart features implemented --- amazon/api.py | 418 ++++++++++++++++++++++++++++++++++++++++---------- tests.py | 153 +++++++++++++++++- 2 files changed, 482 insertions(+), 89 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index a4c81f7..5f7b78d 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +# !/usr/bin/python # # Copyright (C) 2012 Yoav Aviram. # @@ -6,7 +6,7 @@ # 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 +# 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, @@ -44,6 +44,18 @@ class AmazonException(Exception): pass +class CartException(AmazonException): + """Cart related Exception + """ + pass + + +class CartInfoMismatchException(CartException): + """HMAC, CartId and AssociateTag did not match + """ + pass + + class AsinNotFound(AmazonException): """ASIN Not Found Exception. """ @@ -133,6 +145,10 @@ def __init__(self, aws_key, aws_secret, aws_associate_tag, **kwargs): if 'region' in kwargs: kwargs['Region'] = kwargs['region'] del kwargs['region'] + + if 'Version' not in kwargs: + kwargs['Version'] = '2013-08-01' + self.api = bottlenose.Amazon( aws_key, aws_secret, aws_associate_tag, **kwargs) self.aws_associate_tag = aws_associate_tag @@ -241,12 +257,232 @@ def search_n(self, n, **kwargs): items = AmazonSearch(self.api, self.aws_associate_tag, **kwargs) return list(islice(items, n)) + def cart_create(self, items, **kwargs): + """CartCreate. + :param items: + A dictionary containing the items to be added to the cart. Or a list containing these dictionaries + It is not possible to create an empty cart! + example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + + :return: + An :class:`~.AmazonCart`. + """ + + if isinstance(items, dict): + items = [items] + + 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 + + for item in items: + i += 1 + kwargs[offer_id_key_template % (i, )] = item['offer_id'] + kwargs[quantity_key_template % (i, )] = item['quantity'] + + response = self.api.CartCreate(**kwargs) + root = objectify.fromstring(response) + + return AmazonCart(root) + + def cart_add(self, items, CartId=None, HMAC=None, **kwargs): + """CartAdd. + :param items: + A dictionary containing the items to be added to the cart. Or a list containing these dictionaries + It is not possible to create an empty cart! + example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + :param CartId: Id of Cart + :param HMAC: HMAC of Cart, see CartCreate for more info + :return: + An :class:`~.AmazonCart`. + """ + if not CartId or not HMAC: + raise CartException('CartId required for CartClear call') + + if isinstance(items, dict): + items = [items] + + 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 + + for item in items: + i += 1 + kwargs[offer_id_key_template % (i, )] = item['offer_id'] + kwargs[quantity_key_template % (i, )] = item['quantity'] + + response = self.api.CartAdd(CartId=CartId, HMAC=HMAC, **kwargs) + root = objectify.fromstring(response) + + new_cart = AmazonCart(root) + self._check_for_cart_error(new_cart) + + return new_cart + + def cart_clear(self, CartId=None, HMAC=None, **kwargs): + """CartClear. Removes all items from cart + :param CartId: Id of cart + :param HMAC: HMAC of cart. Do not use url encoded + :return: An :class:`~.AmazonCart`. + """ + if not CartId or not HMAC: + raise CartException('CartId required for CartClear call') + response = self.api.CartClear(CartId=CartId, HMAC=HMAC, **kwargs) + root = objectify.fromstring(response) + + new_cart = AmazonCart(root) + self._check_for_cart_error(new_cart) + + return new_cart + + def cart_get(self, CartId=None, HMAC=None, **kwargs): + """CartGet fetches existing cart + :param CartId: see CartCreate + :param HMAC: see CartCreate + :return: An :class:`~.AmazonCart`. + """ + if not CartId or not HMAC: + raise CartException('CartId required for CartGet call') + response = self.api.CartGet(CartId=CartId, HMAC=HMAC, **kwargs) + root = objectify.fromstring(response) + + cart = AmazonCart(root) + self._check_for_cart_error(cart) + + return cart + + def cart_modify(self, items, CartId=None, HMAC=None, **kwargs): + """CartAdd. + :param items: + A dictionary containing the items to be added to the cart. Or a list containing these dictionaries + example: [{'cart_item_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + :param CartId: Id of Cart + :param HMAC: HMAC of Cart, see CartCreate for more info + :return: + An :class:`~.AmazonCart`. + """ + if not CartId or not HMAC: + raise CartException('CartId required for CartModify call') + + if isinstance(items, dict): + items = [items] + + 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 + + for item in items: + i += 1 + kwargs[cart_item_id_key_template % (i, )] = item['cart_item_id'] + kwargs[quantity_key_template % (i, )] = item['quantity'] + + response = self.api.CartModify(CartId=CartId, HMAC=HMAC, **kwargs) + root = objectify.fromstring(response) + + new_cart = AmazonCart(root) + self._check_for_cart_error(new_cart) + + return new_cart + + def _check_for_cart_error(self, cart): + if cart._safe_get_element('Cart.Request.Errors') is not None: + error = cart._safe_get_element('Cart.Request.Errors.Error.Code').text + if error == 'AWS.ECommerceService.CartInfoMismatch': + raise CartInfoMismatchException( + 'CartGet failed: AWS.ECommerceService.CartInfoMismatch make sure ' + 'AssociateTag, CartId and HMAC are correct (dont use URLEncodedHMAC!!!)' + ) + raise CartException('CartGet failed: ' + error) + + +class LXMLWrapper(object): + def __init__(self, parsed_response): + self.parsed_response = parsed_response + + def to_string(self): + """Convert Item XML to string. + + :return: + A string representation of the Item xml. + """ + return etree.tostring(self.parsed_response, pretty_print=True) + + def _safe_get_element(self, path, root=None): + """Safe Get Element. + + Get a child element of root (multiple levels deep) failing silently + if any descendant does not exist. + + :param root: + Lxml element. + :param path: + String path (i.e. 'Items.Item.Offers.Offer'). + :return: + Element or None. + """ + elements = path.split('.') + parent = root if root is not None else self.parsed_response + for element in elements[:-1]: + parent = getattr(parent, element, None) + if parent is None: + return None + return getattr(parent, elements[-1], None) + + def _safe_get_element_text(self, path, root=None): + """Safe get element text. + + Get element as string or None, + :param root: + Lxml element. + :param path: + String path (i.e. 'Items.Item.Offers.Offer'). + :return: + String or None. + """ + element = self._safe_get_element(path, root) + if element is not None: + return element.text + else: + return None + + def _safe_get_element_date(self, path, root=None): + """Safe get elemnent date. + + Get element as datetime.date or None, + :param root: + Lxml element. + :param path: + String path (i.e. 'Items.Item.Offers.Offer'). + :return: + datetime.date or None. + """ + value = self._safe_get_element_text(path=path, root=root) + if value is not None: + try: + value = dateutil.parser.parse(value) + if value: + value = value.date() + except ValueError: + value = None + + return value + class AmazonSearch(object): """ Amazon Search. A class providing an iterable over amazon search results. """ + def __init__(self, api, aws_associate_tag, **kwargs): """Initialise @@ -313,11 +549,7 @@ def _query(self, ResponseGroup="Large", **kwargs): return root -class AmazonBrowseNode(object): - - def __init__(self, element): - self.element = element - +class AmazonBrowseNode(LXMLWrapper): @property def id(self): """Browse Node ID. @@ -327,8 +559,8 @@ def id(self): :return: ID (integer) """ - if hasattr(self.element, 'BrowseNodeId'): - return int(self.element['BrowseNodeId']) + if hasattr(self.parsed_response, 'BrowseNodeId'): + return int(self.parsed_response['BrowseNodeId']) return None @property @@ -338,14 +570,14 @@ def name(self): :return: Name (string) """ - return getattr(self.element, 'Name', None) + return getattr(self.parsed_response, 'Name', None) @property def is_category_root(self): """Boolean value that specifies if the browse node is at the top of the browse node tree. """ - return getattr(self.element, 'IsCategoryRoot', False) + return getattr(self.parsed_response, 'IsCategoryRoot', False) @property def ancestor(self): @@ -354,7 +586,7 @@ def ancestor(self): :return: The ancestor as an :class:`~.AmazonBrowseNode`, or None. """ - ancestors = getattr(self.element, 'Ancestors', None) + ancestors = getattr(self.parsed_response, 'Ancestors', None) if hasattr(ancestors, 'BrowseNode'): return AmazonBrowseNode(ancestors['BrowseNode']) return None @@ -381,13 +613,13 @@ def children(self): A list of this browse node's children in the browse node tree. """ children = [] - child_nodes = getattr(self.element, 'Children') + child_nodes = getattr(self.parsed_response, 'Children') for child in getattr(child_nodes, 'BrowseNode', []): - children.append(AmazonBrowseNode(child)) + children.append(AmazonBrowseNode(child)) return children -class AmazonProduct(object): +class AmazonProduct(LXMLWrapper): """A wrapper class for an Amazon product. """ @@ -397,80 +629,12 @@ def __init__(self, item, aws_associate_tag, api, *args, **kwargs): :param item: Lxml Item element. """ - self.item = item + super(AmazonProduct, self).__init__(item) self.aws_associate_tag = aws_associate_tag self.api = api self.parent = None self.region = kwargs.get('region', 'US') - def to_string(self): - """Convert Item XML to string. - - :return: - A string representation of the Item xml. - """ - return etree.tostring(self.item, pretty_print=True) - - def _safe_get_element(self, path, root=None): - """Safe Get Element. - - Get a child element of root (multiple levels deep) failing silently - if any descendant does not exist. - - :param root: - Lxml element. - :param path: - String path (i.e. 'Items.Item.Offers.Offer'). - :return: - Element or None. - """ - elements = path.split('.') - parent = root if root is not None else self.item - for element in elements[:-1]: - parent = getattr(parent, element, None) - if parent is None: - return None - return getattr(parent, elements[-1], None) - - def _safe_get_element_text(self, path, root=None): - """Safe get element text. - - Get element as string or None, - :param root: - Lxml element. - :param path: - String path (i.e. 'Items.Item.Offers.Offer'). - :return: - String or None. - """ - element = self._safe_get_element(path, root) - if element is not None: - return element.text - else: - return None - - def _safe_get_element_date(self, path, root=None): - """Safe get elemnent date. - - Get element as datetime.date or None, - :param root: - Lxml element. - :param path: - String path (i.e. 'Items.Item.Offers.Offer'). - :return: - datetime.date or None. - """ - value = self._safe_get_element_text(path=path, root=root) - if value is not None: - try: - value = dateutil.parser.parse(value) - if value: - value = value.date() - except ValueError: - value = None - - return value - @property def price_and_currency(self): """Get Offer Price and Currency. @@ -979,3 +1143,89 @@ def browse_nodes(self): return [] return [AmazonBrowseNode(child) for child in root.iterchildren()] + + +class AmazonCart(LXMLWrapper): + """Wrapper around Amazon shopping cart. Allows iterating over Items in the cart. + """ + + @property + def cart_id(self): + return self._safe_get_element_text('Cart.CartId') + + @property + def purchase_url(self): + return self._safe_get_element_text('Cart.PurchaseURL') + + @property + def amount(self): + return self._safe_get_element_text('Cart.SubTotal.Amount') + + @property + def formatted_price(self): + return self._safe_get_element_text('Cart.SubTotal.FormattedPrice') + + @property + def currency_code(self): + return self._safe_get_element_text('Cart.SubTotal.CurrencyCode') + + @property + def hmac(self): + return self._safe_get_element_text('Cart.HMAC') + + @property + def url_encoded_hmac(self): + return self._safe_get_element_text('Cart.URLEncodedHMAC') + + def __len__(self): + return len(self._safe_get_element('Cart.CartItems.CartItem')) + + def __iter__(self): + items = self._safe_get_element('Cart.CartItems.CartItem') + if items is not None: + for item in items: + yield AmazonCartItem(item) + + def __getitem__(self, cart_item_id): + """ + :param cart_item_id: access item by CartItemId + :return: AmazonCartItem + """ + 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,)) + + +class AmazonCartItem(LXMLWrapper): + @property + def asin(self): + return self._safe_get_element_text('ASIN') + + @property + def quantity(self): + return self._safe_get_element_text('Quantity') + + @property + def cart_item_id(self): + return self._safe_get_element_text('CartItemId') + + @property + def title(self): + return self._safe_get_element_text('Title') + + @property + def product_group(self): + return self._safe_get_element_text('ProductGroup') + + @property + def formatted_price(self): + return self._safe_get_element_text('Price.FormattedPrice') + + @property + def amount(self): + return self._safe_get_element_text('Price.Amount') + + @property + def currency_code(self): + return self._safe_get_element_text('Price.CurrencyCode') diff --git a/tests.py b/tests.py index 2d12eee..1d16b3e 100644 --- a/tests.py +++ b/tests.py @@ -3,7 +3,7 @@ from nose.tools import assert_equals, assert_true, assert_false import datetime -from amazon.api import AmazonAPI +from amazon.api import AmazonAPI, CartException, CartInfoMismatchException from test_settings import (AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) @@ -11,7 +11,6 @@ TEST_ASIN = "B007HCCNJU" - PRODUCT_ATTRIBUTES = [ 'asin', 'author', 'binding', 'brand', 'browse_nodes', 'ean', 'edition', 'editorial_review', 'eisbn', 'features', 'get_parent', 'isbn', 'label', @@ -22,12 +21,36 @@ 'title', 'upc' ] +CART_ATTRIBUTES = [ + 'cart_id', 'purchase_url', 'amount', 'formatted_price', 'currency_code', 'url_encoded_hmac', 'hmac' +] + +CART_ITEM_ATTRIBUTES = [ + 'cart_item_id', 'asin', 'title', 'amount', 'formatted_price', 'currency_code', 'quantity', 'product_group', +] + +CACHE = {} + + +def cache_writer(url, response): + CACHE[url] = response + + +def cache_reader(url): + return CACHE.get(url, None) + + +def cache_clear(): + global CACHE + CACHE = {} + class TestAmazonApi(TestCase): """Test Amazon API Test Class for Amazon simple API wrapper. """ + def setUp(self): """Set Up. @@ -40,7 +63,13 @@ 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 + ) def test_lookup(self): """Test Product Lookup. @@ -61,7 +90,7 @@ def test_lookup(self): ) assert_equals(product.get_attributes( ['ItemDimensions.Width', 'ItemDimensions.Height']), - {'ItemDimensions.Width': '650', 'ItemDimensions.Height': '130'}) + {'ItemDimensions.Width': '650', 'ItemDimensions.Height': '130'}) 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) @@ -238,7 +267,7 @@ def test_languages_spanish(self): def test_region(self): amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, - AMAZON_ASSOC_TAG) + AMAZON_ASSOC_TAG) assert_equals(amazon.region, 'US') # old 'region' parameter @@ -251,3 +280,117 @@ def test_region(self): def test_kwargs(self): amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQPS=0.7) + + +class TestAmazonCart(TestCase): + def setUp(self): + self.amazon = AmazonAPI( + AMAZON_ACCESS_KEY, + AMAZON_SECRET_KEY, + AMAZON_ASSOC_TAG, + CacheReader=cache_reader, + CacheWriter=cache_writer, + MaxQPS=0.5 + ) + + 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') + + 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'), + 'quantity': 1 + } + ) + + def test_cart_create_single_item(self): + cart = self.build_cart_object() + assert_equals(len(cart), 1) + + def test_cart_create_multiple_item(self): + product1 = self.amazon.lookup(ItemId="B0016J8AOC") + product2 = self.amazon.lookup(ItemId="B007HCCNJU") + asins = [product1.asin, product2.asin] + + cart = self.amazon.cart_create([ + { + 'offer_id': product1._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'quantity': 1 + }, + { + 'offer_id': product2._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'quantity': 1 + }, + ]) + assert_equals(len(cart), 2) + for item in cart: + assert_true(item.asin in asins) + + def test_cart_clear(self): + cart = self.build_cart_object() + new_cart = self.amazon.cart_clear(cart.cart_id, cart.hmac) + assert_true(new_cart._safe_get_element('Cart.Request.IsValid')) + + def test_cart_clear_wrong_hmac(self): + cart = self.build_cart_object() + # 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) + + def test_cart_attributes(self): + cart = self.build_cart_object() + for attribute in CART_ATTRIBUTES: + getattr(cart, attribute) + + def test_cart_item_attributes(self): + cart = self.build_cart_object() + for item in cart: + for attribute in CART_ITEM_ATTRIBUTES: + getattr(item, attribute) + + def test_cart_get(self): + # We need to flush the cache here so we will get a new cart that has not been used in test_cart_clear + cache_clear() + cart = self.build_cart_object() + fetched_cart = self.amazon.cart_get(cart.cart_id, cart.hmac) + + assert_equals(fetched_cart.cart_id, cart.cart_id) + assert_equals(len(fetched_cart), len(cart)) + + def test_cart_get_wrong_hmac(self): + # We need to flush the cache here so we will get a new cart that has 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') + + def test_cart_add(self): + cart = self.build_cart_object() + product = self.amazon.lookup(ItemId="B007HCCNJU") + item = { + 'offer_id': product._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'quantity': 1 + } + new_cart = self.amazon.cart_add(item, cart.cart_id, cart.hmac) + assert_true(len(new_cart) > len(cart)) + + def test_cart_modify(self): + cart = self.build_cart_object() + cart_item_id = None + for item in cart: + cart_item_id = item.cart_item_id + item = {'cart_item_id': cart_item_id, 'quantity': 3} + new_cart = self.amazon.cart_modify(item, cart.cart_id, cart.hmac) + assert_equals(new_cart[cart_item_id].quantity, '3') + + def test_cart_delete(self): + cart = self.build_cart_object() + cart_item_id = None + for item in cart: + 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) From f72b3ccc8b71a3b900ca30fd3cbfddf1ba453582 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 5 Sep 2014 15:57:31 +0100 Subject: [PATCH 032/152] Fix bulk lookup test --- tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.py b/tests.py index 2d12eee..9bdc9c2 100644 --- a/tests.py +++ b/tests.py @@ -73,10 +73,10 @@ def test_batch_lookup(self): Tests that a batch product lookup request returns multiple results. """ - asins = ['B00AWH595M', 'B007HCCNJU', 'B00BWYQ9YE', + asins = ['B007HCCNJU', 'B00BWYQ9YE', 'B00BWYRF7E', 'B00D2KJDXA'] products = self.amazon.lookup(ItemId=','.join(asins)) - assert_equals(len(products), 5) + assert_equals(len(products), len(asins)) for i, product in enumerate(products): assert_equals(asins[i], product.asin) From ea846f319ba86683a8ae55c74e3ac0e92626ee06 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 5 Sep 2014 16:05:27 +0100 Subject: [PATCH 033/152] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4cda5b0..c0d37d8 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages -version = '1.5.0' +version = '2.0.0' setup(name='python-amazon-simple-product-api', From a19420aa30d262ad0b9ef80ab571a73e3431be2a Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Tue, 28 Oct 2014 12:03:05 -0500 Subject: [PATCH 034/152] Added color property. --- amazon/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 5f7b78d..6a02ecb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -935,6 +935,15 @@ def upc(self): 'UPCListElement', root=upc_list[0]) return upc + @property + def color(self): + """Color. + + :return: + Color (string) + """ + return self._safe_get_element_text('ItemAttributes.Color') + @property def sku(self): """SKU. From 672f2afacd13482865230e452856b2f1bb805bef Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Tue, 28 Oct 2014 12:39:36 -0500 Subject: [PATCH 035/152] Added other missing ItemAttribute properties. --- amazon/api.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 6a02ecb..5f359e6 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -943,6 +943,60 @@ def color(self): Color (string) """ return self._safe_get_element_text('ItemAttributes.Color') + + @property + def is_autographed(self): + """Is autographed. + + :returns: + is_autographed (string) + """ + return self._safe_get_element_text('ItemAttributes.IsAutographed') + + @property + def is_memorabilia(self): + """Is memorabilia + + :returns: + is_memorabilia (string) + """ + return self._safe_get_element_text('ItemAttributes.IsMemorabilia') + + @property + def number_of_items(self): + """number of items + + :returns: + number of items for listing (string) + """ + return self._safe_get_element_text('ItemAttributes.NumberOfItems') + + @property + def product_group(self): + """product + + :returns: + product group for listing (string) + """ + return self._safe_get_element_text('ItemAttributes.ProductGroup') + + @property + def product_type_name(self): + """product type name + + :returns: + product type name (string) + """ + return self._safe_get_element_text('ItemAttributes.ProductTypeName') + + @property + def studio(self): + """studio + + :returns: + studio for listing + """ + return self._safe_get_element_text('ItemAttributes.Studio') @property def sku(self): From 584f09c8a7fb4bce774b7297cab5a9c112db75eb Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Tue, 28 Oct 2014 13:14:10 -0500 Subject: [PATCH 036/152] updated PyDocs --- amazon/api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 5f359e6..8d14e63 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -943,10 +943,10 @@ def color(self): Color (string) """ return self._safe_get_element_text('ItemAttributes.Color') - + @property def is_autographed(self): - """Is autographed. + """is_autographed. :returns: is_autographed (string) @@ -955,7 +955,7 @@ def is_autographed(self): @property def is_memorabilia(self): - """Is memorabilia + """is_memorabilia :returns: is_memorabilia (string) @@ -964,7 +964,7 @@ def is_memorabilia(self): @property def number_of_items(self): - """number of items + """number_of_items :returns: number of items for listing (string) @@ -973,7 +973,7 @@ def number_of_items(self): @property def product_group(self): - """product + """product_group :returns: product group for listing (string) @@ -982,7 +982,7 @@ def product_group(self): @property def product_type_name(self): - """product type name + """product_type_name :returns: product type name (string) From dd932ad10d067623e336935a861ee1781a27892e Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Tue, 28 Oct 2014 14:37:45 -0500 Subject: [PATCH 037/152] Revert "updated PyDocs" This reverts commit 584f09c8a7fb4bce774b7297cab5a9c112db75eb. --- amazon/api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 8d14e63..5f359e6 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -943,10 +943,10 @@ def color(self): Color (string) """ return self._safe_get_element_text('ItemAttributes.Color') - + @property def is_autographed(self): - """is_autographed. + """Is autographed. :returns: is_autographed (string) @@ -955,7 +955,7 @@ def is_autographed(self): @property def is_memorabilia(self): - """is_memorabilia + """Is memorabilia :returns: is_memorabilia (string) @@ -964,7 +964,7 @@ def is_memorabilia(self): @property def number_of_items(self): - """number_of_items + """number of items :returns: number of items for listing (string) @@ -973,7 +973,7 @@ def number_of_items(self): @property def product_group(self): - """product_group + """product :returns: product group for listing (string) @@ -982,7 +982,7 @@ def product_group(self): @property def product_type_name(self): - """product_type_name + """product type name :returns: product type name (string) From 082f21eb64a72843c3659ef3c37efccf1d20abd5 Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Tue, 28 Oct 2014 14:37:58 -0500 Subject: [PATCH 038/152] Revert "Added other missing ItemAttribute properties." This reverts commit 672f2afacd13482865230e452856b2f1bb805bef. --- amazon/api.py | 54 --------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 5f359e6..6a02ecb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -943,60 +943,6 @@ def color(self): Color (string) """ return self._safe_get_element_text('ItemAttributes.Color') - - @property - def is_autographed(self): - """Is autographed. - - :returns: - is_autographed (string) - """ - return self._safe_get_element_text('ItemAttributes.IsAutographed') - - @property - def is_memorabilia(self): - """Is memorabilia - - :returns: - is_memorabilia (string) - """ - return self._safe_get_element_text('ItemAttributes.IsMemorabilia') - - @property - def number_of_items(self): - """number of items - - :returns: - number of items for listing (string) - """ - return self._safe_get_element_text('ItemAttributes.NumberOfItems') - - @property - def product_group(self): - """product - - :returns: - product group for listing (string) - """ - return self._safe_get_element_text('ItemAttributes.ProductGroup') - - @property - def product_type_name(self): - """product type name - - :returns: - product type name (string) - """ - return self._safe_get_element_text('ItemAttributes.ProductTypeName') - - @property - def studio(self): - """studio - - :returns: - studio for listing - """ - return self._safe_get_element_text('ItemAttributes.Studio') @property def sku(self): From 0f8714339f8697a6a4c6a23e0485bf4181dbc3d3 Mon Sep 17 00:00:00 2001 From: tacy lee Date: Tue, 23 Dec 2014 13:04:17 +0800 Subject: [PATCH 039/152] Fix Region to region --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 572ed07..f5665d7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -Amazon Simple Product API +Amazon Simple Product API ========================== -A simple Python wrapper for the Amazon.com Product Advertising 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) @@ -56,7 +56,7 @@ Lookup on amazon.de instead of amazon.com by setting the region: >>> region_options = bottlenose.api.SERVICE_DOMAINS.keys() >>> region_options ['US', 'FR', 'CN', 'UK', 'IN', 'CA', 'DE', 'JP', 'IT', 'ES'] - >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, Region="DE") + >>> amazon_de = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, region="DE") >>> product = amazon_de.lookup(ItemId='B0051QVF7A') >>> product.title u'Kindle, WLAN, 15 cm (6 Zoll) E Ink Display, deutsches Men\xfc' @@ -144,4 +144,3 @@ License Copyright © 2012 Yoav Aviram See LICENSE for details. - From f022e4816c3450e0b3eca9454b5a6aee2435ba28 Mon Sep 17 00:00:00 2001 From: tacy lee Date: Tue, 23 Dec 2014 13:33:31 +0800 Subject: [PATCH 040/152] Price not divided by 100 in Japan --- amazon/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 6a02ecb..0caccd5 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -669,7 +669,8 @@ def price_and_currency(self): currency = self._safe_get_element_text( 'OfferSummary.LowestNewPrice.CurrencyCode') if price: - return float(price) / 100, currency + fprice = float(price)/100 if 'JP' not in self.region else price + return fprice, currency else: return None, None From 062fd5cea609c93b2970b598e60325a7521d544c Mon Sep 17 00:00:00 2001 From: Alessandro Strada Date: Wed, 31 Dec 2014 17:24:58 +0100 Subject: [PATCH 041/152] Add test_settings.py to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3566106..a4780b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ MANIFEST build/ dist/ .idea +test_settings.py From 385bb519a69fd572e0e376ec0f411d050b57cfd6 Mon Sep 17 00:00:00 2001 From: Alessandro Strada Date: Wed, 31 Dec 2014 19:02:40 +0100 Subject: [PATCH 042/152] Handle empty results in search response - Avoid querying Amazon API 10 times when request is valid but returns no results - Add tests --- amazon/api.py | 3 ++- tests.py | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index 6a02ecb..09511f3 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -538,7 +538,8 @@ def _query(self, ResponseGroup="Large", **kwargs): """ response = self.api.ItemSearch(ResponseGroup=ResponseGroup, **kwargs) root = objectify.fromstring(response) - if root.Items.Request.IsValid == 'False': + if (hasattr(root.Items.Request, 'Errors') and + not hasattr(root.Items, 'Item')): code = root.Items.Request.Errors.Error.Code msg = root.Items.Request.Errors.Error.Message if code == 'AWS.ParameterOutOfRange': diff --git a/tests.py b/tests.py index 903e156..a181db4 100644 --- a/tests.py +++ b/tests.py @@ -3,7 +3,11 @@ from nose.tools import assert_equals, assert_true, assert_false import datetime -from amazon.api import AmazonAPI, CartException, CartInfoMismatchException +from amazon.api import (AmazonAPI, + CartException, + CartInfoMismatchException, + SearchException, + AsinNotFound) from test_settings import (AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG) @@ -97,6 +101,13 @@ def test_lookup(self): assert_equals(product.browse_nodes[0].id, 2642129011) assert_equals(product.browse_nodes[0].name, 'eBook Readers') + def test_lookup_nonexistent_asin(self): + """Test Product Lookup with a nonexistent ASIN. + + Tests that a product lookup for a nonexistent ASIN raises AsinNotFound. + """ + self.assertRaises(AsinNotFound, self.amazon.lookup, ItemId="ABCD1234") + def test_batch_lookup(self): """Test Batch Product Lookup. @@ -136,6 +147,16 @@ def test_search_n(self): ) assert_equals(len(products), 1) + def test_search_no_results(self): + """Test Product Search with no results. + + Tests that a product search with that returns no results throws a + SearchException. + """ + products = self.amazon.search(Title='HarryPotter', + SearchIndex='Automotive') + self.assertRaises(SearchException, (x for x in products).next) + def test_amazon_api_defaults_to_US(self): """Test Amazon API defaults to the US store.""" amazon = AmazonAPI( From 7736a8009fe56fc40238465914626e2d950c588c Mon Sep 17 00:00:00 2001 From: tacy lee Date: Tue, 6 Jan 2015 17:35:57 +0800 Subject: [PATCH 043/152] Fix pep8 --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index 0caccd5..d03afea 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -669,7 +669,7 @@ 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 + fprice = float(price) / 100 if 'JP' not in self.region else price return fprice, currency else: return None, None From 2d37d2ba659d5ae3552414269d0709fc371a0042 Mon Sep 17 00:00:00 2001 From: lechatpito Date: Thu, 26 Mar 2015 14:16:45 -0400 Subject: [PATCH 044/152] Adding properties for Genre, Actor, and Director --- amazon/api.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index f484a87..dba8bbb 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -853,3 +853,38 @@ def browse_nodes(self): return [] return [AmazonBrowseNode(child) for child in root.iterchildren()] + + @property + def genre(self): + """Movie Genre. + + :return: + The genre of a movie. + """ + return self._safe_get_element_text('Genre') + + @property + def actors(self): + """Movie Actors. + + :return: + A list of actors names. + """ + result = [] + actors = self._safe_get_element('Actor') or [] + for actor in actors: + result.append(actor.text) + return result + + @property + def director(self): + """Movie Directors. + + :return: + A list of directors for a movie. + """ + result = [] + directors = self._safe_get_element('Director') or [] + for director in directors: + result.append(director.text) + return result From ff3dd215f201492599e1f1481445183ccc8c1d0f Mon Sep 17 00:00:00 2001 From: lechatpito Date: Thu, 26 Mar 2015 14:28:25 -0400 Subject: [PATCH 045/152] correcting element paths. Directors is now plural. --- amazon/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/amazon/api.py b/amazon/api.py index dba8bbb..9ddb284 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -861,7 +861,7 @@ def genre(self): :return: The genre of a movie. """ - return self._safe_get_element_text('Genre') + return self._safe_get_element_text('ItemAttributes.Genre') @property def actors(self): @@ -871,20 +871,20 @@ def actors(self): A list of actors names. """ result = [] - actors = self._safe_get_element('Actor') or [] + actors = self._safe_get_element('ItemAttributes.Actor') or [] for actor in actors: result.append(actor.text) return result @property - def director(self): + def directors(self): """Movie Directors. :return: A list of directors for a movie. """ result = [] - directors = self._safe_get_element('Director') or [] + directors = self._safe_get_element('ItemAttributes.Director') or [] for director in directors: result.append(director.text) return result From 26b732175e8b0ff9eff06e582e26a589d3f01de7 Mon Sep 17 00:00:00 2001 From: lechatpito Date: Thu, 26 Mar 2015 14:55:19 -0400 Subject: [PATCH 046/152] indentation corrected --- amazon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amazon/api.py b/amazon/api.py index d97c72e..87af2c0 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1154,7 +1154,7 @@ def browse_nodes(self): return [AmazonBrowseNode(child) for child in root.iterchildren()] - @property + @property def genre(self): """Movie Genre. From c8d1fc767f4dd8ea0bb4a05ee6b0c5586c7447cf Mon Sep 17 00:00:00 2001 From: Omer Korner Date: Mon, 4 May 2015 14:50:11 +0300 Subject: [PATCH 047/152] add images property to get all images of responsegroup images --- amazon/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/amazon/api.py b/amazon/api.py index 09511f3..ab6e21c 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -1154,6 +1154,21 @@ def browse_nodes(self): return [AmazonBrowseNode(child) for child in root.iterchildren()] + @property + def images(self): + """List of images for a response. + + When using lookup with RespnoseGroup 'Images', you'll get a list of images. + Parse them so they are returned in an easily used list format. + :return: + A list of `ObjectifiedElement` images + """ + try: + images = [image for image in self._safe_get_element('ImageSets.ImageSet')] + except TypeError: # No images in this ResponseGroup + images = [] + return images + class AmazonCart(LXMLWrapper): """Wrapper around Amazon shopping cart. Allows iterating over Items in the cart. From 86107712e1d9867554b5699a705bdae7f1c3d196 Mon Sep 17 00:00:00 2001 From: Omer Korner Date: Mon, 4 May 2015 14:50:30 +0300 Subject: [PATCH 048/152] add images test, fix existing tests, allow tests to run without nose --- tests.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests.py b/tests.py index a181db4..9c5e580 100644 --- a/tests.py +++ b/tests.py @@ -1,4 +1,4 @@ -from unittest import TestCase +import unittest from nose.tools import assert_equals, assert_true, assert_false @@ -49,7 +49,7 @@ def cache_clear(): CACHE = {} -class TestAmazonApi(TestCase): +class TestAmazonApi(unittest.TestCase): """Test Amazon API Test Class for Amazon simple API wrapper. @@ -81,12 +81,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="B007HCCNJU") + product = self.amazon.lookup(ItemId="B00I15SB16") assert_true('Kindle' in product.title) - assert_equals(product.ean, '0814916017775') + assert_equals(product.ean, '0848719039726') assert_equals( product.large_image_url, - 'http://ecx.images-amazon.com/images/I/41VZlVs8agL.jpg' + 'http://ecx.images-amazon.com/images/I/51XGerXeYeL.jpg' ) assert_equals( product.get_attribute('Publisher'), @@ -94,7 +94,7 @@ def test_lookup(self): ) assert_equals(product.get_attributes( ['ItemDimensions.Width', 'ItemDimensions.Height']), - {'ItemDimensions.Width': '650', 'ItemDimensions.Height': '130'}) + {'ItemDimensions.Width': '469', 'ItemDimensions.Height': '40'}) 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) @@ -302,8 +302,17 @@ def test_region(self): def test_kwargs(self): amazon = AmazonAPI(AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, AMAZON_ASSOC_TAG, MaxQPS=0.7) + def test_images(self): + """Test images property + + Test that the images property has a value when using the Images ResponseGroup + """ + product = self.amazon.lookup(ResponseGroup='Images', ItemId='B00TSVVNQC') + assert_equals(type(product.images), list) + assert_equals(len(product.images), 7) -class TestAmazonCart(TestCase): + +class TestAmazonCart(unittest.TestCase): def setUp(self): self.amazon = AmazonAPI( AMAZON_ACCESS_KEY, @@ -415,3 +424,6 @@ def test_cart_delete(self): 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) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 71888660ce833fa84bba14af2cc8ad941e271320 Mon Sep 17 00:00:00 2001 From: Ahmad Hajar Date: Wed, 24 Jun 2015 18:54:02 +0700 Subject: [PATCH 049/152] change dateutil to dateutils --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5665d7..c581f4c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Before you get started, make sure you have: * Installed [Bottlenose](https://github.com/lionheart/bottlenose) (`pip install bottlenose`) * Installed lxml (`pip install lxml`) -* Installed [dateutil](http://labix.org/python-dateutil) (`pip install dateutil`) +* Installed [dateutil](http://labix.org/python-dateutil) (`pip install dateutils`) * An Amazon Product Advertising account * An AWS account From 34c04d0e4019e181e6c7ea56db3c841998dfe5fa Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Wed, 8 Jul 2015 16:22:04 +0100 Subject: [PATCH 050/152] Add __version__ to package. PEP8 Bump version. --- amazon/__init__.py | 1 + amazon/api.py | 43 ++++++++++++++++++++++------------- setup.py | 7 +++--- tests.py | 56 ++++++++++++++++++++++++++++++---------------- 4 files changed, 69 insertions(+), 38 deletions(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index e69de29..3f39079 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -0,0 +1 @@ +__version__ = '2.0.1' diff --git a/amazon/api.py b/amazon/api.py index 25411c7..2a36f01 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -123,7 +123,8 @@ def __init__(self, aws_key, aws_secret, aws_associate_tag, **kwargs): You generally want to set this a little lower than the max (so 0.9, not 1.0). Amazon limits the number of calls per hour, so for long running - tasks this should be set to 0.9 to ensure you don't hit the maximum. + tasks this should be set to 0.9 to ensure you don't hit the + maximum. Defaults to None (unlimited). :param Timeout: Optional timeout for queries. @@ -260,9 +261,11 @@ def search_n(self, n, **kwargs): def cart_create(self, items, **kwargs): """CartCreate. :param items: - A dictionary containing the items to be added to the cart. Or a list containing these dictionaries + A dictionary containing the items to be added to the cart. + Or a list containing these dictionaries. It is not possible to create an empty cart! - example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', + 'quantity': 1}] :return: An :class:`~.AmazonCart`. @@ -291,9 +294,11 @@ def cart_create(self, items, **kwargs): def cart_add(self, items, CartId=None, HMAC=None, **kwargs): """CartAdd. :param items: - A dictionary containing the items to be added to the cart. Or a list containing these dictionaries + A dictionary containing the items to be added to the cart. + Or a list containing these dictionaries. It is not possible to create an empty cart! - example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + example: [{'offer_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', + 'quantity': 1}] :param CartId: Id of Cart :param HMAC: HMAC of Cart, see CartCreate for more info :return: @@ -360,8 +365,10 @@ def cart_get(self, CartId=None, HMAC=None, **kwargs): def cart_modify(self, items, CartId=None, HMAC=None, **kwargs): """CartAdd. :param items: - A dictionary containing the items to be added to the cart. Or a list containing these dictionaries - example: [{'cart_item_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', 'quantity': 1}] + A dictionary containing the items to be added to the cart. + Or a list containing these dictionaries. + example: [{'cart_item_id': 'rt2ofih3f389nwiuhf8934z87o3f4h', + 'quantity': 1}] :param CartId: Id of Cart :param HMAC: HMAC of Cart, see CartCreate for more info :return: @@ -395,11 +402,13 @@ def cart_modify(self, items, CartId=None, HMAC=None, **kwargs): def _check_for_cart_error(self, cart): if cart._safe_get_element('Cart.Request.Errors') is not None: - error = cart._safe_get_element('Cart.Request.Errors.Error.Code').text + error = cart._safe_get_element( + 'Cart.Request.Errors.Error.Code').text if error == 'AWS.ECommerceService.CartInfoMismatch': raise CartInfoMismatchException( - 'CartGet failed: AWS.ECommerceService.CartInfoMismatch make sure ' - 'AssociateTag, CartId and HMAC are correct (dont use URLEncodedHMAC!!!)' + 'CartGet failed: AWS.ECommerceService.CartInfoMismatch ' + 'make sure AssociateTag, CartId and HMAC are correct ' + '(dont use URLEncodedHMAC!!!)' ) raise CartException('CartGet failed: ' + error) @@ -752,7 +761,8 @@ def creators(self): creators = self._safe_get_element('ItemAttributes.Creator') if creators is not None: for creator in creators: - role = creator.attrib['Role'] if 'Role' in creator.attrib else None + role = creator.attrib['Role'] if \ + 'Role' in creator.attrib else None result.append((creator.text, role)) return result @@ -1159,20 +1169,23 @@ def browse_nodes(self): def images(self): """List of images for a response. - When using lookup with RespnoseGroup 'Images', you'll get a list of images. - Parse them so they are returned in an easily used list format. + When using lookup with RespnoseGroup 'Images', you'll get a + list of images. Parse them so they are returned in an easily + used list format. :return: A list of `ObjectifiedElement` images """ try: - images = [image for image in self._safe_get_element('ImageSets.ImageSet')] + images = [image for image in self._safe_get_element( + 'ImageSets.ImageSet')] except TypeError: # No images in this ResponseGroup images = [] return images class AmazonCart(LXMLWrapper): - """Wrapper around Amazon shopping cart. Allows iterating over Items in the cart. + """Wrapper around Amazon shopping cart. + Allows iterating over Items in the cart. """ @property diff --git a/setup.py b/setup.py index c0d37d8..135da6a 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,10 @@ -from setuptools import setup, find_packages - +import amazon -version = '2.0.0' +from setuptools import setup, find_packages setup(name='python-amazon-simple-product-api', - version=version, + version=amazon.__version__, description="A simple Python wrapper for the Amazon.com Product Advertising API", # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ diff --git a/tests.py b/tests.py index 9c5e580..63336a4 100644 --- a/tests.py +++ b/tests.py @@ -26,11 +26,13 @@ ] CART_ATTRIBUTES = [ - 'cart_id', 'purchase_url', 'amount', 'formatted_price', 'currency_code', 'url_encoded_hmac', 'hmac' + 'cart_id', 'purchase_url', 'amount', 'formatted_price', 'currency_code', + 'url_encoded_hmac', 'hmac' ] CART_ITEM_ATTRIBUTES = [ - 'cart_item_id', 'asin', 'title', 'amount', 'formatted_price', 'currency_code', 'quantity', 'product_group', + 'cart_item_id', 'asin', 'title', 'amount', 'formatted_price', + 'currency_code', 'quantity', 'product_group', ] CACHE = {} @@ -94,7 +96,7 @@ def test_lookup(self): ) assert_equals(product.get_attributes( ['ItemDimensions.Width', 'ItemDimensions.Height']), - {'ItemDimensions.Width': '469', 'ItemDimensions.Height': '40'}) + {'ItemDimensions.Width': '469', 'ItemDimensions.Height': '40'}) 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) @@ -292,22 +294,27 @@ def test_region(self): 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_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 - Test that the images property has a value when using the Images ResponseGroup + Test that the images property has a value when using the + Images ResponseGroup """ - product = self.amazon.lookup(ResponseGroup='Images', ItemId='B00TSVVNQC') + product = self.amazon.lookup(ResponseGroup='Images', + ItemId='B00TSVVNQC') assert_equals(type(product.images), list) assert_equals(len(product.images), 7) @@ -325,14 +332,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') + self.assertRaises(CartException, self.amazon.cart_clear, 'NotNone', + None) + self.assertRaises(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._safe_get_element( + 'Offers.Offer.OfferListing.OfferListingId'), 'quantity': 1 } ) @@ -348,11 +358,13 @@ def test_cart_create_multiple_item(self): cart = self.amazon.cart_create([ { - 'offer_id': product1._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'offer_id': product1._safe_get_element( + 'Offers.Offer.OfferListing.OfferListingId'), 'quantity': 1 }, { - 'offer_id': product2._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'offer_id': product2._safe_get_element( + 'Offers.Offer.OfferListing.OfferListingId'), 'quantity': 1 }, ]) @@ -367,9 +379,11 @@ def test_cart_clear(self): def test_cart_clear_wrong_hmac(self): cart = self.build_cart_object() - # never use urlencoded hmac, as library encodes as well. Just in case hmac = url_encoded_hmac we add some noise + # 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) + self.assertRaises(CartInfoMismatchException, self.amazon.cart_clear, + cart.cart_id, hmac) def test_cart_attributes(self): cart = self.build_cart_object() @@ -383,7 +397,8 @@ def test_cart_item_attributes(self): getattr(item, attribute) def test_cart_get(self): - # We need to flush the cache here so we will get a new cart that has not been used in test_cart_clear + # We need to flush the cache here so we will get a new cart that has + # not been used in test_cart_clear cache_clear() cart = self.build_cart_object() fetched_cart = self.amazon.cart_get(cart.cart_id, cart.hmac) @@ -392,16 +407,19 @@ def test_cart_get(self): assert_equals(len(fetched_cart), len(cart)) def test_cart_get_wrong_hmac(self): - # We need to flush the cache here so we will get a new cart that has not been used in test_cart_clear + # We need to flush the cache here so we will get a new cart that has + # 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') + self.assertRaises(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") item = { - 'offer_id': product._safe_get_element('Offers.Offer.OfferListing.OfferListingId'), + 'offer_id': product._safe_get_element( + 'Offers.Offer.OfferListing.OfferListingId'), 'quantity': 1 } new_cart = self.amazon.cart_add(item, cart.cart_id, cart.hmac) From 7ecbea0bc34271e209fbcabafa588f06d4bbdb41 Mon Sep 17 00:00:00 2001 From: orangain Date: Tue, 18 Aug 2015 22:45:58 +0900 Subject: [PATCH 051/152] Add python-dateutil to install_requires in setup() --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 135da6a..4a41150 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,5 @@ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=True, - install_requires=["bottlenose", "lxml"], + install_requires=["bottlenose", "lxml", "python-dateutil"], ) From d31eac4be503437b19d3f42018f5dcf1f59ecf6a Mon Sep 17 00:00:00 2001 From: orangain Date: Tue, 18 Aug 2015 23:08:16 +0900 Subject: [PATCH 052/152] Fix installation instruction of dateutil Package name should be `python-dateutil` not `dateutils`. `dateutils` is a different package from `python-dateutil`. https://pypi.python.org/pypi/python-dateutil https://pypi.python.org/pypi/dateutils --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c581f4c..b28222f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Before you get started, make sure you have: * Installed [Bottlenose](https://github.com/lionheart/bottlenose) (`pip install bottlenose`) * Installed lxml (`pip install lxml`) -* Installed [dateutil](http://labix.org/python-dateutil) (`pip install dateutils`) +* Installed [dateutil](http://labix.org/python-dateutil) (`pip install python-dateutil`) * An Amazon Product Advertising account * An AWS account From 50252c5c3236bdf41344867d4a74e7478b2e905e Mon Sep 17 00:00:00 2001 From: YPCrumble Date: Thu, 10 Sep 2015 20:20:32 -0400 Subject: [PATCH 053/152] 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 49a966b5ac34a18f8e46bf6a66b64365dfb64e91 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 25 Sep 2015 11:15:44 +0100 Subject: [PATCH 054/152] Added Author info. --- amazon/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amazon/__init__.py b/amazon/__init__.py index 3f39079..d0bc073 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -1 +1,10 @@ +# -*- coding: utf-8 -*- + +""" +Python Amazon Simple Product Api +~~~~~~~ +Google Spreadsheets client library. +""" + __version__ = '2.0.1' +__author__ = 'Anton Burnashev' From 414aed05a7deb153416b2c1a1ad010a5b499850a Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Fri, 25 Sep 2015 15:02:37 +0100 Subject: [PATCH 055/152] Update version and author. --- amazon/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amazon/__init__.py b/amazon/__init__.py index d0bc073..d763e65 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -3,8 +3,8 @@ """ Python Amazon Simple Product Api ~~~~~~~ -Google Spreadsheets client library. +Amazon Product Advertising API Client library. """ __version__ = '2.0.1' -__author__ = 'Anton Burnashev' +__author__ = 'Yoav Aviram' From 834f13eb8a132afb6c8a06a5ffca42e2b25b595e Mon Sep 17 00:00:00 2001 From: Ivica Ceraj Date: Sat, 26 Sep 2015 07:19:58 -0400 Subject: [PATCH 056/152] Requires python-dateutil --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 135da6a..4a41150 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,5 @@ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=True, - install_requires=["bottlenose", "lxml"], + install_requires=["bottlenose", "lxml", "python-dateutil"], ) From a10fc127a1e1391d99850bfee30814bb552306d5 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sat, 17 Oct 2015 21:03:53 +0100 Subject: [PATCH 057/152] Add sphinx documentation. --- .gitignore | 3 + amazon/api.py | 11 +- docs/Makefile | 192 ++++++++++++++++++++++++++++++++ docs/amazon.rst | 5 + docs/conf.py | 290 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 23 ++++ 6 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/amazon.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst diff --git a/.gitignore b/.gitignore index a4780b0..b6feec6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ build/ dist/ .idea test_settings.py +_build +_static +_templates diff --git a/amazon/api.py b/amazon/api.py index 2a36f01..56a2c90 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -717,8 +717,8 @@ def offer_url(self): @property def author(self): """Author. - Depricated, please use `authors`. + :return: Author (string). """ @@ -906,6 +906,7 @@ def reviews(self): """Customer Reviews. Get a iframe URL for customer reviews. + :return: A tuple of: has_reviews (bool), reviews url (string) """ @@ -1041,6 +1042,7 @@ def languages(self): """Languages. Returns a set of languages in lower-case. + :return: Returns a set of languages in lower-case (strings). """ @@ -1058,6 +1060,7 @@ def features(self): """Features. Returns a list of feature descriptions. + :return: Returns a list of 'ItemAttributes.Feature' elements (strings). """ @@ -1100,9 +1103,10 @@ def get_attribute(self, name): def get_attribute_details(self, name): """Get Attribute Details - + Gets XML attributes of the product attribute. These usually contain details about the product attributes such as units. + :param name: Attribute name (string) :return: @@ -1132,6 +1136,7 @@ def parent_asin(self): """Parent ASIN. Can be used to test if product has a parent. + :return: Parent ASIN if product has a parent. """ @@ -1142,6 +1147,7 @@ def get_parent(self): Fetch parent product if it exists. Use `parent_asin` to check if a parent exist before fetching. + :return: An instance of :class:`~.AmazonProduct` representing the parent product. @@ -1172,6 +1178,7 @@ def images(self): When using lookup with RespnoseGroup 'Images', you'll get a list of images. Parse them so they are returned in an easily used list format. + :return: A list of `ObjectifiedElement` images """ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..bcc7fda --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonAmazonSimpleProductAPI.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonAmazonSimpleProductAPI.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonAmazonSimpleProductAPI" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonAmazonSimpleProductAPI" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/amazon.rst b/docs/amazon.rst new file mode 100644 index 0000000..a18616a --- /dev/null +++ b/docs/amazon.rst @@ -0,0 +1,5 @@ +Python Amazon Simple Product API +================================ + +.. automodule:: amazon.api + :members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9aa315b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +# +# Python Amazon Simple Product API documentation build configuration file, created by +# sphinx-quickstart on Fri Oct 16 13:21:48 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) +docs_root = os.path.dirname(__file__) +root_folder = os.path.split(docs_root)[0] +sys.path.insert(0, root_folder) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Python Amazon Simple Product API' +copyright = u'2015, Yoav Aviram' +author = u'Yoav Aviram' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.0.1' +# The full version, including alpha/beta/rc tags. +release = '2.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PythonAmazonSimpleProductAPIdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'PythonAmazonSimpleProductAPI.tex', u'Python Amazon Simple Product API Documentation', + u'Yoav Aviram', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pythonamazonsimpleproductapi', u'Python Amazon Simple Product API Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'PythonAmazonSimpleProductAPI', u'Python Amazon Simple Product API Documentation', + author, 'PythonAmazonSimpleProductAPI', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..cc12971 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +.. Python Amazon Simple Product API documentation master file, created by + sphinx-quickstart on Fri Oct 16 13:21:48 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Python Amazon Simple Product API's documentation! +============================================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + + amazon.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + From 4245799f23c302589cdd7b9c54ed471d93e1e45b Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 18 Oct 2015 15:40:56 +0100 Subject: [PATCH 058/152] Add docs building to travis. --- .travis.yml | 2 ++ amazon/__init__.py | 2 +- test-requirements.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 test-requirements.txt diff --git a/.travis.yml b/.travis.yml index 8ee4693..2bbc920 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,7 @@ install: - pip install . --use-mirrors - pip install pep8 --use-mirrors - pip install -r requirements.txt --use-mirrors + - pip install -r test-requirements.txt --use-mirrors script: - "pep8 --ignore=E501,E225,E128 amazon" + - "sphinx-build -nW -b html -d _build/doctrees . _build/html" diff --git a/amazon/__init__.py b/amazon/__init__.py index d763e65..e27dcd2 100644 --- a/amazon/__init__.py +++ b/amazon/__init__.py @@ -6,5 +6,5 @@ Amazon Product Advertising API Client library. """ -__version__ = '2.0.1' +__version__ = '2.0.2' __author__ = 'Yoav Aviram' diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..aba1014 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +Sphinx==1.1.3 From c17f8efc0066d89ef48163a0cd1e461e077a31e4 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 18 Oct 2015 15:42:13 +0100 Subject: [PATCH 059/152] Fix travis docs build command --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2bbc920..d8de358 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ install: - pip install -r test-requirements.txt --use-mirrors script: - "pep8 --ignore=E501,E225,E128 amazon" - - "sphinx-build -nW -b html -d _build/doctrees . _build/html" + - "sphinx-build -b html -d _build/doctrees . _build/html" From e5dab8b822999a89e81522ac06283a1b8c2bcbde Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 18 Oct 2015 15:45:05 +0100 Subject: [PATCH 060/152] Add RTD badge. Remove --use-mirrors from travis config. --- .travis.yml | 8 ++++---- README.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index d8de358..92d2add 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,10 @@ language: python python: - 2.7 install: - - pip install . --use-mirrors - - pip install pep8 --use-mirrors - - pip install -r requirements.txt --use-mirrors - - pip install -r test-requirements.txt --use-mirrors + - 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 . _build/html" diff --git a/README.md b/README.md index b28222f..3e77d33 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,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) Features From cf6d1ab71508a3d7015707dbebf5e4893f4926b6 Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 18 Oct 2015 15:58:28 +0100 Subject: [PATCH 061/152] Debug travis config --- .travis.yml | 2 +- amazon/api.py | 21 +++++++++++---------- docs/conf.py | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 92d2add..dfbef8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ install: - pip install -r test-requirements.txt script: - "pep8 --ignore=E501,E225,E128 amazon" - - "sphinx-build -b html -d _build/doctrees . _build/html" + - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" diff --git a/amazon/api.py b/amazon/api.py index 777a64e..510d663 100644 --- a/amazon/api.py +++ b/amazon/api.py @@ -123,7 +123,8 @@ def __init__(self, aws_key, aws_secret, aws_associate_tag, **kwargs): You generally want to set this a little lower than the max (so 0.9, not 1.0). Amazon limits the number of calls per hour, so for long running - tasks this should be set to 0.9 to ensure you don't hit the maximum. + tasks this should be set to 0.9 to ensure you don't hit the + maximum. Defaults to None (unlimited). :param Timeout: Optional timeout for queries. @@ -717,7 +718,7 @@ def offer_url(self): def author(self): """Author. Depricated, please use `authors`. - + :return: Author (string). """ @@ -905,7 +906,7 @@ def reviews(self): """Customer Reviews. Get a iframe URL for customer reviews. - + :return: A tuple of: has_reviews (bool), reviews url (string) """ @@ -1041,7 +1042,7 @@ def languages(self): """Languages. Returns a set of languages in lower-case. - + :return: Returns a set of languages in lower-case (strings). """ @@ -1059,7 +1060,7 @@ def features(self): """Features. Returns a list of feature descriptions. - + :return: Returns a list of 'ItemAttributes.Feature' elements (strings). """ @@ -1102,10 +1103,10 @@ def get_attribute(self, name): def get_attribute_details(self, name): """Get Attribute Details - + Gets XML attributes of the product attribute. These usually contain details about the product attributes such as units. - + :param name: Attribute name (string) :return: @@ -1135,7 +1136,7 @@ def parent_asin(self): """Parent ASIN. Can be used to test if product has a parent. - + :return: Parent ASIN if product has a parent. """ @@ -1146,7 +1147,7 @@ def get_parent(self): Fetch parent product if it exists. Use `parent_asin` to check if a parent exist before fetching. - + :return: An instance of :class:`~.AmazonProduct` representing the parent product. @@ -1176,7 +1177,7 @@ def images(self): When using lookup with RespnoseGroup 'Images', you'll get a list of images. Parse them so they are returned in an easily used list format. - + :return: A list of `ObjectifiedElement` images """ diff --git a/docs/conf.py b/docs/conf.py index 9aa315b..3871b7e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -114,7 +114,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the From 3ebf37415b007bef7dbd34808e3d83616030cc0f Mon Sep 17 00:00:00 2001 From: Yoav Aviram Date: Sun, 18 Oct 2015 16:03:50 +0100 Subject: [PATCH 062/152] New travis config format --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index dfbef8d..1c0a025 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,4 @@ install: script: - "pep8 --ignore=E501,E225,E128 amazon" - "sphinx-build -b html -d _build/doctrees ./docs/ _build/html" +sudo: false From fa1b7d794c0c8c9f4b6883eb1391b7f8efd47292 Mon Sep 17 00:00:00 2001 From: Eric Pauley Date: Wed, 11 Nov 2015 14:41:23 -0500 Subject: [PATCH 063/152] 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 064/152] 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 065/152] 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 066/152] 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 067/152] 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 068/152] 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 069/152] 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 070/152] 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 071/152] 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 072/152] 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 073/152] 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 074/152] 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 075/152] 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 076/152] 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 077/152] 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 078/152] 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 079/152] 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 080/152] 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 081/152] 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 082/152] 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 083/152] 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 084/152] 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 085/152] 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 086/152] 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 087/152] 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 088/152] 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 089/152] 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 090/152] 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 091/152] 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 092/152] 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 093/152] 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 094/152] 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 095/152] 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 096/152] 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 097/152] 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 098/152] 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 099/152] 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 100/152] 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 101/152] - 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 102/152] 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 103/152] 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 104/152] 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 105/152] 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 106/152] 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 107/152] 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 108/152] 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 109/152] 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 110/152] 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 111/152] 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 112/152] 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 113/152] 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 114/152] 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 115/152] 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 116/152] 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 117/152] 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 118/152] 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 119/152] 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 120/152] 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 121/152] 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 122/152] 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 123/152] 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 124/152] 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 125/152] 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 126/152] 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 127/152] 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 128/152] 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 129/152] 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 130/152] 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 131/152] 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 132/152] 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 133/152] 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 134/152] 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 135/152] 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 136/152] 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 137/152] 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 138/152] 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 139/152] 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 140/152] 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 141/152] 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 142/152] 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 143/152] 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 144/152] 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 145/152] 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 146/152] 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 147/152] 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 148/152] 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 149/152] 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 150/152] 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 151/152] 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 152/152] 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