Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Changed the way processors are loaded.

You can specify API(locale=...) as

* a class
* a class name as string (e.g. 'mymodule.processor.ProcessorClass')
* an instance

If not specified, the first one of amazonproduct.processors.objectify.Processor, amazonproduct.processors.etree.Processor or amazonproduct.processors.minidom.Processor successfully imported is used. Note that the etree processor will try to import a number of possible implementations before giving up.
  • Loading branch information...
commit fdb6e16ba69fde972a7e9b66ea0d6dfaac7bcef4 1 parent ebdd573
@redtoad authored
View
56 amazonproduct/api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2011 Sebastian Rahlf <basti at redtoad dot de>
+# Copyright (C) 2009-2013 Sebastian Rahlf <basti at redtoad dot de>
#
# This program is release under the BSD License. You can find the full text of
# the license in the LICENSE file.
@@ -39,15 +39,18 @@ def all(iterable):
from amazonproduct.version import VERSION
from amazonproduct.errors import *
-from amazonproduct.utils import load_config, running_on_gae, REQUIRED_KEYS
-from amazonproduct.processors import ITEMS_PAGINATOR
+from amazonproduct.utils import load_config, load_class
+from amazonproduct.utils import running_on_gae, REQUIRED_KEYS
+from amazonproduct.processors import ITEMS_PAGINATOR, BaseProcessor
-# load default processor
-try:
- from amazonproduct.processors.objectify import Processor
-except ImportError:
- from amazonproduct.processors.etree import Processor
+# first processors successfully imported is used
+PROCESSORS = [
+ 'amazonproduct.processors.objectify.Processor',
+ 'amazonproduct.processors.etree.Processor',
+ 'amazonproduct.processors.minidom.Processor',
+]
+
USER_AGENT = ('python-amazon-product-api/%s '
'+http://pypi.python.org/pypi/python-amazon-product-api/' % VERSION)
@@ -166,10 +169,34 @@ def __init__(self, access_key_id=None, secret_access_key=None, locale=None,
self.last_call = datetime(1970, 1, 1)
self.debug = 0 # set to 1 if you want to see HTTP headers
- if processor is not None:
+ if isinstance(processor, BaseProcessor):
self.processor = processor
else:
- self.processor = Processor()
+ self.processor = self._load_processor(processor)()
+
+ @staticmethod
+ def _load_processor(*names):
+ """
+ Loads result processor. If no processor is given (``None``), the first
+ one is taken that can be successfully imported from the list of default
+ processors (:const:`PROCESSORS`).
+ """
+ if len(names) == 0 or names[0] is None:
+ names = PROCESSORS
+ for name in names:
+ # processor was given as string
+ if isinstance(name, (str, unicode)):
+ try:
+ pclass = load_class(name)
+ if issubclass(pclass, BaseProcessor):
+ return pclass
+ except ImportError:
+ continue
+ # processor was given as class
+ elif isinstance(name, type) and issubclass(name, BaseProcessor):
+ return name
+ # nothing successfully loaded
+ raise ImportError('No processor class could be imported!')
def __repr__(self):
return '<API(%s/%s) at %s>' % (self.VERSION, self.locale, hex(id(self)))
@@ -308,8 +335,13 @@ def _parse(self, fp):
def call(self, **qargs):
"""
Builds a signed URL for the operation, fetches the result from Amazon
- and parses the XML. If you want to customise things at any stage, simply
- override the respective method(s):
+ and parses the XML.
+
+ Example::
+
+ xml = api.call(Operation='ItemLookup', ItemId='B067884223')
+
+ .. note:: If you want to customise things at any stage, simply override the respective method(s):
* ``_build_url(**query_parameters)``
* ``_fetch(url)``
View
23 amazonproduct/processors/etree.py
@@ -1,3 +1,8 @@
+# Copyright (C) 2009-2013 Sebastian Rahlf <basti at redtoad dot de>
+#
+# This program is release under the BSD License. You can find the full text of
+# the license in the LICENSE file.
+
import re
from amazonproduct.contrib.cart import Cart, Item
@@ -46,9 +51,23 @@ def extract_nspace(element):
class Processor (BaseProcessor):
+ """
+ Result processor using ElementTree.
+
+ The first implementation of ElementTree which can be successfully imported
+ will be used. Order of import is:
+
+ * lxml.etree
+ * xml.etree.cElementTree
+ * xml.etree.ElementTree
+ * cElementTree
+ * elementtree.ElementTree
+
+ """
+
def __init__(self, *args, **kwargs):
# processor can be told which etree module to use in order to have
- # multiple processors each using a different implementation
+ # multiple processors each using a different implementation
etree_mod = kwargs.pop('module', None)
try:
if etree_mod:
@@ -121,6 +140,7 @@ def load_paginator(cls, paginator_type):
except KeyError:
return None
+
class XPathPaginator (BaseResultPaginator):
"""
@@ -164,6 +184,7 @@ class ItemPaginator (XPathPaginator):
total_results_xpath = './/{}Items/{}TotalResults'
items = './/{}Items/{}Item'
+
class RelatedItemsPaginator (XPathPaginator):
counter = 'RelatedItemPage'
View
6 amazonproduct/processors/minidom.py
@@ -1,8 +1,14 @@
+# Copyright (C) 2009-2013 Sebastian Rahlf <basti at redtoad dot de>
+#
+# This program is release under the BSD License. You can find the full text of
+# the license in the LICENSE file.
+
import xml.dom.minidom
from amazonproduct.errors import AWSError
from amazonproduct.processors import BaseProcessor
+
class Processor(BaseProcessor):
"""
View
4 amazonproduct/processors/objectify.py
@@ -1,3 +1,7 @@
+# Copyright (C) 2009-2013 Sebastian Rahlf <basti at redtoad dot de>
+#
+# This program is release under the BSD License. You can find the full text of
+# the license in the LICENSE file.
from lxml import etree, objectify
View
20 amazonproduct/utils.py
@@ -1,3 +1,8 @@
+# Copyright (C) 2009-2013 Sebastian Rahlf <basti at redtoad dot de>
+#
+# This program is release under the BSD License. You can find the full text of
+# the license in the LICENSE file.
+
from ConfigParser import SafeConfigParser
import os
import sys
@@ -102,7 +107,7 @@ def load_config():
* Config files ``/etc/amazon-product-api.cfg`` or ``~/.amazon-product-api``
where the latter may add or replace values of the former.
* A boto config file [#]_ found in ``/etc/boto.cfg`` or ``~/.boto``.
-
+
Whatever is found first counts.
The returned dictionary may look like this::
@@ -113,7 +118,7 @@ def load_config():
'associate_tag': 'redtoad-10',
'locale': 'uk'
}
-
+
.. _#: http://code.google.com/p/boto/wiki/BotoConfig
"""
config = load_boto_config()
@@ -170,3 +175,14 @@ def running_on_gae():
"""
return 'Google' in os.environ.get('SERVER_SOFTWARE', '')
+
+def load_class(name):
+ """
+ Loads class from string.
+
+ :param name: fully-qualified class name (e.g. ``processors.etree.
+ ItemPaginator``)
+ """
+ module_name, class_name = name.rsplit('.', 1)
+ module = import_module(module_name)
+ return getattr(module, class_name)
View
14 tests/test_utils.py
@@ -1,4 +1,8 @@
+import pytest
+import types
+
from amazonproduct import utils
+from amazonproduct.processors import etree
def test_load_global_boto_config(configfiles):
configfiles.add_file('''
@@ -96,3 +100,13 @@ def test_load_config(configfiles, monkeypatch):
assert cfg['associate_tag'] is None
assert cfg['locale'] == 'OS VARIABLE'
+
+@pytest.mark.parametrize(('txt', 'cls'), [
+ ('amazonproduct.processors.etree.Processor', etree.Processor),
+ ('amazonproduct.processors.etree.XPathPaginator', etree.XPathPaginator),
+ ('amazonproduct.processors.etree.ItemPaginator', etree.ItemPaginator),
+])
+def test_load_class(txt, cls):
+ loaded = utils.load_class(txt)
+ assert isinstance(loaded, types.TypeType)
+ assert loaded == cls
View
11 tests/test_xml_responses.py
@@ -118,11 +118,8 @@ def pytest_funcarg__api(request):
version = request.param['version']
xml_response = request.param['xml_response']
- processor = TESTABLE_PROCESSORS[request.param['processor']]
- if isinstance(processor, type):
- processor = processor()
-
- api = API(locale=locale, processor=processor)
+ api = API(locale=locale,
+ processor=TESTABLE_PROCESSORS[request.param['processor']])
api.VERSION = version
api.REQUESTS_PER_SECOND = 10000 # just for here!
@@ -226,13 +223,15 @@ def __call__(self, fnc):
return fnc
-
class TestAPICredentials (object):
"""
Check that API will complain about missing credentials.
"""
+ # locale CN does not seem to work!
+ locales = ['ca', 'de', 'es', 'fr', 'it', 'jp', 'uk', 'us']
+
def test_without_credentials_fails(self, api):
api.access_key = api.secret_key = ''
pytest.raises(MissingClientTokenId, api.item_lookup, '???')
View
12 tox.ini
@@ -7,11 +7,15 @@ downloadcache = {toxworkdir}/_download
[testenv]
commands =
py.test -v \
- --junitxml=junit-{envname}.xml
-deps =
- lxml
- pytest
+ --junitxml=junit-{envname}.xml \
+ tests
+deps =
+ pytest>=2.0.3,<2.3
pytest-localserver
+ lxml
+ cElementTree
+ elementtree
+ pycrypto
[testenv:docs]
basepython = python
Please sign in to comment.
Something went wrong with that request. Please try again.