Skip to content

Commit

Permalink
Added parsers/reports, modified parsers/base to include mws account c…
Browse files Browse the repository at this point in the history
…reds so that when polling reports we can use a blocking method which returns when getreportrequestlist returns done for the particular report. parsers/errors has been fixed to remove the namespace from the xml response because it was causing issues when hitting a different api endpoint which returned an error with a different namespace
  • Loading branch information
ziplokk1 committed May 12, 2016
1 parent 93a44e4 commit a1b401c
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 19 deletions.
20 changes: 20 additions & 0 deletions mws/mws.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import hashlib
import hmac
import base64

import datetime

import utils
import re
try:
Expand Down Expand Up @@ -180,6 +183,12 @@ def make_request(self, extra_data, method="GET", **kwargs):
}
if self.auth_token:
params['MWSAuthToken'] = self.auth_token

# Convert any datetime objects in params to string format
for k, v in extra_data.items():
if isinstance(v, datetime.datetime):
extra_data[k] = self.get_datetimestamp(v)

params.update(extra_data)
request_description = '&'.join(['%s=%s' % (k, urllib.quote(params[k], safe='-_.~').encode('utf-8')) for k in sorted(params)])
signature = self.calc_signature(method, request_description)
Expand Down Expand Up @@ -234,6 +243,17 @@ def calc_signature(self, method, request_description):
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())

def get_datetimestamp(self, dt=None):
"""
Convert datetime object or current datetime to amazon specific datetime format
:param dt: Datetime object to convert. If None, return current timestamp
:return: Converted datetime to timestamp
"""
fmt = '%Y-%m-%dT%H:%M:%SZ'
if dt:
return dt.strftime(fmt)
return datetime.datetime.now().strftime(fmt)

def get_timestamp(self):
"""
Returns the current timestamp in proper format.
Expand Down
21 changes: 16 additions & 5 deletions mws/parsers/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from lxml import etree


Expand Down Expand Up @@ -27,8 +29,17 @@ def inner(*args, **kwargs):

class BaseElementWrapper(object):

def __init__(self, element):
def __init__(self, element, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
"""
:param element: Etree object of response body
"""
self.element = element
self.mws_access_key = mws_access_key
self.mws_secret_key = mws_secret_key
self.mws_account_id = mws_account_id
self.mws_auth_token = mws_auth_token
self.logger = logging.getLogger(self.__class__.__name__)

def __str__(self):
return etree.tostring(self.element)
Expand All @@ -37,23 +48,23 @@ def __str__(self):
class BaseResponseMixin(object):

@classmethod
def load_from_file(cls, file_location):
def load_from_file(cls, file_location, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
"""
Create an instance of this class from a file.
:param file_location: path to file.
:return:
"""
with open(file_location, 'rb') as f:
return cls.load(f.read())
return cls.load(f.read(), mws_access_key, mws_secret_key, mws_account_id, mws_auth_token)

@classmethod
def load(cls, xml_string):
def load(cls, xml_string, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
"""
Create an instance of this class using an xml string.
:param xml_string:
:return:
"""
tree = etree.fromstring(xml_string)
return cls(tree)
return cls(tree, mws_access_key, mws_secret_key, mws_account_id, mws_auth_token)
38 changes: 25 additions & 13 deletions mws/parsers/errors.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
import re
from base import first_element, BaseResponseMixin, BaseElementWrapper
from lxml import etree


class ErrorResponse(ValueError, BaseElementWrapper, BaseResponseMixin):

namespaces = {
'a': 'http://mws.amazonservices.com/schema/Products/2011-10-01'
}

def __init__(self, element):
def __init__(self, element, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
BaseElementWrapper.__init__(self, element)
ValueError.__init__(self, self.message)

@property
@first_element
def type(self):
return self.element.xpath('//a:ErrorResponse/a:Error/a:Type/text()', namespaces=self.namespaces)
return self.element.xpath('//ErrorResponse/Error/Type/text()')

@property
@first_element
def code(self):
return self.element.xpath('//a:ErrorResponse/a:Error/a:Code/text()', namespaces=self.namespaces)
return self.element.xpath('//ErrorResponse/Error/Code/text()')

@property
@first_element
def message(self):
return self.element.xpath('//a:ErrorResponse/a:Error/a:Message/text()', namespaces=self.namespaces)
return self.element.xpath('//ErrorResponse/Error/Message/text()')

@property
@first_element
def request_id(self):
return self.element.xpath('//a:ErrorResponse/a:RequestID/text()', namespaces=self.namespaces)
return self.element.xpath('//ErrorResponse/RequestID/text()')

@classmethod
def load(cls, xml_string, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
"""
Create an instance of this class using an xml string.
overridden so that we can remove any namespace from the response.
:param xml_string:
:return:
"""
ptn = '\s+xmlns=\".*?\"'
xml_string = re.sub(ptn, '', xml_string)
tree = etree.fromstring(xml_string)
return cls(tree, mws_access_key, mws_secret_key, mws_account_id, mws_auth_token)


class ProductError(ValueError, BaseElementWrapper):
Expand All @@ -41,22 +53,22 @@ class ProductError(ValueError, BaseElementWrapper):
'b': 'http://mws.amazonservices.com/schema/Products/2011-10-01/default.xsd'
}

def __init__(self, element, identifier):
def __init__(self, element, identifier, mws_access_key=None, mws_secret_key=None, mws_account_id=None, mws_auth_token=None):
BaseElementWrapper.__init__(self, element)
ValueError.__init__(self, self.message)
self.identifier = identifier

@property
@first_element
def message(self):
return self.element.xpath('./a:Message/text()', namespaces=self.namespaces)
return self.element.xpath('./a:Message/text()')

@property
@first_element
def code(self):
return self.element.xpath('./a:Code/text()', namespaces=self.namespaces)
return self.element.xpath('./a:Code/text()')

@property
@first_element
def type(self):
return self.element.xpath('./a:Type/text()', namespaces=self.namespaces)
return self.element.xpath('./a:Type/text()')
Empty file added mws/parsers/reports/__init__.py
Empty file.
Loading

0 comments on commit a1b401c

Please sign in to comment.