diff --git a/README.md b/README.md index c2b8e9cd..a143f82b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # mws -This is a fork and continuation of https://github.com/czpython/python-amazon-mws. +This is a fork and continuation of https://github.com/czpython/python-amazon-mws with preliminary Python 2/3 support. Install from PyPI with `pip install mws`. diff --git a/docs/source/conf.py b/docs/source/conf.py index a72c5308..5f3b00dc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,8 +11,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os - # 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. @@ -48,9 +46,9 @@ # built documents. # # The short X.Y version. -version = '0.6' +version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.6' +release = '0.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/mws/__init__.py b/mws/__init__.py index 51d1c912..2f0b5710 100644 --- a/mws/__init__.py +++ b/mws/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -__version__ = '0.6' +from __future__ import absolute_import -from mws import * \ No newline at end of file +from .mws import * diff --git a/mws/mws.py b/mws/mws.py index 7c48cdd1..5a6a975a 100644 --- a/mws/mws.py +++ b/mws/mws.py @@ -1,24 +1,25 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -# -# Basic interface to Amazon MWS -# Based on http://code.google.com/p/amazon-mws-python -# +from __future__ import absolute_import -import urllib +import base64 import hashlib import hmac -import base64 -import utils import re +from time import gmtime, strftime + +from requests import request +from requests.exceptions import HTTPError + +from . import utils + +try: + from urllib.parse import quote +except ImportError: + from urllib import quote try: from xml.etree.ElementTree import ParseError as XMLError except ImportError: from xml.parsers.expat import ExpatError as XMLError -from time import strftime, gmtime - -from requests import request -from requests.exceptions import HTTPError __all__ = [ @@ -64,15 +65,19 @@ def calc_md5(string): """ md = hashlib.md5() md.update(string) - return base64.encodestring(md.digest()).strip('\n') + return base64.encodebytes(md.digest()).strip('\n') def remove_empty(d): + """Helper function that removes all keys from a dictionary (d), that have an empty value. + + Args: + d (dict) + + Return: + dict """ - Helper function that removes all keys from a dictionary (d), - that have an empty value. - """ - for key in d.keys(): + for key in set(d.keys()): if not d[key]: del d[key] return d @@ -88,7 +93,7 @@ def __init__(self, xml, rootkey=None): self.original = xml self._rootkey = rootkey self._mydict = utils.xml2dict().fromstring(remove_namespace(xml)) - self._response_dict = self._mydict.get(self._mydict.keys()[0], + self._response_dict = self._mydict.get(list(self._mydict.keys())[0], self._mydict) @property @@ -180,9 +185,9 @@ def make_request(self, extra_data, method="GET", **kwargs): if self.auth_token: params['MWSAuthToken'] = self.auth_token params.update(extra_data) - request_description = '&'.join(['%s=%s' % (k, urllib.quote(params[k], safe='-_.~').encode('utf-8')) for k in sorted(params)]) + request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)]) signature = self.calc_signature(method, request_description) - url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, urllib.quote(signature)) + url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature)) headers = {'User-Agent': 'python-amazon-mws/0.0.1 (Language=Python)'} headers.update(kwargs.get('extra_headers', {})) @@ -196,16 +201,18 @@ def make_request(self, extra_data, method="GET", **kwargs): # When retrieving data from the response object, # be aware that response.content returns the content in bytes while response.text calls # response.content and converts it to unicode. - data = response.content + data = response.content # I do not check the headers to decide which content structure to server simply because sometimes # Amazon's MWS API returns XML error responses with "text/plain" as the Content-Type. try: parsed_response = DictWrapper(data, extra_data.get("Action") + "Result") + except TypeError: # raised when using Python 3 and trying to remove_namespace() + parsed_response = DictWrapper(response.text, extra_data.get("Action") + "Result") except XMLError: parsed_response = DataWrapper(data, response.headers) - except HTTPError, e: + except HTTPError as e: error = MWSError(str(e.response.text)) error.response = e.response raise error @@ -224,9 +231,18 @@ def get_service_status(self): def calc_signature(self, method, request_description): """Calculate MWS signature to interface with Amazon + + Args: + method (str) + request_description (str) """ - sig_data = method + '\n' + self.domain.replace('https://', '').lower() + '\n' + self.uri + '\n' + request_description - return base64.b64encode(hmac.new(str(self.secret_key), sig_data, hashlib.sha256).digest()) + sig_data = '\n'.join([ + method, + self.domain.replace('https://', '').lower(), + self.uri, + request_description + ]) + return base64.b64encode(hmac.new(self.secret_key.encode(), sig_data.encode(), hashlib.sha256).digest()) def get_timestamp(self): """ @@ -398,9 +414,9 @@ def get_report_schedule_count(self, types=()): class Orders(MWS): """ Amazon Orders API """ - URI = "/Orders/2011-01-01" - VERSION = "2011-01-01" - NS = '{https://mws.amazonservices.com/Orders/2011-01-01}' + URI = "/Orders/2013-09-01" + VERSION = "2013-09-01" + NS = '{https://mws.amazonservices.com/Orders/2013-09-01}' def list_orders(self, marketplaceids, created_after=None, created_before=None, lastupdatedafter=None, lastupdatedbefore=None, orderstatus=(), fulfillment_channels=(), diff --git a/mws/offamazonpayments.py b/mws/offamazonpayments.py index a6b7c7c8..ec146d1c 100644 --- a/mws/offamazonpayments.py +++ b/mws/offamazonpayments.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + from .mws import MWS @@ -171,4 +174,3 @@ def close_order_reference(self, order_ref): AmazonOrderReferenceId=order_ref ) ) - diff --git a/mws/utils.py b/mws/utils.py index 8d31c258..dd91af10 100644 --- a/mws/utils.py +++ b/mws/utils.py @@ -6,9 +6,10 @@ @author: pierre """ +from __future__ import absolute_import -import xml.etree.ElementTree as ET import re +import xml.etree.ElementTree as ET class object_dict(dict): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..7d6476aa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +requests + +# dev requirements +pypandoc==1.3.3 +sphinx==1.4.8 diff --git a/setup.py b/setup.py index 326206c1..4d6351db 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,22 @@ # -*- coding: utf-8 -*- +short_description = 'Python library for interacting with the Amazon MWS API' +try: + from pypandoc import convert +except (ImportError, OSError): # either pypandoc or pandoc isn't installed + long_description = "See README.md" +else: + long_description = convert("README.md", 'rst') + from setuptools import setup setup( name='mws', - version='0.6', + version='0.7', maintainer="James Hiew", maintainer_email="james@hiew.net", url="http://github.com/jameshiew/mws", - description='A python interface for Amazon MWS', + description=short_description, + long_description=long_description, packages=['mws'], install_requires=[ 'requests' @@ -23,11 +32,14 @@ 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], platforms=['OS Independent'], - license='LICENSE.txt', + license='Unlicense', include_package_data=True, zip_safe=False )