From 29e15791844a6305f9ac827365f3e69e351ed9db Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 3 Aug 2011 10:15:02 -0700 Subject: [PATCH 001/218] commit all project files --- .gitignore | 7 + MANIFEST.in | 6 + ebay.yaml | 26 +++ ebaysdk/__init__.py | 545 +++++++++++++++++++++++++++++++++++++++++++ ebaysdk/utils.py | 303 ++++++++++++++++++++++++ setup.py | 43 ++++ test/test_ebaysdk.py | 22 ++ 7 files changed, 952 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 ebay.yaml create mode 100644 ebaysdk/__init__.py create mode 100644 ebaysdk/utils.py create mode 100644 setup.py create mode 100644 test/test_ebaysdk.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ef237d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +dist/ +tkebay.yaml +ebaysdk.egg-info/ +*.swp +*.pyc +.svn diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..190d13b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +recursive-include ebaysdk *.py +recursive-include test *.py +prune ebaysdk/contrib +include ebay.yaml +include Changes + diff --git a/ebay.yaml b/ebay.yaml new file mode 100644 index 0000000..c638eee --- /dev/null +++ b/ebay.yaml @@ -0,0 +1,26 @@ +# ebaysdk API Configurations + +name: ebay_api_config + +# Trading - External +api.ebay.com: + password: + username: + appid: + certid: + devid: + token: + version: 671 + https: 1 + +# Shopping +open.api.ebay.com: + appid: + certid: + devid: + version: 671 + +# Finding/Merchandising +svcs.ebay.com: + appid: + version: 1.0.0 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py new file mode 100644 index 0000000..8a7ae6f --- /dev/null +++ b/ebaysdk/__init__.py @@ -0,0 +1,545 @@ +import os +import sys +import httplib, ConfigParser +import md5 +import string +import base64 +import re +import yaml +from types import DictType +import pprint + +from urlparse import urlsplit +from xml.dom.minidom import parseString, Node +from BeautifulSoup import BeautifulStoneSoup +from ebaysdk.utils import object_dict, xml2dict, dict2xml, dict_to_xml + + +def nodeText(node): + + rc = [] + + if hasattr(node, 'childNodes'): + for cn in node.childNodes: + if cn.nodeType == cn.TEXT_NODE: + rc.append(cn.data) + elif cn.nodeType == cn.CDATA_SECTION_NODE: + rc.append(cn.data) + + return ''.join(rc) + +def tag(name, value): + return "<%s>%s" % ( name, value, name ) + +class ebaybase(object): + + def __init__(self,debug=0, proxy_host=None, proxy_port=80): + self.verb = None + self.debug = debug + self.proxy_host = proxy_host + self.proxy_port = proxy_port + self.spooled_calls = []; + self._reset() + + def v(self, *args, **kwargs): + + args_a = [w for w in args] + first = args_a[0] + args_a.remove(first) + + h = kwargs.get('mydict', {}) + if h: + h = h.get(first, {}) + else: + h = self.response_dict().get(first, {}) + + if len(args) == 1: + try: + return h.get('value', None) + except: + return h + + last = args_a.pop() + + for a in args_a: + h = h.get(a, {}) + + h = h.get(last, {}) + + try: + return h.get('value', None) + except: + return h + + def load_yaml(self, config_file): + + dirs = [ '.', os.environ.get('HOME'), '/etc' ] + + for mydir in dirs: + myfile = "%s/%s" % (mydir, config_file) + + if os.path.exists( myfile ): + try: + f = open( myfile, "r" ) + except IOError, e: + print "unable to open file %s" % e + + #print "using file %s" % myfile + + yData = yaml.load( f.read() ) + domain = self.api_config.get('domain', '') + + self.api_config_append( yData.get(domain, {}) ) + return + + def api_config_append(self, config): + for c in config: + self.api_config[c] = config[c] + + def getNodeText(self,nodelist): + rc = "" + for node in nodelist: + if node.nodeType == node.TEXT_NODE: + rc = rc + node.data + return rc + + def _reset(self): + self._response_content = None + self._response_dom = None + self._response_soup = None + self._response_dict = None + self._response_error = None + + def do(self,verb,call_data=dict()): + return self.execute(verb,call_data) + + def execute(self,verb,xml): + self.verb = verb + self.call_xml = xml + + self._reset() + self._response_content = self._execute_http_request() + + # remove xml namespace + regex = re.compile( 'xmlns="[^"]+"' ) + self._response_content = regex.sub( '', self._response_content ) + + def response_soup(self): + if not self._response_soup: + self._response_soup = BeautifulStoneSoup(unicode(self._response_content)) + + return self._response_soup + + def response_dom(self): + if not self._response_dom: + dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb))) ) + self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] + + return self._response_dom + + def response_dict(self): + if not self._response_dict: + mydict = xml2dict().fromstring(self._response_content) + self._response_dict = mydict.get(self.verb+'Response', mydict) + + return self._response_dict + + def api_init(self,config_items): + for config in config_items: + self.api_config[config[0]] = config[1] + + + + def _execute_http_request(self): + "performs the http post and returns the XML response body" + + try: + connection = None + + if self.api_config.get('https'): + if self.proxy_host: + connection = httplib.HTTPSConnection(self.proxy_host, self.proxy_port) + connection.connect() + url = self.api_config.get('domain', None) + if self.api_config.get('uri', None): + url = "%s/%s" % ( url, self.api_config.get('uri', None) ) + + connection.request( + "POST", + url, + self._build_request_xml(), + self._build_request_headers() + ) + + + else: + connection = httplib.HTTPSConnection( + self.api_config.get('domain', None), + ) + connection.request( + "POST", + self.api_config.get('uri', None), + self._build_request_xml(), + self._build_request_headers() + ) + + + else: + if self.proxy_host: + connection = httplib.HTTPConnection(self.proxy_host, self.proxy_port) + connection.connect() + url = self.api_config.get('domain', None) + + if self.api_config.get('uri', None): + url = "%s/%s" % ( url, self.api_config.get('uri', None) ) + + connection.request( + "POST", + url, + self._build_request_xml(), + self._build_request_headers() + ) + + + else: + connection = httplib.HTTPConnection( + self.api_config.get('domain', None), + ) + connection.request( + "POST", + self.api_config.get('uri', None), + self._build_request_xml(), + self._build_request_headers() + ) + + response = connection.getresponse() + xml = response.read() + connection.close() + + except Exception, e: + self._response_error = "%s" % e + return "" + + if response.status != 200: + self._response_error = "Error sending request:" + response.reason + else: + return xml + + def error(self): + "builds and returns the api error message" + + str = [] + + if self._response_error: + str.append( self._response_error ) + + for e in self.response_dom().getElementsByTagName("Errors"): + + if e.getElementsByTagName('ErrorClassification'): + str.append( '- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0]) ) + + if e.getElementsByTagName('SeverityCode'): + str.append( '- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0]) ) + + if e.getElementsByTagName('ErrorCode'): + str.append( '- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0]) ) + + if e.getElementsByTagName('ShortMessage'): + str.append( '- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0]) ) + + if e.getElementsByTagName('LongMessage'): + str.append( '- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0]) ) + + if ( len(str) > 0 ): + return "%s error:\n%s\n" % (self.verb, "\n".join(str)) + + return "\n".join(str) + +class shopping(ebaybase): + """ + Shopping backend for ebaysdk. + http://developer.ebay.com/products/shopping/ + + >>> s = shopping() + >>> s.execute('FindItemsAdvanced', tag('CharityID', '3897')) + >>> print s.v('Ack') + Success + >>> print s.error() + + """ + + def __init__(self, + domain='open.api.ebay.com', + uri='/shopping', + https=False, + siteid=0, + response_encoding='XML', + proxy_host = None, + proxy_port = None, + request_encoding='XML', + config_file='ebay.yaml' ): + + ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + + self.api_config = { + 'domain' : domain, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, + 'response_encoding' : response_encoding, + 'request_encoding' : request_encoding, + } + + self.load_yaml(config_file) + + def _build_request_headers(self): + return { + "X-EBAY-API-VERSION": self.api_config.get('version', ''), + "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), + "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), + "X-EBAY-API-CALL-NAME": self.verb, + "X-EBAY-API-REQUEST-ENCODING": "XML", + "Content-Type": "text/xml" + } + + def _build_request_xml(self): + xml = "" + xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" + xml += self.call_xml + xml += "" + + return xml + +class html(ebaybase): + """ + HTML backend for ebaysdk. + + >>> h = html() + >>> h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> print h.v('rss', 'channel', 'ttl') + 60 + >>> title = h.response_dom().getElementsByTagName('title')[0] + >>> print nodeText( title ) + mytouch slide + >>> print title.toxml() + <![CDATA[mytouch slide]]> + >>> print h.error() + None + """ + + def __init__(self, proxy_host=None, proxy_port=None): + ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + + def response_dom(self): + if not self._response_dom: + self._response_dom = parseString(self._response_content) + + return self._response_dom + + def response_dict(self): + if not self._response_dict: + self._response_dict = xml2dict().fromstring(self._response_content) + + return self._response_dict + + def execute(self,url, call_data=dict()): + self.url = url + self.call_data = call_data + + self._reset() + self._response_content = self._execute_http_request() + + # remove xml namespace + regex = re.compile( 'xmlns="[^"]+"' ) + self._response_content = regex.sub( '', self._response_content ) + + def _execute_http_request(self): + "performs the http post and returns the XML response body" + + try: + connection = None + + if self.proxy_host: + connection = httplib.HTTPConnection(self.proxy_host, self.proxy_port) + connection.connect() + connection.request( + "GET", + self.url, + self.call_data + ) + else: + nil, domain, uri, args, nil = urlsplit(self.url) + connection = httplib.HTTPConnection(domain) + + connection.request( + "GET", + "%s?%s" % (uri,args), + self.call_data + ) + + response = connection.getresponse() + xml = response.read() + connection.close() + except Exception, e: + self._response_error = "failed to connect: %s" % e + return "" + + if response.status != 200: + self._response_error = "Error sending request:" + response.reason + else: + return xml + + def error(self): + "builds and returns the api error message" + + return self._response_error + +class trading(ebaybase): + """ + Trading backend for the ebaysdk + http://developer.ebay.com/products/trading/ + + >>> t = trading() + >>> t.execute('GetCharities', tag('CharityID', '3897')) + >>> nodeText(t.response_dom().getElementsByTagName('Name')[0]) + u'Sunshine Kids Foundation' + >>> print t.error() + + """ + + def __init__(self, + domain='api.ebay.com', + uri='/ws/api.dll', + https=False, + siteid='0', + response_encoding='XML', + request_encoding='XML', + proxy_host = None, + proxy_port = None, + config_file='ebay.yaml' ): + + ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + + self.api_config = { + 'domain' : domain, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, + 'response_encoding' : response_encoding, + 'request_encoding' : request_encoding, + } + + self.load_yaml(config_file) + + def _build_request_headers(self): + return { + "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('compatability','648'), + "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), + "X-EBAY-API-APP-NAME": self.api_config.get('appid',''), + "X-EBAY-API-CERT-NAME": self.api_config.get('certid',''), + "X-EBAY-API-SITEID": self.api_config.get('siteid',''), + "X-EBAY-API-CALL-NAME": self.verb, + "Content-Type": "text/xml" + } + + def _build_request_xml(self): + xml = "" + xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" + xml += "" + if self.api_config.get('token', None): + xml += "%s" % self.api_config.get('token') + else: + xml += "%s" % self.api_config.get('username', '') + xml += "%s" % self.api_config.get('password', '') + + xml += "" + xml += self.call_xml + xml += "" + + return xml + +class finding(ebaybase): + """ + Finding backend for ebaysdk. + http://developer.ebay.com/products/finding/ + + >>> f = finding() + >>> f.execute('findItemsAdvanced', tag('keywords', 'shoes')) + >>> print f.v('itemSearchURL') != '' + True + >>> items = f.v('searchResult', 'item') + >>> print len(items) + 100 + >>> print f.v('ack') + Success + >>> print f.error() + + >>> print nodeText(f.response_dom().getElementsByTagName('ack')[0]) + Success + """ + + """ + print "itemId" + print f.v('country', mydict=items[0]) + print f.v('itemId', mydict=items[0]) + print f.v('title', mydict=items[0]) + print f.v('primaryCategory', 'categoryId', mydict=items[0]) + + # examples using the response dom + titles = f.response_dom().getElementsByTagName('title') + print titles[0].toxml() + print nodeText(titles[0]) + + items = f.response_dom().getElementsByTagName('item') + + # 4.75 + shipCost = items[0].getElementsByTagName('shippingServiceCost')[0] + print shipCost.attributes['currencyId'].value + print nodeText(shipCost) + """ + + def __init__(self, + domain='svcs.ebay.com', + service='FindingService', + uri='/services/search/FindingService/v1', + https=False, + siteid='EBAY-US', + response_encoding='XML', + request_encoding='XML', + proxy_host = None, + proxy_port = None, + config_file='ebay.yaml' ): + + ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + + self.api_config = { + 'domain' : domain, + 'service' : service, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, + 'response_encoding' : response_encoding, + 'request_encoding' : request_encoding, + } + + self.load_yaml(config_file) + + def _build_request_headers(self): + return { + "X-EBAY-SOA-SERVICE-NAME" : self.api_config.get('service',''), + "X-EBAY-SOA-SERVICE-VERSION" : self.api_config.get('version',''), + "X-EBAY-SOA-SECURITY-APPNAME" : self.api_config.get('appid',''), + "X-EBAY-SOA-GLOBAL-ID" : self.api_config.get('siteid',''), + "X-EBAY-SOA-OPERATION-NAME" : self.verb, + "X-EBAY-SOA-REQUEST-DATA-FORMAT" : self.api_config.get('request_encoding',''), + "X-EBAY-SOA-RESPONSE-DATA-FORMAT" : self.api_config.get('response_encoding',''), + "Content-Type" : "text/xml" + } + + def _build_request_xml(self): + xml = "" + xml += "<" + self.verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" + xml += self.call_xml + xml += "" + + return xml diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py new file mode 100644 index 0000000..42a9a10 --- /dev/null +++ b/ebaysdk/utils.py @@ -0,0 +1,303 @@ +try: + import xml.etree.ElementTree as ET +except: + import cElementTree as ET # for 2.4 + +import re + +class object_dict(dict): + """object view of dict, you can + >>> a = object_dict() + >>> a.fish = 'fish' + >>> a['fish'] + 'fish' + >>> a['water'] = 'water' + >>> a.water + 'water' + >>> a.test = {'value': 1} + >>> a.test2 = object_dict({'name': 'test2', 'value': 2}) + >>> a.test, a.test2.name, a.test2.value + (1, 'test2', 2) + """ + def __init__(self, initd=None): + if initd is None: + initd = {} + dict.__init__(self, initd) + + def __getattr__(self, item): + + d = self.__getitem__(item) + + if isinstance(d, dict) and 'value' in d and len(d) == 1: + return d['value'] + else: + return d + + # if value is the only key in object, you can omit it + + def __setattr__(self, item, value): + self.__setitem__(item, value) + +class xml2dict(object): + + def __init__(self): + pass + + def _parse_node(self, node): + node_tree = object_dict() + # Save attrs and text, hope there will not be a child with same name + if node.text: + node_tree.value = node.text + for (k,v) in node.attrib.items(): + k,v = self._namespace_split(k, object_dict({'value':v})) + node_tree[k] = v + #Save childrens + for child in node.getchildren(): + tag, tree = self._namespace_split(child.tag, self._parse_node(child)) + if tag not in node_tree: # the first time, so store it in dict + node_tree[tag] = tree + continue + old = node_tree[tag] + if not isinstance(old, list): + node_tree.pop(tag) + node_tree[tag] = [old] # multi times, so change old dict to a list + node_tree[tag].append(tree) # add the new one + + return node_tree + + def _namespace_split(self, tag, value): + """ + Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients' + ns = http://cs.sfsu.edu/csc867/myscheduler + name = patients + """ + result = re.compile("\{(.*)\}(.*)").search(tag) + if result: + value.namespace, tag = result.groups() + + return (tag,value) + + def parse(self, file): + """parse a xml file to a dict""" + f = open(file, 'r') + return self.fromstring(f.read()) + + def fromstring(self, s): + """parse a string""" + + if not s: + return object_dict({}) + #print "fromstring:" + s + + t = ET.fromstring(s) + root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) + return object_dict({root_tag: root_tree}) + + +class dict2xml: + xml = "" + level = 0 + + def __init__(self): + self.xml = "" + self.level = 0 + + def __del__(self): + pass + + def setXml(self,Xml): + self.xml = Xml + + def setLevel(self,Level): + self.level = Level + + def dict2xml(self,map): + if (str(type(map)) == "" or str(type(map)) == ""): + for key, value in map.items(): + if (str(type(value)) == "" or str(type(value)) == ""): + if(len(value) > 0): + self.xml += "\t"*self.level + self.xml += "<%s>\n" % (key) + self.level += 1 + self.dict2xml(value) + self.level -= 1 + self.xml += "\t"*self.level + self.xml += "\n" % (key) + else: + self.xml += "\t"*(self.level) + self.xml += "<%s>\n" % (key,key) + #end if + else: + self.xml += "\t"*(self.level) + self.xml += "<%s>%s\n" % (key,value, key) + #end if + else: + self.xml += "\t"*self.level + self.xml += "<%s>%s\n" % (key,value, key) + + return self.xml + +def list_to_xml(name, l, stream): + for d in l: + dict_to_xml(d, name, stream) + +def dict_to_xml(d, root_node_name, stream): + """ Transform a dict into a XML, writing to a stream """ + stream.write('\n<' + root_node_name) + attributes = StringIO() + nodes = StringIO() + for item in d.items(): + key, value = item + if isinstance(value, dict): + dict_to_xml(value, key, nodes) + elif isinstance(value, list): + list_to_xml(key, value, nodes) + elif isinstance(value, str) or isinstance(value, unicode): + attributes.write('\n %s="%s" ' % (key, value)) + else: + raise TypeError('sorry, we support only dicts, lists and strings') + + stream.write(attributes.getvalue()) + nodes_str = nodes.getvalue() + if len(nodes_str) == 0: + stream.write('/>') + else: + stream.write('>') + stream.write(nodes_str) + stream.write('\n' % root_node_name) + +def escape(html): + """Returns the given HTML with ampersands, quotes and carets encoded.""" + return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') + +def dict_from_xml(xml): + """ Load a dict from a XML string """ + + def list_to_dict(l, ignore_root = True): + """ Convert our internal format list to a dict. We need this + because we use a list as a intermediate format during xml load """ + root_dict = {} + inside_dict = {} + # index 0: node name + # index 1: attributes list + # index 2: children node list + root_dict[l[0]] = inside_dict + inside_dict.update(l[1]) + # if it's a node containing lot's of nodes with same name, + # like + for x in l[2]: + d = list_to_dict(x, False) + for k, v in d.iteritems(): + if not inside_dict.has_key(k): + inside_dict[k] = [] + + inside_dict[k].append(v) + + ret = root_dict + if ignore_root: + ret = root_dict.values()[0] + + return ret + + class M: + """ This is our expat event sink """ + def __init__(self): + self.lists_stack = [] + self.current_list = None + def start_element(self, name, attrs): + l = [] + # root node? + if self.current_list is None: + self.current_list = [name, attrs, l] + else: + self.current_list.append([name, attrs, l]) + + self.lists_stack.append(self.current_list) + self.current_list = l + pass + + def end_element(self, name): + self.current_list = self.lists_stack.pop() + def char_data(self, data): + # We don't write char_data to file (beyond \n and spaces). + # What to do? Raise? + pass + + p = expat.ParserCreate() + m = M() + + p.StartElementHandler = m.start_element + p.EndElementHandler = m.end_element + p.CharacterDataHandler = m.char_data + + p.Parse(xml) + + d = list_to_dict(m.current_list) + + return d + +class ConfigHolder: + def __init__(self, d=None): + """ + Init from dict d + """ + if d is None: + self.d = {} + else: + self.d = d + + def __str__(self): + return self.d.__str__() + + __repr__ = __str__ + + def load_from_xml(self, xml): + self.d = dict_from_xml(xml) + + def load_from_dict(self, d): + self.d = d + + def get_must_exist(self, key): + v = self.get(key) + + if v is None: + raise KeyError('the required config key "%s" was not found' % key) + + return v + + def __getitem__(self, key): + """ + Support for config['path/key'] syntax + """ + return self.get_must_exist(key) + + def get(self, key, default=None): + """ + Get from config using a filesystem-like syntax + + value = 'start/sub/key' will + return config_map['start']['sub']['key'] + """ + try: + d = self.d + + path = key.split('/') + # handle 'key/subkey[2]/value/' + if path[-1] == '' : + path = path[:-1] + + for x in path[:len(path)-1]: + i = x.find('[') + if i: + if x[-1] != ']': + raise Exception('invalid syntax') + index = int(x[i+1:-1]) + + d = d[x[:i]][index] + else: + d = d[x] + + return d[path[-1]] + + except: + return default diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..18a014c --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +# 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. + +try: + from setuptools import setup, find_packages +except ImportError: + from distutils.core import setup + +import sys +import ebaysdk + +long_desc = """This SDK cuts development time and simplifies tasks like +error handling and enables you to make Finding, Shopping, Merchandising, +and Trading API calls. In Addition, the SDK comes with RSS and +HTML back-end libraries.""" + +setup( + name="ebaysdk", + version="0.0.3", + description="Simple and Extensible eBay SDK for Python", + author="Tim Keefer", + author_email="tim@timkeefer.com", + url="http://code.google.com/p/ebay-sdk-python/", + license="Apache Software License", + packages=find_packages(), + provides=['ebaysdk'], + install_requires=['BeautifulSoup', 'PyYAML', 'elementtree'], + test_suite='test', + long_description=long_desc, + classifiers=[ + 'Topic :: Internet :: WWW/HTTP', + 'Intended Audience :: Developers', + ] +) diff --git a/test/test_ebaysdk.py b/test/test_ebaysdk.py new file mode 100644 index 0000000..009e90c --- /dev/null +++ b/test/test_ebaysdk.py @@ -0,0 +1,22 @@ +import unittest +import doctest +import sys +import os + +sys.path.append('.') +import ebaysdk + +def runtests(): + unittest.main() + +class Test(unittest.TestCase): + """Unit tests for ebaysdk.""" + + def test_doctests(self): + """Run ebaysdk doctests""" + #doctest.testmod(ebaysdk) + +if __name__ == "__main__": + unittest.main() + + From c839762be0be479c50d5929ba28415b85258775d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 3 Aug 2011 10:29:31 -0700 Subject: [PATCH 002/218] readme --- README.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8db0756 --- /dev/null +++ b/README.rst @@ -0,0 +1,30 @@ +Welcome to the ebaysdk for Ptyhon +================================= + +Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. + +In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Developer Site`_ (see the ebay.yaml for a way to easily tie eBay credentials to the SDK) Finding Services. + +Example:: + + from ebaysdk import finding, tag, nodeText + + f = finding() + f.execute('findItemsAdvanced', tag('keywords', 'shoes')) + + dom = f.response_dom() + mydict = f.response_dict() + + # shortcut to response data + print f.v('itemSearchURL') + + # process the response via DOM + items = dom.getElementsByTagName('item') + + for item in items: + print nodeText(item.getElementsByTagName('title')[0]) + +.. _eBay Developer Site: http://developer.ebay.com/ + + + From 937d21260099031b0aa17a9b458d9243616151f8 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 3 Aug 2011 11:47:11 -0700 Subject: [PATCH 003/218] docs dir --- docs/index.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/index.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..73f6519 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,28 @@ +Welcome to the ebaysdk for Ptyhon +================================= + +Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. + +In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Developer Site`_ (see the ebay.yaml for a way to easily tie eBay credentials to the SDK) Finding Services. + +Example:: + + from ebaysdk import finding, tag, nodeText + + f = finding() + f.execute('findItemsAdvanced', tag('keywords', 'shoes')) + + dom = f.response_dom() + mydict = f.response_dict() + + # shortcut to response data + print f.v('itemSearchURL') + + # process the response via DOM + items = dom.getElementsByTagName('item') + + for item in items: + print nodeText(item.getElementsByTagName('title')[0]) + +.. _eBay Developer Site: http://developer.ebay.com/ + From b2b7778eceea234c84e47412c8e37f374db867ae Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 3 Aug 2011 11:53:15 -0700 Subject: [PATCH 004/218] docs update --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 73f6519..e1f1ad8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -Welcome to the ebaysdk for Ptyhon +Welcome to the ebaysdk for Python ================================= Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. From 4701322ded137baa23a9b349797f2d59ca52e44d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 3 Jan 2012 17:44:59 -0800 Subject: [PATCH 005/218] Update README.rst --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 8db0756..1dd4593 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,9 @@ + +This code is in the process of migrating from google code to github. For now, please get the lastest code from google code, + +http://code.google.com/p/ebay-sdk-python/ + + Welcome to the ebaysdk for Ptyhon ================================= From 2afd053b5d262c2d8c6bf6ea3f5c93fc4d4b4696 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sun, 15 Apr 2012 14:39:47 -0700 Subject: [PATCH 006/218] migrate code to pycurl --- Changes | 5 +- INSTALL | 20 +++ MANIFEST.in | 5 +- ebaysdk/__init__.py | 383 ++++++++++++++++++++++-------------------- ebaysdk/utils.py | 59 ++++--- setup.py | 22 ++- test/test_ebaysdk.py | 22 --- tests/__init__.py | 12 ++ tests/test_ebaysdk.py | 3 + 9 files changed, 294 insertions(+), 237 deletions(-) create mode 100644 INSTALL delete mode 100644 test/test_ebaysdk.py create mode 100644 tests/__init__.py create mode 100644 tests/test_ebaysdk.py diff --git a/Changes b/Changes index 16caf09..2f25a0a 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Changes for ebaysdk -- add proxy support +0.0.4 +- moved from httplib to pycurl + * pycurl was chosen because of its https/proxy support and + that it's thread-safe. 0.0.3 Wed May 11 11:03:58 PDT 2011 - fix escape username/password diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0e05ce6 --- /dev/null +++ b/INSTALL @@ -0,0 +1,20 @@ + +Dependancy: pycurl + + How to install pycurl on Mac + + 1) Install Fink + http://www.finkproject.org/download/ + + 2) Install libssh2, libcurl4-shlibs + sudo /sw/bin/apt-get install libssh2 + sudo /sw/bin/apt-get -q0 -f install libcurl4-shlibs + + 3) Download pycurl from http://pycurl.sourceforge.net/download/ + + 4) Extract and install pycurl + tar -zxvf pycurl-7.16.4.tar.gz + cd pycurl-7.16.4 + sudo env ARCHFLAGS="-arch i386" python setup.py install --curl-config=/sw/bin/curl-config + + \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 190d13b..79c2b36 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,5 @@ recursive-include ebaysdk *.py -recursive-include test *.py -prune ebaysdk/contrib +recursive-include tests *.py include ebay.yaml include Changes - +include INSTALL diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 8a7ae6f..9d0b526 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1,22 +1,23 @@ -import os -import sys -import httplib, ConfigParser -import md5 -import string -import base64 -import re -import yaml -from types import DictType -import pprint +import os, sys, re +import string, StringIO, base64 +import yaml, pycurl, urllib -from urlparse import urlsplit from xml.dom.minidom import parseString, Node from BeautifulSoup import BeautifulStoneSoup +from types import DictType + from ebaysdk.utils import object_dict, xml2dict, dict2xml, dict_to_xml - -def nodeText(node): +VERSION = (0, 1, 3) + +def get_version(): + version = '%s.%s.%s' % (VERSION[0], VERSION[1], VERSION[2]) + return version + +__version__ = get_version() + +def nodeText(node): rc = [] if hasattr(node, 'childNodes'): @@ -33,14 +34,19 @@ def tag(name, value): class ebaybase(object): - def __init__(self,debug=0, proxy_host=None, proxy_port=80): - self.verb = None - self.debug = debug + def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, **kwargs): + self.verb = None + self.debug = debug + self.method = method + self.timeout = timeout self.proxy_host = proxy_host self.proxy_port = proxy_port self.spooled_calls = []; self._reset() - + + def debug_callback(self, debug_type, debug_message): + sys.stderr.write('type: ' + str(debug_type) + ' message'+str(debug_message) + "\n") + def v(self, *args, **kwargs): args_a = [w for w in args] @@ -96,7 +102,7 @@ def api_config_append(self, config): for c in config: self.api_config[c] = config[c] - def getNodeText(self,nodelist): + def getNodeText(self, nodelist): rc = "" for node in nodelist: if node.nodeType == node.TEXT_NODE: @@ -110,10 +116,10 @@ def _reset(self): self._response_dict = None self._response_error = None - def do(self,verb,call_data=dict()): + def do(self, verb, call_data=dict()): return self.execute(verb,call_data) - def execute(self,verb,xml): + def execute(self, verb, xml): self.verb = verb self.call_xml = xml @@ -121,7 +127,7 @@ def execute(self,verb,xml): self._response_content = self._execute_http_request() # remove xml namespace - regex = re.compile( 'xmlns="[^"]+"' ) + regex = re.compile('xmlns="[^"]+"') self._response_content = regex.sub( '', self._response_content ) def response_soup(self): @@ -147,83 +153,71 @@ def response_dict(self): def api_init(self,config_items): for config in config_items: self.api_config[config[0]] = config[1] - - def _execute_http_request(self): "performs the http post and returns the XML response body" - try: - connection = None + curl = pycurl.Curl() - if self.api_config.get('https'): - if self.proxy_host: - connection = httplib.HTTPSConnection(self.proxy_host, self.proxy_port) - connection.connect() - url = self.api_config.get('domain', None) - if self.api_config.get('uri', None): - url = "%s/%s" % ( url, self.api_config.get('uri', None) ) - - connection.request( - "POST", - url, - self._build_request_xml(), - self._build_request_headers() - ) - - - else: - connection = httplib.HTTPSConnection( - self.api_config.get('domain', None), - ) - connection.request( - "POST", - self.api_config.get('uri', None), - self._build_request_xml(), - self._build_request_headers() - ) + if self.proxy_host: + curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) + else: + curl.setopt(pycurl.PROXY, '') + # construct headers + request_headers = self._build_request_headers() + curl.setopt( pycurl.HTTPHEADER, [ + str( '%s: %s' % ( k, v ) ) for k, v in request_headers.items() + ] ) - else: - if self.proxy_host: - connection = httplib.HTTPConnection(self.proxy_host, self.proxy_port) - connection.connect() - url = self.api_config.get('domain', None) - - if self.api_config.get('uri', None): - url = "%s/%s" % ( url, self.api_config.get('uri', None) ) - - connection.request( - "POST", - url, - self._build_request_xml(), - self._build_request_headers() - ) - - - else: - connection = httplib.HTTPConnection( - self.api_config.get('domain', None), - ) - connection.request( - "POST", - self.api_config.get('uri', None), - self._build_request_xml(), - self._build_request_headers() - ) - - response = connection.getresponse() - xml = response.read() - connection.close() + # construct URL & post data + request_url = self.api_config.get('domain', None) + + if self.api_config.get('uri', None): + request_url = "%s%s" % ( request_url, self.api_config.get('uri', None) ) + + if self.api_config.get('https', None): + request_url = "https://%s" % request_url + + if self.method == 'POST': + request_xml = self._build_request_xml() + curl.setopt(pycurl.POST, True) + curl.setopt(pycurl.POSTFIELDS, str(request_xml)) + + curl.setopt(pycurl.FOLLOWLOCATION, 1) + curl.setopt(pycurl.URL, str(request_url)) + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + + response_header = StringIO.StringIO() + response_body = StringIO.StringIO() + + curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) + curl.setopt(pycurl.TIMEOUT, self.timeout) + curl.setopt(pycurl.HEADERFUNCTION, response_header.write) + curl.setopt(pycurl.WRITEFUNCTION, response_body.write) + + if self.debug: + sys.stderr.write("CURL Request: %s\n" % request_url) + curl.setopt(pycurl.VERBOSE, 1) + curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) + + curl.perform() + + response_code = curl.getinfo(pycurl.HTTP_CODE) + response_status = response_header.getvalue().splitlines()[0] + response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) + response_data = response_body.getvalue() + + if response_code != 200: + self._response_error = "Error: %s" % response_reason + raise Exception('%s' % response_reason) + else: + return response_data + except Exception, e: - self._response_error = "%s" % e - return "" - - if response.status != 200: - self._response_error = "Error sending request:" + response.reason - else: - return xml + self._response_error = "Exception: %s" % e + raise Exception("%s" % e) def error(self): "builds and returns the api error message" @@ -231,26 +225,26 @@ def error(self): str = [] if self._response_error: - str.append( self._response_error ) + str.append(self._response_error) for e in self.response_dom().getElementsByTagName("Errors"): if e.getElementsByTagName('ErrorClassification'): - str.append( '- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0]) ) + str.append('- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) if e.getElementsByTagName('SeverityCode'): - str.append( '- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0]) ) + str.append('- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) if e.getElementsByTagName('ErrorCode'): - str.append( '- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0]) ) + str.append('- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) if e.getElementsByTagName('ShortMessage'): - str.append( '- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0]) ) + str.append('- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) if e.getElementsByTagName('LongMessage'): - str.append( '- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0]) ) + str.append('- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) - if ( len(str) > 0 ): + if (len(str) > 0): return "%s error:\n%s\n" % (self.verb, "\n".join(str)) return "\n".join(str) @@ -259,6 +253,8 @@ class shopping(ebaybase): """ Shopping backend for ebaysdk. http://developer.ebay.com/products/shopping/ + + shopping(debug=False, domain='open.api.ebay.com', uri='/shopping', method='POST', https=False, siteid=0, response_encoding='XML', request_encoding='XML', config_file='ebay.yaml') >>> s = shopping() >>> s.execute('FindItemsAdvanced', tag('CharityID', '3897')) @@ -274,12 +270,11 @@ def __init__(self, https=False, siteid=0, response_encoding='XML', - proxy_host = None, - proxy_port = None, request_encoding='XML', - config_file='ebay.yaml' ): + config_file='ebay.yaml', + **kwargs ): - ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + ebaybase.__init__(self, method='POST', **kwargs) self.api_config = { 'domain' : domain, @@ -294,7 +289,7 @@ def __init__(self, def _build_request_headers(self): return { - "X-EBAY-API-VERSION": self.api_config.get('version', ''), + "X-EBAY-API-VERSION": self.api_config.get('version', ''), "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), "X-EBAY-API-CALL-NAME": self.verb, @@ -313,7 +308,9 @@ def _build_request_xml(self): class html(ebaybase): """ HTML backend for ebaysdk. - + + (self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80) + >>> h = html() >>> h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> print h.v('rss', 'channel', 'ttl') @@ -327,8 +324,8 @@ class html(ebaybase): None """ - def __init__(self, proxy_host=None, proxy_port=None): - ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + def __init__(self, **kwargs): + ebaybase.__init__(self, method='GET', **kwargs) def response_dom(self): if not self._response_dom: @@ -342,10 +339,12 @@ def response_dict(self): return self._response_dict - def execute(self,url, call_data=dict()): + def execute(self, url, call_data=dict()): + "execute(self, url, call_data=dict())" + self.url = url self.call_data = call_data - + self._reset() self._response_content = self._execute_http_request() @@ -355,43 +354,60 @@ def execute(self,url, call_data=dict()): def _execute_http_request(self): "performs the http post and returns the XML response body" - + try: - connection = None - + curl = pycurl.Curl() + if self.proxy_host: - connection = httplib.HTTPConnection(self.proxy_host, self.proxy_port) - connection.connect() - connection.request( - "GET", - self.url, - self.call_data - ) + curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) else: - nil, domain, uri, args, nil = urlsplit(self.url) - connection = httplib.HTTPConnection(domain) - - connection.request( - "GET", - "%s?%s" % (uri,args), - self.call_data - ) - - response = connection.getresponse() - xml = response.read() - connection.close() - except Exception, e: - self._response_error = "failed to connect: %s" % e - return "" + curl.setopt(pycurl.PROXY, '') - if response.status != 200: - self._response_error = "Error sending request:" + response.reason - else: - return xml + request_url = self.url + if self.call_data and self.method == 'GET': + request_url = request_url + '?' + urllib.urlencode(self.call_data) + + if self.method == 'POST': + curl.setopt( pycurl.POST, True ) + curl.setopt( pycurl.POSTFIELDS, str( request_xm ) ) + + curl.setopt(pycurl.FOLLOWLOCATION, 1) + curl.setopt(pycurl.URL, str(request_url)) + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + + response_header = StringIO.StringIO() + response_body = StringIO.StringIO() + + curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) + curl.setopt(pycurl.TIMEOUT, self.timeout) + + curl.setopt(pycurl.HEADERFUNCTION, response_header.write) + curl.setopt(pycurl.WRITEFUNCTION, response_body.write) + + if self.debug: + sys.stderr.write("CURL Request: %s\n" % request_url) + curl.setopt(pycurl.VERBOSE, 1) + curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) + curl.perform() + + response_code = curl.getinfo(pycurl.HTTP_CODE) + response_status = response_header.getvalue().splitlines()[0] + response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) + response_data = response_body.getvalue() + + if response_code != 200: + self._response_error = "Error: %s" % response_reason + raise Exception('%s' % response_reason) + else: + return response_data + + except Exception, e: + self._response_error = "Exception: %s" % e + raise Exception("%s" % e) + def error(self): - "builds and returns the api error message" - + "builds and returns the api error message" return self._response_error class trading(ebaybase): @@ -408,32 +424,55 @@ class trading(ebaybase): """ def __init__(self, - domain='api.ebay.com', - uri='/ws/api.dll', - https=False, - siteid='0', - response_encoding='XML', - request_encoding='XML', - proxy_host = None, - proxy_port = None, - config_file='ebay.yaml' ): + domain=None, + uri=None, + https=None, + siteid=None, + response_encoding=None, + request_encoding=None, + proxy_host=None, + proxy_port=None, + username=None, + password=None, + token=None, + appid=None, + certid=None, + devid=None, + version=None, + config_file='ebay.yaml', + **kwargs ): - ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + ebaybase.__init__(self, method='POST', **kwargs) self.api_config = { - 'domain' : domain, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, - 'response_encoding' : response_encoding, - 'request_encoding' : request_encoding, - } + 'domain' : 'api.ebay.com', + 'uri' : '/ws/api.dll', + 'https' : False, + 'siteid' : '0', + 'response_encoding' : 'XML', + 'request_encoding' : 'XML', + 'version' : '648', + } self.load_yaml(config_file) + self.api_config['domain']=domain or self.api_config.get('domain') + self.api_config['uri']=uri or self.api_config.get('uri') + self.api_config['https']=https or self.api_config.get('https') + self.api_config['siteid']=siteid or self.api_config.get('siteid') + self.api_config['response_encoding']=response_encoding or self.api_config.get('response_encoding') + self.api_config['request_encoding']=request_encoding or self.api_config.get('request_encoding') + self.api_config['username']=username or self.api_config.get('username') + self.api_config['password']=password or self.api_config.get('password') + self.api_config['token']=token or self.api_config.get('token') + self.api_config['appid']=appid or self.api_config.get('appid') + self.api_config['certid']=certid or self.api_config.get('certid') + self.api_config['devid']=devid or self.api_config.get('devid') + self.api_config['version']=version or self.api_config.get('compatability') or self.api_config.get('version') + def _build_request_headers(self): return { - "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('compatability','648'), + "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('version', ''), "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), "X-EBAY-API-APP-NAME": self.api_config.get('appid',''), "X-EBAY-API-CERT-NAME": self.api_config.get('certid',''), @@ -449,8 +488,10 @@ def _build_request_xml(self): if self.api_config.get('token', None): xml += "%s" % self.api_config.get('token') else: - xml += "%s" % self.api_config.get('username', '') - xml += "%s" % self.api_config.get('password', '') + if self.api_config.get('username', None): + xml += "%s" % self.api_config.get('username', '') + if self.api_config.get('password', None): + xml += "%s" % self.api_config.get('password', '') xml += "" xml += self.call_xml @@ -476,26 +517,7 @@ class finding(ebaybase): >>> print nodeText(f.response_dom().getElementsByTagName('ack')[0]) Success - """ - - """ - print "itemId" - print f.v('country', mydict=items[0]) - print f.v('itemId', mydict=items[0]) - print f.v('title', mydict=items[0]) - print f.v('primaryCategory', 'categoryId', mydict=items[0]) - - # examples using the response dom - titles = f.response_dom().getElementsByTagName('title') - print titles[0].toxml() - print nodeText(titles[0]) - - items = f.response_dom().getElementsByTagName('item') - - # 4.75 - shipCost = items[0].getElementsByTagName('shippingServiceCost')[0] - print shipCost.attributes['currencyId'].value - print nodeText(shipCost) + """ def __init__(self, @@ -506,11 +528,10 @@ def __init__(self, siteid='EBAY-US', response_encoding='XML', request_encoding='XML', - proxy_host = None, - proxy_port = None, - config_file='ebay.yaml' ): + config_file='ebay.yaml', + **kwargs ): - ebaybase.__init__(self, proxy_host=proxy_host, proxy_port=proxy_port) + ebaybase.__init__(self, method='POST', **kwargs) self.api_config = { 'domain' : domain, diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 42a9a10..7870ff2 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -38,6 +38,9 @@ def __getattr__(self, item): def __setattr__(self, item, value): self.__setitem__(item, value) + def getvalue(self, item, value=None): + return self.get(item, {}).get('value', value) + class xml2dict(object): def __init__(self): @@ -84,11 +87,6 @@ def parse(self, file): def fromstring(self, s): """parse a string""" - - if not s: - return object_dict({}) - #print "fromstring:" + s - t = ET.fromstring(s) root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) return object_dict({root_tag: root_tree}) @@ -98,9 +96,10 @@ class dict2xml: xml = "" level = 0 - def __init__(self): + def __init__(self,encoding=None): self.xml = "" self.level = 0 + self.encoding = encoding def __del__(self): pass @@ -111,32 +110,52 @@ def setXml(self,Xml): def setLevel(self,Level): self.level = Level + def tostring(self,d): + return self.dict2xml(d) + def dict2xml(self,map): - if (str(type(map)) == "" or str(type(map)) == ""): + if type(map) == object_dict or type(map) == dict: for key, value in map.items(): - if (str(type(value)) == "" or str(type(value)) == ""): + if type(value) == object_dict or type(value) == dict: if(len(value) > 0): - self.xml += "\t"*self.level + self.xml += " "*self.level self.xml += "<%s>\n" % (key) self.level += 1 self.dict2xml(value) self.level -= 1 - self.xml += "\t"*self.level + self.xml += " "*self.level self.xml += "\n" % (key) else: - self.xml += "\t"*(self.level) + self.xml += " "*(self.level) self.xml += "<%s>\n" % (key,key) - #end if + elif type(value) == list: + for v in value: + self.dict2xml({key:v}) else: - self.xml += "\t"*(self.level) - self.xml += "<%s>%s\n" % (key,value, key) - #end if + self.xml += " "*(self.level) + self.xml += "<%s>%s\n" % (key,self.encode(value),key) else: - self.xml += "\t"*self.level - self.xml += "<%s>%s\n" % (key,value, key) - + self.xml += " "*self.level + self.xml += "<%s>%s\n" % (key,self.encode(value),key) return self.xml + def encode(self,str1): + if type(str1) != str and type(str1) != unicode: + str1 = str(str1) + if self.encoding: + str1 = str1.encode(self.encoding) + str2 = '' + for c in str1: + if c == '&': + str2 += '&' + elif c == '<': + str2 += '`' + elif c == '>': + str2 += 'b' + else: + str2 += c + return str2 + def list_to_xml(name, l, stream): for d in l: dict_to_xml(d, name, stream) @@ -166,10 +185,6 @@ def dict_to_xml(d, root_node_name, stream): stream.write(nodes_str) stream.write('\n' % root_node_name) -def escape(html): - """Returns the given HTML with ampersands, quotes and carets encoded.""" - return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') - def dict_from_xml(xml): """ Load a dict from a XML string """ diff --git a/setup.py b/setup.py index 18a014c..f2380d2 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python + +#from distutils.core import setup + # 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 @@ -10,13 +14,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from setuptools import setup, find_packages -except ImportError: - from distutils.core import setup +#try: +from setuptools import setup, find_packages +#except ImportError: +# from distutils.core import setup import sys -import ebaysdk + +execfile('./ebaysdk/__init__.py') +VERSION = __version__ long_desc = """This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, @@ -25,7 +31,7 @@ setup( name="ebaysdk", - version="0.0.3", + version=VERSION, description="Simple and Extensible eBay SDK for Python", author="Tim Keefer", author_email="tim@timkeefer.com", @@ -33,8 +39,8 @@ license="Apache Software License", packages=find_packages(), provides=['ebaysdk'], - install_requires=['BeautifulSoup', 'PyYAML', 'elementtree'], - test_suite='test', + install_requires=['BeautifulSoup', 'PyYAML', 'pycurl', 'elementtree'], + test_suite='tests', long_description=long_desc, classifiers=[ 'Topic :: Internet :: WWW/HTTP', diff --git a/test/test_ebaysdk.py b/test/test_ebaysdk.py deleted file mode 100644 index 009e90c..0000000 --- a/test/test_ebaysdk.py +++ /dev/null @@ -1,22 +0,0 @@ -import unittest -import doctest -import sys -import os - -sys.path.append('.') -import ebaysdk - -def runtests(): - unittest.main() - -class Test(unittest.TestCase): - """Unit tests for ebaysdk.""" - - def test_doctests(self): - """Run ebaysdk doctests""" - #doctest.testmod(ebaysdk) - -if __name__ == "__main__": - unittest.main() - - diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..2324e24 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,12 @@ +import unittest +import doctest +import ebaysdk + +def getTestSuite(): + suite = unittest.TestSuite() + + suite.addTest(doctest.DocTestSuite(ebaysdk)) + return suite + +runner = unittest.TextTestRunner() +runner.run(getTestSuite()) diff --git a/tests/test_ebaysdk.py b/tests/test_ebaysdk.py new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/tests/test_ebaysdk.py @@ -0,0 +1,3 @@ + + + From 27819d634de28c3261da817f91004e1df6c8b9ba Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 20 Apr 2012 14:06:24 -0700 Subject: [PATCH 007/218] fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1dd4593..1fb5d0c 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ This code is in the process of migrating from google code to github. For now, pl http://code.google.com/p/ebay-sdk-python/ -Welcome to the ebaysdk for Ptyhon +Welcome to the ebaysdk for Python ================================= Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. From 650706078576de18d0512942b7acca9fbf33805c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 20 Apr 2012 23:12:36 -0700 Subject: [PATCH 008/218] add dict to xml support --- Changes | 7 + ebay.yaml | 18 +- ebaysdk/__init__.py | 57 ++-- ebaysdk/utils.py | 628 +++++++++++++++++++++++++++++--------------- 4 files changed, 473 insertions(+), 237 deletions(-) diff --git a/Changes b/Changes index 2f25a0a..a830ef2 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,12 @@ Changes for ebaysdk +0.0.5 +- added response_obj() which turns a dict into a Struct + e.g. { a : { 'name' : 'tim' } } can be accessed like a.name +- add support to dict args in the execute call. execute will now check the + data type and convert to xml if necessary +- clean doctests + 0.0.4 - moved from httplib to pycurl * pycurl was chosen because of its https/proxy support and diff --git a/ebay.yaml b/ebay.yaml index c638eee..b66923a 100644 --- a/ebay.yaml +++ b/ebay.yaml @@ -4,23 +4,23 @@ name: ebay_api_config # Trading - External api.ebay.com: - password: - username: - appid: - certid: - devid: + password: _password_ + username: _username_ + appid: _appid_ + certid: _certid_ + devid: _devid_ token: version: 671 https: 1 # Shopping open.api.ebay.com: - appid: - certid: - devid: + appid: _appid_ + certid: _certid_ + devid: _devid_ version: 671 # Finding/Merchandising svcs.ebay.com: - appid: + appid: _appid_ version: 1.0.0 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 9d0b526..9c41673 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1,12 +1,14 @@ import os, sys, re import string, StringIO, base64 import yaml, pycurl, urllib +from types import DictType, ListType from xml.dom.minidom import parseString, Node from BeautifulSoup import BeautifulStoneSoup from types import DictType -from ebaysdk.utils import object_dict, xml2dict, dict2xml, dict_to_xml +from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct + VERSION = (0, 1, 3) @@ -112,16 +114,23 @@ def getNodeText(self, nodelist): def _reset(self): self._response_content = None self._response_dom = None + self._response_obj = None self._response_soup = None self._response_dict = None self._response_error = None def do(self, verb, call_data=dict()): - return self.execute(verb,call_data) + return self.execute(verb, call_data) - def execute(self, verb, xml): - self.verb = verb - self.call_xml = xml + def execute(self, verb, data): + self.verb = verb + + if type(data) == DictType: + self.call_xml = dict2xml(data, roottag=None) + elif type(data) == ListType: + self.call_xml = list2xml(data, roottag=None) + else: + self.call_xml = data self._reset() self._response_content = self._execute_http_request() @@ -136,6 +145,12 @@ def response_soup(self): return self._response_soup + def response_obj(self): + if not self._response_obj: + self._response_obj = make_struct(self.response_dict()) + + return self._response_obj + def response_dom(self): if not self._response_dom: dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb))) ) @@ -257,8 +272,8 @@ class shopping(ebaybase): shopping(debug=False, domain='open.api.ebay.com', uri='/shopping', method='POST', https=False, siteid=0, response_encoding='XML', request_encoding='XML', config_file='ebay.yaml') >>> s = shopping() - >>> s.execute('FindItemsAdvanced', tag('CharityID', '3897')) - >>> print s.v('Ack') + >>> s.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> print s.response_obj().Ack Success >>> print s.error() @@ -313,7 +328,7 @@ class html(ebaybase): >>> h = html() >>> h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> print h.v('rss', 'channel', 'ttl') + >>> print h.response_obj().rss.channel.ttl 60 >>> title = h.response_dom().getElementsByTagName('title')[0] >>> print nodeText( title ) @@ -416,8 +431,11 @@ class trading(ebaybase): http://developer.ebay.com/products/trading/ >>> t = trading() - >>> t.execute('GetCharities', tag('CharityID', '3897')) - >>> nodeText(t.response_dom().getElementsByTagName('Name')[0]) + >>> t.execute('GetCharities', { 'CharityID': 3897 }) + >>> charity_name = '' + >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: + ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) + >>> print charity_name u'Sunshine Kids Foundation' >>> print t.error() @@ -505,17 +523,18 @@ class finding(ebaybase): http://developer.ebay.com/products/finding/ >>> f = finding() - >>> f.execute('findItemsAdvanced', tag('keywords', 'shoes')) - >>> print f.v('itemSearchURL') != '' + >>> f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> error = f.error() + >>> print error + + + >>> if len( error ) <= 0: + ... print f.response_obj().itemSearchURL != '' + ... items = f.response_obj().searchResult.item + ... print len(items) + ... print f.response_obj().ack True - >>> items = f.v('searchResult', 'item') - >>> print len(items) 100 - >>> print f.v('ack') - Success - >>> print f.error() - - >>> print nodeText(f.response_dom().getElementsByTagName('ack')[0]) Success """ diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 7870ff2..837219a 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -1,10 +1,8 @@ -try: - import xml.etree.ElementTree as ET -except: - import cElementTree as ET # for 2.4 - +# encoding: utf-8 +import xml.etree.ElementTree as ET import re - +from StringIO import StringIO + class object_dict(dict): """object view of dict, you can >>> a = object_dict() @@ -92,227 +90,439 @@ def fromstring(self, s): return object_dict({root_tag: root_tree}) -class dict2xml: - xml = "" - level = 0 +# Basic conversation goal here is converting a dict to an object allowing +# more comfortable access. `Struct()` and `make_struct()` are used to archive +# this goal. +# See http://stackoverflow.com/questions/1305532/convert-python-dict-to-object for the inital Idea +# +# The reasoning for this is the observation that we ferry arround hundreds of dicts via JSON +# and accessing them as `obj['key']` is tiresome after some time. `obj.key` is much nicer. +class Struct(object): + """Emulate a cross over between a dict() and an object().""" + def __init__(self, entries, default=None, nodefault=False): + # ensure all keys are strings and nothing else + entries = dict([(str(x), y) for x, y in entries.items()]) + self.__dict__.update(entries) + self.__default = default + self.__nodefault = nodefault + + def __getattr__(self, name): + """Emulate Object access. + + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.a + 'b' + >>> obj.foobar + 'c' + + `hasattr` results in strange behaviour if you give a default value. This might change in the future. + >>> hasattr(obj, 'a') + True + >>> hasattr(obj, 'foobar') + True + """ + if name.startswith('_'): + # copy expects __deepcopy__, __getnewargs__ to raise AttributeError + # see http://groups.google.com/group/comp.lang.python/browse_thread/thread/6ac8a11de4e2526f/ + # e76b9fbb1b2ee171?#e76b9fbb1b2ee171 + raise AttributeError("'' object has no attribute '%s'" % name) + if self.__nodefault: + raise AttributeError("'' object has no attribute '%s'" % name) + return self.__default - def __init__(self,encoding=None): - self.xml = "" - self.level = 0 - self.encoding = encoding + def __getitem__(self, key): + """Emulate dict like access. + + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj['a'] + 'b' + + While the standard dict access via [key] uses the default given when creating the struct, + access via get(), results in None for keys not set. This might be considered a bug and + should change in the future. + >>> obj['foobar'] + 'c' + >>> obj.get('foobar') + 'c' + """ + # warnings.warn("dict_accss[foo] on a Struct, use object_access.foo instead", + # DeprecationWarning, stacklevel=2) + if self.__nodefault: + return self.__dict__[key] + return self.__dict__.get(key, self.__default) - def __del__(self): - pass + def get(self, key, default=None): + """Emulate dictionary access. - def setXml(self,Xml): - self.xml = Xml - - def setLevel(self,Level): - self.level = Level - - def tostring(self,d): - return self.dict2xml(d) - - def dict2xml(self,map): - if type(map) == object_dict or type(map) == dict: - for key, value in map.items(): - if type(value) == object_dict or type(value) == dict: - if(len(value) > 0): - self.xml += " "*self.level - self.xml += "<%s>\n" % (key) - self.level += 1 - self.dict2xml(value) - self.level -= 1 - self.xml += " "*self.level - self.xml += "\n" % (key) - else: - self.xml += " "*(self.level) - self.xml += "<%s>\n" % (key,key) - elif type(value) == list: - for v in value: - self.dict2xml({key:v}) - else: - self.xml += " "*(self.level) - self.xml += "<%s>%s\n" % (key,self.encode(value),key) - else: - self.xml += " "*self.level - self.xml += "<%s>%s\n" % (key,self.encode(value),key) - return self.xml - - def encode(self,str1): - if type(str1) != str and type(str1) != unicode: - str1 = str(str1) - if self.encoding: - str1 = str1.encode(self.encoding) - str2 = '' - for c in str1: - if c == '&': - str2 += '&' - elif c == '<': - str2 += '`' - elif c == '>': - str2 += 'b' - else: - str2 += c - return str2 - -def list_to_xml(name, l, stream): - for d in l: - dict_to_xml(d, name, stream) - -def dict_to_xml(d, root_node_name, stream): - """ Transform a dict into a XML, writing to a stream """ - stream.write('\n<' + root_node_name) - attributes = StringIO() - nodes = StringIO() - for item in d.items(): - key, value = item - if isinstance(value, dict): - dict_to_xml(value, key, nodes) - elif isinstance(value, list): - list_to_xml(key, value, nodes) - elif isinstance(value, str) or isinstance(value, unicode): - attributes.write('\n %s="%s" ' % (key, value)) - else: - raise TypeError('sorry, we support only dicts, lists and strings') - - stream.write(attributes.getvalue()) - nodes_str = nodes.getvalue() - if len(nodes_str) == 0: - stream.write('/>') - else: - stream.write('>') - stream.write(nodes_str) - stream.write('\n' % root_node_name) - -def dict_from_xml(xml): - """ Load a dict from a XML string """ - - def list_to_dict(l, ignore_root = True): - """ Convert our internal format list to a dict. We need this - because we use a list as a intermediate format during xml load """ - root_dict = {} - inside_dict = {} - # index 0: node name - # index 1: attributes list - # index 2: children node list - root_dict[l[0]] = inside_dict - inside_dict.update(l[1]) - # if it's a node containing lot's of nodes with same name, - # like - for x in l[2]: - d = list_to_dict(x, False) - for k, v in d.iteritems(): - if not inside_dict.has_key(k): - inside_dict[k] = [] - - inside_dict[k].append(v) - - ret = root_dict - if ignore_root: - ret = root_dict.values()[0] - - return ret - - class M: - """ This is our expat event sink """ - def __init__(self): - self.lists_stack = [] - self.current_list = None - def start_element(self, name, attrs): - l = [] - # root node? - if self.current_list is None: - self.current_list = [name, attrs, l] - else: - self.current_list.append([name, attrs, l]) - - self.lists_stack.append(self.current_list) - self.current_list = l - pass - - def end_element(self, name): - self.current_list = self.lists_stack.pop() - def char_data(self, data): - # We don't write char_data to file (beyond \n and spaces). - # What to do? Raise? - pass - - p = expat.ParserCreate() - m = M() - - p.StartElementHandler = m.start_element - p.EndElementHandler = m.end_element - p.CharacterDataHandler = m.char_data - - p.Parse(xml) - - d = list_to_dict(m.current_list) - - return d - -class ConfigHolder: - def __init__(self, d=None): + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.get('a') + 'b' + >>> obj.get('foobar') + 'c' """ - Init from dict d + if key in self.__dict__: + return self.__dict__[key] + if not self.__nodefault: + return self.__default + return default + + def __contains__(self, item): + """Emulate dict 'in' functionality. + + >>> obj = Struct({'a': 'b'}, default='c') + >>> 'a' in obj + True + >>> 'foobar' in obj + False """ - if d is None: - self.d = {} - else: - self.d = d + return item in self.__dict__ - def __str__(self): - return self.d.__str__() + def __nonzero__(self): + """Returns whether the instance evaluates to False""" + return bool(self.items()) - __repr__ = __str__ + def has_key(self, item): + """Emulate dict.has_key() functionality. - def load_from_xml(self, xml): - self.d = dict_from_xml(xml) - - def load_from_dict(self, d): - self.d = d + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.has_key('a') + True + >>> obj.has_key('foobar') + False + """ + return item in self - def get_must_exist(self, key): - v = self.get(key) + def items(self): + """Emulate dict.items() functionality. - if v is None: - raise KeyError('the required config key "%s" was not found' % key) + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.items() + [('a', 'b')] + """ + return [(k, v) for (k, v) in self.__dict__.items() if not k.startswith('_Struct__')] - return v + def keys(self): + """Emulate dict.keys() functionality. - def __getitem__(self, key): - """ - Support for config['path/key'] syntax + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.keys() + ['a'] """ - return self.get_must_exist(key) + return [k for (k, _v) in self.__dict__.items() if not k.startswith('_Struct__')] - def get(self, key, default=None): - """ - Get from config using a filesystem-like syntax + def values(self): + """Emulate dict.values() functionality. - value = 'start/sub/key' will - return config_map['start']['sub']['key'] + >>> obj = Struct({'a': 'b'}, default='c') + >>> obj.values() + ['b'] """ - try: - d = self.d + return [v for (k, v) in self.__dict__.items() if not k.startswith('_Struct__')] + + def __repr__(self): + return "" % dict(self.items()) + + def as_dict(self): + """Return a dict representing the content of this struct.""" + return self.__dict__ + + +def make_struct(obj, default=None, nodefault=False): + """Converts a dict to an object, leaves objects untouched. + + Someting like obj.vars() = dict() - Read Only! + + >>> obj = make_struct(dict(foo='bar')) + >>> obj.foo + 'bar' + + `make_struct` leaves objects alone. + >>> class MyObj(object): pass + >>> data = MyObj() + >>> data.foo = 'bar' + >>> obj = make_struct(data) + >>> obj.foo + 'bar' + + `make_struct` also is idempotent + >>> obj = make_struct(make_struct(dict(foo='bar'))) + >>> obj.foo + 'bar' + + `make_struct` recursively handles dicts and lists of dicts + >>> obj = make_struct(dict(foo=dict(bar='baz'))) + >>> obj.foo.bar + 'baz' + + >>> obj = make_struct([dict(foo='baz')]) + >>> obj + [] + >>> obj[0].foo + 'baz' + + >>> obj = make_struct(dict(foo=dict(bar=dict(baz='end')))) + >>> obj.foo.bar.baz + 'end' + + >>> obj = make_struct(dict(foo=[dict(bar='baz')])) + >>> obj.foo[0].bar + 'baz' + >>> obj.items() + [('foo', [])] + """ + if type(obj) == type(Struct): + return obj + if (not hasattr(obj, '__dict__')) and hasattr(obj, 'iterkeys'): + # this should be a dict + struc = Struct(obj, default, nodefault) + # handle recursive sub-dicts + for key, val in obj.items(): + setattr(struc, key, make_struct(val, default, nodefault)) + return struc + elif hasattr(obj, '__delslice__') and hasattr(obj, '__getitem__'): + # + return [make_struct(v, default, nodefault) for v in obj] + else: + return obj + + +# Code is based on http://code.activestate.com/recipes/573463/ +def _convert_dict_to_xml_recurse(parent, dictitem, listnames): + """Helper Function for XML conversion.""" + # we can't convert bare lists + assert not isinstance(dictitem, list) + + if isinstance(dictitem, dict): + for (tag, child) in sorted(dictitem.iteritems()): + if isinstance(child, list): + # iterate through the array and convert + listelem = ET.Element(tag) + parent.append(listelem) + for listchild in child: + elem = ET.Element(listnames.get(tag, 'item')) + listelem.append(elem) + _convert_dict_to_xml_recurse(elem, listchild, listnames) + else: + elem = ET.Element(tag) + parent.append(elem) + _convert_dict_to_xml_recurse(elem, child, listnames) + elif not dictitem is None: + parent.text = unicode(dictitem) + + +def dict2et(xmldict, roottag='data', listnames=None): + """Converts a dict to an ElementTree. + + Converts a dictionary to an XML ElementTree Element:: + + >>> data = {"nr": "xq12", "positionen": [{"m": 12}, {"m": 2}]} + >>> root = dict2et(data) + >>> ET.tostring(root) + 'xq12122' + + Per default ecerything ins put in an enclosing '' element. Also per default lists are converted + to collecitons of `` elements. But by provding a mapping between list names and element names, + you van generate different elements:: + + >>> data = {"positionen": [{"m": 12}, {"m": 2}]} + >>> root = dict2et(data, roottag='xml') + >>> ET.tostring(root) + '122' + + >>> root = dict2et(data, roottag='xml', listnames={'positionen': 'position'}) + >>> ET.tostring(root) + '122' + + >>> data = {"kommiauftragsnr":2103839, "anliefertermin":"2009-11-25", "prioritaet": 7, + ... "ort": u"Hücksenwagen", + ... "positionen": [{"menge": 12, "artnr": "14640/XL", "posnr": 1},], + ... "versandeinweisungen": [{"guid": "2103839-XalE", "bezeichner": "avisierung48h", + ... "anweisung": "48h vor Anlieferung unter 0900-LOGISTIK avisieren"}, + ... ]} + + >>> print ET.tostring(dict2et(data, 'kommiauftrag', + ... listnames={'positionen': 'position', 'versandeinweisungen': 'versandeinweisung'})) + ... # doctest: +SKIP + ''' + 2009-11-25 + + + 1 + 12 + 14640/XL + + + Hücksenwagen + + + avisierung48h + 48h vor Anlieferung unter 0900-LOGISTIK avisieren + 2103839-XalE + + + 7 + 2103839 + ''' + """ + + if not listnames: + listnames = {} + root = ET.Element(roottag) + _convert_dict_to_xml_recurse(root, xmldict, listnames) + return root - path = key.split('/') - # handle 'key/subkey[2]/value/' - if path[-1] == '' : - path = path[:-1] - for x in path[:len(path)-1]: - i = x.find('[') - if i: - if x[-1] != ']': - raise Exception('invalid syntax') - index = int(x[i+1:-1]) +def list2et(xmllist, root, elementname): + """Converts a list to an ElementTree. + + See also dict2et() + """ - d = d[x[:i]][index] - else: - d = d[x] + basexml = dict2et({root: xmllist}, 'xml', listnames={root: elementname}) + return basexml.find(root) - return d[path[-1]] - except: - return default +def dict2xml(datadict, roottag='data', listnames=None, pretty=False): + """Converts a dictionary to an UTF-8 encoded XML string. + + See also dict2et() + """ + root = dict2et(datadict, roottag, listnames) + return to_string(root, pretty=pretty) + + +def list2xml(datalist, roottag, elementname, pretty=False): + """Converts a list to an UTF-8 encoded XML string. + + See also dict2et() + """ + root = list2et(datalist, roottag, elementname) + return to_string(root, pretty=pretty) + + +def to_string(root, encoding='utf-8', pretty=False): + """Converts an ElementTree to a string""" + + if pretty: + indent(root) + + tree = ET.ElementTree(root) + fileobj = StringIO() + #fileobj.write('' % encoding) + if pretty: + fileobj.write('\n') + tree.write(fileobj, 'utf-8') + return fileobj.getvalue() + + +# From http://effbot.org/zone/element-lib.htm +# prettyprint: Prints a tree with each node indented according to its depth. This is +# done by first indenting the tree (see below), and then serializing it as usual. +# indent: Adds whitespace to the tree, so that saving it as usual results in a prettyprinted tree. +# in-place prettyprint formatter + +def indent(elem, level=0): + """XML prettyprint: Prints a tree with each node indented according to its depth.""" + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for child in elem: + indent(child, level + 1) + if child: + if not child.tail or not child.tail.strip(): + child.tail = i + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def test(): + """Simple selftest.""" + + data = {"guid": "3104247-7", + "menge": 7, + "artnr": "14695", + "batchnr": "3104247"} + xmlstr = dict2xml(data, roottag='warenzugang') + assert xmlstr == ('14695' + '31042473104247-77') + + data = {"kommiauftragsnr": 2103839, + "anliefertermin": "2009-11-25", + "fixtermin": True, + "prioritaet": 7, + "info_kunde": "Besuch H. Gerlach", + "auftragsnr": 1025575, + "kundenname": "Ute Zweihaus 400424990", + "kundennr": "21548", + "name1": "Uwe Zweihaus", + "name2": "400424990", + "name3": "", + u"strasse": u"Bahnhofstr. 2", + "land": "DE", + "plz": "42499", + "ort": u"Hücksenwagen", + "positionen": [{"menge": 12, + "artnr": "14640/XL", + "posnr": 1}, + {"menge": 4, + "artnr": "14640/03", + "posnr": 2}, + {"menge": 2, + "artnr": "10105", + "posnr": 3}], + "versandeinweisungen": [{"guid": "2103839-XalE", + "bezeichner": "avisierung48h", + "anweisung": "48h vor Anlieferung unter 0900-LOGISTIK avisieren"}, + {"guid": "2103839-GuTi", + "bezeichner": "abpackern140", + "anweisung": u"Paletten höchstens auf 140 cm Packen"}] + } + + xmlstr = dict2xml(data, roottag='kommiauftrag') + + data = {"kommiauftragsnr": 2103839, + "positionen": [{"menge": 4, + "artnr": "14640/XL", + "posnr": 1, + "nve": "23455326543222553"}, + {"menge": 8, + "artnr": "14640/XL", + "posnr": 1, + "nve": "43255634634653546"}, + {"menge": 4, + "artnr": "14640/03", + "posnr": 2, + "nve": "43255634634653546"}, + {"menge": 2, + "artnr": "10105", + "posnr": 3, + "nve": "23455326543222553"}], + "nves": [{"nve": "23455326543222553", + "gewicht": 28256, + "art": "paket"}, + {"nve": "43255634634653546", + "gewicht": 28256, + "art": "paket"}]} + + xmlstr = dict2xml(data, roottag='rueckmeldung') + print xmlstr + +if __name__ == '__main__': + import doctest + import sys + failure_count, test_count = doctest.testmod() + d = make_struct({ + 'item1': 'string', + 'item2': ['dies', 'ist', 'eine', 'liste'], + 'item3': dict(dies=1, ist=2, ein=3, dict=4), + 'item4': 10, + 'item5': [dict(dict=1, in_einer=2, liste=3)]}) + test() + sys.exit(failure_count) + From 1f5e4b3f9d3644dcabec346b97925b90f03fe1d3 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 20 Apr 2012 23:17:11 -0700 Subject: [PATCH 009/218] update README --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1fb5d0c..b9d9d06 100644 --- a/README.rst +++ b/README.rst @@ -13,16 +13,16 @@ In order to use eBay aspects of this utility you must first register with eBay t Example:: - from ebaysdk import finding, tag, nodeText + from ebaysdk import finding, nodeText f = finding() - f.execute('findItemsAdvanced', tag('keywords', 'shoes')) + f.execute('findItemsAdvanced', {'keywords': 'shoes'}) dom = f.response_dom() mydict = f.response_dict() + myobj = f.response_obj() - # shortcut to response data - print f.v('itemSearchURL') + print myobj.itemSearchURL # process the response via DOM items = dom.getElementsByTagName('item') From 93016e39aa925af3c8574baa30ee7eb031d4ecec Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 18 May 2012 20:51:10 -0700 Subject: [PATCH 010/218] coding error with POST method in the html() backend --- ebaysdk/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 9c41673..c191a10 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -383,8 +383,9 @@ def _execute_http_request(self): request_url = request_url + '?' + urllib.urlencode(self.call_data) if self.method == 'POST': - curl.setopt( pycurl.POST, True ) - curl.setopt( pycurl.POSTFIELDS, str( request_xm ) ) + request_xml = self._build_request_xml() + curl.setopt(pycurl.POST, True) + curl.setopt(pycurl.POSTFIELDS, str(request_xml)) curl.setopt(pycurl.FOLLOWLOCATION, 1) curl.setopt(pycurl.URL, str(request_url)) From ac7d109767c578afa0f9975821a02259e58dca11 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 22 May 2012 22:06:16 -0700 Subject: [PATCH 011/218] modify dict2xml to properly handle array content --- Changes | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Changes b/Changes index a830ef2..b8d4ec2 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,24 @@ Changes for ebaysdk +- modify dict2xml to handle nodes with array content + +for example, +{ + 'keywords': 'shoes', + 'paginationInput': { 'pageNumber': 1 }, + 'itemFilter': [ + { 'name': 'MinBids', 'value': 10 }, + { 'name': 'MaxBids', 'value': 14 } + ], +} + + +MinBids10 +MaxBids14 +shoes +1 + + 0.0.5 - added response_obj() which turns a dict into a Struct e.g. { a : { 'name' : 'tim' } } can be accessed like a.name From 9f65780cdbd7d1dcde182d50f408e9ac51d682ba Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Jun 2012 15:02:17 -0700 Subject: [PATCH 012/218] adding older utils.py under the new name of utils2.py --- ebaysdk/utils2.py | 326 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 ebaysdk/utils2.py diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py new file mode 100644 index 0000000..7514e69 --- /dev/null +++ b/ebaysdk/utils2.py @@ -0,0 +1,326 @@ +try: + import xml.etree.ElementTree as ET +except: + import cElementTree as ET # for 2.4 + +import re + +class object_dict(dict): + """object view of dict, you can + >>> a = object_dict() + >>> a.fish = 'fish' + >>> a['fish'] + 'fish' + >>> a['water'] = 'water' + >>> a.water + 'water' + >>> a.test = {'value': 1} + >>> a.test2 = object_dict({'name': 'test2', 'value': 2}) + >>> a.test, a.test2.name, a.test2.value + (1, 'test2', 2) + """ + def __init__(self, initd=None): + if initd is None: + initd = {} + dict.__init__(self, initd) + + def __getattr__(self, item): + + d = self.__getitem__(item) + + if isinstance(d, dict) and 'value' in d and len(d) == 1: + return d['value'] + else: + return d + + # if value is the only key in object, you can omit it + + def __setattr__(self, item, value): + self.__setitem__(item, value) + + def getvalue(self, item, value=None): + return self.get(item, object_dict()).get('value', value) + +class xml2dict(object): + + def __init__(self): + pass + + def _parse_node(self, node): + node_tree = object_dict() + # Save attrs and text, hope there will not be a child with same name + if node.text: + node_tree.value = node.text + for (k,v) in node.attrib.items(): + k,v = self._namespace_split(k, object_dict({'value':v})) + node_tree[k] = v + #Save childrens + for child in node.getchildren(): + tag, tree = self._namespace_split(child.tag, self._parse_node(child)) + if tag not in node_tree: # the first time, so store it in dict + node_tree[tag] = tree + continue + old = node_tree[tag] + if not isinstance(old, list): + node_tree.pop(tag) + node_tree[tag] = [old] # multi times, so change old dict to a list + node_tree[tag].append(tree) # add the new one + + return node_tree + + def _namespace_split(self, tag, value): + """ + Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients' + ns = http://cs.sfsu.edu/csc867/myscheduler + name = patients + """ + result = re.compile("\{(.*)\}(.*)").search(tag) + if result: + value.namespace, tag = result.groups() + + return (tag,value) + + def parse(self, file): + """parse a xml file to a dict""" + f = open(file, 'r') + return self.fromstring(f.read()) + + def fromstring(self, s): + """parse a string""" + t = ET.fromstring(s) + root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) + return object_dict({root_tag: root_tree}) + + +class dict2xml: + xml = "" + level = 0 + + def __init__(self,encoding=None,attributes=False): + self.xml = "" + self.level = 0 + self.encoding = encoding + self.attributes = attributes + + def __del__(self): + pass + + def setXml(self,Xml): + self.xml = Xml + + def setLevel(self,Level): + self.level = Level + + def tostring(self,d): + return self.dict2xml(d) + + def dict2xml(self,map): + if type(map) == object_dict or type(map) == dict: + for key, value in map.items(): + keyo = key + if self.attributes: + # FIXME: This assumes the attributes do not require encoding + keyc = re.sub(r' .+$',r'',key) + else: + keyc = key + if type(value) == object_dict or type(value) == dict: + if(len(value) > 0): + self.xml += " "*self.level + self.xml += "<%s>\n" % (keyo) + self.level += 1 + self.dict2xml(value) + self.level -= 1 + self.xml += " "*self.level + self.xml += "\n" % (keyc) + else: + self.xml += " "*(self.level) + self.xml += "<%s>\n" % (keyo,keyc) + elif type(value) == list: + for v in value: + self.dict2xml({key:v}) + else: + self.xml += " "*(self.level) + self.xml += "<%s>%s\n" % (keyo,self.encode(value),keyc) + else: + self.xml += " "*self.level + self.xml += "<%s>%s\n" % (keyo,self.encode(value),keyc) + return self.xml + + def encode(self,str1): + if type(str1) != str and type(str1) != unicode: + str1 = str(str1) + if self.encoding: + str1 = str1.encode(self.encoding) + str2 = '' + for c in str1: + if c == '&': + str2 += '&' + elif c == '<': + str2 += '`' + elif c == '>': + str2 += 'b' + else: + str2 += c + return str2 + +def list_to_xml(name, l, stream): + for d in l: + dict_to_xml(d, name, stream) + +def dict_to_xml(d, root_node_name, stream): + """ Transform a dict into a XML, writing to a stream """ + stream.write('\n<' + root_node_name) + attributes = StringIO() + nodes = StringIO() + for item in d.items(): + key, value = item + if isinstance(value, dict): + dict_to_xml(value, key, nodes) + elif isinstance(value, list): + list_to_xml(key, value, nodes) + elif isinstance(value, str) or isinstance(value, unicode): + attributes.write('\n %s="%s" ' % (key, value)) + else: + raise TypeError('sorry, we support only dicts, lists and strings') + + stream.write(attributes.getvalue()) + nodes_str = nodes.getvalue() + if len(nodes_str) == 0: + stream.write('/>') + else: + stream.write('>') + stream.write(nodes_str) + stream.write('\n' % root_node_name) + +def dict_from_xml(xml): + """ Load a dict from a XML string """ + + def list_to_dict(l, ignore_root = True): + """ Convert our internal format list to a dict. We need this + because we use a list as a intermediate format during xml load """ + root_dict = {} + inside_dict = {} + # index 0: node name + # index 1: attributes list + # index 2: children node list + root_dict[l[0]] = inside_dict + inside_dict.update(l[1]) + # if it's a node containing lot's of nodes with same name, + # like + for x in l[2]: + d = list_to_dict(x, False) + for k, v in d.iteritems(): + if not inside_dict.has_key(k): + inside_dict[k] = [] + + inside_dict[k].append(v) + + ret = root_dict + if ignore_root: + ret = root_dict.values()[0] + + return ret + + class M: + """ This is our expat event sink """ + def __init__(self): + self.lists_stack = [] + self.current_list = None + def start_element(self, name, attrs): + l = [] + # root node? + if self.current_list is None: + self.current_list = [name, attrs, l] + else: + self.current_list.append([name, attrs, l]) + + self.lists_stack.append(self.current_list) + self.current_list = l + pass + + def end_element(self, name): + self.current_list = self.lists_stack.pop() + def char_data(self, data): + # We don't write char_data to file (beyond \n and spaces). + # What to do? Raise? + pass + + p = expat.ParserCreate() + m = M() + + p.StartElementHandler = m.start_element + p.EndElementHandler = m.end_element + p.CharacterDataHandler = m.char_data + + p.Parse(xml) + + d = list_to_dict(m.current_list) + + return d + +class ConfigHolder: + def __init__(self, d=None): + """ + Init from dict d + """ + if d is None: + self.d = {} + else: + self.d = d + + def __str__(self): + return self.d.__str__() + + __repr__ = __str__ + + def load_from_xml(self, xml): + self.d = dict_from_xml(xml) + + def load_from_dict(self, d): + self.d = d + + def get_must_exist(self, key): + v = self.get(key) + + if v is None: + raise KeyError('the required config key "%s" was not found' % key) + + return v + + def __getitem__(self, key): + """ + Support for config['path/key'] syntax + """ + return self.get_must_exist(key) + + def get(self, key, default=None): + """ + Get from config using a filesystem-like syntax + + value = 'start/sub/key' will + return config_map['start']['sub']['key'] + """ + try: + d = self.d + + path = key.split('/') + # handle 'key/subkey[2]/value/' + if path[-1] == '' : + path = path[:-1] + + for x in path[:len(path)-1]: + i = x.find('[') + if i: + if x[-1] != ']': + raise Exception('invalid syntax') + index = int(x[i+1:-1]) + + d = d[x[:i]][index] + else: + d = d[x] + + return d[path[-1]] + + except: + return default + From 8fd42745eadfbac7a699ccd961da5635bc01050e Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 6 Jul 2012 13:03:55 -0700 Subject: [PATCH 013/218] fixes for older Element tree --- Changes | 3 +++ ebaysdk/__init__.py | 4 ++-- ebaysdk/utils.py | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index b8d4ec2..d756d7d 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +0.0.6 + +- support older version of Element tree - modify dict2xml to handle nodes with array content for example, diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index c191a10..a027d48 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -126,9 +126,9 @@ def execute(self, verb, data): self.verb = verb if type(data) == DictType: - self.call_xml = dict2xml(data, roottag=None) + self.call_xml = dict2xml(data, roottag='TRASHME') elif type(data) == ListType: - self.call_xml = list2xml(data, roottag=None) + self.call_xml = list2xml(data, roottag='TRASHME') else: self.call_xml = data diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 837219a..5f4713e 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -296,10 +296,11 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): for (tag, child) in sorted(dictitem.iteritems()): if isinstance(child, list): # iterate through the array and convert - listelem = ET.Element(tag) + listelem = ET.Element(None) + listelem2 = ET.Element(tag) parent.append(listelem) for listchild in child: - elem = ET.Element(listnames.get(tag, 'item')) + elem = ET.Element(listnames.get(tag, listelem2.tag)) listelem.append(elem) _convert_dict_to_xml_recurse(elem, listchild, listnames) else: @@ -382,15 +383,16 @@ def list2et(xmllist, root, elementname): return basexml.find(root) -def dict2xml(datadict, roottag='data', listnames=None, pretty=False): - """Converts a dictionary to an UTF-8 encoded XML string. - +def dict2xml(datadict, roottag='TRASHME', listnames=None, pretty=False): + """ + Converts a dictionary to an UTF-8 encoded XML string. See also dict2et() """ root = dict2et(datadict, roottag, listnames) - return to_string(root, pretty=pretty) - - + + xml = to_string(root, pretty=pretty) + return xml.replace('<%s>' % roottag, '').replace('' % roottag, '') + def list2xml(datalist, roottag, elementname, pretty=False): """Converts a list to an UTF-8 encoded XML string. From 15acc0c7880ff7c2e13f4e6f071cbf8d5ef9e8a1 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 6 Jul 2012 13:05:23 -0700 Subject: [PATCH 014/218] fixes for older Element tree --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index a027d48..faed1fe 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -10,7 +10,7 @@ from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct -VERSION = (0, 1, 3) +VERSION = (0, 1, 6) def get_version(): From 696d9f6c31a07a156a62ca3ca4c1005251a1b51c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 6 Jul 2012 13:13:46 -0700 Subject: [PATCH 015/218] fix test --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index faed1fe..8d419a9 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -437,7 +437,7 @@ class trading(ebaybase): >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) >>> print charity_name - u'Sunshine Kids Foundation' + Sunshine Kids Foundation >>> print t.error() """ From f86f7b23c89655a01bf5424dc739b4d86eed2950 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 18 Jul 2012 15:23:39 -0700 Subject: [PATCH 016/218] Addded support for IAF Tokens in Trading API --- ebaysdk/__init__.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 8d419a9..66f64a3 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -454,6 +454,7 @@ def __init__(self, username=None, password=None, token=None, + iaf_token=None, appid=None, certid=None, devid=None, @@ -484,13 +485,14 @@ def __init__(self, self.api_config['username']=username or self.api_config.get('username') self.api_config['password']=password or self.api_config.get('password') self.api_config['token']=token or self.api_config.get('token') + self.api_config['iaf_token']=iaf_token or self.api_config.get('iaf_token') self.api_config['appid']=appid or self.api_config.get('appid') self.api_config['certid']=certid or self.api_config.get('certid') self.api_config['devid']=devid or self.api_config.get('devid') self.api_config['version']=version or self.api_config.get('compatability') or self.api_config.get('version') def _build_request_headers(self): - return { + headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('version', ''), "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), "X-EBAY-API-APP-NAME": self.api_config.get('appid',''), @@ -499,25 +501,26 @@ def _build_request_headers(self): "X-EBAY-API-CALL-NAME": self.verb, "Content-Type": "text/xml" } + if self.api_config.get('iaf_token', None): + headers["X-EBAY-API-IAF-TOKEN"] = self.api_config.get('iaf_token') + return headers def _build_request_xml(self): xml = "" xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" - xml += "" - if self.api_config.get('token', None): - xml += "%s" % self.api_config.get('token') - else: - if self.api_config.get('username', None): + if not self.api_config.get('iaf_token', None): + xml += "" + if self.api_config.get('token', None): + xml += "%s" % self.api_config.get('token') + elif self.api_config.get('username', None): xml += "%s" % self.api_config.get('username', '') - if self.api_config.get('password', None): - xml += "%s" % self.api_config.get('password', '') - - xml += "" + if self.api_config.get('password', None): + xml += "%s" % self.api_config.get('password', '') + xml += "" xml += self.call_xml xml += "" - return xml - + class finding(ebaybase): """ Finding backend for ebaysdk. From 693b115db906b06b3ce8a1ae67e1d7479dcd9b91 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 31 Aug 2012 10:43:09 -0700 Subject: [PATCH 017/218] ignore invalid certs --- ebaysdk/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 66f64a3..ccede08 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -173,6 +173,9 @@ def _execute_http_request(self): "performs the http post and returns the XML response body" try: curl = pycurl.Curl() + + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + curl.setopt(pycurl.SSL_VERIFYHOST, 0) if self.proxy_host: curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) From 712c3ea9bc8f1e89b83099a372a2caddb1c7449b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 4 Sep 2012 20:50:45 -0700 Subject: [PATCH 018/218] add dict2xml test --- ebaysdk/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index ccede08..2171ce3 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -8,7 +8,7 @@ from types import DictType from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct - +import ebaysdk.utils2 VERSION = (0, 1, 6) @@ -35,7 +35,12 @@ def tag(name, value): return "<%s>%s" % ( name, value, name ) class ebaybase(object): - + """ + + >>> d = { 'list': ['a', 'b', 'c']} + >>> print dict2xml(d) + abc + """ def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, **kwargs): self.verb = None self.debug = debug From 837feeaa6fcc1e1cf5059a5de2131b60ef4ba9dc Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 4 Sep 2012 21:46:38 -0700 Subject: [PATCH 019/218] bug fix for older python versions or ET --- ebaysdk/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 5f4713e..e924060 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -296,7 +296,7 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): for (tag, child) in sorted(dictitem.iteritems()): if isinstance(child, list): # iterate through the array and convert - listelem = ET.Element(None) + listelem = ET.Element('') listelem2 = ET.Element(tag) parent.append(listelem) for listchild in child: @@ -391,6 +391,7 @@ def dict2xml(datadict, roottag='TRASHME', listnames=None, pretty=False): root = dict2et(datadict, roottag, listnames) xml = to_string(root, pretty=pretty) + xml = xml.replace('<>', '').replace('','') return xml.replace('<%s>' % roottag, '').replace('' % roottag, '') def list2xml(datalist, roottag, elementname, pretty=False): From be5277317731dea5be1b3e9e60bfb828b7849a81 Mon Sep 17 00:00:00 2001 From: tim0th3us Date: Wed, 12 Sep 2012 17:02:41 -0700 Subject: [PATCH 020/218] return self form execute --- ebaysdk/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 2171ce3..646631c 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -143,6 +143,7 @@ def execute(self, verb, data): # remove xml namespace regex = re.compile('xmlns="[^"]+"') self._response_content = regex.sub( '', self._response_content ) + return self def response_soup(self): if not self._response_soup: @@ -374,6 +375,7 @@ def execute(self, url, call_data=dict()): # remove xml namespace regex = re.compile( 'xmlns="[^"]+"' ) self._response_content = regex.sub( '', self._response_content ) + return self def _execute_http_request(self): "performs the http post and returns the XML response body" From acb13509184e1ecdd032619da7a56dda8094903c Mon Sep 17 00:00:00 2001 From: tim0th3us Date: Thu, 20 Sep 2012 09:04:52 -0700 Subject: [PATCH 021/218] do not error on keyerror when accessing keys in the response dict --- ebaysdk/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index e924060..aaa8512 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -23,9 +23,11 @@ def __init__(self, initd=None): dict.__init__(self, initd) def __getattr__(self, item): - - d = self.__getitem__(item) - + try: + d = self.__getitem__(item) + except KeyError: + return None + if isinstance(d, dict) and 'value' in d and len(d) == 1: return d['value'] else: From 5e20c14b3537a42bccc5b57a0deefa032eb579d5 Mon Sep 17 00:00:00 2001 From: Georgy Vladimirov Date: Mon, 5 Nov 2012 18:26:31 -0800 Subject: [PATCH 022/218] - added SOAService class for SOAP-style eBay services - changed ebaybase:_execute_http_request() to trap exceptions - added ebaybase:_get_resp_body_errors() to handle XML body errors - cleanup --- ebaysdk/__init__.py | 260 +++++++++++++++++++++++++++++++------------- 1 file changed, 185 insertions(+), 75 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 646631c..e91f074 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -5,9 +5,8 @@ from xml.dom.minidom import parseString, Node from BeautifulSoup import BeautifulStoneSoup -from types import DictType -from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct +from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct, object_dict import ebaysdk.utils2 VERSION = (0, 1, 6) @@ -28,7 +27,7 @@ def nodeText(node): rc.append(cn.data) elif cn.nodeType == cn.CDATA_SECTION_NODE: rc.append(cn.data) - + return ''.join(rc) def tag(name, value): @@ -41,7 +40,7 @@ class ebaybase(object): >>> print dict2xml(d) abc """ - def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, **kwargs): + def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, **kwargs): self.verb = None self.debug = debug self.method = method @@ -85,12 +84,12 @@ def v(self, *args, **kwargs): return h def load_yaml(self, config_file): - + dirs = [ '.', os.environ.get('HOME'), '/etc' ] for mydir in dirs: myfile = "%s/%s" % (mydir, config_file) - + if os.path.exists( myfile ): try: f = open( myfile, "r" ) @@ -98,17 +97,17 @@ def load_yaml(self, config_file): print "unable to open file %s" % e #print "using file %s" % myfile - + yData = yaml.load( f.read() ) domain = self.api_config.get('domain', '') - + self.api_config_append( yData.get(domain, {}) ) return - + def api_config_append(self, config): for c in config: self.api_config[c] = config[c] - + def getNodeText(self, nodelist): rc = "" for node in nodelist: @@ -123,10 +122,11 @@ def _reset(self): self._response_soup = None self._response_dict = None self._response_error = None + self._resp_body_errors = [] def do(self, verb, call_data=dict()): return self.execute(verb, call_data) - + def execute(self, verb, data): self.verb = verb @@ -134,10 +134,10 @@ def execute(self, verb, data): self.call_xml = dict2xml(data, roottag='TRASHME') elif type(data) == ListType: self.call_xml = list2xml(data, roottag='TRASHME') - else: + else: self.call_xml = data - self._reset() + self._reset() self._response_content = self._execute_http_request() # remove xml namespace @@ -154,35 +154,35 @@ def response_soup(self): def response_obj(self): if not self._response_obj: self._response_obj = make_struct(self.response_dict()) - + return self._response_obj - + def response_dom(self): if not self._response_dom: dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb))) ) self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] - + return self._response_dom def response_dict(self): if not self._response_dict: mydict = xml2dict().fromstring(self._response_content) self._response_dict = mydict.get(self.verb+'Response', mydict) - + return self._response_dict - + def api_init(self,config_items): for config in config_items: self.api_config[config[0]] = config[1] - + def _execute_http_request(self): "performs the http post and returns the XML response body" try: curl = pycurl.Curl() - curl.setopt(pycurl.SSL_VERIFYPEER, 0) + curl.setopt(pycurl.SSL_VERIFYPEER, 0) curl.setopt(pycurl.SSL_VERIFYHOST, 0) - + if self.proxy_host: curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) else: @@ -196,22 +196,22 @@ def _execute_http_request(self): # construct URL & post data request_url = self.api_config.get('domain', None) - + if self.api_config.get('uri', None): request_url = "%s%s" % ( request_url, self.api_config.get('uri', None) ) - + if self.api_config.get('https', None): request_url = "https://%s" % request_url - + if self.method == 'POST': request_xml = self._build_request_xml() curl.setopt(pycurl.POST, True) curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - + curl.setopt(pycurl.FOLLOWLOCATION, 1) curl.setopt(pycurl.URL, str(request_url)) curl.setopt(pycurl.SSL_VERIFYPEER, 0) - + response_header = StringIO.StringIO() response_body = StringIO.StringIO() @@ -227,52 +227,60 @@ def _execute_http_request(self): curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) curl.perform() - + response_code = curl.getinfo(pycurl.HTTP_CODE) response_status = response_header.getvalue().splitlines()[0] response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) response_data = response_body.getvalue() - + if response_code != 200: self._response_error = "Error: %s" % response_reason - raise Exception('%s' % response_reason) - else: - return response_data - + except Exception, e: self._response_error = "Exception: %s" % e - raise Exception("%s" % e) - - def error(self): - "builds and returns the api error message" - str = [] + return response_data - if self._response_error: - str.append(self._response_error) + # Child classes can override this method based on what the errors in the + # XML response body look like. They can choose to look at the 'ack', + # 'Errors', 'errorMessage' or whatever other fields the service returns. + # the implementation below is the original code that was part of error() + def _get_resp_body_errors(self): + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + err = [] for e in self.response_dom().getElementsByTagName("Errors"): if e.getElementsByTagName('ErrorClassification'): - str.append('- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) + err.append('- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) if e.getElementsByTagName('SeverityCode'): - str.append('- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) + err.append('- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) if e.getElementsByTagName('ErrorCode'): - str.append('- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) + err.append('- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) if e.getElementsByTagName('ShortMessage'): - str.append('- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) + err.append('- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) if e.getElementsByTagName('LongMessage'): - str.append('- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) + err.append('- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) + + self._resp_body_errors = err + return err + + def error(self): + "builds and returns the api error message" + + err = [] + if self._response_error: err.append(self._response_error) + err.extend(self._get_resp_body_errors()) + + if len(err) > 0: return "%s error:\n%s\n" % (self.verb, "\n".join(err)) - if (len(str) > 0): - return "%s error:\n%s\n" % (self.verb, "\n".join(str)) + return "" - return "\n".join(str) - class shopping(ebaybase): """ Shopping backend for ebaysdk. @@ -287,9 +295,9 @@ class shopping(ebaybase): >>> print s.error() """ - - def __init__(self, - domain='open.api.ebay.com', + + def __init__(self, + domain='open.api.ebay.com', uri='/shopping', https=False, siteid=0, @@ -307,7 +315,7 @@ def __init__(self, 'siteid' : siteid, 'response_encoding' : response_encoding, 'request_encoding' : request_encoding, - } + } self.load_yaml(config_file) @@ -360,16 +368,16 @@ def response_dom(self): def response_dict(self): if not self._response_dict: self._response_dict = xml2dict().fromstring(self._response_content) - + return self._response_dict def execute(self, url, call_data=dict()): "execute(self, url, call_data=dict())" - + self.url = url self.call_data = call_data - - self._reset() + + self._reset() self._response_content = self._execute_http_request() # remove xml namespace @@ -379,15 +387,15 @@ def execute(self, url, call_data=dict()): def _execute_http_request(self): "performs the http post and returns the XML response body" - + try: curl = pycurl.Curl() - + if self.proxy_host: curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) else: curl.setopt(pycurl.PROXY, '') - + request_url = self.url if self.call_data and self.method == 'GET': request_url = request_url + '?' + urllib.urlencode(self.call_data) @@ -396,11 +404,11 @@ def _execute_http_request(self): request_xml = self._build_request_xml() curl.setopt(pycurl.POST, True) curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - + curl.setopt(pycurl.FOLLOWLOCATION, 1) curl.setopt(pycurl.URL, str(request_url)) curl.setopt(pycurl.SSL_VERIFYPEER, 0) - + response_header = StringIO.StringIO() response_body = StringIO.StringIO() @@ -416,26 +424,26 @@ def _execute_http_request(self): curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) curl.perform() - + response_code = curl.getinfo(pycurl.HTTP_CODE) response_status = response_header.getvalue().splitlines()[0] response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) response_data = response_body.getvalue() - + if response_code != 200: self._response_error = "Error: %s" % response_reason raise Exception('%s' % response_reason) else: return response_data - + except Exception, e: self._response_error = "Exception: %s" % e raise Exception("%s" % e) - + def error(self): - "builds and returns the api error message" + "builds and returns the api error message" return self._response_error - + class trading(ebaybase): """ Trading backend for the ebaysdk @@ -452,7 +460,7 @@ class trading(ebaybase): """ - def __init__(self, + def __init__(self, domain=None, uri=None, https=None, @@ -471,7 +479,7 @@ def __init__(self, version=None, config_file='ebay.yaml', **kwargs ): - + ebaybase.__init__(self, method='POST', **kwargs) self.api_config = { @@ -484,7 +492,7 @@ def __init__(self, 'version' : '648', } - self.load_yaml(config_file) + self.load_yaml(config_file) self.api_config['domain']=domain or self.api_config.get('domain') self.api_config['uri']=uri or self.api_config.get('uri') @@ -552,10 +560,10 @@ class finding(ebaybase): Success """ - - def __init__(self, - domain='svcs.ebay.com', - service='FindingService', + + def __init__(self, + domain='svcs.ebay.com', + service='FindingService', uri='/services/search/FindingService/v1', https=False, siteid='EBAY-US', @@ -574,7 +582,7 @@ def __init__(self, 'siteid' : siteid, 'response_encoding' : response_encoding, 'request_encoding' : request_encoding, - } + } self.load_yaml(config_file) @@ -597,3 +605,105 @@ def _build_request_xml(self): xml += "" return xml + + +class SOAService( ebaybase ): + """ + This class provides a base for eBay's SOAP-style web services. + """ + + def __init__(self, app_config=None, site_id='EBAY-US', debug=False): + self.api_config = { + 'https' : False, + 'site_id' : site_id, + 'content_type' : 'text/xml;charset=UTF-8', + 'request_encoding' : 'XML', + 'response_encoding' : 'XML', + 'message_protocol' : 'SOAP12', + 'soap_env_str' : 'http://www.ebay.com/marketplace/fundraising/v1/services', + } + + ph = None + pp = 80 + if app_config: + self.load_from_app_config(app_config) + ph = self.api_config.get('proxy_host', ph) + pp = self.api_config.get('proxy_port', pp) + + super(SOAService, self).__init__( + debug = debug, + method = 'POST', + proxy_host = ph, + proxy_port = pp, + ) + + # override this method, to provide setup through a config object, which + # should provide a get() method for extracting constants we care about + # this method should then set the .api_config[] dict (e.g. the comment below) + def load_from_app_config(self, app_config): + #self.api_config['domain'] = app_config.get('API_SERVICE_DOMAIN') + #self.api_config['uri'] = app_config.get('API_SERVICE_URI') + pass + + # Note: this method will always return at least an empty object_dict! + # It used to return None in some cases. If you get an empty dict, + # you can use the .errors() method to look for the cause. + def response_dict( self ): + if self._response_dict: return self._response_dict + + mydict = object_dict() + try: + mydict = xml2dict().fromstring(self._response_content) + verb = self.verb + 'Response' + self._response_dict = mydict['Envelope']['Body'][ verb ] + + except Exception, e: + self._response_dict = mydict + self._resp_body_errors.append("Error parsing SOAP response: %s" % e) + + return self._response_dict + + def _build_request_headers(self): + return { + 'Content-Type' : self.api_config['content_type'], + 'X-EBAY-SOA-SERVICE-NAME' : self.api_config['service'], + 'X-EBAY-SOA-OPERATION-NAME' : self.verb, + 'X-EBAY-SOA-GLOBAL-ID' : self.api_config['site_id'], + 'X-EBAY-SOA-REQUEST-DATA-FORMAT' : self.api_config['request_encoding'], + 'X-EBAY-SOA-RESPONSE-DATA-FORMAT' : self.api_config['response_encoding'], + 'X-EBAY-SOA-MESSAGE-PROTOCOL' : self.api_config['message_protocol'], + } + + def _build_request_xml(self): + xml = '' + xml += ' Date: Tue, 6 Nov 2012 14:33:14 -0800 Subject: [PATCH 023/218] Handle cases when _get_resp_body_errors() may get called before execute() --- ebaysdk/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index e91f074..d5179da 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -250,7 +250,11 @@ def _get_resp_body_errors(self): return self._resp_body_errors err = [] - for e in self.response_dom().getElementsByTagName("Errors"): + if self.verb is None: return err + dom = self.response_dom() + if dom is None: return err + + for e in dom.getElementsByTagName("Errors"): if e.getElementsByTagName('ErrorClassification'): err.append('- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) From 0ca46a3ee85ae300ce5280cde9519183a1cec129 Mon Sep 17 00:00:00 2001 From: Georgy Vladimirov Date: Tue, 6 Nov 2012 14:38:37 -0800 Subject: [PATCH 024/218] fix a few comments --- ebaysdk/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index d5179da..917ce08 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -241,7 +241,7 @@ def _execute_http_request(self): return response_data - # Child classes can override this method based on what the errors in the + # Child classes should override this method based on what the errors in the # XML response body look like. They can choose to look at the 'ack', # 'Errors', 'errorMessage' or whatever other fields the service returns. # the implementation below is the original code that was part of error() @@ -651,7 +651,7 @@ def load_from_app_config(self, app_config): # Note: this method will always return at least an empty object_dict! # It used to return None in some cases. If you get an empty dict, - # you can use the .errors() method to look for the cause. + # you can use the .error() method to look for the cause. def response_dict( self ): if self._response_dict: return self._response_dict From da301020d77c62d53c710cbb4bebc6a56cb1e7e6 Mon Sep 17 00:00:00 2001 From: Georgy Vladimirov Date: Wed, 7 Nov 2012 10:06:21 -0800 Subject: [PATCH 025/218] Last change, I promise :-) Use "" as default for response_data --- ebaysdk/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 917ce08..ce49e7f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -177,6 +177,7 @@ def api_init(self,config_items): def _execute_http_request(self): "performs the http post and returns the XML response body" + response_data = '' try: curl = pycurl.Curl() From cf043a02cd7cf2cf298561b42cfea470fcbe952d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 28 Nov 2012 21:16:18 -0800 Subject: [PATCH 026/218] fix SOA class --- ebaysdk/__init__.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index ce49e7f..8865d95 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -635,7 +635,8 @@ def __init__(self, app_config=None, site_id='EBAY-US', debug=False): ph = self.api_config.get('proxy_host', ph) pp = self.api_config.get('proxy_port', pp) - super(SOAService, self).__init__( + ebaysdk.__init__( + self, debug = debug, method = 'POST', proxy_host = ph, @@ -693,11 +694,25 @@ def _build_request_xml(self): return xml def execute(self, verb, data): - soap_data = data - if type(soap_data) == DictType: - soap_data = dict2xml( self.soapify(data) ) - res = super(SOAService, self).execute(verb, soap_data) - return res + if type(data) == DictType: + data = dict2xml( self.soapify(data) ) + + self.verb = verb + + if type(data) == DictType: + self.call_xml = dict2xml(data, roottag='TRASHME') + elif type(data) == ListType: + self.call_xml = list2xml(data, roottag='TRASHME') + else: + self.call_xml = data + + self._reset() + self._response_content = self._execute_http_request() + + # remove xml namespace + regex = re.compile('xmlns="[^"]+"') + self._response_content = regex.sub( '', self._response_content ) + return self def soapify( self, xml ): xml_type = type( xml ) From 627b4470925c7b24f9e3701ade9637e6efb53c0e Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 28 Nov 2012 21:48:49 -0800 Subject: [PATCH 027/218] update ebaysdk --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 8865d95..310f8d1 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -635,7 +635,7 @@ def __init__(self, app_config=None, site_id='EBAY-US', debug=False): ph = self.api_config.get('proxy_host', ph) pp = self.api_config.get('proxy_port', pp) - ebaysdk.__init__( + ebaybase.__init__( self, debug = debug, method = 'POST', From a3e8dadc184a67c8875afe6ab279d77e2840f7a8 Mon Sep 17 00:00:00 2001 From: Jud Date: Thu, 29 Nov 2012 14:31:02 -0500 Subject: [PATCH 028/218] This removes the version info out of __init__.py and moves it to _version.py. Loading __init__.py in setup.py was preventing the dependencies from being installed, since they are imported in __init__.py. This solution was found here: http://stackoverflow.com/a/7071358 --- ebaysdk/__init__.py | 51 +++++++++++++++++++++++---------------------- ebaysdk/_version.py | 1 + setup.py | 27 ++++++++++++------------ 3 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 ebaysdk/_version.py diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 310f8d1..4430e57 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,14 +9,16 @@ from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct, object_dict import ebaysdk.utils2 -VERSION = (0, 1, 6) - - def get_version(): - version = '%s.%s.%s' % (VERSION[0], VERSION[1], VERSION[2]) + # Get the version + PKG = 'ebaysdk' + VERSIONFILE = os.path.join(PKG, "_version.py") + version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + open(VERSIONFILE, "rt").read(), re.M).group(1) + return version -__version__ = get_version() +__version__ = get_version() def nodeText(node): rc = [] @@ -35,7 +37,6 @@ def tag(name, value): class ebaybase(object): """ - >>> d = { 'list': ['a', 'b', 'c']} >>> print dict2xml(d) abc @@ -49,12 +50,12 @@ def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy self.proxy_port = proxy_port self.spooled_calls = []; self._reset() - + def debug_callback(self, debug_type, debug_message): sys.stderr.write('type: ' + str(debug_type) + ' message'+str(debug_message) + "\n") - + def v(self, *args, **kwargs): - + args_a = [w for w in args] first = args_a[0] args_a.remove(first) @@ -64,25 +65,25 @@ def v(self, *args, **kwargs): h = h.get(first, {}) else: h = self.response_dict().get(first, {}) - + if len(args) == 1: try: return h.get('value', None) except: return h - + last = args_a.pop() - + for a in args_a: h = h.get(a, {}) h = h.get(last, {}) - + try: return h.get('value', None) except: - return h - + return h + def load_yaml(self, config_file): dirs = [ '.', os.environ.get('HOME'), '/etc' ] @@ -123,7 +124,7 @@ def _reset(self): self._response_dict = None self._response_error = None self._resp_body_errors = [] - + def do(self, verb, call_data=dict()): return self.execute(verb, call_data) @@ -148,9 +149,9 @@ def execute(self, verb, data): def response_soup(self): if not self._response_soup: self._response_soup = BeautifulStoneSoup(unicode(self._response_content)) - + return self._response_soup - + def response_obj(self): if not self._response_obj: self._response_obj = make_struct(self.response_dict()) @@ -292,7 +293,7 @@ class shopping(ebaybase): http://developer.ebay.com/products/shopping/ shopping(debug=False, domain='open.api.ebay.com', uri='/shopping', method='POST', https=False, siteid=0, response_encoding='XML', request_encoding='XML', config_file='ebay.yaml') - + >>> s = shopping() >>> s.execute('FindItemsAdvanced', {'CharityID': 3897}) >>> print s.response_obj().Ack @@ -345,9 +346,9 @@ def _build_request_xml(self): class html(ebaybase): """ HTML backend for ebaysdk. - + (self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80) - + >>> h = html() >>> h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> print h.response_obj().rss.channel.ttl @@ -453,7 +454,7 @@ class trading(ebaybase): """ Trading backend for the ebaysdk http://developer.ebay.com/products/trading/ - + >>> t = trading() >>> t.execute('GetCharities', { 'CharityID': 3897 }) >>> charity_name = '' @@ -548,13 +549,13 @@ class finding(ebaybase): """ Finding backend for ebaysdk. http://developer.ebay.com/products/finding/ - + >>> f = finding() >>> f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() >>> print error - + >>> if len( error ) <= 0: ... print f.response_obj().itemSearchURL != '' ... items = f.response_obj().searchResult.item @@ -563,7 +564,7 @@ class finding(ebaybase): True 100 Success - + """ def __init__(self, diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py new file mode 100644 index 0000000..0a8da88 --- /dev/null +++ b/ebaysdk/_version.py @@ -0,0 +1 @@ +__version__ = "0.1.6" diff --git a/setup.py b/setup.py index f2380d2..f4743a2 100644 --- a/setup.py +++ b/setup.py @@ -14,15 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -#try: from setuptools import setup, find_packages -#except ImportError: -# from distutils.core import setup +import re, os -import sys +PKG = 'ebaysdk' + +# Get the version +VERSIONFILE = os.path.join(PKG, "_version.py") +version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + open(VERSIONFILE, "rt").read(), re.M).group(1) -execfile('./ebaysdk/__init__.py') -VERSION = __version__ long_desc = """This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, @@ -30,20 +31,20 @@ HTML back-end libraries.""" setup( - name="ebaysdk", - version=VERSION, + name=PKG, + version=version, description="Simple and Extensible eBay SDK for Python", author="Tim Keefer", author_email="tim@timkeefer.com", - url="http://code.google.com/p/ebay-sdk-python/", + url="https://github.com/timotheus/ebaysdk-python", license="Apache Software License", packages=find_packages(), - provides=['ebaysdk'], - install_requires=['BeautifulSoup', 'PyYAML', 'pycurl', 'elementtree'], + provides=[PKG], + install_requires=['PyYaml', 'pycurl', 'Beautifulsoup'], test_suite='tests', long_description=long_desc, classifiers=[ 'Topic :: Internet :: WWW/HTTP', 'Intended Audience :: Developers', - ] -) + ] +) From 4eb5b8a4173e14ebccda4414d62522858134b07d Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 3 Dec 2012 12:19:12 -0500 Subject: [PATCH 029/218] Fixing incorrect location for the _version.py file --- ebaysdk/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 4430e57..3e39bb9 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -11,10 +11,9 @@ def get_version(): # Get the version - PKG = 'ebaysdk' - VERSIONFILE = os.path.join(PKG, "_version.py") + VERSIONFILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "_version.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - open(VERSIONFILE, "rt").read(), re.M).group(1) + open(VERSIONFILE, "rt").read(), re.M).group(1) return version From 5efe1160d4a44f95df0e29bdbb35a15b857779a9 Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 3 Dec 2012 12:19:37 -0500 Subject: [PATCH 030/218] Removing trailing whitespace --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 3e39bb9..b2a8bd1 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -27,7 +27,7 @@ def nodeText(node): if cn.nodeType == cn.TEXT_NODE: rc.append(cn.data) elif cn.nodeType == cn.CDATA_SECTION_NODE: - rc.append(cn.data) + rc.append(cn.data) return ''.join(rc) From 169b5228c1cb53027cd362afb84e7a1d43d8c1ee Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 3 Dec 2012 12:33:00 -0500 Subject: [PATCH 031/218] Fix formatting --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b2a8bd1..295f6ec 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -13,7 +13,7 @@ def get_version(): # Get the version VERSIONFILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "_version.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - open(VERSIONFILE, "rt").read(), re.M).group(1) + open(VERSIONFILE, "rt").read(), re.M).group(1) return version From ce818bc689202242327cdeaaf31ef50d00ca1826 Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 3 Dec 2012 12:21:26 -0500 Subject: [PATCH 032/218] Require https for trading api --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 295f6ec..fb96d8b 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -490,7 +490,7 @@ def __init__(self, self.api_config = { 'domain' : 'api.ebay.com', 'uri' : '/ws/api.dll', - 'https' : False, + 'https' : True, 'siteid' : '0', 'response_encoding' : 'XML', 'request_encoding' : 'XML', From c42b42ef5a59bfb686ed2765eff24a165623fd1a Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 3 Dec 2012 16:02:57 -0500 Subject: [PATCH 033/218] Normalize the Shopping, Trading, and Finding modules The modules now allow the appid, certid, and devid to be set when they are initialized, as well as via a yaml config file. I also removed the username/password authorization option for the trading API, since it seems it has been depricated in favor of the user token. Since the trading API doesn't allow http access, only https, if a user tries to pass in https=False to the trading init function, they will now get an error, and it will be set to true. The same goes for the other modules and their respective https defaults. Normalize the Shopping, Trading, and Finding modules The modules now allow the appid, certid, and devid to be set when they are initialized, as well as via a yaml config file. I also removed the username/password authorization option for the trading API, since it seems it has been depricated in favor of the user token. Since the trading API doesn't allow http access, only https, if a user tries to pass in https=False to the trading init function, they will now get an error, and it will be set to true. The same goes for the other modules and their respective https defaults. Code/spacing cleanup Adding the certid and appid to the Shopping module Removing HTTP requirement for Finding api since HTTPS is supported --- ebaysdk/__init__.py | 119 +++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 47 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index fb96d8b..7e37089 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -46,7 +46,7 @@ def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy self.method = method self.timeout = timeout self.proxy_host = proxy_host - self.proxy_port = proxy_port + self.proxy_port = proxy_port self.spooled_calls = []; self._reset() @@ -308,18 +308,34 @@ def __init__(self, siteid=0, response_encoding='XML', request_encoding='XML', + proxy_host=None, + proxy_port=None, + appid=None, + certid=None, + devid=None, + version='799', config_file='ebay.yaml', - **kwargs ): + **kwargs): ebaybase.__init__(self, method='POST', **kwargs) + if https: + print "HTTPS is not supported on the Shopping API. HTTPS has been set to False" + https = False + self.api_config = { - 'domain' : domain, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, - 'response_encoding' : response_encoding, + 'domain' : domain, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, + 'response_encoding': response_encoding, 'request_encoding' : request_encoding, + 'proxy_host': proxy_host, + 'proxy_port': proxy_port, + 'appid' : appid, + 'certid' : certid, + 'devid' : devid, + 'version' : version } self.load_yaml(config_file) @@ -327,8 +343,8 @@ def __init__(self, def _build_request_headers(self): return { "X-EBAY-API-VERSION": self.api_config.get('version', ''), - "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), - "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), + "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), + "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), "X-EBAY-API-CALL-NAME": self.verb, "X-EBAY-API-REQUEST-ENCODING": "XML", "Content-Type": "text/xml" @@ -466,52 +482,49 @@ class trading(ebaybase): """ def __init__(self, - domain=None, - uri=None, - https=None, - siteid=None, - response_encoding=None, - request_encoding=None, + domain='api.ebay.com', + uri='/ws/api.dll', + https=True, + siteid=0, + response_encoding='XML', + request_encoding='XML', proxy_host=None, proxy_port=None, - username=None, - password=None, token=None, iaf_token=None, appid=None, certid=None, devid=None, - version=None, + version='648', config_file='ebay.yaml', - **kwargs ): + **kwargs): ebaybase.__init__(self, method='POST', **kwargs) + if not https: + print "HTTPS is required on the Trading API. HTTPS has been set to True" + https = True + self.api_config = { - 'domain' : 'api.ebay.com', - 'uri' : '/ws/api.dll', - 'https' : True, - 'siteid' : '0', - 'response_encoding' : 'XML', - 'request_encoding' : 'XML', - 'version' : '648', + 'domain' : domain, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, + 'response_encoding' : response_encoding, + 'request_encoding' : request_encoding, + 'proxy_host': proxy_host, + 'proxy_port': proxy_port, + 'token' : token, + 'iaf_token' : iaf_token, + 'appid' : appid, + 'devid' : devid, + 'certid' : certid, + 'version' : version, } self.load_yaml(config_file) - self.api_config['domain']=domain or self.api_config.get('domain') - self.api_config['uri']=uri or self.api_config.get('uri') - self.api_config['https']=https or self.api_config.get('https') - self.api_config['siteid']=siteid or self.api_config.get('siteid') - self.api_config['response_encoding']=response_encoding or self.api_config.get('response_encoding') - self.api_config['request_encoding']=request_encoding or self.api_config.get('request_encoding') - self.api_config['username']=username or self.api_config.get('username') - self.api_config['password']=password or self.api_config.get('password') - self.api_config['token']=token or self.api_config.get('token') - self.api_config['iaf_token']=iaf_token or self.api_config.get('iaf_token') - self.api_config['appid']=appid or self.api_config.get('appid') - self.api_config['certid']=certid or self.api_config.get('certid') - self.api_config['devid']=devid or self.api_config.get('devid') + # allow yaml to specify compatibility self.api_config['version']=version or self.api_config.get('compatability') or self.api_config.get('version') def _build_request_headers(self): @@ -550,14 +563,14 @@ class finding(ebaybase): http://developer.ebay.com/products/finding/ >>> f = finding() - >>> f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() >>> print error >>> if len( error ) <= 0: ... print f.response_obj().itemSearchURL != '' - ... items = f.response_obj().searchResult.item + ... items = f.response_obj().searchResult.item ... print len(items) ... print f.response_obj().ack True @@ -574,19 +587,31 @@ def __init__(self, siteid='EBAY-US', response_encoding='XML', request_encoding='XML', + proxy_host=None, + proxy_port=None, + appid=None, + certid=None, + devid=None, + version='1.0.0', config_file='ebay.yaml', - **kwargs ): + **kwargs): ebaybase.__init__(self, method='POST', **kwargs) self.api_config = { - 'domain' : domain, - 'service' : service, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, + 'domain' : domain, + 'service' : service, + 'uri' : uri, + 'https' : https, + 'siteid' : siteid, 'response_encoding' : response_encoding, 'request_encoding' : request_encoding, + 'proxy_host': proxy_host, + 'proxy_port': proxy_port, + 'appid' : appid, + 'certid' : certid, + 'devid' : devid, + 'version' : version } self.load_yaml(config_file) From 8d2404130c0325eab704e419d664ab0a2d356f83 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 4 Dec 2012 19:00:07 +0000 Subject: [PATCH 034/218] :::modify tests and fix response_obj() method --- Changes | 4 ++++ ebaysdk/__init__.py | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index d756d7d..814446d 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Changes for ebaysdk +0.1.7 +- update tests +- modify response_obj() to return response_dict() + 0.0.6 - support older version of Element tree diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index fb96d8b..223d8f4 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -152,10 +152,11 @@ def response_soup(self): return self._response_soup def response_obj(self): - if not self._response_obj: - self._response_obj = make_struct(self.response_dict()) + return self.response_dict() - return self._response_obj + #if not self._response_obj: + # self._response_obj = make_struct(self.response_dict()) + #return self._response_obj def response_dom(self): if not self._response_dom: @@ -294,7 +295,7 @@ class shopping(ebaybase): shopping(debug=False, domain='open.api.ebay.com', uri='/shopping', method='POST', https=False, siteid=0, response_encoding='XML', request_encoding='XML', config_file='ebay.yaml') >>> s = shopping() - >>> s.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> retval = s.execute('FindItemsAdvanced', {'CharityID': 3897}) >>> print s.response_obj().Ack Success >>> print s.error() @@ -349,7 +350,7 @@ class html(ebaybase): (self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80) >>> h = html() - >>> h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> print h.response_obj().rss.channel.ttl 60 >>> title = h.response_dom().getElementsByTagName('title')[0] @@ -455,7 +456,7 @@ class trading(ebaybase): http://developer.ebay.com/products/trading/ >>> t = trading() - >>> t.execute('GetCharities', { 'CharityID': 3897 }) + >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) @@ -550,16 +551,16 @@ class finding(ebaybase): http://developer.ebay.com/products/finding/ >>> f = finding() - >>> f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() >>> print error >>> if len( error ) <= 0: - ... print f.response_obj().itemSearchURL != '' - ... items = f.response_obj().searchResult.item + ... print f.response_dict().itemSearchURL != '' + ... items = f.response_dict().searchResult.item ... print len(items) - ... print f.response_obj().ack + ... print f.response_dict().ack True 100 Success From a82fa067b051b237f19fc8bead46bad6eb89c523 Mon Sep 17 00:00:00 2001 From: Jud Date: Wed, 5 Dec 2012 14:29:49 -0500 Subject: [PATCH 035/218] Only print message if debug is enabled --- ebaysdk/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 7e37089..74954e1 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -319,9 +319,8 @@ def __init__(self, ebaybase.__init__(self, method='POST', **kwargs) - if https: - print "HTTPS is not supported on the Shopping API. HTTPS has been set to False" - https = False + if https and self.debug: + print "HTTPS is not supported on the Shopping API." self.api_config = { 'domain' : domain, @@ -501,9 +500,8 @@ def __init__(self, ebaybase.__init__(self, method='POST', **kwargs) - if not https: - print "HTTPS is required on the Trading API. HTTPS has been set to True" - https = True + if not https and self.debug: + print "HTTPS is required on the Trading API." self.api_config = { 'domain' : domain, From f0aea75489e5c4d76994dad96370cd3a58b9a7da Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Tue, 11 Dec 2012 13:58:04 -0800 Subject: [PATCH 036/218] Added pickling support to object_dict --- ebaysdk/utils.py | 6 ++++++ ebaysdk/utils2.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index aaa8512..7d1f1cb 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -41,6 +41,12 @@ def __setattr__(self, item, value): def getvalue(self, item, value=None): return self.get(item, {}).get('value', value) + def __getstate__(self): + return self.items() + + def __setstate__(self, items): + self.update(items) + class xml2dict(object): def __init__(self): diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index 7514e69..668e30d 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -41,6 +41,12 @@ def __setattr__(self, item, value): def getvalue(self, item, value=None): return self.get(item, object_dict()).get('value', value) + def __getstate__(self): + return self.items() + + def __setstate__(self, items): + self.update(items) + class xml2dict(object): def __init__(self): From 00ce67a77f940f44d871ca7c105948e079432658 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 14 Dec 2012 09:27:27 -0800 Subject: [PATCH 037/218] modify config_file yaml function to accept and absolute path --- ebaysdk/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index bb0a80d..c300b3f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -84,9 +84,22 @@ def v(self, *args, **kwargs): return h def load_yaml(self, config_file): + + # check for absolute path + if os.path.exists( config_file ): + try: + f = open( config_file, "r" ) + except IOError, e: + print "unable to open file %s" % e - dirs = [ '.', os.environ.get('HOME'), '/etc' ] + yData = yaml.load( f.read() ) + domain = self.api_config.get('domain', '') + + self.api_config_append( yData.get(domain, {}) ) + return + # check other directories + dirs = [ '.', os.environ.get('HOME'), '/etc' ] for mydir in dirs: myfile = "%s/%s" % (mydir, config_file) @@ -96,8 +109,6 @@ def load_yaml(self, config_file): except IOError, e: print "unable to open file %s" % e - #print "using file %s" % myfile - yData = yaml.load( f.read() ) domain = self.api_config.get('domain', '') From 379bea63b41ff1202d1ad05a373bc452d2c851d6 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 18 Jan 2013 16:18:14 -0800 Subject: [PATCH 038/218] Update README.rst --- README.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.rst b/README.rst index b9d9d06..a9f4735 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,3 @@ - -This code is in the process of migrating from google code to github. For now, please get the lastest code from google code, - -http://code.google.com/p/ebay-sdk-python/ - - Welcome to the ebaysdk for Python ================================= From 9749b9f3737f1951e18c0362509a0077edc537fb Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 18 Jan 2013 16:38:17 -0800 Subject: [PATCH 039/218] bump version --- ebaysdk/_version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index 0a8da88..f1380ee 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" diff --git a/setup.py b/setup.py index f4743a2..5326ca1 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ version=version, description="Simple and Extensible eBay SDK for Python", author="Tim Keefer", - author_email="tim@timkeefer.com", + author_email="tkeefer@gmail.com", url="https://github.com/timotheus/ebaysdk-python", license="Apache Software License", packages=find_packages(), From 8838ab898072ccdd4b1f0960839917434a9ef04a Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 11:18:06 -0800 Subject: [PATCH 040/218] Added prepare/process methods --- ebaysdk/__init__.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index c300b3f..8fb505e 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -148,13 +148,23 @@ def execute(self, verb, data): else: self.call_xml = data + self.prepare() + self._reset() self._response_content = self._execute_http_request() + if self._response_content: + self.process() + + return self + + def prepare(self): + pass + + def process(self): # remove xml namespace regex = re.compile('xmlns="[^"]+"') self._response_content = regex.sub( '', self._response_content ) - return self def response_soup(self): if not self._response_soup: @@ -409,12 +419,14 @@ def execute(self, url, call_data=dict()): self.url = url self.call_data = call_data + self.prepare() + self._reset() self._response_content = self._execute_http_request() - # remove xml namespace - regex = re.compile( 'xmlns="[^"]+"' ) - self._response_content = regex.sub( '', self._response_content ) + if self._response_content: + self.process() + return self def _execute_http_request(self): @@ -741,12 +753,14 @@ def execute(self, verb, data): else: self.call_xml = data + self.prepare() + self._reset() self._response_content = self._execute_http_request() - # remove xml namespace - regex = re.compile('xmlns="[^"]+"') - self._response_content = regex.sub( '', self._response_content ) + if self._response_content: + self.process() + return self def soapify( self, xml ): From 000f005745b6cccf9125dedb5098945d8cf8c0f5 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 13:18:59 -0800 Subject: [PATCH 041/218] Added parallel support through ebayparallel --- Changes | 3 + ebaysdk/__init__.py | 170 +++++++++++++++++++++++++++++--------------- 2 files changed, 116 insertions(+), 57 deletions(-) diff --git a/Changes b/Changes index 814446d..6bc8a33 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +- added parallel support using the ebayparallel class +- created new prepare, process, and _process_http_request methods + 0.1.7 - update tests - modify response_obj() to return response_dict() diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 8fb505e..808f779 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -40,13 +40,14 @@ class ebaybase(object): >>> print dict2xml(d) abc """ - def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, **kwargs): + def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, parallel=None, **kwargs): self.verb = None self.debug = debug self.method = method self.timeout = timeout self.proxy_host = proxy_host self.proxy_port = proxy_port + self.parallel = parallel self.spooled_calls = []; self._reset() @@ -199,21 +200,22 @@ def api_init(self,config_items): def _execute_http_request(self): "performs the http post and returns the XML response body" + response_data = '' try: - curl = pycurl.Curl() + self._curl = pycurl.Curl() - curl.setopt(pycurl.SSL_VERIFYPEER, 0) - curl.setopt(pycurl.SSL_VERIFYHOST, 0) + self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self._curl.setopt(pycurl.SSL_VERIFYHOST, 0) if self.proxy_host: - curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) + self._curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) else: - curl.setopt(pycurl.PROXY, '') + self._curl.setopt(pycurl.PROXY, '') # construct headers request_headers = self._build_request_headers() - curl.setopt( pycurl.HTTPHEADER, [ + self._curl.setopt( pycurl.HTTPHEADER, [ str( '%s: %s' % ( k, v ) ) for k, v in request_headers.items() ] ) @@ -228,41 +230,53 @@ def _execute_http_request(self): if self.method == 'POST': request_xml = self._build_request_xml() - curl.setopt(pycurl.POST, True) - curl.setopt(pycurl.POSTFIELDS, str(request_xml)) + self._curl.setopt(pycurl.POST, True) + self._curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - curl.setopt(pycurl.FOLLOWLOCATION, 1) - curl.setopt(pycurl.URL, str(request_url)) - curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self._curl.setopt(pycurl.FOLLOWLOCATION, 1) + self._curl.setopt(pycurl.URL, str(request_url)) + self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - response_header = StringIO.StringIO() - response_body = StringIO.StringIO() + self._response_header = StringIO.StringIO() + self._response_body = StringIO.StringIO() - curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) - curl.setopt(pycurl.TIMEOUT, self.timeout) + self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) + self._curl.setopt(pycurl.TIMEOUT, self.timeout) - curl.setopt(pycurl.HEADERFUNCTION, response_header.write) - curl.setopt(pycurl.WRITEFUNCTION, response_body.write) + self._curl.setopt(pycurl.HEADERFUNCTION, self._response_header.write) + self._curl.setopt(pycurl.WRITEFUNCTION, self._response_body.write) if self.debug: sys.stderr.write("CURL Request: %s\n" % request_url) - curl.setopt(pycurl.VERBOSE, 1) - curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - - curl.perform() - - response_code = curl.getinfo(pycurl.HTTP_CODE) - response_status = response_header.getvalue().splitlines()[0] - response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) - response_data = response_body.getvalue() + self._curl.setopt(pycurl.VERBOSE, 1) + self._curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - if response_code != 200: - self._response_error = "Error: %s" % response_reason + if self.parallel: + self.parallel._add_request(self) + return None + else: + self._curl.perform() + return self._process_http_request() except Exception, e: self._response_error = "Exception: %s" % e + raise Exception("%s" % e) + + def _process_http_request(self): + response_code = self._curl.getinfo(pycurl.HTTP_CODE) + response_status = self._response_header.getvalue().splitlines()[0] + response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) + response_data = self._response_body.getvalue() - return response_data + self._response_header = None + self._response_body = None + self._curl.close() + + if response_code != 200: + self._response_error = "Error: %s" % response_reason + raise Exception('%s' % response_reason) + else: + return response_data # Child classes should override this method based on what the errors in the # XML response body look like. They can choose to look at the 'ack', @@ -433,12 +447,12 @@ def _execute_http_request(self): "performs the http post and returns the XML response body" try: - curl = pycurl.Curl() + self._curl = pycurl.Curl() if self.proxy_host: - curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) + self._curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) else: - curl.setopt(pycurl.PROXY, '') + self._curl.setopt(pycurl.PROXY, '') request_url = self.url if self.call_data and self.method == 'GET': @@ -446,39 +460,33 @@ def _execute_http_request(self): if self.method == 'POST': request_xml = self._build_request_xml() - curl.setopt(pycurl.POST, True) - curl.setopt(pycurl.POSTFIELDS, str(request_xml)) + self._curl.setopt(pycurl.POST, True) + self._curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - curl.setopt(pycurl.FOLLOWLOCATION, 1) - curl.setopt(pycurl.URL, str(request_url)) - curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self._curl.setopt(pycurl.FOLLOWLOCATION, 1) + self._curl.setopt(pycurl.URL, str(request_url)) + self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - response_header = StringIO.StringIO() - response_body = StringIO.StringIO() + self._response_header = StringIO.StringIO() + self._response_body = StringIO.StringIO() - curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) - curl.setopt(pycurl.TIMEOUT, self.timeout) + self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) + self._curl.setopt(pycurl.TIMEOUT, self.timeout) - curl.setopt(pycurl.HEADERFUNCTION, response_header.write) - curl.setopt(pycurl.WRITEFUNCTION, response_body.write) + self._curl.setopt(pycurl.HEADERFUNCTION, self._response_header.write) + self._curl.setopt(pycurl.WRITEFUNCTION, self._response_body.write) if self.debug: sys.stderr.write("CURL Request: %s\n" % request_url) - curl.setopt(pycurl.VERBOSE, 1) - curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - - curl.perform() + self._curl.setopt(pycurl.VERBOSE, 1) + self._curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - response_code = curl.getinfo(pycurl.HTTP_CODE) - response_status = response_header.getvalue().splitlines()[0] - response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) - response_data = response_body.getvalue() - - if response_code != 200: - self._response_error = "Error: %s" % response_reason - raise Exception('%s' % response_reason) + if self.parallel: + self.parallel._add_request(self) + return None else: - return response_data + self._curl.perform() + return self._process_http_request() except Exception, e: self._response_error = "Exception: %s" % e @@ -776,3 +784,51 @@ def soapify( self, xml ): else: soap = xml return soap + +class ebayparallel(object): + _requests = None + + def __init__(self): + self._requests = [] + self._errors = [] + + def _add_request(self, request): + self._requests.append(request) + + def wait(self, timeout=20): + "wait for all of the api requests to complete" + + self._errors = [] + try: + if timeout > 0: + multi = pycurl.CurlMulti() + for request in self._requests: + multi.add_handle(request._curl) + while True: + while True: + ret, num = multi.perform() + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + if num == 0: + break + if multi.select(timeout) < 0: + raise pycurl.error(pycurl.E_OPERATION_TIMEOUTED) + for request in self._requests: + multi.remove_handle(request._curl) + request._response_content = request._process_http_request() + if request._response_error: + self._errors.append(request._response_error) + self._errors.extend(request._get_resp_body_errors()) + multi.close() + self._requests = [] + except Exception, e: + self._errors.append("Exception: %s" % e) + raise Exception("%s" % e) + + def error(self): + "builds and returns the api error message" + + if len(self._errors) > 0: + return "parallel error:\n%s\n" % ("\n".join(self._errors)) + + return "" From 55277d235d03f754df82f3d31a32fa11a7c127f7 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 13:34:20 -0800 Subject: [PATCH 042/218] Added docs and fixed final processing --- ebaysdk/__init__.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 808f779..c9e5a77 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -40,6 +40,7 @@ class ebaybase(object): >>> print dict2xml(d) abc """ + def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, parallel=None, **kwargs): self.verb = None self.debug = debug @@ -786,7 +787,24 @@ def soapify( self, xml ): return soap class ebayparallel(object): - _requests = None + """ + >>> p = ebayparallel() + >>> r1 = html(parallel=p,debug=1) + >>> r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> r2 = finding(parallel=p) + >>> r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> r3 = shopping(parallel=p) + >>> r3.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> p.wait() + >>> print p.error() + + >>> print r1.response_obj().rss.channel.ttl + 60 + >>> print r2.response_dict().ack + Success + >>> print r3.response_obj().Ack + Success + """ def __init__(self): self._requests = [] @@ -816,6 +834,8 @@ def wait(self, timeout=20): for request in self._requests: multi.remove_handle(request._curl) request._response_content = request._process_http_request() + if request._response_content: + request.process() if request._response_error: self._errors.append(request._response_error) self._errors.extend(request._get_resp_body_errors()) From 7b7c062cb0df1eb1eec46276138c949d698e780a Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 13:45:22 -0800 Subject: [PATCH 043/218] Changed class name to parallel --- ebaysdk/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index c9e5a77..2848c30 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -786,9 +786,9 @@ def soapify( self, xml ): soap = xml return soap -class ebayparallel(object): +class parallel(object): """ - >>> p = ebayparallel() + >>> p = parallel() >>> r1 = html(parallel=p,debug=1) >>> r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> r2 = finding(parallel=p) From 04a33e4bb375c4e2f4ec0aff44efec420923b827 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 13:53:16 -0800 Subject: [PATCH 044/218] Added additional comments --- ebaysdk/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 2848c30..0b28559 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -161,9 +161,13 @@ def execute(self, verb, data): return self def prepare(self): + "performs any final changes to the request" + pass def process(self): + "performs any final changes to the response" + # remove xml namespace regex = re.compile('xmlns="[^"]+"') self._response_content = regex.sub( '', self._response_content ) @@ -200,7 +204,7 @@ def api_init(self,config_items): self.api_config[config[0]] = config[1] def _execute_http_request(self): - "performs the http post and returns the XML response body" + "performs the http request and returns the XML response body" response_data = '' try: @@ -264,6 +268,8 @@ def _execute_http_request(self): raise Exception("%s" % e) def _process_http_request(self): + "performs the final processing of the http request and returns the response data" + response_code = self._curl.getinfo(pycurl.HTTP_CODE) response_status = self._response_header.getvalue().splitlines()[0] response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) @@ -445,7 +451,7 @@ def execute(self, url, call_data=dict()): return self def _execute_http_request(self): - "performs the http post and returns the XML response body" + "performs the http request and returns the XML response body" try: self._curl = pycurl.Curl() From a797b68c22d60dd316a34e1e91d216fe39123dcb Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 23 Jan 2013 14:09:08 -0800 Subject: [PATCH 045/218] Updated tests --- ebaysdk/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 0b28559..112bfa9 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -795,21 +795,25 @@ def soapify( self, xml ): class parallel(object): """ >>> p = parallel() - >>> r1 = html(parallel=p,debug=1) - >>> r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> r1 = html(parallel=p) + >>> retval = r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> r2 = finding(parallel=p) - >>> r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> r3 = shopping(parallel=p) - >>> r3.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> r4 = trading(parallel=p) + >>> retval = r4.execute('GetCharities', { 'CharityID': 3897 }) >>> p.wait() >>> print p.error() - + >>> print r1.response_obj().rss.channel.ttl 60 >>> print r2.response_dict().ack Success >>> print r3.response_obj().Ack Success + >>> print r4.response_obj().Ack + Success """ def __init__(self): From 8463b0a6a6d328860e2466f8eb909c0bb9161328 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 23 Jan 2013 21:30:03 -0800 Subject: [PATCH 046/218] fix html class bug --- Changes | 3 ++ ebaysdk/__init__.py | 96 +++++++++++++++++++++++++++++---------------- ebaysdk/_version.py | 2 +- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/Changes b/Changes index 6bc8a33..412d328 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +0.1.8 +- fix bug in html class that allows for POST +- refactor and style cleanup - added parallel support using the ebayparallel class - created new prepare, process, and _process_http_request methods diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 112bfa9..e01d5e6 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -32,7 +32,7 @@ def nodeText(node): return ''.join(rc) def tag(name, value): - return "<%s>%s" % ( name, value, name ) + return "<%s>%s" % (name, value, name) class ebaybase(object): """ @@ -49,7 +49,6 @@ def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy self.proxy_host = proxy_host self.proxy_port = proxy_port self.parallel = parallel - self.spooled_calls = []; self._reset() def debug_callback(self, debug_type, debug_message): @@ -88,26 +87,26 @@ def v(self, *args, **kwargs): def load_yaml(self, config_file): # check for absolute path - if os.path.exists( config_file ): + if os.path.exists(config_file): try: - f = open( config_file, "r" ) + f = open(config_file, "r") except IOError, e: print "unable to open file %s" % e - yData = yaml.load( f.read() ) + yData = yaml.load(f.read()) domain = self.api_config.get('domain', '') - self.api_config_append( yData.get(domain, {}) ) + self.api_config_append(yData.get(domain, {})) return # check other directories - dirs = [ '.', os.environ.get('HOME'), '/etc' ] + dirs = ['.', os.environ.get('HOME'), '/etc'] for mydir in dirs: myfile = "%s/%s" % (mydir, config_file) - if os.path.exists( myfile ): + if os.path.exists(myfile): try: - f = open( myfile, "r" ) + f = open(myfile, "r") except IOError, e: print "unable to open file %s" % e @@ -129,6 +128,9 @@ def getNodeText(self, nodelist): return rc def _reset(self): + self._response_reason = None + self._response_status = None + self._response_code = None self._response_content = None self._response_dom = None self._response_obj = None @@ -140,16 +142,22 @@ def _reset(self): def do(self, verb, call_data=dict()): return self.execute(verb, call_data) - def execute(self, verb, data): - self.verb = verb + def _to_xml(self, data): + xml = '' if type(data) == DictType: - self.call_xml = dict2xml(data, roottag='TRASHME') + xml = dict2xml(data, roottag='TRASHME') elif type(data) == ListType: - self.call_xml = list2xml(data, roottag='TRASHME') + xml = list2xml(data, roottag='TRASHME') else: - self.call_xml = data + xml = data + + return xml + + def execute(self, verb, data): + self.verb = verb + self.call_xml = self._to_xml(data) self.prepare() self._reset() @@ -172,6 +180,15 @@ def process(self): regex = re.compile('xmlns="[^"]+"') self._response_content = regex.sub( '', self._response_content ) + def response_status(self): + return self._response_status + + def response_code(self): + return self._response_code + + def response_content(self): + return self._response_content + def response_soup(self): if not self._response_soup: self._response_soup = BeautifulStoneSoup(unicode(self._response_content)) @@ -268,20 +285,24 @@ def _execute_http_request(self): raise Exception("%s" % e) def _process_http_request(self): - "performs the final processing of the http request and returns the response data" - - response_code = self._curl.getinfo(pycurl.HTTP_CODE) - response_status = self._response_header.getvalue().splitlines()[0] - response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', response_status ).group(1) + """ + performs the final processing of the http + request and returns the response data + """ + + self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) + self._response_status = self._response_header.getvalue().splitlines()[0] + self._response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', self._response_status ).group(1) + response_data = self._response_body.getvalue() self._response_header = None self._response_body = None self._curl.close() - if response_code != 200: - self._response_error = "Error: %s" % response_reason - raise Exception('%s' % response_reason) + if self._response_code != 200: + self._response_error = "Error: %s" % self._response_reason + raise Exception('%s' % self._response_reason) else: return response_data @@ -417,10 +438,16 @@ class html(ebaybase): <![CDATA[mytouch slide]]> >>> print h.error() None + >>> h = html(method='POST', debug=False) + >>> retval = h.execute('http://www.ebay.com/') + >>> print h.response_content() != '' + True + >>> print h.response_code() + 200 """ - def __init__(self, **kwargs): - ebaybase.__init__(self, method='GET', **kwargs) + def __init__(self, method='GET', **kwargs): + ebaybase.__init__(self, method=method, **kwargs) def response_dom(self): if not self._response_dom: @@ -465,7 +492,7 @@ def _execute_http_request(self): if self.call_data and self.method == 'GET': request_url = request_url + '?' + urllib.urlencode(self.call_data) - if self.method == 'POST': + elif self.method == 'POST': request_xml = self._build_request_xml() self._curl.setopt(pycurl.POST, True) self._curl.setopt(pycurl.POSTFIELDS, str(request_xml)) @@ -503,6 +530,15 @@ def error(self): "builds and returns the api error message" return self._response_error + def _build_request_xml(self): + + self.call_xml = self._to_xml(self.call_data) + + xml = "" + xml += self.call_xml + + return xml + class trading(ebaybase): """ Trading backend for the ebaysdk @@ -761,13 +797,7 @@ def execute(self, verb, data): self.verb = verb - if type(data) == DictType: - self.call_xml = dict2xml(data, roottag='TRASHME') - elif type(data) == ListType: - self.call_xml = list2xml(data, roottag='TRASHME') - else: - self.call_xml = data - + self.call_xml = self._to_xml(data) self.prepare() self._reset() @@ -778,7 +808,7 @@ def execute(self, verb, data): return self - def soapify( self, xml ): + def soapify(self, xml): xml_type = type( xml ) if xml_type == dict: soap = {} diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index f1380ee..9cb17e7 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" From cbf16c9cc450deea0ccf02f86a8114b6903e1493 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 23 Jan 2013 21:40:02 -0800 Subject: [PATCH 047/218] update README --- README.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.rst b/README.rst index a9f4735..c36d6c2 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,35 @@ Example:: for item in items: print nodeText(item.getElementsByTagName('title')[0]) + +Parallel Example:: + + from ebaysdk import finding, parallel, nodeText + + p = parallel() + + h = html(parallel=p) + h.execute('http://www.ebay.com') + + f1 = finding(parallel=p) + f1.execute('findItemsAdvanced', {'keywords': 'shoes'}) + + f2 = finding(parallel=p) + f2.execute('findItemsAdvanced', {'keywords': 'shirts'}) + + f3 = finding(parallel=p) + f3.execute('findItemsAdvanced', {'keywords': 'pants'}) + + p.wait() + + print h.response_content() + print f1.response_content() + print f2.response_content() + print f3.response_content() + + dom1 = f1.response_dom() + dom2 = f2.response_dom() + .. _eBay Developer Site: http://developer.ebay.com/ From 0fb7428846a0c57a9c6154202ca9e2b12b537e10 Mon Sep 17 00:00:00 2001 From: Jud Date: Mon, 28 Jan 2013 17:11:21 -0500 Subject: [PATCH 048/218] Update ebaysdk/__init__.py Removing a debug statement that was incorrect. --- ebaysdk/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index e01d5e6..1cbc350 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -575,9 +575,6 @@ def __init__(self, ebaybase.__init__(self, method='POST', **kwargs) - if not https and self.debug: - print "HTTPS is required on the Trading API." - self.api_config = { 'domain' : domain, 'uri' : uri, @@ -596,7 +593,7 @@ def __init__(self, } self.load_yaml(config_file) - + # allow yaml to specify compatibility self.api_config['version']=version or self.api_config.get('compatability') or self.api_config.get('version') From b0389d107f8c61f0b7e6f1994066dc12986cb7d3 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 1 Feb 2013 10:49:28 -0800 Subject: [PATCH 049/218] bug fix SOA class --- Changes | 2 ++ ebaysdk/__init__.py | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 412d328..35ecb01 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Changes for ebaysdk +- bug fix for SOA class + 0.1.8 - fix bug in html class that allows for POST - refactor and style cleanup diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 1cbc350..cc92965 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -789,14 +789,12 @@ def _build_request_xml(self): return xml def execute(self, verb, data): - if type(data) == DictType: - data = dict2xml( self.soapify(data) ) - + self.verb = verb - self.call_xml = self._to_xml(data) + self.call_xml = self._to_xml(self.soapify(data)) self.prepare() - + self._reset() self._response_content = self._execute_http_request() From 79f7f8dfbf24bb15cb920b2f49ce8980e1ec6d05 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 17:30:09 -0800 Subject: [PATCH 050/218] add samples, and fix yaml defaults --- ebay.yaml | 43 ++-- ebaysdk/__init__.py | 453 ++++++++++++++++++++++++++---------------- samples/finding.py | 42 ++++ samples/parallel.py | 63 ++++++ samples/shopping.py | 43 ++++ samples/trading.py | 51 +++++ tests/test_ebaysdk.py | 3 - 7 files changed, 506 insertions(+), 192 deletions(-) create mode 100644 samples/finding.py create mode 100644 samples/parallel.py create mode 100644 samples/shopping.py create mode 100644 samples/trading.py delete mode 100644 tests/test_ebaysdk.py diff --git a/ebay.yaml b/ebay.yaml index b66923a..3533004 100644 --- a/ebay.yaml +++ b/ebay.yaml @@ -1,26 +1,29 @@ -# ebaysdk API Configurations +# eBay SDK Defaults name: ebay_api_config - -# Trading - External -api.ebay.com: - password: _password_ - username: _username_ - appid: _appid_ - certid: _certid_ - devid: _devid_ - token: - version: 671 - https: 1 -# Shopping -open.api.ebay.com: - appid: _appid_ - certid: _certid_ - devid: _devid_ - version: 671 +# Trading API Sandbox - https://www.x.com/developers/ebay/products/trading-api +api.sandbox.ebay.com: + compatability: 719 + appid: ENTER_YOUR_APPID_HERE + certid: ENTER_YOUR_CERTID_HERE + devid: ENTER_YOUR_DEVID_HERE + token: ENTER_YOUR_TOKEN_HERE -# Finding/Merchandising +# Trading API - https://www.x.com/developers/ebay/products/trading-api +api.ebay.com: + compatability: 719 + appid: ENTER_YOUR_APPID_HERE + certid: ENTER_YOUR_CERTID_HERE + devid: ENTER_YOUR_DEVID_HERE + token: ENTER_YOUR_TOKEN_HERE + +# Finding API - https://www.x.com/developers/ebay/products/finding-api svcs.ebay.com: - appid: _appid_ + appid: ENTER_YOUR_APPID_HERE version: 1.0.0 + +# Shopping API - https://www.x.com/developers/ebay/products/shopping-api +open.api.ebay.com: + appid: ENTER_YOUR_APPID_HERE + version: 671 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index cc92965..df35ad1 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -3,14 +3,15 @@ import yaml, pycurl, urllib from types import DictType, ListType +import simplejson as json from xml.dom.minidom import parseString, Node from BeautifulSoup import BeautifulStoneSoup -from ebaysdk.utils import xml2dict, dict2xml, list2xml, make_struct, object_dict -import ebaysdk.utils2 +from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict def get_version(): - # Get the version + "Get the version." + VERSIONFILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "_version.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", open(VERSIONFILE, "rt").read(), re.M).group(1) @@ -20,6 +21,8 @@ def get_version(): __version__ = get_version() def nodeText(node): + "Returns the node's text string." + rc = [] if hasattr(node, 'childNodes'): @@ -35,13 +38,18 @@ def tag(name, value): return "<%s>%s" % (name, value, name) class ebaybase(object): - """ + """Base API class. + + Doctests: >>> d = { 'list': ['a', 'b', 'c']} >>> print dict2xml(d) abc """ - def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, parallel=None, **kwargs): + def __init__(self, debug=False, method='GET', + proxy_host=None, timeout=20, proxy_port=80, + parallel=None, **kwargs): + self.verb = None self.debug = debug self.method = method @@ -83,21 +91,19 @@ def v(self, *args, **kwargs): return h.get('value', None) except: return h - - def load_yaml(self, config_file): + def yaml_defaults(self, config_file, domain): + "Returns a dictionary of YAML defaults." + # check for absolute path - if os.path.exists(config_file): + if os.path.exists(config_file): try: f = open(config_file, "r") except IOError, e: print "unable to open file %s" % e yData = yaml.load(f.read()) - domain = self.api_config.get('domain', '') - - self.api_config_append(yData.get(domain, {})) - return + return yData.get(domain, {}) # check other directories dirs = ['.', os.environ.get('HOME'), '/etc'] @@ -110,15 +116,24 @@ def load_yaml(self, config_file): except IOError, e: print "unable to open file %s" % e - yData = yaml.load( f.read() ) + yData = yaml.load(f.read()) domain = self.api_config.get('domain', '') - self.api_config_append( yData.get(domain, {}) ) - return + return yData.get(domain, {}) - def api_config_append(self, config): - for c in config: - self.api_config[c] = config[c] + def set_config(self, cKey, defaultValue): + + # pull for kwargs first + if self._kwargs.has_key(cKey) and self._kwargs[cKey] != None: + self.api_config.update({cKey: self._kwargs[cKey]}) + + # otherwise, use yaml default and then fall back to + # the default set in the __init__() + else: + if not self.api_config.has_key(cKey): + self.api_config.update({cKey: defaultValue}) + else: + pass def getNodeText(self, nodelist): rc = "" @@ -143,6 +158,8 @@ def do(self, verb, call_data=dict()): return self.execute(verb, call_data) def _to_xml(self, data): + "Converts a list of dictionary to XML and returns it." + xml = '' if type(data) == DictType: @@ -155,6 +172,8 @@ def _to_xml(self, data): return xml def execute(self, verb, data): + "Executes the HTTP request." + self.verb = verb self.call_xml = self._to_xml(data) @@ -169,27 +188,32 @@ def execute(self, verb, data): return self def prepare(self): - "performs any final changes to the request" - + "Performs any final changes to the request." pass def process(self): - "performs any final changes to the response" + "Performs any final changes to the response." # remove xml namespace regex = re.compile('xmlns="[^"]+"') - self._response_content = regex.sub( '', self._response_content ) + self._response_content = regex.sub('', self._response_content) def response_status(self): + "Retuns the HTTP response status string." + return self._response_status def response_code(self): + "Returns the HTTP response status code." + return self._response_code def response_content(self): return self._response_content def response_soup(self): + "Returns a BeautifulSoup object of the response." + if not self._response_soup: self._response_soup = BeautifulStoneSoup(unicode(self._response_content)) @@ -198,11 +222,9 @@ def response_soup(self): def response_obj(self): return self.response_dict() - #if not self._response_obj: - # self._response_obj = make_struct(self.response_dict()) - #return self._response_obj - def response_dom(self): + "Returns the response DOM (xml.dom.minidom)." + if not self._response_dom: dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb))) ) self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] @@ -210,18 +232,21 @@ def response_dom(self): return self._response_dom def response_dict(self): - if not self._response_dict: + "Returns the response dictionary." + + if not self._response_dict and self._response_content: mydict = xml2dict().fromstring(self._response_content) self._response_dict = mydict.get(self.verb+'Response', mydict) return self._response_dict - def api_init(self,config_items): - for config in config_items: - self.api_config[config[0]] = config[1] + def response_json(self): + "Returns the response JSON." + + return json.dumps(self.response_dict()) def _execute_http_request(self): - "performs the http request and returns the XML response body" + "Performs the http request and returns the XML response body." response_data = '' try: @@ -285,9 +310,8 @@ def _execute_http_request(self): raise Exception("%s" % e) def _process_http_request(self): - """ - performs the final processing of the http - request and returns the response data + """Final processing for the HTTP response. + Returns the response data. """ self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) @@ -301,16 +325,20 @@ def _process_http_request(self): self._curl.close() if self._response_code != 200: - self._response_error = "Error: %s" % self._response_reason - raise Exception('%s' % self._response_reason) + self._response_error = "%s" % self._response_reason + #raise Exception('%s' % self._response_reason) else: return response_data - # Child classes should override this method based on what the errors in the - # XML response body look like. They can choose to look at the 'ack', - # 'Errors', 'errorMessage' or whatever other fields the service returns. - # the implementation below is the original code that was part of error() def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + if self._resp_body_errors and len(self._resp_body_errors) > 0: return self._resp_body_errors @@ -322,86 +350,102 @@ def _get_resp_body_errors(self): for e in dom.getElementsByTagName("Errors"): if e.getElementsByTagName('ErrorClassification'): - err.append('- Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) + err.append('Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) if e.getElementsByTagName('SeverityCode'): - err.append('- Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) + err.append('Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) if e.getElementsByTagName('ErrorCode'): - err.append('- Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) + err.append('Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) if e.getElementsByTagName('ShortMessage'): - err.append('- %s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) + err.append('%s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) if e.getElementsByTagName('LongMessage'): - err.append('- %s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) + err.append('%s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) self._resp_body_errors = err return err def error(self): - "builds and returns the api error message" + "Builds and returns the api error message." err = [] if self._response_error: err.append(self._response_error) err.extend(self._get_resp_body_errors()) - if len(err) > 0: return "%s error:\n%s\n" % (self.verb, "\n".join(err)) + if len(err) > 0: return "%s: %s" % (self.verb, ", ".join(err)) return "" class shopping(ebaybase): - """ - Shopping backend for ebaysdk. + """Shopping API class + + API documentation: http://developer.ebay.com/products/shopping/ - shopping(debug=False, domain='open.api.ebay.com', uri='/shopping', method='POST', https=False, siteid=0, response_encoding='XML', request_encoding='XML', config_file='ebay.yaml') + Supported calls: + getSingleItem + getMultipleItems + (all others, see API docs) + Doctests: >>> s = shopping() - >>> retval = s.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) >>> print s.response_obj().Ack Success >>> print s.error() """ - def __init__(self, - domain='open.api.ebay.com', - uri='/shopping', - https=False, - siteid=0, - response_encoding='XML', - request_encoding='XML', - proxy_host=None, - proxy_port=None, - appid=None, - certid=None, - devid=None, - version='799', - config_file='ebay.yaml', - **kwargs): - + def __init__(self, **kwargs): + """Shopping class constructor. + + Keyword arguments: + domain -- API endpoint (default: open.api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /shopping) + appid -- eBay application id + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 799) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ ebaybase.__init__(self, method='POST', **kwargs) - if https and self.debug: - print "HTTPS is not supported on the Shopping API." - + self._kwargs = kwargs + self.api_config = { - 'domain' : domain, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, - 'response_encoding': response_encoding, - 'request_encoding' : request_encoding, - 'proxy_host': proxy_host, - 'proxy_port': proxy_port, - 'appid' : appid, - 'certid' : certid, - 'devid' : devid, - 'version' : version + 'domain': kwargs.get('domain', 'open.api.ebay.com'), + 'config_file': kwargs.get('config_file', 'ebay.yaml'), } - self.load_yaml(config_file) + # pull stuff in value yaml defaults + self.api_config.update( + self.yaml_defaults(self.api_config['config_file'], self.api_config['domain']) + ) + + # override yaml defaults with args sent to the constructor + self.set_config('uri', '/shopping') + self.set_config('https', False) + self.set_config('siteid', 0) + self.set_config('response_encoding', 'XML') + self.set_config('request_encoding', 'XML') + self.set_config('proxy_host', None) + self.set_config('proxy_port', None) + self.set_config('appid', None) + self.set_config('version', '799') + + + if self.api_config['https'] and self.debug: + print "HTTPS is not supported on the Shopping API." def _build_request_headers(self): return { @@ -422,11 +466,9 @@ def _build_request_xml(self): return xml class html(ebaybase): - """ - HTML backend for ebaysdk. - - (self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80) + """HTML class for traditional calls. + Doctests: >>> h = html() >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> print h.response_obj().rss.channel.ttl @@ -447,22 +489,36 @@ class html(ebaybase): """ def __init__(self, method='GET', **kwargs): + """HTML class constructor. + + Keyword arguments: + debug -- debugging enabled (default: False) + method -- GET/POST/PUT (default: GET) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + """ ebaybase.__init__(self, method=method, **kwargs) def response_dom(self): + "Returns the HTTP response dom." + if not self._response_dom: self._response_dom = parseString(self._response_content) return self._response_dom def response_dict(self): - if not self._response_dict: + "Return the HTTP response dictionary." + + if not self._response_dict and self.response_content: self._response_dict = xml2dict().fromstring(self._response_content) return self._response_dict def execute(self, url, call_data=dict()): - "execute(self, url, call_data=dict())" + "Excute method for the HTTP request." self.url = url self.call_data = call_data @@ -478,7 +534,7 @@ def execute(self, url, call_data=dict()): return self def _execute_http_request(self): - "performs the http request and returns the XML response body" + "Executes and returns the XML response body." try: self._curl = pycurl.Curl() @@ -527,11 +583,11 @@ def _execute_http_request(self): raise Exception("%s" % e) def error(self): - "builds and returns the api error message" + "Builds and returns the api error message." return self._response_error def _build_request_xml(self): - + "Builds and returns the request XML." self.call_xml = self._to_xml(self.call_data) xml = "" @@ -540,10 +596,18 @@ def _build_request_xml(self): return xml class trading(ebaybase): - """ - Trading backend for the ebaysdk - http://developer.ebay.com/products/trading/ + """Trading API class + + API documentation: + https://www.x.com/developers/ebay/products/trading-api + + Supported calls: + AddItem + ReviseItem + GetUser + (all others, see API docs) + Doctests: >>> t = trading() >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) >>> charity_name = '' @@ -555,49 +619,62 @@ class trading(ebaybase): """ - def __init__(self, - domain='api.ebay.com', - uri='/ws/api.dll', - https=True, - siteid=0, - response_encoding='XML', - request_encoding='XML', - proxy_host=None, - proxy_port=None, - token=None, - iaf_token=None, - appid=None, - certid=None, - devid=None, - version='648', - config_file='ebay.yaml', - **kwargs): - + def __init__(self, **kwargs): + """Trading class constructor. + + Keyword arguments: + domain -- API endpoint (default: api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /ws/api.dll) + appid -- eBay application id + devid -- eBay developer id + certid -- eBay cert id + token -- eBay application/user token + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 648) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ ebaybase.__init__(self, method='POST', **kwargs) + self._kwargs = kwargs + self.api_config = { - 'domain' : domain, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, - 'response_encoding' : response_encoding, - 'request_encoding' : request_encoding, - 'proxy_host': proxy_host, - 'proxy_port': proxy_port, - 'token' : token, - 'iaf_token' : iaf_token, - 'appid' : appid, - 'devid' : devid, - 'certid' : certid, - 'version' : version, + 'domain': kwargs.get('domain', 'api.ebay.com'), + 'config_file': kwargs.get('config_file', 'ebay.yaml'), } - self.load_yaml(config_file) - - # allow yaml to specify compatibility - self.api_config['version']=version or self.api_config.get('compatability') or self.api_config.get('version') + # pull stuff in value yaml defaults + self.api_config.update( + self.yaml_defaults(self.api_config['config_file'], self.api_config['domain']) + ) + + # override yaml defaults with args sent to the constructor + self.set_config('uri', '/ws/api.dll') + self.set_config('https', True) + self.set_config('siteid', 0) + self.set_config('response_encoding', 'XML') + self.set_config('request_encoding', 'XML') + self.set_config('proxy_host', None) + self.set_config('proxy_port', None) + self.set_config('token', None) + self.set_config('iaf_token', None) + self.set_config('appid', None) + self.set_config('devid', None) + self.set_config('certid', None) + self.set_config('version', '648') + self.set_config('compatibility', '648') def _build_request_headers(self): + "Builds HTTP headers" + headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('version', ''), "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), @@ -609,9 +686,12 @@ def _build_request_headers(self): } if self.api_config.get('iaf_token', None): headers["X-EBAY-API-IAF-TOKEN"] = self.api_config.get('iaf_token') + return headers def _build_request_xml(self): + "Builds the XML request" + xml = "" xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" if not self.api_config.get('iaf_token', None): @@ -628,10 +708,17 @@ def _build_request_xml(self): return xml class finding(ebaybase): - """ - Finding backend for ebaysdk. - http://developer.ebay.com/products/finding/ + """Finding API class + API documentation: + https://www.x.com/developers/ebay/products/finding-api + + Supported calls: + findItemsAdvanced + findItemsByCategory + (all others, see API docs) + + Doctests: >>> f = finding() >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() @@ -649,42 +736,55 @@ class finding(ebaybase): """ - def __init__(self, - domain='svcs.ebay.com', - service='FindingService', - uri='/services/search/FindingService/v1', - https=False, - siteid='EBAY-US', - response_encoding='XML', - request_encoding='XML', - proxy_host=None, - proxy_port=None, - appid=None, - certid=None, - devid=None, - version='1.0.0', - config_file='ebay.yaml', - **kwargs): - + def __init__(self, **kwargs): + """Finding class constructor. + + Keyword arguments: + domain -- API endpoint (default: svcs.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /services/search/FindingService/v1) + appid -- eBay application id + siteid -- eBay country site id (default: EBAY-US) + compatibility -- version number (default: 1.0.0) + https -- execute of https (default: False) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ ebaybase.__init__(self, method='POST', **kwargs) + self._kwargs = kwargs + self.api_config = { - 'domain' : domain, - 'service' : service, - 'uri' : uri, - 'https' : https, - 'siteid' : siteid, - 'response_encoding' : response_encoding, - 'request_encoding' : request_encoding, - 'proxy_host': proxy_host, - 'proxy_port': proxy_port, - 'appid' : appid, - 'certid' : certid, - 'devid' : devid, - 'version' : version + 'domain': kwargs.get('domain', 'svcs.ebay.com'), + 'config_file': kwargs.get('config_file', 'ebay.yaml'), } - self.load_yaml(config_file) + # pull stuff in value yaml defaults + self.api_config.update( + self.yaml_defaults(self.api_config['config_file'], + self.api_config['domain']) + ) + + # override yaml defaults with args sent to the constructor + self.set_config('uri', '/services/search/FindingService/v1') + self.set_config('https', False) + self.set_config('siteid', 'EBAY-US') + self.set_config('response_encoding', 'XML') + self.set_config('request_encoding', 'XML') + self.set_config('proxy_host', None) + self.set_config('proxy_port', None) + self.set_config('token', None) + self.set_config('iaf_token', None) + self.set_config('appid', None) + self.set_config('version', '1.0.0') + self.set_config('compatibility', '1.0.0') + def _build_request_headers(self): return { @@ -706,11 +806,26 @@ def _build_request_xml(self): return xml + def error(self): + "Builds and returns the api error message." -class SOAService( ebaybase ): - """ - This class provides a base for eBay's SOAP-style web services. - """ + err = [] + if self._response_error: + err.append(self._response_error) + + try: + if self.response_dict().ack == 'Failure': + err.append(self.response_dict().errorMessage.error.message) + except AttributeError: + pass + + if len(err) > 0: + return "%s: %s" % (self.verb, ", ".join(err)) + + return "" + +class SOAService(ebaybase): + "SOAP class." def __init__(self, app_config=None, site_id='EBAY-US', debug=False): self.api_config = { diff --git a/samples/finding.py b/samples/finding.py new file mode 100644 index 0000000..ef7324e --- /dev/null +++ b/samples/finding.py @@ -0,0 +1,42 @@ +import os, sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import finding + +usage = "usage: %prog [options]" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") +parser.add_option("-y", "--yaml",# + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") +parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + +(opts, args) = parser.parse_args() + +api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) +api.execute('findItemsAdvanced', {'keywords': 'python'}) + +print "Finding samples for SDK version %s" % ebaysdk.get_version() + +if api.error(): + print "Call Failed: (%s)" % api.error() +else: + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] + + + diff --git a/samples/parallel.py b/samples/parallel.py new file mode 100644 index 0000000..e743f0d --- /dev/null +++ b/samples/parallel.py @@ -0,0 +1,63 @@ +import os, sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import finding, html, parallel + +usage = "usage: %prog [options]" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") +parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") +parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + +(opts, args) = parser.parse_args() + +parallel = parallel() + +apis = [] + +api1 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) +api1.execute('findItemsAdvanced', {'keywords': 'python'}) +apis.append(api1) + +api2 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) +api2.execute('findItemsAdvanced', {'keywords': 'perl'}) +apis.append(api2) + +api3 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) +api3.execute('findItemsAdvanced', {'keywords': 'php'}) +apis.append(api3) + +api4 = html() +api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') +apis.append(api4) + +parallel.wait() + +print "Parallel example for SDK version %s" % ebaysdk.get_version() + +if parallel.error(): + raise Exception(parallel.error()) + +for api in apis: + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s...\n" % dictstr[:50] + + + diff --git a/samples/shopping.py b/samples/shopping.py new file mode 100644 index 0000000..14d9003 --- /dev/null +++ b/samples/shopping.py @@ -0,0 +1,43 @@ +import os, sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import shopping + +usage = "usage: %prog [options]" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") +parser.add_option("-y", "--yaml",# + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") +parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + +(opts, args) = parser.parse_args() + +api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) +api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + +print "Shopping samples for SDK version %s" % ebaysdk.get_version() + +if api.error(): + raise Exception(api.error()) + +if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + +print "Response code: %s" % api.response_code() +print "Response DOM: %s" % api.response_dom() + +dictstr = "%s" % api.response_dict() +print "Response dictionary: %s..." % dictstr[:50] + +print "Matching Titles:" +for item in api.response_dict().ItemArray.Item: + print item.Title diff --git a/samples/trading.py b/samples/trading.py new file mode 100644 index 0000000..3551436 --- /dev/null +++ b/samples/trading.py @@ -0,0 +1,51 @@ +import os, sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import trading + +usage = "usage: %prog [options]" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") +parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") +parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") +parser.add_option("-p", "--devid", + dest="devid", default=None, + help="Specifies the eBay developer id to use.") +parser.add_option("-c", "--certid", + dest="certid", default=None, + help="Specifies the eBay cert id to use.") + +(opts, args) = parser.parse_args() + +print "Trading samples for SDK version %s" % ebaysdk.get_version() + +api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid) + +api.execute('GetCharities', {'CharityID': 3897}) + +# checkfor errors +if api.error(): + raise Exception(api.error()) + + +if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + +print "Response code: %s" % api.response_code() +print "Response DOM: %s" % api.response_dom() + +dictstr = "%s" % api.response_dict() +print "Response dictionary: %s..." % dictstr[:150] + +print api.response_dict().Charity.Name diff --git a/tests/test_ebaysdk.py b/tests/test_ebaysdk.py deleted file mode 100644 index b28b04f..0000000 --- a/tests/test_ebaysdk.py +++ /dev/null @@ -1,3 +0,0 @@ - - - From 8e19c741f3b8bb3618dd1501c93a9b1b093f6d68 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 17:41:10 -0800 Subject: [PATCH 051/218] update README --- README.rst | 60 ++++++++++++++++-------------------------------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/README.rst b/README.rst index c36d6c2..0226c94 100644 --- a/README.rst +++ b/README.rst @@ -1,59 +1,35 @@ -Welcome to the ebaysdk for Python -================================= +Welcome to the python ebaysdk +============================= -Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. +This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standerizing calls, response processing, error handling, debugging across the Finding, Shopping, Merchandising, & Trading APIs. In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Developer Site`_ (see the ebay.yaml for a way to easily tie eBay credentials to the SDK) Finding Services. -Example:: +Support ...TBD - from ebaysdk import finding, nodeText - f = finding() - f.execute('findItemsAdvanced', {'keywords': 'shoes'}) +Quick Example:: - dom = f.response_dom() - mydict = f.response_dict() - myobj = f.response_obj() + from ebaysdk import finding - print myobj.itemSearchURL + api = finding(--appid='YOUR_APPID_HERE') + api.execute('findItemsAdvanced', {'keywords': 'shoes'}) - # process the response via DOM - items = dom.getElementsByTagName('item') + print api.response_dict() - for item in items: - print nodeText(item.getElementsByTagName('title')[0]) +Getting Started +--------------- +`Trading Docs`_ +`Finding Docs`_ -Parallel Example:: - - from ebaysdk import finding, parallel, nodeText - - p = parallel() - - h = html(parallel=p) - h.execute('http://www.ebay.com') - - f1 = finding(parallel=p) - f1.execute('findItemsAdvanced', {'keywords': 'shoes'}) - - f2 = finding(parallel=p) - f2.execute('findItemsAdvanced', {'keywords': 'shirts'}) - - f3 = finding(parallel=p) - f3.execute('findItemsAdvanced', {'keywords': 'pants'}) - - p.wait() - - print h.response_content() - print f1.response_content() - print f2.response_content() - print f3.response_content() - - dom1 = f1.response_dom() - dom2 = f2.response_dom() .. _eBay Developer Site: http://developer.ebay.com/ +.. _Trading Docs: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class +.. _Finding Docs: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class +.. _Shopping Docs: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class +.. _HTML Docs: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class +.. _Parallel Docs: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class From 99523cb43320b23d6ac847b86fa45da1ff68888d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 17:43:49 -0800 Subject: [PATCH 052/218] update README --- README.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 0226c94..108877f 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ In order to use eBay aspects of this utility you must first register with eBay t Support ...TBD -Quick Example:: +##Quick Example from ebaysdk import finding @@ -17,11 +17,10 @@ Quick Example:: print api.response_dict() -Getting Started ---------------- +##Getting Started -`Trading Docs`_ -`Finding Docs`_ +* `Trading Docs`_(asdf) +* `Finding Docs`_ .. _eBay Developer Site: http://developer.ebay.com/ From d52ad84c26d4768a97c2625f35af41ff45e8a7fa Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 17:47:48 -0800 Subject: [PATCH 053/218] update README --- README.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 108877f..de33517 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ In order to use eBay aspects of this utility you must first register with eBay t Support ...TBD -##Quick Example +Quick Example:: from ebaysdk import finding @@ -17,18 +17,22 @@ Support ...TBD print api.response_dict() -##Getting Started - -* `Trading Docs`_(asdf) -* `Finding Docs`_ +## Getting Started +* `Trading API Class`_ +* `Finding API Class`_ +* `Shopping API Class`_ +* `HTML Class`_ +* `Parallel API Class`_ +* `YAML Configuration`_ .. _eBay Developer Site: http://developer.ebay.com/ -.. _Trading Docs: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class -.. _Finding Docs: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class -.. _Shopping Docs: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class -.. _HTML Docs: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class -.. _Parallel Docs: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class +.. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration +.. _Trading API Class: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class +.. _Finding API Class: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class +.. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class +.. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class +.. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class From 35ade3760240f2ab73870b741ff19f6a81d2bc46 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 21:52:50 -0800 Subject: [PATCH 054/218] Update README.rst --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index de33517..152e878 100644 --- a/README.rst +++ b/README.rst @@ -12,18 +12,18 @@ Quick Example:: from ebaysdk import finding - api = finding(--appid='YOUR_APPID_HERE') + api = finding(appid='YOUR_APPID_HERE') api.execute('findItemsAdvanced', {'keywords': 'shoes'}) print api.response_dict() -## Getting Started +##Getting Started * `Trading API Class`_ * `Finding API Class`_ * `Shopping API Class`_ * `HTML Class`_ -* `Parallel API Class`_ +* `Parallel Class`_ * `YAML Configuration`_ .. _eBay Developer Site: http://developer.ebay.com/ From 8b85e55e11ca4e0ad7c722f8118e682b89e35e00 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 22:21:17 -0800 Subject: [PATCH 055/218] Update README.rst --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 152e878..9849ed2 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,8 @@ Quick Example:: print api.response_dict() -##Getting Started +Getting Started +--------------- * `Trading API Class`_ * `Finding API Class`_ @@ -25,7 +26,9 @@ Quick Example:: * `HTML Class`_ * `Parallel Class`_ * `YAML Configuration`_ +* `Understanding eBay Credentials`_ +.. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ .. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration .. _Trading API Class: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class From eedcb54cc08a31dc41e9b09f29671e97bfe4b477 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 22:35:48 -0800 Subject: [PATCH 056/218] update Changes, and README --- Changes | 7 +++++-- README.rst | 3 --- setup.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index 35ecb01..9acc175 100644 --- a/Changes +++ b/Changes @@ -1,11 +1,15 @@ Changes for ebaysdk - bug fix for SOA class +- add documentation and sample scripts +- clean up YAML file +- YAML values are now overridden by values defined + when building the object 0.1.8 - fix bug in html class that allows for POST - refactor and style cleanup -- added parallel support using the ebayparallel class +- added parallel support using the parallel class - created new prepare, process, and _process_http_request methods 0.1.7 @@ -13,7 +17,6 @@ Changes for ebaysdk - modify response_obj() to return response_dict() 0.0.6 - - support older version of Element tree - modify dict2xml to handle nodes with array content diff --git a/README.rst b/README.rst index 9849ed2..d2f676f 100644 --- a/README.rst +++ b/README.rst @@ -3,11 +3,8 @@ Welcome to the python ebaysdk This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standerizing calls, response processing, error handling, debugging across the Finding, Shopping, Merchandising, & Trading APIs. -In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Developer Site`_ (see the ebay.yaml for a way to easily tie eBay credentials to the SDK) Finding Services. - Support ...TBD - Quick Example:: from ebaysdk import finding diff --git a/setup.py b/setup.py index 5326ca1..cb6c5d6 100644 --- a/setup.py +++ b/setup.py @@ -25,10 +25,10 @@ open(VERSIONFILE, "rt").read(), re.M).group(1) -long_desc = """This SDK cuts development time and simplifies tasks like -error handling and enables you to make Finding, Shopping, Merchandising, -and Trading API calls. In Addition, the SDK comes with RSS and -HTML back-end libraries.""" +long_desc = """his SDK is a dead-simple, programatic inteface into the eBay +APIs. It simplifies development and cuts development time by standerizing +calls, response processing, error handling, debugging across the Finding, +Shopping, Merchandising, & Trading APIs. """ setup( name=PKG, From 908ba3dc081ee12dd3e1788a66241822b30b11c0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 13 Feb 2013 22:50:51 -0800 Subject: [PATCH 057/218] refactor samples --- samples/finding.py | 43 +++++++++++---------- samples/parallel.py | 81 ++++++++++++++++++++------------------- samples/shopping.py | 59 ++++++++++++++++------------- samples/trading.py | 92 ++++++++++++++++++++++++--------------------- 4 files changed, 149 insertions(+), 126 deletions(-) diff --git a/samples/finding.py b/samples/finding.py index ef7324e..a614174 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -6,29 +6,32 @@ import ebaysdk from ebaysdk import finding -usage = "usage: %prog [options]" -parser = OptionParser(usage=usage) +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) -parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") -parser.add_option("-y", "--yaml",# - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") -parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml",# + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") -(opts, args) = parser.parse_args() + (opts, args) = parser.parse_args() + return opts, args -api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) -api.execute('findItemsAdvanced', {'keywords': 'python'}) +def run(opts): + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('findItemsAdvanced', {'keywords': 'python'}) -print "Finding samples for SDK version %s" % ebaysdk.get_version() + print "Finding samples for SDK version %s" % ebaysdk.get_version() + + if api.error(): + raise Exception(api.error()) -if api.error(): - print "Call Failed: (%s)" % api.error() -else: if api.response_content(): print "Call Success: %s in length" % len(api.response_content()) @@ -38,5 +41,7 @@ dictstr = "%s" % api.response_dict() print "Response dictionary: %s..." % dictstr[:50] - +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) diff --git a/samples/parallel.py b/samples/parallel.py index e743f0d..93d298d 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -6,58 +6,63 @@ import ebaysdk from ebaysdk import finding, html, parallel -usage = "usage: %prog [options]" -parser = OptionParser(usage=usage) +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) -parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") -parser.add_option("-y", "--yaml", - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") -parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") -(opts, args) = parser.parse_args() + (opts, args) = parser.parse_args() + return opts, args -parallel = parallel() +def run(opts): -apis = [] + p = parallel() + apis = [] -api1 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) -api1.execute('findItemsAdvanced', {'keywords': 'python'}) -apis.append(api1) + api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api1.execute('findItemsAdvanced', {'keywords': 'python'}) + apis.append(api1) -api2 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) -api2.execute('findItemsAdvanced', {'keywords': 'perl'}) -apis.append(api2) + api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api2.execute('findItemsAdvanced', {'keywords': 'perl'}) + apis.append(api2) -api3 = finding(parallel=parallel, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) -api3.execute('findItemsAdvanced', {'keywords': 'php'}) -apis.append(api3) + api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api3.execute('findItemsAdvanced', {'keywords': 'php'}) + apis.append(api3) -api4 = html() -api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') -apis.append(api4) + api4 = html(parallel=p) + api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') + apis.append(api4) -parallel.wait() + p.wait() -print "Parallel example for SDK version %s" % ebaysdk.get_version() + print "Parallel example for SDK version %s" % ebaysdk.get_version() -if parallel.error(): - raise Exception(parallel.error()) + if p.error(): + raise Exception(p.error()) -for api in apis: + for api in apis: - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() - - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s...\n" % dictstr[:50] + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s...\n" % dictstr[:50] +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) diff --git a/samples/shopping.py b/samples/shopping.py index 14d9003..57f4117 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -6,38 +6,45 @@ import ebaysdk from ebaysdk import shopping -usage = "usage: %prog [options]" -parser = OptionParser(usage=usage) +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) -parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") -parser.add_option("-y", "--yaml",# - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") -parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml",# + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") -(opts, args) = parser.parse_args() + (opts, args) = parser.parse_args() + return opts, args -api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) -api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) +def run(opts): + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) -print "Shopping samples for SDK version %s" % ebaysdk.get_version() + print "Shopping samples for SDK version %s" % ebaysdk.get_version() -if api.error(): - raise Exception(api.error()) + if api.error(): + raise Exception(api.error()) -if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) -print "Response code: %s" % api.response_code() -print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() -dictstr = "%s" % api.response_dict() -print "Response dictionary: %s..." % dictstr[:50] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] -print "Matching Titles:" -for item in api.response_dict().ItemArray.Item: - print item.Title + print "Matching Titles:" + for item in api.response_dict().ItemArray.Item: + print item.Title + +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) \ No newline at end of file diff --git a/samples/trading.py b/samples/trading.py index 3551436..521f9dc 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -6,46 +6,52 @@ import ebaysdk from ebaysdk import trading -usage = "usage: %prog [options]" -parser = OptionParser(usage=usage) - -parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") -parser.add_option("-y", "--yaml", - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") -parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") -parser.add_option("-p", "--devid", - dest="devid", default=None, - help="Specifies the eBay developer id to use.") -parser.add_option("-c", "--certid", - dest="certid", default=None, - help="Specifies the eBay cert id to use.") - -(opts, args) = parser.parse_args() - -print "Trading samples for SDK version %s" % ebaysdk.get_version() - -api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid) - -api.execute('GetCharities', {'CharityID': 3897}) - -# checkfor errors -if api.error(): - raise Exception(api.error()) - - -if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) - -print "Response code: %s" % api.response_code() -print "Response DOM: %s" % api.response_dom() - -dictstr = "%s" % api.response_dict() -print "Response dictionary: %s..." % dictstr[:150] - -print api.response_dict().Charity.Name +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + parser.add_option("-p", "--devid", + dest="devid", default=None, + help="Specifies the eBay developer id to use.") + parser.add_option("-c", "--certid", + dest="certid", default=None, + help="Specifies the eBay cert id to use.") + + (opts, args) = parser.parse_args() + return opts, args + +def run(opts): + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid) + + api.execute('GetCharities', {'CharityID': 3897}) + + # checkfor errors + if api.error(): + raise Exception(api.error()) + + print "Trading samples for SDK version %s" % ebaysdk.get_version() + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] + + print api.response_dict().Charity.Name + +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) From 820abbf07798f9144d0e314d4bc955d162b457eb Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 14 Feb 2013 12:17:21 -0800 Subject: [PATCH 058/218] update doctests --- INSTALL | 4 +++- ebaysdk/__init__.py | 52 ++++++++++++++++++++++++++++++++++++--------- samples/trading.py | 1 + tests/__init__.py | 9 ++++---- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/INSTALL b/INSTALL index 0e05ce6..45794be 100644 --- a/INSTALL +++ b/INSTALL @@ -17,4 +17,6 @@ Dependancy: pycurl cd pycurl-7.16.4 sudo env ARCHFLAGS="-arch i386" python setup.py install --curl-config=/sw/bin/curl-config - \ No newline at end of file + +Testing the SDK: +~/> export EBAY_YAML='myebay.yaml'; python setup.py test \ No newline at end of file diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index df35ad1..b38adc0 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -226,9 +226,13 @@ def response_dom(self): "Returns the response DOM (xml.dom.minidom)." if not self._response_dom: - dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb))) ) - self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] - + dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb)))) + + try: + self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] + except IndexError: + self._response_dom = dom + return self._response_dom def response_dict(self): @@ -353,7 +357,8 @@ def _get_resp_body_errors(self): err.append('Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) if e.getElementsByTagName('SeverityCode'): - err.append('Severity: %s' % nodeText(e.getElementsByTagName('SeverityCode')[0])) + severity = nodeText(e.getElementsByTagName('SeverityCode')[0]) + err.append('Severity: %s' % severity) if e.getElementsByTagName('ErrorCode'): err.append('Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) @@ -390,7 +395,7 @@ class shopping(ebaybase): (all others, see API docs) Doctests: - >>> s = shopping() + >>> s = shopping(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) >>> print s.response_obj().Ack Success @@ -434,6 +439,7 @@ def __init__(self, **kwargs): # override yaml defaults with args sent to the constructor self.set_config('uri', '/shopping') + self.set_config('warnings', True) self.set_config('https', False) self.set_config('siteid', 0) self.set_config('response_encoding', 'XML') @@ -465,6 +471,27 @@ def _build_request_xml(self): return xml + def error(self): + "Builds and returns the api error message." + + err = [] + if self._response_error: + err.append(self._response_error) + + try: + if self.response_dict().ack == 'Failure': + err.append(self.response_dict().errorMessage.error.message) + elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings'): + sys.stderr.write(self.response_dict().errorMessage.error.message) + + except AttributeError: + pass + + if len(err) > 0: + return "%s: %s" % (self.verb, ", ".join(err)) + + return "" + class html(ebaybase): """HTML class for traditional calls. @@ -608,7 +635,7 @@ class trading(ebaybase): (all others, see API docs) Doctests: - >>> t = trading() + >>> t = trading(config_file=os.environ.get('EBAY_YAML')) >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: @@ -658,6 +685,7 @@ def __init__(self, **kwargs): # override yaml defaults with args sent to the constructor self.set_config('uri', '/ws/api.dll') + self.set_config('warnings', True) self.set_config('https', True) self.set_config('siteid', 0) self.set_config('response_encoding', 'XML') @@ -719,7 +747,7 @@ class finding(ebaybase): (all others, see API docs) Doctests: - >>> f = finding() + >>> f = finding(config_file=os.environ.get('EBAY_YAML')) >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() >>> print error @@ -774,6 +802,7 @@ def __init__(self, **kwargs): # override yaml defaults with args sent to the constructor self.set_config('uri', '/services/search/FindingService/v1') self.set_config('https', False) + self.set_config('warnings', True) self.set_config('siteid', 'EBAY-US') self.set_config('response_encoding', 'XML') self.set_config('request_encoding', 'XML') @@ -816,6 +845,9 @@ def error(self): try: if self.response_dict().ack == 'Failure': err.append(self.response_dict().errorMessage.error.message) + elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings', False): + sys.stderr.write(self.response_dict().errorMessage.error.message) + except AttributeError: pass @@ -937,11 +969,11 @@ class parallel(object): >>> p = parallel() >>> r1 = html(parallel=p) >>> retval = r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> r2 = finding(parallel=p) + >>> r2 = finding(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) - >>> r3 = shopping(parallel=p) + >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) - >>> r4 = trading(parallel=p) + >>> r4 = trading(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r4.execute('GetCharities', { 'CharityID': 3897 }) >>> p.wait() >>> print p.error() diff --git a/samples/trading.py b/samples/trading.py index 521f9dc..6832c4e 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -51,6 +51,7 @@ def run(opts): print "Response dictionary: %s..." % dictstr[:150] print api.response_dict().Charity.Name + print api.response_content() if __name__ == "__main__": (opts, args) = init_options() diff --git a/tests/__init__.py b/tests/__init__.py index 2324e24..7bc814e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,12 +1,13 @@ import unittest import doctest import ebaysdk +import os def getTestSuite(): - suite = unittest.TestSuite() - - suite.addTest(doctest.DocTestSuite(ebaysdk)) - return suite + suite = unittest.TestSuite() + + suite.addTest(doctest.DocTestSuite(ebaysdk)) + return suite runner = unittest.TextTestRunner() runner.run(getTestSuite()) From 08a07eacb7b6e5c224f2e9cf249657b1bcfc5d3d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 14 Feb 2013 13:37:30 -0800 Subject: [PATCH 059/218] add sample with xml attribute --- samples/finding.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/samples/finding.py b/samples/finding.py index a614174..d343eb6 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -13,7 +13,7 @@ def init_options(): parser.add_option("-d", "--debug", action="store_true", dest="debug", default=False, help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml",# + parser.add_option("-y", "--yaml", dest="yaml", default='ebay.yaml', help="Specifies the name of the YAML defaults file. [default: %default]") parser.add_option("-a", "--appid", @@ -27,7 +27,21 @@ def run(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsAdvanced', {'keywords': 'python'}) - print "Finding samples for SDK version %s" % ebaysdk.get_version() + if api.error(): + raise Exception(api.error()) + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] + +def run2(opts): + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('findItemsByProduct', '53039031') if api.error(): raise Exception(api.error()) @@ -42,6 +56,8 @@ def run(opts): print "Response dictionary: %s..." % dictstr[:50] if __name__ == "__main__": - (opts, args) = init_options() - run(opts) + print "Finding samples for SDK version %s" % ebaysdk.get_version() + (opts, args) = init_options() + run(opts) + run2(opts) From 05f374e36bc4e88bfc26e16e316af3f9eadaadaa Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 14 Feb 2013 16:19:45 -0800 Subject: [PATCH 060/218] editor litters some tabs --- samples/finding.py | 78 +++++++++++++++++++++---------------------- samples/parallel.py | 80 ++++++++++++++++++++++----------------------- samples/shopping.py | 58 ++++++++++++++++---------------- samples/trading.py | 72 ++++++++++++++++++++-------------------- tests/__init__.py | 6 ++-- 5 files changed, 147 insertions(+), 147 deletions(-) diff --git a/samples/finding.py b/samples/finding.py index d343eb6..bfeb071 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -7,57 +7,57 @@ from ebaysdk import finding def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml", - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") - parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") - - (opts, args) = parser.parse_args() - return opts, args + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + + (opts, args) = parser.parse_args() + return opts, args def run(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsAdvanced', {'keywords': 'python'}) + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('findItemsAdvanced', {'keywords': 'python'}) - if api.error(): - raise Exception(api.error()) + if api.error(): + raise Exception(api.error()) - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] def run2(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsByProduct', '53039031') + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('findItemsByProduct', '53039031') - if api.error(): - raise Exception(api.error()) + if api.error(): + raise Exception(api.error()) - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] if __name__ == "__main__": - print "Finding samples for SDK version %s" % ebaysdk.get_version() - (opts, args) = init_options() - run(opts) - run2(opts) + print "Finding samples for SDK version %s" % ebaysdk.get_version() + (opts, args) = init_options() + run(opts) + run2(opts) diff --git a/samples/parallel.py b/samples/parallel.py index 93d298d..b1a22d2 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -7,60 +7,60 @@ from ebaysdk import finding, html, parallel def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml", - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") - parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") - - (opts, args) = parser.parse_args() - return opts, args + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + + (opts, args) = parser.parse_args() + return opts, args def run(opts): - p = parallel() - apis = [] + p = parallel() + apis = [] - api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api1.execute('findItemsAdvanced', {'keywords': 'python'}) - apis.append(api1) + api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api1.execute('findItemsAdvanced', {'keywords': 'python'}) + apis.append(api1) - api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api2.execute('findItemsAdvanced', {'keywords': 'perl'}) - apis.append(api2) + api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api2.execute('findItemsAdvanced', {'keywords': 'perl'}) + apis.append(api2) - api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api3.execute('findItemsAdvanced', {'keywords': 'php'}) - apis.append(api3) + api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api3.execute('findItemsAdvanced', {'keywords': 'php'}) + apis.append(api3) - api4 = html(parallel=p) - api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') - apis.append(api4) + api4 = html(parallel=p) + api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') + apis.append(api4) - p.wait() + p.wait() - print "Parallel example for SDK version %s" % ebaysdk.get_version() + print "Parallel example for SDK version %s" % ebaysdk.get_version() - if p.error(): - raise Exception(p.error()) + if p.error(): + raise Exception(p.error()) - for api in apis: + for api in apis: - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s...\n" % dictstr[:50] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s...\n" % dictstr[:50] if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/shopping.py b/samples/shopping.py index 57f4117..8487641 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -7,43 +7,43 @@ from ebaysdk import shopping def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml",# - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") - parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") - - (opts, args) = parser.parse_args() - return opts, args + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml",# + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + + (opts, args) = parser.parse_args() + return opts, args def run(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - print "Shopping samples for SDK version %s" % ebaysdk.get_version() + print "Shopping samples for SDK version %s" % ebaysdk.get_version() - if api.error(): - raise Exception(api.error()) + if api.error(): + raise Exception(api.error()) - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:50] - print "Matching Titles:" - for item in api.response_dict().ItemArray.Item: - print item.Title + print "Matching Titles:" + for item in api.response_dict().ItemArray.Item: + print item.Title if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/trading.py b/samples/trading.py index 6832c4e..bbbd315 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -7,51 +7,51 @@ from ebaysdk import trading def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml", - dest="yaml", default='ebay.yaml', - help="Specifies the name of the YAML defaults file. [default: %default]") - parser.add_option("-a", "--appid", - dest="appid", default=None, - help="Specifies the eBay application id to use.") - parser.add_option("-p", "--devid", - dest="devid", default=None, - help="Specifies the eBay developer id to use.") - parser.add_option("-c", "--certid", - dest="certid", default=None, - help="Specifies the eBay cert id to use.") - - (opts, args) = parser.parse_args() - return opts, args + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + parser.add_option("-p", "--devid", + dest="devid", default=None, + help="Specifies the eBay developer id to use.") + parser.add_option("-c", "--certid", + dest="certid", default=None, + help="Specifies the eBay cert id to use.") + + (opts, args) = parser.parse_args() + return opts, args def run(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid) + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid) - api.execute('GetCharities', {'CharityID': 3897}) + api.execute('GetCharities', {'CharityID': 3897}) - # checkfor errors - if api.error(): - raise Exception(api.error()) + # checkfor errors + if api.error(): + raise Exception(api.error()) - print "Trading samples for SDK version %s" % ebaysdk.get_version() + print "Trading samples for SDK version %s" % ebaysdk.get_version() - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] - print api.response_dict().Charity.Name - print api.response_content() + print api.response_dict().Charity.Name + print api.response_content() if __name__ == "__main__": (opts, args) = init_options() diff --git a/tests/__init__.py b/tests/__init__.py index 7bc814e..4f2c93a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,10 +4,10 @@ import os def getTestSuite(): - suite = unittest.TestSuite() + suite = unittest.TestSuite() - suite.addTest(doctest.DocTestSuite(ebaysdk)) - return suite + suite.addTest(doctest.DocTestSuite(ebaysdk)) + return suite runner = unittest.TextTestRunner() runner.run(getTestSuite()) From 550ebf3fdbb218ec23b044a938d5dafd58bf8827 Mon Sep 17 00:00:00 2001 From: Andrew Dittes Date: Fri, 15 Feb 2013 15:56:10 -0800 Subject: [PATCH 061/218] add windows instructions --- INSTALL | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index 45794be..87af170 100644 --- a/INSTALL +++ b/INSTALL @@ -1,5 +1,7 @@ -Dependancy: pycurl + + +Dependency: pycurl How to install pycurl on Mac @@ -19,4 +21,45 @@ Dependancy: pycurl Testing the SDK: -~/> export EBAY_YAML='myebay.yaml'; python setup.py test \ No newline at end of file +~/> export EBAY_YAML='myebay.yaml'; python setup.py test + +Installing ebaysdk on Windows: + + +1) Download and install the latest release of Python 2.7: + +http://python.org/download/ + +Choose either "Python 3.3.0 Windows x86 MSI Installer" or "Python +3.3.0 Windows X86-64 MSI Installer". To use the latter, you must be +running a 64-bit version of Windows. + +2) Install setuptools: + +First, visit http://pypi.python.org/pypi/setuptools + +If you chose the 32-bit version of Python in step 1, you can simply +use latest setuptools package for "MS Windows installer" for Python +2.7 (example setuptools-0.6c11.win32-py2.7.exe). In this case, simply +download the file and run it to install setuptools. + +If you chose the X86-64 version in step 1, you must download +ez_setup.py from the setuptools page to download and install +setuptools, then run it from the command prompt as follows: + + a. Open the Command Prompt + b. Change to the directory in which ez_setup.py was downloaded + c. Install setuptools as follows: c:\Python27\python.exe ez_setup.py + +The last step assumes that Python was installed to its default +location. + +3) Install ebaysdk: + +Download and extract the zipball, or clone the ebaysdk-python +repository. Then open the Command Prompt and change to the root +directory of the distribution and execute: + + c:\Python27\python.exe setup.py install + +If there were no errors, ebaysdk should be ready to use! From ca2ef6409a818ee0f819606cbb78345239a4fd47 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 15 Feb 2013 15:56:38 -0800 Subject: [PATCH 062/218] yaml bug fix when file does not exist --- ebaysdk/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b38adc0..b73c239 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -121,6 +121,8 @@ def yaml_defaults(self, config_file, domain): return yData.get(domain, {}) + return {} + def set_config(self, cKey, defaultValue): # pull for kwargs first From 02d5107d4b372cb57e11b2ead6949c69e40304c3 Mon Sep 17 00:00:00 2001 From: Andrew Dittes Date: Fri, 15 Feb 2013 16:04:03 -0800 Subject: [PATCH 063/218] use normal json library if simplejson is not present --- ebaysdk/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b38adc0..7c903ce 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -3,7 +3,11 @@ import yaml, pycurl, urllib from types import DictType, ListType -import simplejson as json +try: + import simplejson as json +except ImportError: + import json + from xml.dom.minidom import parseString, Node from BeautifulSoup import BeautifulStoneSoup From 55f493c207d532698e41fc9ce437ae780e2e96da Mon Sep 17 00:00:00 2001 From: Andrew Dittes Date: Fri, 15 Feb 2013 16:04:43 -0800 Subject: [PATCH 064/218] simplejson notes --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 9acc175..dff0440 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,7 @@ Changes for ebaysdk - clean up YAML file - YAML values are now overridden by values defined when building the object +- remove silent depedency on simplejson, at least for Python >= 2.6 0.1.8 - fix bug in html class that allows for POST From fd62820eddac020badf1672f232fd49d18335c2e Mon Sep 17 00:00:00 2001 From: Andrew Dittes Date: Fri, 15 Feb 2013 16:05:21 -0800 Subject: [PATCH 065/218] add missing step for Windows --- INSTALL | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 87af170..a76a74d 100644 --- a/INSTALL +++ b/INSTALL @@ -54,7 +54,23 @@ setuptools, then run it from the command prompt as follows: The last step assumes that Python was installed to its default location. -3) Install ebaysdk: +3) Install pycurl: + +This could be complicated because pycurl requires libcurl to be +installed. Since this is a native library, this can't be installed +using pip or easy_install. Luckily Christoph Gohlke has pre-complied +many common libraries for Python on Windows, including 32- and 64-bit +versions. + +Simply visit this site: + +http://www.lfd.uci.edu/~gohlke/pythonlibs/ + +Then download and install the appropriate version of pycurl for Python +2.7. Use the amd64 version if you are running 64-bit +Python. Otherwise, use the win32 version. + +4) Install ebaysdk: Download and extract the zipball, or clone the ebaysdk-python repository. Then open the Command Prompt and change to the root From 20c59f1e73f47da0f2c55ac182899ae0d270de55 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 21 Feb 2013 10:38:19 -0800 Subject: [PATCH 066/218] Update README.rst --- README.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index d2f676f..762264d 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,6 @@ Welcome to the python ebaysdk This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standerizing calls, response processing, error handling, debugging across the Finding, Shopping, Merchandising, & Trading APIs. -Support ...TBD - Quick Example:: from ebaysdk import finding @@ -25,6 +23,15 @@ Getting Started * `YAML Configuration`_ * `Understanding eBay Credentials`_ + + +Support +------- + +For developer support regarding the SDK code base please use this project's github issue tracking. + +For developer support regarding the eBay APIs please use the `eBay Developer Forums`_ + .. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ .. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration @@ -33,6 +40,4 @@ Getting Started .. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class .. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class - - - +.. _eBay Developer Forums: https://www.x.com/developers/ebay/forums From 9a20ea0eb3b46c88ab4328caf8f827ac286cd2cd Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 21 Feb 2013 10:39:42 -0800 Subject: [PATCH 067/218] Update README.rst --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 762264d..89e9911 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ Getting Started Support ------- -For developer support regarding the SDK code base please use this project's github issue tracking. +For developer support regarding the SDK code base please use this project's `Github issue tracking`_. For developer support regarding the eBay APIs please use the `eBay Developer Forums`_ @@ -41,3 +41,4 @@ For developer support regarding the eBay APIs please use the `eBay Developer For .. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class .. _eBay Developer Forums: https://www.x.com/developers/ebay/forums +.. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues From 99c8cecead6199f7592dd1b5c20364b5e1fff65f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 21 Feb 2013 12:48:16 -0800 Subject: [PATCH 068/218] Update README.rst --- README.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 89e9911..af7a4d8 100644 --- a/README.rst +++ b/README.rst @@ -15,22 +15,26 @@ Quick Example:: Getting Started --------------- -* `Trading API Class`_ -* `Finding API Class`_ -* `Shopping API Class`_ -* `HTML Class`_ -* `Parallel Class`_ +SDK Classes + +* `Trading API Class`_ - secure, authenticated access to private eBay data. +* `Finding API Class`_ - access eBay's next generation search capabilities. +* `Shopping API Class`_ - performance-optimized, lightweight APIs for accessing public eBay data. +* `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. +* `Parallel Class`_ - SDK support for concurrent API calls. + +SDK Configuration + * `YAML Configuration`_ * `Understanding eBay Credentials`_ - Support ------- For developer support regarding the SDK code base please use this project's `Github issue tracking`_. -For developer support regarding the eBay APIs please use the `eBay Developer Forums`_ +For developer support regarding the eBay APIs please use the `eBay Developer Forums`_. .. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ From e8c82cee2dde65d91fba054c6cdbbcd4d81a9500 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 22 Feb 2013 11:24:27 -0800 Subject: [PATCH 069/218] add retry support --- Changes | 1 + ebaysdk/__init__.py | 108 ++++++++++++++++++++++++++++++++++---------- samples/parallel.py | 11 ++--- 3 files changed, 89 insertions(+), 31 deletions(-) diff --git a/Changes b/Changes index dff0440..31dd0ab 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Changes for ebaysdk +- add retry to standard & parallel calls - bug fix for SOA class - add documentation and sample scripts - clean up YAML file diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 17fa50c..b901083 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1,4 +1,4 @@ -import os, sys, re +import os, sys, re, traceback import string, StringIO, base64 import yaml, pycurl, urllib from types import DictType, ListType @@ -312,9 +312,18 @@ def _execute_http_request(self): self.parallel._add_request(self) return None else: - self._curl.perform() - return self._process_http_request() - + e = None + for i in range(3): + print "try %d" % i + try: + self._curl.perform() + return self._process_http_request() + except Exception, e: + continue + break + + raise Exception(e) + except Exception, e: self._response_error = "Exception: %s" % e raise Exception("%s" % e) @@ -325,6 +334,10 @@ def _process_http_request(self): """ self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) + + if self._response_code == 0: + return None + self._response_status = self._response_header.getvalue().splitlines()[0] self._response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', self._response_status ).group(1) @@ -1007,32 +1020,77 @@ def wait(self, timeout=20): self._errors = [] try: if timeout > 0: - multi = pycurl.CurlMulti() - for request in self._requests: - multi.add_handle(request._curl) - while True: - while True: - ret, num = multi.perform() - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - if num == 0: - break - if multi.select(timeout) < 0: - raise pycurl.error(pycurl.E_OPERATION_TIMEOUTED) - for request in self._requests: - multi.remove_handle(request._curl) - request._response_content = request._process_http_request() - if request._response_content: - request.process() - if request._response_error: - self._errors.append(request._response_error) - self._errors.extend(request._get_resp_body_errors()) - multi.close() + creqs = self._requests + for i in range(3): + failed_calls = self.execute_multi(creqs, timeout) + + if failed_calls: + creqs = failed_calls + continue + else: + creqs = [] + + break + + for request in creqs: + self._errors.append("%s" % self._get_curl_http_error(request._curl)) + self._requests = [] except Exception, e: self._errors.append("Exception: %s" % e) + traceback.print_exc() raise Exception("%s" % e) + def _get_curl_http_error(self, curl, info=None): + code = curl.getinfo(pycurl.HTTP_CODE) + url = curl.getinfo(pycurl.EFFECTIVE_URL) + if code == 403: + return 'Server refuses to fullfil the request for: %s' % url + else: + if info is None: + msg = '' + else: + msg = ': ' + info + + return '%s : Unable to handle http code %d%s' % (url, code, msg) + + def execute_multi(self, calls, timeout): + + multi = pycurl.CurlMulti() + for request in calls: + multi.add_handle(request._curl) + + while True: + while True: + ret, num = multi.perform() + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + if num == 0: + break + if multi.select(timeout) < 0: + raise pycurl.error(pycurl.E_OPERATION_TIMEOUTED) + + failed_calls = [] + + for request in calls: + multi.remove_handle(request._curl) + + request._response_content = request._process_http_request() + + if request.response_code() == 0: + failed_calls.append(request) + else: + if request._response_content: + request.process() + if request._response_error: + self._errors.append(request._response_error) + + self._errors.extend(request._get_resp_body_errors()) + + multi.close() + + return failed_calls + def error(self): "builds and returns the api error message" diff --git a/samples/parallel.py b/samples/parallel.py index b1a22d2..5f25822 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -32,6 +32,10 @@ def run(opts): api1.execute('findItemsAdvanced', {'keywords': 'python'}) apis.append(api1) + api4 = html(parallel=p) + api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') + apis.append(api4) + api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api2.execute('findItemsAdvanced', {'keywords': 'perl'}) apis.append(api2) @@ -40,10 +44,6 @@ def run(opts): api3.execute('findItemsAdvanced', {'keywords': 'php'}) apis.append(api3) - api4 = html(parallel=p) - api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') - apis.append(api4) - p.wait() print "Parallel example for SDK version %s" % ebaysdk.get_version() @@ -53,8 +53,7 @@ def run(opts): for api in apis: - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print "Call Success: %s in length" % len(api.response_content()) print "Response code: %s" % api.response_code() print "Response DOM: %s" % api.response_dom() From 49252a9feffb49c3db08a42ea52a5f1a24e80dcc Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 22 Feb 2013 11:25:57 -0800 Subject: [PATCH 070/218] add retry support --- ebaysdk/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b901083..c12cf2b 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -314,7 +314,6 @@ def _execute_http_request(self): else: e = None for i in range(3): - print "try %d" % i try: self._curl.perform() return self._process_http_request() From abc2819968a8f138931972e5228c07ab50258394 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 23 Feb 2013 09:58:17 -0800 Subject: [PATCH 071/218] Update INSTALL --- INSTALL | 57 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/INSTALL b/INSTALL index a76a74d..61787f5 100644 --- a/INSTALL +++ b/INSTALL @@ -1,31 +1,20 @@ +Running the tests: +~/> export EBAY_YAML='myebay.yaml'; python setup.py test +Installing ebaysdk on Mac: +1) Install Xcode from the app center +2) Download and install the Xcode Command Line Tools from https://developer.apple.com/downloads/ +3) Install the SDK + + sudo easy_install ebaysdk + + Or you can install the bleeding edge version, -Dependency: pycurl - - How to install pycurl on Mac - - 1) Install Fink - http://www.finkproject.org/download/ - - 2) Install libssh2, libcurl4-shlibs - sudo /sw/bin/apt-get install libssh2 - sudo /sw/bin/apt-get -q0 -f install libcurl4-shlibs - - 3) Download pycurl from http://pycurl.sourceforge.net/download/ - - 4) Extract and install pycurl - tar -zxvf pycurl-7.16.4.tar.gz - cd pycurl-7.16.4 - sudo env ARCHFLAGS="-arch i386" python setup.py install --curl-config=/sw/bin/curl-config - - -Testing the SDK: -~/> export EBAY_YAML='myebay.yaml'; python setup.py test - + sudo easy_install https://github.com/timotheus/ebaysdk-python/archive/master.zip + Installing ebaysdk on Windows: - 1) Download and install the latest release of Python 2.7: http://python.org/download/ @@ -79,3 +68,25 @@ directory of the distribution and execute: c:\Python27\python.exe setup.py install If there were no errors, ebaysdk should be ready to use! + + + +Legacy Install on Mac: + +Dependency: pycurl + + How to install pycurl on Mac + + 1) Install Fink + http://www.finkproject.org/download/ + + 2) Install libssh2, libcurl4-shlibs + sudo /sw/bin/apt-get install libssh2 + sudo /sw/bin/apt-get -q0 -f install libcurl4-shlibs + + 3) Download pycurl from http://pycurl.sourceforge.net/download/ + + 4) Extract and install pycurl + tar -zxvf pycurl-7.16.4.tar.gz + cd pycurl-7.16.4 + sudo env ARCHFLAGS="-arch i386" python setup.py install --curl-config=/sw/bin/curl-config From ff02ab726a7a7c5803de4c56f3a7c4825f1b533b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 23 Feb 2013 17:04:02 -0800 Subject: [PATCH 072/218] add html sample --- samples/html.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 samples/html.py diff --git a/samples/html.py b/samples/html.py new file mode 100644 index 0000000..cd251f6 --- /dev/null +++ b/samples/html.py @@ -0,0 +1,100 @@ +import os, sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import html, parallel + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + + (opts, args) = parser.parse_args() + return opts, args + +def _intuit(api): + + soup = api.response_soup() + t = soup.findAll('td', {'class': 'td1'}) + + print "Intuit" + print "------\n" + + for link in t[1::]: + + print link.findNext('a').findAll(text=True)[0] + linkdict = dict((x, y) for x, y in link.findNext('a').attrs) + print linkdict.get('href', '') + + +def _amazon(api): + + soup = api.response_soup() + t = soup.findAll('a') + + print "Amazon" + print "------\n" + + for link in t[1::2]: + linkdict = dict((x, y) for x, y in link.attrs) + print linkdict.get('title', '') + print linkdict.get('href', '') + +def _tivo(api): + + soup = api.response_soup() + t = soup.findAll('dd', {'id': 'Marketing, Sales, Product Management'}) + + links = t[0].findAll('a') + + print "Tivo" + print "------\n" + + for link in links: + + linkdict = dict((x, y) for x, y in link.attrs) + print link.findAll(text=True)[0] + print "http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2] + +def _mozilla(api): + soup = api.response_soup() + t = soup.findAll(text='Product Management') #td', {'class': 'iCIMS_JobsTableField_2'}) + print t + + print "Mozilla" + print "------\n" + + #for link in t[1::2]: + # linkdict = dict((x, y) for x, y in link.attrs) + # print linkdict.get('title', '') + # print linkdict.get('href', '') + +def run(opts): + p = parallel() + api1 = html(parallel=p, debug=opts.debug) + api1.execute('http://jobs.intuit.com/search/advanced-search/ASCategory/Product%20Management/ASPostedDate/-1/ASCountry/-1/ASState/California/ASCity/-1/ASLocation/-1/ASCompanyName/-1/ASCustom1/-1/ASCustom2/-1/ASCustom3/-1/ASCustom4/-1/ASCustom5/-1/ASIsRadius/false/ASCityStateZipcode/-1/ASDistance/-1/ASLatitude/-1/ASLongitude/-1/ASDistanceType/-1') + api2 = html(parallel=p, debug=opts.debug) + api2.execute('https://highvolsubs-amazon.icims.com/jobs/search?ss=1&searchKeyword=&searchCategory=30651') + api3 = html(parallel=p, debug=opts.debug) + api3.execute('http://hire.jobvite.com/CompanyJobs/Careers.aspx?c=qMW9Vfww') + api4 = html(parallel=p, debug=opts.debug) + api4.execute('http://careers.mozilla.org/en-US/') + p.wait() + + if p.error(): + raise(p.error()) + + _intuit(api1) + _amazon(api2) + _tivo(api3) + #_mozilla(api4) + +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) + From 2914429b6db4f72cdf727bf561ce69810e0d065b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 23 Feb 2013 17:06:13 -0800 Subject: [PATCH 073/218] handle utf-8 for BeautifulStoneSoup --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index c12cf2b..dae4525 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -221,7 +221,7 @@ def response_soup(self): "Returns a BeautifulSoup object of the response." if not self._response_soup: - self._response_soup = BeautifulStoneSoup(unicode(self._response_content)) + self._response_soup = BeautifulStoneSoup(unicode(self._response_content, encoding='utf-8')) return self._response_soup From cb3b16d7b232ba967751bd2414037dead9e5c9dc Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 23 Feb 2013 17:07:05 -0800 Subject: [PATCH 074/218] update Changes --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 31dd0ab..65d6a85 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Changes for ebaysdk +- handle utf-8 when parsing with BeautifulStoneSoup - add retry to standard & parallel calls - bug fix for SOA class - add documentation and sample scripts From 260133440e2cfed5534d6622eca370a0c83a3da5 Mon Sep 17 00:00:00 2001 From: Keefer Date: Sat, 23 Feb 2013 20:55:08 -0800 Subject: [PATCH 075/218] add html sample --- samples/html.py | 139 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 112 insertions(+), 27 deletions(-) diff --git a/samples/html.py b/samples/html.py index cd251f6..f0a6a2f 100644 --- a/samples/html.py +++ b/samples/html.py @@ -1,4 +1,5 @@ import os, sys +import smtplib from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -22,28 +23,34 @@ def _intuit(api): soup = api.response_soup() t = soup.findAll('td', {'class': 'td1'}) - print "Intuit" - print "------\n" + msg = [] + msg.append("\nIntuit") + msg.append("-----------------------------") for link in t[1::]: - - print link.findNext('a').findAll(text=True)[0] + msg.append(link.findNext('a').findAll(text=True)[0]) linkdict = dict((x, y) for x, y in link.findNext('a').attrs) - print linkdict.get('href', '') + msg.append(linkdict.get('href', '')) + msg.append("\n") + return "\n".join(msg) def _amazon(api): soup = api.response_soup() t = soup.findAll('a') - print "Amazon" - print "------\n" + msg = [] + msg.append("\nAmazon") + msg.append("-----------------------------") for link in t[1::2]: linkdict = dict((x, y) for x, y in link.attrs) - print linkdict.get('title', '') - print linkdict.get('href', '') + msg.append(linkdict.get('title', '')) + msg.append(linkdict.get('href', '')) + msg.append("\n") + + return "\n".join(msg) def _tivo(api): @@ -52,27 +59,61 @@ def _tivo(api): links = t[0].findAll('a') - print "Tivo" - print "------\n" + msg = [] + msg.append("\nTivo") + msg.append("-----------------------------") for link in links: linkdict = dict((x, y) for x, y in link.attrs) - print link.findAll(text=True)[0] - print "http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2] + msg.append(link.findAll(text=True)[0]) + msg.append("http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2]) + msg.append("\n") + + return "\n".join(msg) + +def _oracle(api): -def _mozilla(api): soup = api.response_soup() - t = soup.findAll(text='Product Management') #td', {'class': 'iCIMS_JobsTableField_2'}) - print t + #print soup + #return "" + + rows = soup.findAll('table', {'class': 'x1h'}) + print rows[0].findAll('a') + + for row in rows: + print row - print "Mozilla" - print "------\n" + msg = [] + msg.append("\Oracle") + msg.append("-----------------------------") + ''' + for link in links: + + linkdict = dict((x, y) for x, y in link.attrs) + msg.append(link.findAll(text=True)[0]) + msg.append("http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2]) + msg.append("\n") + ''' + return "\n".join(msg) + - #for link in t[1::2]: - # linkdict = dict((x, y) for x, y in link.attrs) - # print linkdict.get('title', '') - # print linkdict.get('href', '') +def _ebay(api): + + soup = api.response_soup() + t = soup.findAll('td', {'class': 'td1'}) + + msg = [] + msg.append("\neBay") + msg.append("-----------------------------") + + for link in t[1::]: + msg.append(link.findNext('a').findAll(text=True)[0]) + linkdict = dict((x, y) for x, y in link.findNext('a').attrs) + msg.append(linkdict.get('href', '')) + msg.append("\n") + + return "\n".join(msg) def run(opts): p = parallel() @@ -83,16 +124,60 @@ def run(opts): api3 = html(parallel=p, debug=opts.debug) api3.execute('http://hire.jobvite.com/CompanyJobs/Careers.aspx?c=qMW9Vfww') api4 = html(parallel=p, debug=opts.debug) - api4.execute('http://careers.mozilla.org/en-US/') + api4.execute('http://jobs.ebaycareers.com/search/product-manager/ASCategory/Product%20Management/ASPostedDate/-1/ASCountry/-1/ASState/California/ASCity/-1/ASLocation/-1/ASCompanyName/-1/ASCustom1/-1/ASCustom2/-1/ASCustom3/-1/ASCustom4/-1/ASCustom5/-1/ASIsRadius/false/ASCityStateZipcode/-1/ASDistance/-1/ASLatitude/-1/ASLongitude/-1/ASDistanceType/-1') + api5 = html(parallel=p, debug=opts.debug) + api5.execute('https://irecruitment.oracle.com/OA_HTML/OA.jsp?page=/oracle/apps/irc/candidateSelfService/webui/VisJobSchPG&_ri=821&SeededSearchFlag=N&Contractor=Y&Employee=Y&OASF=IRC_VIS_JOB_SEARCH_PAGE&_ti=345582308&retainAM=Y&addBreadCrumb=N&oapc=4&oas=HfwdjxRAvk-kqRstV7E-tA..') p.wait() if p.error(): raise(p.error()) - _intuit(api1) - _amazon(api2) - _tivo(api3) - #_mozilla(api4) + msg = [ + _intuit(api1), + _amazon(api2), + _tivo(api3), + _ebay(api4), + #_oracle(api5), + ] + + msg.append("Other Job Boards\n") + msg.append( + "Mozilla Board:\nhttp://careers.mozilla.org/en-US/position/oUFGWfwV\n" + ) + msg.append( + "Oracle Board:\nhttps://irecruitment.oracle.com/OA_HTML/OA.jsp?page=/oracle/apps/irc/candidateSelfService/webui/VisJobSchPG&_ri=821&SeededSearchFlag=N&Contractor=Y&Employee=Y&OASF=IRC_VIS_JOB_SEARCH_PAGE&_ti=345582308&retainAM=Y&addBreadCrumb=N&oapc=4&oas=HfwdjxRAvk-kqRstV7E-tA..\n" + ) + msg.append( + "Amazon Board:\nhttps://www.a9.com/careers/?search_text=&group=Product%20Manager&type=\n" + ) + msg.append( + "Yelp Board:\nhttp://www.yelp.com/careers\n" + ) + msg.append( + "Intel Board:\nhttp://www.intel.com/jobs/jobsearch/index_js.htm?Location=200000016&JobCategory=30160190084\n" + ) + msg.append( + "Electonic Arts:\nhttps://performancemanager4.successfactors.com/career?company=EA&career_ns=job_listing_summary&navBarLevel=JOB_SEARCH&_s.crb=hyfmSy2nuvCyziY826tSvCbNp08%3d\n" + ) + + sess = smtplib.SMTP('smtp.gmail.com', 587) + sess.starttls() + sess.login('tkeefer@gmail.com', 'kum@@r') + + headers = [ + "Subject: Bright New Future", + "MIME-Version: 1.0", + "Content-Type: text/plain" + ] + + headers = "\r\n".join(headers) + body = "\n".join(msg) + + body = body.encode('utf-8') + + sess.sendmail('tkeefer@gmail.com', ['tkeefer@gmail.com', 'sdunavan@gmail.com'], "%s\r\n\r\n%s" %(headers, body)) + + sess.quit() if __name__ == "__main__": (opts, args) = init_options() From 4c547184396bbc22f9b97d223d90315a41ba739f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 11 Mar 2013 17:06:15 -0700 Subject: [PATCH 076/218] add license --- LICENSE | 318 ++++++++++++++++++++++++++++++++++++++++++++ README.rst | 9 ++ ebaysdk/__init__.py | 6 + ebaysdk/_version.py | 6 + ebaysdk/utils.py | 7 + ebaysdk/utils2.py | 7 + samples/finding.py | 7 + samples/html.py | 7 + samples/parallel.py | 7 + samples/shopping.py | 7 + samples/trading.py | 7 + tests/__init__.py | 7 + 12 files changed, 395 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b46c53e --- /dev/null +++ b/LICENSE @@ -0,0 +1,318 @@ +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + 1. Definitions. + 1.1. "Contributor" means each individual or entity that + creates or contributes to the creation of Modifications. + 1.2. "Contributor Version" means the combination of the + Original Software, prior Modifications used by a + Contributor (if any), and the Modifications made by that + particular Contributor. + 1.3. "Covered Software" means (a) the Original Software, or + (b) Modifications, or (c) the combination of files + containing Original Software with files containing + Modifications, in each case including portions thereof. + 1.4. "Executable" means the Covered Software in any form + other than Source Code. + 1.5. "Initial Developer" means the individual or entity + that first makes Original Software available under this + License. + 1.6. "Larger Work" means a work which combines Covered + Software or portions thereof with code not governed by the + terms of this License. + 1.7. "License" means this document. + 1.8. "Licensable" means having the right to grant, to the + maximum extent possible, whether at the time of the initial + grant or subsequently acquired, any and all of the rights + conveyed herein. + 1.9. "Modifications" means the Source Code and Executable + form of any of the following: + A. Any file that results from an addition to, + deletion from or modification of the contents of a + file containing Original Software or previous + Modifications; + B. Any new file that contains any part of the + Original Software or previous Modification; or + C. Any new file that is contributed or otherwise made + available under the terms of this License. + 1.10. "Original Software" means the Source Code and + Executable form of computer software code that is + originally released under this License. + 1.11. "Patent Claims" means any patent claim(s), now owned + or hereafter acquired, including without limitation, + method, process, and apparatus claims, in any patent + Licensable by grantor. + 1.12. "Source Code" means (a) the common form of computer + software code in which modifications are made and (b) + associated documentation included in or with such code. + 1.13. "You" (or "Your") means an individual or a legal + entity exercising rights under, and complying with all of + the terms of, this License. For legal entities, "You" + includes any entity which controls, is controlled by, or is + under common control with You. For purposes of this + definition, "control" means (a) the power, direct or + indirect, to cause the direction or management of such + entity, whether by contract or otherwise, or (b) ownership + of more than fifty percent (50%) of the outstanding shares + or beneficial ownership of such entity. + 2. License Grants. + 2.1. The Initial Developer Grant. + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the + Initial Developer hereby grants You a world-wide, + royalty-free, non-exclusive license: + (a) under intellectual property rights (other than + patent or trademark) Licensable by Initial Developer, + to use, reproduce, modify, display, perform, + sublicense and distribute the Original Software (or + portions thereof), with or without Modifications, + and/or as part of a Larger Work; and + (b) under Patent Claims infringed by the making, + using or selling of Original Software, to make, have + made, use, practice, sell, and offer for sale, and/or + otherwise dispose of the Original Software (or + portions thereof). + (c) The licenses granted in Sections 2.1(a) and (b) + are effective on the date Initial Developer first + distributes or otherwise makes the Original Software + available to a third party under the terms of this + License. + (d) Notwithstanding Section 2.1(b) above, no patent + license is granted: (1) for code that You delete from + the Original Software, or (2) for infringements + caused by: (i) the modification of the Original + Software, or (ii) the combination of the Original + Software with other software or devices. + 2.2. Contributor Grant. + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + (a) under intellectual property rights (other than + patent or trademark) Licensable by Contributor to + use, reproduce, modify, display, perform, sublicense + and distribute the Modifications created by such + Contributor (or portions thereof), either on an + unmodified basis, with other Modifications, as + Covered Software and/or as part of a Larger Work; and + (b) under Patent Claims infringed by the making, + using, or selling of Modifications made by that + Contributor either alone and/or in combination with + its Contributor Version (or portions of such + combination), to make, use, sell, offer for sale, + have made, and/or otherwise dispose of: (1) + Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications + made by that Contributor with its Contributor Version + (or portions of such combination). + (c) The licenses granted in Sections 2.2(a) and + 2.2(b) are effective on the date Contributor first + distributes or otherwise makes the Modifications + available to a third party. + (d) Notwithstanding Section 2.2(b) above, no patent + license is granted: (1) for any code that Contributor + has deleted from the Contributor Version; (2) for + infringements caused by: (i) third party + modifications of Contributor Version, or (ii) the + combination of Modifications made by that Contributor + with other software (except as part of the + Contributor Version) or other devices; or (3) under + Patent Claims infringed by Covered Software in the + absence of Modifications made by that Contributor. + 3. Distribution Obligations. + 3.1. Availability of Source Code. + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in + Source Code form and that Source Code form must be + distributed only under the terms of this License. You must + include a copy of this License with every copy of the + Source Code form of the Covered Software You distribute or + otherwise make available. You must inform recipients of any + such Covered Software in Executable form as to how they can + obtain such Covered Software in Source Code form in a + reasonable manner on or through a medium customarily used + for software exchange. + 3.2. Modifications. + The Modifications that You create or to which You + contribute are governed by the terms of this License. You + represent that You believe Your Modifications are Your + original creation(s) and/or You have sufficient rights to + grant the rights conveyed by this License. + 3.3. Required Notices. + You must include a notice in each of Your Modifications + that identifies You as the Contributor of the Modification. + You may not remove or alter any copyright, patent or + trademark notices contained within the Covered Software, or + any notices of licensing or any descriptive text giving + attribution to any Contributor or the Initial Developer. + 3.4. Application of Additional Terms. + You may not offer or impose any terms on any Covered + Software in Source Code form that alters or restricts the + applicable version of this License or the recipients' + rights hereunder. You may choose to offer, and to charge a + fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Software. + However, you may do so only on Your own behalf, and not on + behalf of the Initial Developer or any Contributor. You + must make it absolutely clear that any such warranty, + support, indemnity or liability obligation is offered by + You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of + warranty, support, indemnity or liability terms You offer. + 3.5. Distribution of Executable Versions. + You may distribute the Executable form of the Covered + Software under the terms of this License or under the terms + of a license of Your choice, which may contain terms + different from this License, provided that You are in + compliance with the terms of this License and that the + license for the Executable form does not attempt to limit + or alter the recipient's rights in the Source Code form + from the rights set forth in this License. If You + distribute the Covered Software in Executable form under a + different license, You must make it absolutely clear that + any terms which differ from this License are offered by You + alone, not by the Initial Developer or Contributor. You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial + Developer or such Contributor as a result of any such terms + You offer. + 3.6. Larger Works. + You may create a Larger Work by combining Covered Software + with other code not governed by the terms of this License + and distribute the Larger Work as a single product. In such + a case, You must make sure the requirements of this License + are fulfilled for the Covered Software. + 4. Versions of the License. + 4.1. New Versions. + Sun Microsystems, Inc. is the initial license steward and + may publish revised and/or new versions of this License + from time to time. Each version will be given a + distinguishing version number. Except as provided in + Section 4.3, no one other than the license steward has the + right to modify this License. + 4.2. Effect of New Versions. + You may always continue to use, distribute or otherwise + make the Covered Software available under the terms of the + version of the License under which You originally received + the Covered Software. If the Initial Developer includes a + notice in the Original Software prohibiting it from being + distributed or otherwise made available under any + subsequent version of the License, You must distribute and + make the Covered Software available under the terms of the + version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to + use, distribute or otherwise make the Covered Software + available under the terms of any subsequent version of the + License published by the license steward. + 4.3. Modified Versions. + When You are an Initial Developer and You want to create a + new license for Your Original Software, You may create and + use a modified version of this License if You: (a) rename + the license and remove any references to the name of the + license steward (except to note that the license differs + from this License); and (b) otherwise make it clear that + the license contains terms which differ from this License. + 5. DISCLAIMER OF WARRANTY. + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF + ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + 6. TERMINATION. + 6.1. This License and the rights granted hereunder will + terminate automatically if You fail to comply with terms + herein and fail to cure such breach within 30 days of + becoming aware of the breach. Provisions which, by their + nature, must remain in effect beyond the termination of + this License shall survive. + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or + a Contributor (the Initial Developer or Contributor against + whom You assert such claim is referred to as "Participant") + alleging that the Participant Software (meaning the + Contributor Version where the Participant is a Contributor + or the Original Software where the Participant is the + Initial Developer) directly or indirectly infringes any + patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial + Developer (if the Initial Developer is not the Participant) + and all Contributors under Sections 2.1 and/or 2.2 of this + License shall, upon 60 days notice from Participant + terminate prospectively and automatically at the expiration + of such 60 day notice period, unless if within such 60 day + period You withdraw Your claim with respect to the + Participant Software against such Participant either + unilaterally or pursuant to a written agreement with + Participant. + 6.3. In the event of termination under Sections 6.1 or 6.2 + above, all end user licenses that have been validly granted + by You or any distributor hereunder prior to termination + (excluding licenses granted to You by any distributor) + shall survive termination. + 7. LIMITATION OF LIABILITY. + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + 8. U.S. GOVERNMENT END USERS. + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 C.F.R. ¤ + 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 (Sept. + 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 + through 227.7202-4 (June 1995), all U.S. Government End Users + acquire Covered Software with only those rights set forth herein. + This U.S. Government Rights clause is in lieu of, and supersedes, + any other FAR, DFAR, or other clause or provision that addresses + Government rights in computer software under this License. + 9. MISCELLANEOUS. + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the + extent necessary to make it enforceable. This License shall be + governed by the law of the jurisdiction specified in a notice + contained within the Original Software (except to the extent + applicable law, if any, provides otherwise), excluding such + jurisdiction's conflict-of-law provisions. Any litigation + relating to this License shall be subject to the jurisdiction of + the courts located in the jurisdiction and venue specified in a + notice contained within the Original Software, with the losing + party responsible for costs, including, without limitation, court + costs and reasonable attorneys' fees and expenses. The + application of the United Nations Convention on Contracts for the + International Sale of Goods is expressly excluded. Any law or + regulation which provides that the language of a contract shall + be construed against the drafter shall not apply to this License. + You agree that You alone are responsible for compliance with the + United States export administration regulations (and the export + control laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + 10. RESPONSIBILITY FOR CLAIMS. + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + + diff --git a/README.rst b/README.rst index af7a4d8..2991a6b 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,13 @@ For developer support regarding the SDK code base please use this project's `Git For developer support regarding the eBay APIs please use the `eBay Developer Forums`_. +License +------- + +`COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0)`_ + + +.. _COMMON DEVELOPMENT AND DISTRIBUTION LICENSE: http://opensource.org/licenses/CDDL-1.0 .. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ .. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration @@ -46,3 +53,5 @@ For developer support regarding the eBay APIs please use the `eBay Developer For .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class .. _eBay Developer Forums: https://www.x.com/developers/ebay/forums .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues + + diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index dae4525..7a7ac17 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1,3 +1,9 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' import os, sys, re, traceback import string, StringIO, base64 import yaml, pycurl, urllib diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index 9cb17e7..83316c3 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -1 +1,7 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' __version__ = "0.1.8" diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 7d1f1cb..4461fd9 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -1,4 +1,11 @@ # encoding: utf-8 + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import xml.etree.ElementTree as ET import re from StringIO import StringIO diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index 668e30d..ea74353 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + try: import xml.etree.ElementTree as ET except: diff --git a/samples/finding.py b/samples/finding.py index bfeb071..2711bc7 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import os, sys from optparse import OptionParser diff --git a/samples/html.py b/samples/html.py index f0a6a2f..2841e2c 100644 --- a/samples/html.py +++ b/samples/html.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import os, sys import smtplib from optparse import OptionParser diff --git a/samples/parallel.py b/samples/parallel.py index 5f25822..efe8a16 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import os, sys from optparse import OptionParser diff --git a/samples/shopping.py b/samples/shopping.py index 8487641..0786b34 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import os, sys from optparse import OptionParser diff --git a/samples/trading.py b/samples/trading.py index bbbd315..ffd48ed 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import os, sys from optparse import OptionParser diff --git a/tests/__init__.py b/tests/__init__.py index 4f2c93a..05e3826 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,10 @@ + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + import unittest import doctest import ebaysdk From 0c07d0206146932a16c9fb44981e2fea3c9cd3a9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 18 Mar 2013 16:40:50 -0700 Subject: [PATCH 077/218] add encoding --- README.rst | 2 +- ebaysdk/__init__.py | 1 + ebaysdk/_version.py | 1 + ebaysdk/utils.py | 2 +- ebaysdk/utils2.py | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 2991a6b..731cb95 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ For developer support regarding the eBay APIs please use the `eBay Developer For License ------- -`COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0)`_ +`COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0) .. _COMMON DEVELOPMENT AND DISTRIBUTION LICENSE: http://opensource.org/licenses/CDDL-1.0 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 7a7ac17..b14d6ab 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index 83316c3..a304c28 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 4461fd9..f407eb3 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -1,4 +1,4 @@ -# encoding: utf-8 +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index ea74353..86ba2cf 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation From 9bd0190e25df7fe52e511b21c1ca7a193daf858d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 25 Mar 2013 15:00:00 -0700 Subject: [PATCH 078/218] add encoding to test file --- tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/__init__.py b/tests/__init__.py index 05e3826..d942bd1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation From 14bbb7b1e70064995e0999415845ed62db879cd0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 1 Apr 2013 10:46:13 -0700 Subject: [PATCH 079/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 731cb95..2cb161d 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Welcome to the python ebaysdk ============================= -This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standerizing calls, response processing, error handling, debugging across the Finding, Shopping, Merchandising, & Trading APIs. +This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, debugging across the Finding, Shopping, & Trading APIs. Quick Example:: From 8dab6d1fafabeac675bdaabff483dafcb3c4162c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 18 Apr 2013 10:40:01 -0700 Subject: [PATCH 080/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2cb161d..b744327 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Welcome to the python ebaysdk ============================= -This SDK is a dead-simple, programatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, debugging across the Finding, Shopping, & Trading APIs. +This SDK is a programmatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, & Trading APIs. Quick Example:: From 93667169b632e8862ff64d4521252207fbc2815b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 18 Apr 2013 12:43:49 -0700 Subject: [PATCH 081/218] Update README.rst --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index b744327..8222efd 100644 --- a/README.rst +++ b/README.rst @@ -36,12 +36,18 @@ For developer support regarding the SDK code base please use this project's `Git For developer support regarding the eBay APIs please use the `eBay Developer Forums`_. +Install +------- + +Installation instructions for *nix and windows can be found in the `INSTALL file`_. + License ------- `COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0) +.. _INSTALL file: https://github.com/timotheus/ebaysdk-python/blob/master/INSTALL .. _COMMON DEVELOPMENT AND DISTRIBUTION LICENSE: http://opensource.org/licenses/CDDL-1.0 .. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ From 43544aed756e2364b5ee73278c793aa5bef01224 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 23 Apr 2013 15:28:07 -0700 Subject: [PATCH 082/218] pep8 cleanup --- Changes | 2 + ebaysdk/__init__.py | 258 +++++++++++++++++++++++--------------------- ebaysdk/utils.py | 2 +- ebaysdk/utils2.py | 2 +- 4 files changed, 141 insertions(+), 123 deletions(-) diff --git a/Changes b/Changes index 65d6a85..2f7c81a 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Changes for ebaysdk +- fix deprecation warning in utils.py +- pep8 cleanup - handle utf-8 when parsing with BeautifulStoneSoup - add retry to standard & parallel calls - bug fix for SOA class diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b14d6ab..4a5c922 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -5,9 +5,14 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -import os, sys, re, traceback -import string, StringIO, base64 -import yaml, pycurl, urllib +import os +import sys +import re +import traceback +import StringIO +import yaml +import pycurl +import urllib from types import DictType, ListType try: @@ -15,22 +20,24 @@ except ImportError: import json -from xml.dom.minidom import parseString, Node +from xml.dom.minidom import parseString from BeautifulSoup import BeautifulStoneSoup from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict + def get_version(): "Get the version." VERSIONFILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "_version.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - open(VERSIONFILE, "rt").read(), re.M).group(1) + open(VERSIONFILE, "rt").read(), re.M).group(1) return version __version__ = get_version() + def nodeText(node): "Returns the node's text string." @@ -45,29 +52,31 @@ def nodeText(node): return ''.join(rc) + def tag(name, value): return "<%s>%s" % (name, value, name) + class ebaybase(object): """Base API class. - Doctests: + Doctests: >>> d = { 'list': ['a', 'b', 'c']} >>> print dict2xml(d) abc """ - def __init__(self, debug=False, method='GET', - proxy_host=None, timeout=20, proxy_port=80, + def __init__(self, debug=False, method='GET', + proxy_host=None, timeout=20, proxy_port=80, parallel=None, **kwargs): - - self.verb = None - self.debug = debug - self.method = method - self.timeout = timeout + + self.verb = None + self.debug = debug + self.method = method + self.timeout = timeout self.proxy_host = proxy_host self.proxy_port = proxy_port - self.parallel = parallel + self.parallel = parallel self._reset() def debug_callback(self, debug_type, debug_message): @@ -76,7 +85,7 @@ def debug_callback(self, debug_type, debug_message): def v(self, *args, **kwargs): args_a = [w for w in args] - first = args_a[0] + first = args_a[0] args_a.remove(first) h = kwargs.get('mydict', {}) @@ -102,18 +111,18 @@ def v(self, *args, **kwargs): return h.get('value', None) except: return h - + def yaml_defaults(self, config_file, domain): "Returns a dictionary of YAML defaults." # check for absolute path if os.path.exists(config_file): try: - f = open(config_file, "r") + f = open(config_file, "r") except IOError, e: print "unable to open file %s" % e - yData = yaml.load(f.read()) + yData = yaml.load(f.read()) return yData.get(domain, {}) # check other directories @@ -123,27 +132,26 @@ def yaml_defaults(self, config_file, domain): if os.path.exists(myfile): try: - f = open(myfile, "r") + f = open(myfile, "r") except IOError, e: print "unable to open file %s" % e - yData = yaml.load(f.read()) + yData = yaml.load(f.read()) domain = self.api_config.get('domain', '') return yData.get(domain, {}) return {} - + def set_config(self, cKey, defaultValue): - - # pull for kwargs first - if self._kwargs.has_key(cKey) and self._kwargs[cKey] != None: + + if cKey in self._kwargs and self._kwargs[cKey] is not None: self.api_config.update({cKey: self._kwargs[cKey]}) - + # otherwise, use yaml default and then fall back to # the default set in the __init__() else: - if not self.api_config.has_key(cKey): + if not cKey in self.api_config: self.api_config.update({cKey: defaultValue}) else: pass @@ -156,15 +164,15 @@ def getNodeText(self, nodelist): return rc def _reset(self): - self._response_reason = None - self._response_status = None - self._response_code = None + self._response_reason = None + self._response_status = None + self._response_code = None self._response_content = None - self._response_dom = None - self._response_obj = None - self._response_soup = None - self._response_dict = None - self._response_error = None + self._response_dom = None + self._response_obj = None + self._response_soup = None + self._response_dict = None + self._response_error = None self._resp_body_errors = [] def do(self, verb, call_data=dict()): @@ -240,12 +248,12 @@ def response_dom(self): if not self._response_dom: dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb)))) - + try: self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] except IndexError: self._response_dom = dom - + return self._response_dom def response_dict(self): @@ -265,7 +273,6 @@ def response_json(self): def _execute_http_request(self): "Performs the http request and returns the XML response body." - response_data = '' try: self._curl = pycurl.Curl() @@ -279,15 +286,15 @@ def _execute_http_request(self): # construct headers request_headers = self._build_request_headers() - self._curl.setopt( pycurl.HTTPHEADER, [ - str( '%s: %s' % ( k, v ) ) for k, v in request_headers.items() - ] ) + self._curl.setopt(pycurl.HTTPHEADER, [ + str('%s: %s' % (k, v)) for k, v in request_headers.items() + ]) # construct URL & post data request_url = self.api_config.get('domain', None) if self.api_config.get('uri', None): - request_url = "%s%s" % ( request_url, self.api_config.get('uri', None) ) + request_url = "%s%s" % (request_url, self.api_config.get('uri', None)) if self.api_config.get('https', None): request_url = "https://%s" % request_url @@ -302,7 +309,7 @@ def _execute_http_request(self): self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) self._response_header = StringIO.StringIO() - self._response_body = StringIO.StringIO() + self._response_body = StringIO.StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -329,7 +336,7 @@ def _execute_http_request(self): break raise Exception(e) - + except Exception, e: self._response_error = "Exception: %s" % e raise Exception("%s" % e) @@ -339,18 +346,17 @@ def _process_http_request(self): Returns the response data. """ - self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) + self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) if self._response_code == 0: return None self._response_status = self._response_header.getvalue().splitlines()[0] - self._response_reason = re.match( r'^HTTP.+? +\d+ +(.*) *$', self._response_status ).group(1) - - response_data = self._response_body.getvalue() + self._response_reason = re.match(r'^HTTP.+? +\d+ +(.*) *$', self._response_status).group(1) + response_data = self._response_body.getvalue() self._response_header = None - self._response_body = None + self._response_body = None self._curl.close() if self._response_code != 200: @@ -372,9 +378,11 @@ def _get_resp_body_errors(self): return self._resp_body_errors err = [] - if self.verb is None: return err + if self.verb is None: + return err dom = self.response_dom() - if dom is None: return err + if dom is None: + return err for e in dom.getElementsByTagName("Errors"): @@ -401,20 +409,23 @@ def error(self): "Builds and returns the api error message." err = [] - if self._response_error: err.append(self._response_error) + if self._response_error: + err.append(self._response_error) err.extend(self._get_resp_body_errors()) - if len(err) > 0: return "%s: %s" % (self.verb, ", ".join(err)) + if len(err) > 0: + return "%s: %s" % (self.verb, ", ".join(err)) return "" + class shopping(ebaybase): """Shopping API class API documentation: http://developer.ebay.com/products/shopping/ - Supported calls: + Supported calls: getSingleItem getMultipleItems (all others, see API docs) @@ -451,7 +462,7 @@ def __init__(self, **kwargs): ebaybase.__init__(self, method='POST', **kwargs) self._kwargs = kwargs - + self.api_config = { 'domain': kwargs.get('domain', 'open.api.ebay.com'), 'config_file': kwargs.get('config_file', 'ebay.yaml'), @@ -474,7 +485,6 @@ def __init__(self, **kwargs): self.set_config('appid', None) self.set_config('version', '799') - if self.api_config['https'] and self.debug: print "HTTPS is not supported on the Shopping API." @@ -500,23 +510,24 @@ def error(self): "Builds and returns the api error message." err = [] - if self._response_error: + if self._response_error: err.append(self._response_error) try: if self.response_dict().ack == 'Failure': - err.append(self.response_dict().errorMessage.error.message) + err.append(self.response_dict().errorMessage.error.message) elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings'): sys.stderr.write(self.response_dict().errorMessage.error.message) except AttributeError: pass - if len(err) > 0: + if len(err) > 0: return "%s: %s" % (self.verb, ", ".join(err)) return "" + class html(ebaybase): """HTML class for traditional calls. @@ -610,7 +621,7 @@ def _execute_http_request(self): self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) self._response_header = StringIO.StringIO() - self._response_body = StringIO.StringIO() + self._response_body = StringIO.StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -635,8 +646,9 @@ def _execute_http_request(self): raise Exception("%s" % e) def error(self): - "Builds and returns the api error message." - return self._response_error + "Builds and returns the api error message." + + return self._response_error def _build_request_xml(self): "Builds and returns the request XML." @@ -647,13 +659,14 @@ def _build_request_xml(self): return xml + class trading(ebaybase): """Trading API class API documentation: https://www.x.com/developers/ebay/products/trading-api - Supported calls: + Supported calls: AddItem ReviseItem GetUser @@ -661,11 +674,11 @@ class trading(ebaybase): Doctests: >>> t = trading(config_file=os.environ.get('EBAY_YAML')) - >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) + >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) - >>> print charity_name + >>> print charity_name Sunshine Kids Foundation >>> print t.error() @@ -697,7 +710,7 @@ def __init__(self, **kwargs): ebaybase.__init__(self, method='POST', **kwargs) self._kwargs = kwargs - + self.api_config = { 'domain': kwargs.get('domain', 'api.ebay.com'), 'config_file': kwargs.get('config_file', 'ebay.yaml'), @@ -731,15 +744,15 @@ def _build_request_headers(self): headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('version', ''), "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), - "X-EBAY-API-APP-NAME": self.api_config.get('appid',''), - "X-EBAY-API-CERT-NAME": self.api_config.get('certid',''), - "X-EBAY-API-SITEID": self.api_config.get('siteid',''), + "X-EBAY-API-APP-NAME": self.api_config.get('appid', ''), + "X-EBAY-API-CERT-NAME": self.api_config.get('certid', ''), + "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), "X-EBAY-API-CALL-NAME": self.verb, "Content-Type": "text/xml" } if self.api_config.get('iaf_token', None): headers["X-EBAY-API-IAF-TOKEN"] = self.api_config.get('iaf_token') - + return headers def _build_request_xml(self): @@ -760,20 +773,21 @@ def _build_request_xml(self): xml += "" return xml + class finding(ebaybase): """Finding API class API documentation: https://www.x.com/developers/ebay/products/finding-api - Supported calls: + Supported calls: findItemsAdvanced findItemsByCategory (all others, see API docs) Doctests: >>> f = finding(config_file=os.environ.get('EBAY_YAML')) - >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() >>> print error @@ -791,7 +805,7 @@ class finding(ebaybase): def __init__(self, **kwargs): """Finding class constructor. - + Keyword arguments: domain -- API endpoint (default: svcs.ebay.com) config_file -- YAML defaults (default: ebay.yaml) @@ -812,7 +826,7 @@ def __init__(self, **kwargs): ebaybase.__init__(self, method='POST', **kwargs) self._kwargs = kwargs - + self.api_config = { 'domain': kwargs.get('domain', 'svcs.ebay.com'), 'config_file': kwargs.get('config_file', 'ebay.yaml'), @@ -820,7 +834,7 @@ def __init__(self, **kwargs): # pull stuff in value yaml defaults self.api_config.update( - self.yaml_defaults(self.api_config['config_file'], + self.yaml_defaults(self.api_config['config_file'], self.api_config['domain']) ) @@ -839,17 +853,16 @@ def __init__(self, **kwargs): self.set_config('version', '1.0.0') self.set_config('compatibility', '1.0.0') - def _build_request_headers(self): return { - "X-EBAY-SOA-SERVICE-NAME" : self.api_config.get('service',''), - "X-EBAY-SOA-SERVICE-VERSION" : self.api_config.get('version',''), - "X-EBAY-SOA-SECURITY-APPNAME" : self.api_config.get('appid',''), - "X-EBAY-SOA-GLOBAL-ID" : self.api_config.get('siteid',''), - "X-EBAY-SOA-OPERATION-NAME" : self.verb, - "X-EBAY-SOA-REQUEST-DATA-FORMAT" : self.api_config.get('request_encoding',''), - "X-EBAY-SOA-RESPONSE-DATA-FORMAT" : self.api_config.get('response_encoding',''), - "Content-Type" : "text/xml" + "X-EBAY-SOA-SERVICE-NAME": self.api_config.get('service', ''), + "X-EBAY-SOA-SERVICE-VERSION": self.api_config.get('version', ''), + "X-EBAY-SOA-SECURITY-APPNAME": self.api_config.get('appid', ''), + "X-EBAY-SOA-GLOBAL-ID": self.api_config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": self.verb, + "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.api_config.get('request_encoding', ''), + "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.api_config.get('response_encoding', ''), + "Content-Type": "text/xml" } def _build_request_xml(self): @@ -864,35 +877,36 @@ def error(self): "Builds and returns the api error message." err = [] - if self._response_error: + if self._response_error: err.append(self._response_error) try: if self.response_dict().ack == 'Failure': - err.append(self.response_dict().errorMessage.error.message) + err.append(self.response_dict().errorMessage.error.message) elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings', False): sys.stderr.write(self.response_dict().errorMessage.error.message) except AttributeError: pass - if len(err) > 0: + if len(err) > 0: return "%s: %s" % (self.verb, ", ".join(err)) return "" + class SOAService(ebaybase): "SOAP class." def __init__(self, app_config=None, site_id='EBAY-US', debug=False): self.api_config = { - 'https' : False, - 'site_id' : site_id, - 'content_type' : 'text/xml;charset=UTF-8', - 'request_encoding' : 'XML', - 'response_encoding' : 'XML', - 'message_protocol' : 'SOAP12', - 'soap_env_str' : 'http://www.ebay.com/marketplace/fundraising/v1/services', + 'https': False, + 'site_id': site_id, + 'content_type': 'text/xml;charset=UTF-8', + 'request_encoding': 'XML', + 'response_encoding': 'XML', + 'message_protocol': 'SOAP12', + 'soap_env_str': 'http://www.ebay.com/marketplace/fundraising/v1/services', } ph = None @@ -904,10 +918,10 @@ def __init__(self, app_config=None, site_id='EBAY-US', debug=False): ebaybase.__init__( self, - debug = debug, - method = 'POST', - proxy_host = ph, - proxy_port = pp, + debug=debug, + method='POST', + proxy_host=ph, + proxy_port=pp, ) # override this method, to provide setup through a config object, which @@ -921,14 +935,15 @@ def load_from_app_config(self, app_config): # Note: this method will always return at least an empty object_dict! # It used to return None in some cases. If you get an empty dict, # you can use the .error() method to look for the cause. - def response_dict( self ): - if self._response_dict: return self._response_dict + def response_dict(self): + if self._response_dict: + return self._response_dict mydict = object_dict() try: mydict = xml2dict().fromstring(self._response_content) verb = self.verb + 'Response' - self._response_dict = mydict['Envelope']['Body'][ verb ] + self._response_dict = mydict['Envelope']['Body'][verb] except Exception, e: self._response_dict = mydict @@ -938,17 +953,17 @@ def response_dict( self ): def _build_request_headers(self): return { - 'Content-Type' : self.api_config['content_type'], - 'X-EBAY-SOA-SERVICE-NAME' : self.api_config['service'], - 'X-EBAY-SOA-OPERATION-NAME' : self.verb, - 'X-EBAY-SOA-GLOBAL-ID' : self.api_config['site_id'], - 'X-EBAY-SOA-REQUEST-DATA-FORMAT' : self.api_config['request_encoding'], - 'X-EBAY-SOA-RESPONSE-DATA-FORMAT' : self.api_config['response_encoding'], - 'X-EBAY-SOA-MESSAGE-PROTOCOL' : self.api_config['message_protocol'], + 'Content-Type': self.api_config['content_type'], + 'X-EBAY-SOA-SERVICE-NAME': self.api_config['service'], + 'X-EBAY-SOA-OPERATION-NAME': self.verb, + 'X-EBAY-SOA-GLOBAL-ID': self.api_config['site_id'], + 'X-EBAY-SOA-REQUEST-DATA-FORMAT': self.api_config['request_encoding'], + 'X-EBAY-SOA-RESPONSE-DATA-FORMAT': self.api_config['response_encoding'], + 'X-EBAY-SOA-MESSAGE-PROTOCOL': self.api_config['message_protocol'], } def _build_request_xml(self): - xml = '' + xml = '' xml += '>> p = parallel() @@ -1027,12 +1043,12 @@ def wait(self, timeout=20): try: if timeout > 0: creqs = self._requests - for i in range(3): + for i in range(3): failed_calls = self.execute_multi(creqs, timeout) if failed_calls: creqs = failed_calls - continue + continue else: creqs = [] @@ -1057,15 +1073,15 @@ def _get_curl_http_error(self, curl, info=None): msg = '' else: msg = ': ' + info - + return '%s : Unable to handle http code %d%s' % (url, code, msg) - + def execute_multi(self, calls, timeout): multi = pycurl.CurlMulti() for request in calls: multi.add_handle(request._curl) - + while True: while True: ret, num = multi.perform() @@ -1076,13 +1092,13 @@ def execute_multi(self, calls, timeout): if multi.select(timeout) < 0: raise pycurl.error(pycurl.E_OPERATION_TIMEOUTED) - failed_calls = [] + failed_calls = [] for request in calls: multi.remove_handle(request._curl) request._response_content = request._process_http_request() - + if request.response_code() == 0: failed_calls.append(request) else: @@ -1090,7 +1106,7 @@ def execute_multi(self, calls, timeout): request.process() if request._response_error: self._errors.append(request._response_error) - + self._errors.extend(request._get_resp_body_errors()) multi.close() diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index f407eb3..d30b51c 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -68,7 +68,7 @@ def _parse_node(self, node): k,v = self._namespace_split(k, object_dict({'value':v})) node_tree[k] = v #Save childrens - for child in node.getchildren(): + for child in list(node): tag, tree = self._namespace_split(child.tag, self._parse_node(child)) if tag not in node_tree: # the first time, so store it in dict node_tree[tag] = tree diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index 86ba2cf..020b95e 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -69,7 +69,7 @@ def _parse_node(self, node): k,v = self._namespace_split(k, object_dict({'value':v})) node_tree[k] = v #Save childrens - for child in node.getchildren(): + for child in list(node): tag, tree = self._namespace_split(child.tag, self._parse_node(child)) if tag not in node_tree: # the first time, so store it in dict node_tree[tag] = tree From 5e00438bdca4b8ec39a8311fe9207560fd90674d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 23 Apr 2013 15:33:49 -0700 Subject: [PATCH 083/218] update license in setup --- Changes | 7 +++++-- setup.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index 2f7c81a..225d8da 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,10 @@ Changes for ebaysdk + +- update license in setup.py + +0.1.8 Tue Apr 23 15:33:02 PDT 2013 +- push dist to pypi - fix deprecation warning in utils.py - pep8 cleanup - handle utf-8 when parsing with BeautifulStoneSoup @@ -10,8 +15,6 @@ Changes for ebaysdk - YAML values are now overridden by values defined when building the object - remove silent depedency on simplejson, at least for Python >= 2.6 - -0.1.8 - fix bug in html class that allows for POST - refactor and style cleanup - added parallel support using the parallel class diff --git a/setup.py b/setup.py index cb6c5d6..98bca9a 100644 --- a/setup.py +++ b/setup.py @@ -15,19 +15,20 @@ # limitations under the License. from setuptools import setup, find_packages -import re, os +import re +import os PKG = 'ebaysdk' # Get the version VERSIONFILE = os.path.join(PKG, "_version.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - open(VERSIONFILE, "rt").read(), re.M).group(1) + open(VERSIONFILE, "rt").read(), re.M).group(1) -long_desc = """his SDK is a dead-simple, programatic inteface into the eBay -APIs. It simplifies development and cuts development time by standerizing -calls, response processing, error handling, debugging across the Finding, +long_desc = """This SDK is a programatic inteface into the eBay +APIs. It simplifies development and cuts development time by standerizing +calls, response processing, error handling, debugging across the Finding, Shopping, Merchandising, & Trading APIs. """ setup( @@ -37,14 +38,14 @@ author="Tim Keefer", author_email="tkeefer@gmail.com", url="https://github.com/timotheus/ebaysdk-python", - license="Apache Software License", + license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], install_requires=['PyYaml', 'pycurl', 'Beautifulsoup'], test_suite='tests', long_description=long_desc, classifiers=[ - 'Topic :: Internet :: WWW/HTTP', - 'Intended Audience :: Developers', + 'Topic :: Internet :: WWW/HTTP', + 'Intended Audience :: Developers', ] ) From eb7e00f098238bb5b5400973b4c92aafa6f7916d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 23 Apr 2013 16:32:18 -0700 Subject: [PATCH 084/218] pep8 cleanup --- samples/finding.py | 11 ++- samples/html.py | 192 --------------------------------------------- 2 files changed, 7 insertions(+), 196 deletions(-) delete mode 100644 samples/html.py diff --git a/samples/finding.py b/samples/finding.py index 2711bc7..d9bbadf 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -5,7 +5,8 @@ Licensed under CDDL 1.0 ''' -import os, sys +import os +import sys from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -13,6 +14,7 @@ import ebaysdk from ebaysdk import finding + def init_options(): usage = "usage: %prog [options]" parser = OptionParser(usage=usage) @@ -30,8 +32,9 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args + def run(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsAdvanced', {'keywords': 'python'}) if api.error(): @@ -46,8 +49,9 @@ def run(opts): dictstr = "%s" % api.response_dict() print "Response dictionary: %s..." % dictstr[:50] + def run2(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsByProduct', '53039031') if api.error(): @@ -67,4 +71,3 @@ def run2(opts): (opts, args) = init_options() run(opts) run2(opts) - diff --git a/samples/html.py b/samples/html.py deleted file mode 100644 index 2841e2c..0000000 --- a/samples/html.py +++ /dev/null @@ -1,192 +0,0 @@ - -''' -© 2012-2013 eBay Software Foundation -Authored by: Tim Keefer -Licensed under CDDL 1.0 -''' - -import os, sys -import smtplib -from optparse import OptionParser - -sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) - -import ebaysdk -from ebaysdk import html, parallel - -def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - - (opts, args) = parser.parse_args() - return opts, args - -def _intuit(api): - - soup = api.response_soup() - t = soup.findAll('td', {'class': 'td1'}) - - msg = [] - msg.append("\nIntuit") - msg.append("-----------------------------") - - for link in t[1::]: - msg.append(link.findNext('a').findAll(text=True)[0]) - linkdict = dict((x, y) for x, y in link.findNext('a').attrs) - msg.append(linkdict.get('href', '')) - msg.append("\n") - - return "\n".join(msg) - -def _amazon(api): - - soup = api.response_soup() - t = soup.findAll('a') - - msg = [] - msg.append("\nAmazon") - msg.append("-----------------------------") - - for link in t[1::2]: - linkdict = dict((x, y) for x, y in link.attrs) - msg.append(linkdict.get('title', '')) - msg.append(linkdict.get('href', '')) - msg.append("\n") - - return "\n".join(msg) - -def _tivo(api): - - soup = api.response_soup() - t = soup.findAll('dd', {'id': 'Marketing, Sales, Product Management'}) - - links = t[0].findAll('a') - - msg = [] - msg.append("\nTivo") - msg.append("-----------------------------") - - for link in links: - - linkdict = dict((x, y) for x, y in link.attrs) - msg.append(link.findAll(text=True)[0]) - msg.append("http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2]) - msg.append("\n") - - return "\n".join(msg) - -def _oracle(api): - - soup = api.response_soup() - #print soup - #return "" - - rows = soup.findAll('table', {'class': 'x1h'}) - print rows[0].findAll('a') - - for row in rows: - print row - - msg = [] - msg.append("\Oracle") - msg.append("-----------------------------") - ''' - for link in links: - - linkdict = dict((x, y) for x, y in link.attrs) - msg.append(link.findAll(text=True)[0]) - msg.append("http://hire.jobvite.com/CompanyJobs/Careers.aspx?page=Job%%20Description&j=%s" % linkdict.get('href', '').split("'")[-2]) - msg.append("\n") - ''' - return "\n".join(msg) - - -def _ebay(api): - - soup = api.response_soup() - t = soup.findAll('td', {'class': 'td1'}) - - msg = [] - msg.append("\neBay") - msg.append("-----------------------------") - - for link in t[1::]: - msg.append(link.findNext('a').findAll(text=True)[0]) - linkdict = dict((x, y) for x, y in link.findNext('a').attrs) - msg.append(linkdict.get('href', '')) - msg.append("\n") - - return "\n".join(msg) - -def run(opts): - p = parallel() - api1 = html(parallel=p, debug=opts.debug) - api1.execute('http://jobs.intuit.com/search/advanced-search/ASCategory/Product%20Management/ASPostedDate/-1/ASCountry/-1/ASState/California/ASCity/-1/ASLocation/-1/ASCompanyName/-1/ASCustom1/-1/ASCustom2/-1/ASCustom3/-1/ASCustom4/-1/ASCustom5/-1/ASIsRadius/false/ASCityStateZipcode/-1/ASDistance/-1/ASLatitude/-1/ASLongitude/-1/ASDistanceType/-1') - api2 = html(parallel=p, debug=opts.debug) - api2.execute('https://highvolsubs-amazon.icims.com/jobs/search?ss=1&searchKeyword=&searchCategory=30651') - api3 = html(parallel=p, debug=opts.debug) - api3.execute('http://hire.jobvite.com/CompanyJobs/Careers.aspx?c=qMW9Vfww') - api4 = html(parallel=p, debug=opts.debug) - api4.execute('http://jobs.ebaycareers.com/search/product-manager/ASCategory/Product%20Management/ASPostedDate/-1/ASCountry/-1/ASState/California/ASCity/-1/ASLocation/-1/ASCompanyName/-1/ASCustom1/-1/ASCustom2/-1/ASCustom3/-1/ASCustom4/-1/ASCustom5/-1/ASIsRadius/false/ASCityStateZipcode/-1/ASDistance/-1/ASLatitude/-1/ASLongitude/-1/ASDistanceType/-1') - api5 = html(parallel=p, debug=opts.debug) - api5.execute('https://irecruitment.oracle.com/OA_HTML/OA.jsp?page=/oracle/apps/irc/candidateSelfService/webui/VisJobSchPG&_ri=821&SeededSearchFlag=N&Contractor=Y&Employee=Y&OASF=IRC_VIS_JOB_SEARCH_PAGE&_ti=345582308&retainAM=Y&addBreadCrumb=N&oapc=4&oas=HfwdjxRAvk-kqRstV7E-tA..') - p.wait() - - if p.error(): - raise(p.error()) - - msg = [ - _intuit(api1), - _amazon(api2), - _tivo(api3), - _ebay(api4), - #_oracle(api5), - ] - - msg.append("Other Job Boards\n") - msg.append( - "Mozilla Board:\nhttp://careers.mozilla.org/en-US/position/oUFGWfwV\n" - ) - msg.append( - "Oracle Board:\nhttps://irecruitment.oracle.com/OA_HTML/OA.jsp?page=/oracle/apps/irc/candidateSelfService/webui/VisJobSchPG&_ri=821&SeededSearchFlag=N&Contractor=Y&Employee=Y&OASF=IRC_VIS_JOB_SEARCH_PAGE&_ti=345582308&retainAM=Y&addBreadCrumb=N&oapc=4&oas=HfwdjxRAvk-kqRstV7E-tA..\n" - ) - msg.append( - "Amazon Board:\nhttps://www.a9.com/careers/?search_text=&group=Product%20Manager&type=\n" - ) - msg.append( - "Yelp Board:\nhttp://www.yelp.com/careers\n" - ) - msg.append( - "Intel Board:\nhttp://www.intel.com/jobs/jobsearch/index_js.htm?Location=200000016&JobCategory=30160190084\n" - ) - msg.append( - "Electonic Arts:\nhttps://performancemanager4.successfactors.com/career?company=EA&career_ns=job_listing_summary&navBarLevel=JOB_SEARCH&_s.crb=hyfmSy2nuvCyziY826tSvCbNp08%3d\n" - ) - - sess = smtplib.SMTP('smtp.gmail.com', 587) - sess.starttls() - sess.login('tkeefer@gmail.com', 'kum@@r') - - headers = [ - "Subject: Bright New Future", - "MIME-Version: 1.0", - "Content-Type: text/plain" - ] - - headers = "\r\n".join(headers) - body = "\n".join(msg) - - body = body.encode('utf-8') - - sess.sendmail('tkeefer@gmail.com', ['tkeefer@gmail.com', 'sdunavan@gmail.com'], "%s\r\n\r\n%s" %(headers, body)) - - sess.quit() - -if __name__ == "__main__": - (opts, args) = init_options() - run(opts) - From c8c61d2aa9e0660ee3dcd50659f007113a4ea4f0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 24 Apr 2013 14:36:25 -0700 Subject: [PATCH 085/218] update error handing --- Changes | 4 +- ebaysdk/__init__.py | 320 ++++++++++++++++++++++++++++++++++++-------- ebaysdk/_version.py | 2 +- samples/finding.py | 15 ++- samples/mytest.py | 88 ++++++++++++ samples/parallel.py | 17 ++- samples/shopping.py | 14 +- samples/trading.py | 94 ++++++++++++- 8 files changed, 473 insertions(+), 81 deletions(-) create mode 100644 samples/mytest.py diff --git a/Changes b/Changes index 225d8da..0ef7a9f 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Changes for ebaysdk - +0.1.9 +- update error handling +- add VerifyAddItem sample call - update license in setup.py 0.1.8 Tue Apr 23 15:33:02 PDT 2013 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 4a5c922..56fe870 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -205,6 +205,7 @@ def execute(self, verb, data): if self._response_content: self.process() + self.error() return self @@ -377,44 +378,33 @@ def _get_resp_body_errors(self): if self._resp_body_errors and len(self._resp_body_errors) > 0: return self._resp_body_errors - err = [] + errors = [] + if self.verb is None: - return err + return errors + dom = self.response_dom() if dom is None: - return err - - for e in dom.getElementsByTagName("Errors"): - - if e.getElementsByTagName('ErrorClassification'): - err.append('Class: %s' % nodeText(e.getElementsByTagName('ErrorClassification')[0])) + return errors - if e.getElementsByTagName('SeverityCode'): - severity = nodeText(e.getElementsByTagName('SeverityCode')[0]) - err.append('Severity: %s' % severity) - - if e.getElementsByTagName('ErrorCode'): - err.append('Code: %s' % nodeText(e.getElementsByTagName('ErrorCode')[0])) - - if e.getElementsByTagName('ShortMessage'): - err.append('%s ' % nodeText(e.getElementsByTagName('ShortMessage')[0])) - - if e.getElementsByTagName('LongMessage'): - err.append('%s ' % nodeText(e.getElementsByTagName('LongMessage')[0])) - - self._resp_body_errors = err - return err + return [] def error(self): "Builds and returns the api error message." - err = [] + error_array = [] if self._response_error: - err.append(self._response_error) - err.extend(self._get_resp_body_errors()) + error_array.append(self._response_error) + + error_array.extend(self._get_resp_body_errors()) + + if len(error_array) > 0: + error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - if len(err) > 0: - return "%s: %s" % (self.verb, ", ".join(err)) + if self.api_config.get('warnings', False): + sys.stderr.write(error_string) + + return error_string return "" @@ -506,26 +496,69 @@ def _build_request_xml(self): return xml - def error(self): - "Builds and returns the api error message." + def _get_resp_body_errors(self): + """Parses the response content to pull errors. - err = [] - if self._response_error: - err.append(self._response_error) + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ - try: - if self.response_dict().ack == 'Failure': - err.append(self.response_dict().errorMessage.error.message) - elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings'): - sys.stderr.write(self.response_dict().errorMessage.error.message) + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors - except AttributeError: - pass + errors = [] + warnings = [] - if len(err) > 0: - return "%s: %s" % (self.verb, ", ".join(err)) + if self.verb is None: + return errors - return "" + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("Errors"): + eSeverity = None + eClass = None + eShortMsg = None + eLongMsg = None + eCode = None + + if e.getElementsByTagName('SeverityCode'): + eSeverity = nodeText(e.getElementsByTagName('SeverityCode')[0]) + + if e.getElementsByTagName('ErrorClassification'): + eClass = nodeText(e.getElementsByTagName('ErrorClassification')[0]) + + if e.getElementsByTagName('ErrorCode'): + eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) + + if e.getElementsByTagName('ShortMessage'): + eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) + + if e.getElementsByTagName('LongMessage'): + eLongMsg = nodeText(e.getElementsByTagName('LongMessage')[0]) + + msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ + % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + + if self.api_config['warnings'] and len(warnings) > 0: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + if self.response_dict().Ack == 'Failure': + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + + return [] class html(ebaybase): @@ -773,6 +806,94 @@ def _build_request_xml(self): xml += "" return xml + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("Errors"): + eSeverity = None + eClass = None + eShortMsg = None + eLongMsg = None + eCode = None + + if e.getElementsByTagName('SeverityCode'): + eSeverity = nodeText(e.getElementsByTagName('SeverityCode')[0]) + + if e.getElementsByTagName('ErrorClassification'): + eClass = nodeText(e.getElementsByTagName('ErrorClassification')[0]) + + if e.getElementsByTagName('ErrorCode'): + eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) + + if e.getElementsByTagName('ShortMessage'): + eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) + + if e.getElementsByTagName('LongMessage'): + eLongMsg = nodeText(e.getElementsByTagName('LongMessage')[0]) + + msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ + % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + + if self.api_config['warnings'] and len(warnings) > 0: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + if self.response_dict().Ack == 'Failure': + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + + return [] + + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def error(self): + "Builds and returns the api error message." + + error_array = [] + if self._response_error: + error_array.append(self._response_error) + + error_array.extend(self._get_resp_body_errors()) + + if len(error_array) > 0: + error_string = "%s: %s" % (self.verb, ", ".join(error_array)) + + return error_string + + return "" class finding(ebaybase): """Finding API class @@ -873,24 +994,109 @@ def _build_request_xml(self): return xml + def _process_http_request(self): + """Final processing for the HTTP response. + Returns the response data. + """ + + self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) + + if self._response_code == 0: + return None + + self._response_status = self._response_header.getvalue().splitlines()[0] + self._response_reason = re.match(r'^HTTP.+? +\d+ +(.*) *$', self._response_status).group(1) + response_data = self._response_body.getvalue() + + self._response_header = None + self._response_body = None + self._curl.close() + + if self._response_code != 200: + self._response_error = "%s" % self._response_reason + return response_data + #raise Exception('%s' % self._response_reason) + else: + return response_data + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("error"): + eSeverity = None + eDomain = None + eMsg = None + eId = None + + if e.getElementsByTagName('severity'): + eSeverity = nodeText(e.getElementsByTagName('severity')[0]) + + if e.getElementsByTagName('domain'): + eDomain = nodeText(e.getElementsByTagName('domain')[0]) + + if e.getElementsByTagName('errorId'): + eId = nodeText(e.getElementsByTagName('errorId')[0]) + + if e.getElementsByTagName('message'): + eMsg = nodeText(e.getElementsByTagName('message')[0]) + + msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ + % (eDomain, eSeverity, eId, eMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + + if self.api_config['warnings'] and len(warnings) > 0: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + try: + if self.response_dict().ack == 'Success' and len(errors) > 0: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + elif len(errors) > 0: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + except AttributeError: + pass + + return [] + def error(self): "Builds and returns the api error message." - err = [] + error_array = [] if self._response_error: - err.append(self._response_error) + error_array.append(self._response_error) - try: - if self.response_dict().ack == 'Failure': - err.append(self.response_dict().errorMessage.error.message) - elif self.response_dict().ack == 'Warning' and self.api_config.get('warnings', False): - sys.stderr.write(self.response_dict().errorMessage.error.message) + error_array.extend(self._get_resp_body_errors()) - except AttributeError: - pass + if len(error_array) > 0: + error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - if len(err) > 0: - return "%s: %s" % (self.verb, ", ".join(err)) + return error_string return "" @@ -1104,10 +1310,10 @@ def execute_multi(self, calls, timeout): else: if request._response_content: request.process() - if request._response_error: - self._errors.append(request._response_error) - self._errors.extend(request._get_resp_body_errors()) + error_string = request.error() + if error_string: + self._errors.append(error_string) multi.close() diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index a304c28..4c0ed09 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -5,4 +5,4 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -__version__ = "0.1.8" +__version__ = "0.1.9" diff --git a/samples/finding.py b/samples/finding.py index d9bbadf..233c56c 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -1,4 +1,4 @@ - +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation Authored by: Tim Keefer @@ -34,8 +34,14 @@ def init_options(): def run(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsAdvanced', {'keywords': 'python'}) + api = finding(siteid='EBAY-NLBE', debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + + api.execute('findItemsAdvanced', { + 'keywords': 'python', + 'affiliate': {'trackingId': 1}, + 'sortOrder': 'CountryDescending', + }) if api.error(): raise Exception(api.error()) @@ -47,8 +53,7 @@ def run(opts): print "Response DOM: %s" % api.response_dom() dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] - + print "Response dictionary: %s..." % dictstr[:250] def run2(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) diff --git a/samples/mytest.py b/samples/mytest.py new file mode 100644 index 0000000..86d48b1 --- /dev/null +++ b/samples/mytest.py @@ -0,0 +1,88 @@ +from ebaysdk.utils import xml2dict + +xml = """ + + + ABC...123 + + en_US + High + + Harry Potter and the Philosopher's Stone + + This is the first book in the Harry Potter series. In excellent condition! + + + 377 + + 1.0 + true + US + USD + 3 + Days_7 + Chinese + PayPal + magicalbookseller@yahoo.com + + http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007 + + 95125 + 1 + + ReturnsAccepted + MoneyBack + Days_30 + If you are not satisfied, return the book for refund. + Buyer + + + Flat + + 1 + USPSMedia + 2.50 + + + US + + +""" + +data = { + "Item": { + "Title": "Harry Potter and the Philosopher's Stone", + "Description": "This is the first book in the Harry Potter series. In excellent condition!", + "PrimaryCategory": { "CategoryID": "377" }, + "StartPrice": "1.0", + "CategoryMappingAllowed": "true", + "Condition": "3000", + "Country": "US", + "Currency": "USD", + "DispatchTimeMax": "3", + "ListingDuration": "Days_7", + "ListingType": "Chinese", + "PaymentMethods": "PayPal", + "PayPalEmailAddress": "magicalbookseller@yahoo.com", + "PictureDetails": { "PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007" }, + "PostalCode": "95125", + "Quantity": "1", + "ReturnPolicy": { + "ReturnsAcceptedOption": "ReturnsAccepted", + "RefundOption": "MoneyBack", + "ReturnsWithinOption": "Days_30", + "Description": "If you are not satisfied, return the book for refund.", + "ShippingCostPaidByOption": "Buyer" + }, + "ShippingDetails": { + "ShippingType": "Flat", + "ShippingServiceOptions": { + "ShippingServicePriority": "1", + "ShippingService": "USPSMedia", + "ShippingServiceCost": "2.50" + } + }, + "Site": "US" + } +} +print "%s" % xml2dict().fromstring(xml) diff --git a/samples/parallel.py b/samples/parallel.py index efe8a16..d760c54 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -1,18 +1,19 @@ - +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -import os, sys +import os +import sys from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) -import ebaysdk from ebaysdk import finding, html, parallel + def init_options(): usage = "usage: %prog [options]" parser = OptionParser(usage=usage) @@ -30,12 +31,13 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args + def run(opts): p = parallel() apis = [] - api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api1.execute('findItemsAdvanced', {'keywords': 'python'}) apis.append(api1) @@ -43,18 +45,16 @@ def run(opts): api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') apis.append(api4) - api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api2.execute('findItemsAdvanced', {'keywords': 'perl'}) apis.append(api2) - api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api3.execute('findItemsAdvanced', {'keywords': 'php'}) apis.append(api3) p.wait() - print "Parallel example for SDK version %s" % ebaysdk.get_version() - if p.error(): raise Exception(p.error()) @@ -71,4 +71,3 @@ def run(opts): if __name__ == "__main__": (opts, args) = init_options() run(opts) - diff --git a/samples/shopping.py b/samples/shopping.py index 0786b34..5f466f9 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -1,11 +1,12 @@ - +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -import os, sys +import os +import sys from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -13,6 +14,7 @@ import ebaysdk from ebaysdk import shopping + def init_options(): usage = "usage: %prog [options]" parser = OptionParser(usage=usage) @@ -20,7 +22,7 @@ def init_options(): parser.add_option("-d", "--debug", action="store_true", dest="debug", default=False, help="Enabled debugging [default: %default]") - parser.add_option("-y", "--yaml",# + parser.add_option("-y", "--yaml", dest="yaml", default='ebay.yaml', help="Specifies the name of the YAML defaults file. [default: %default]") parser.add_option("-a", "--appid", @@ -30,8 +32,10 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args + def run(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) print "Shopping samples for SDK version %s" % ebaysdk.get_version() @@ -54,4 +58,4 @@ def run(opts): if __name__ == "__main__": (opts, args) = init_options() - run(opts) \ No newline at end of file + run(opts) diff --git a/samples/trading.py b/samples/trading.py index ffd48ed..8c5f9c2 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -1,11 +1,12 @@ - +# -*- coding: utf-8 -*- ''' © 2012-2013 eBay Software Foundation Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -import os, sys +import os +import sys from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -13,6 +14,7 @@ import ebaysdk from ebaysdk import trading + def init_options(): usage = "usage: %prog [options]" parser = OptionParser(usage=usage) @@ -36,6 +38,7 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args + def run(opts): api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, certid=opts.certid, devid=opts.devid) @@ -58,8 +61,93 @@ def run(opts): print "Response dictionary: %s..." % dictstr[:150] print api.response_dict().Charity.Name - print api.response_content() + #print api.response_content() + + +def feedback(opts): + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + api.execute('GetFeedback', {'UserID': 'tim0th3us'}) + + if api.error(): + raise Exception(api.error()) + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + if int(api.response_dict().FeedbackScore) > 50: + print "Doing good!" + else: + print "Sell more, buy more.." + + #import pprint + #pprint.pprint(api.response_dict()) + +def addItem(opts): + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + myitem = { + "Item": { + "Title": "Harry Potter and the Philosopher's Stone", + "Description": "This is the first book in the Harry Potter series. In excellent condition!", + "PrimaryCategory": {"CategoryID": "377"}, + "StartPrice": "1.0", + "CategoryMappingAllowed": "true", + "Country": "US", + "ConditionID": "3000", + "Currency": "USD", + "DispatchTimeMax": "3", + "ListingDuration": "Days_7", + "ListingType": "Chinese", + "PaymentMethods": "PayPal", + "PayPalEmailAddress": "tkeefdddder@gmail.com", + "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, + "PostalCode": "95125", + "Quantity": "1", + "ReturnPolicy": { + "ReturnsAcceptedOption": "ReturnsAccepted", + "RefundOption": "MoneyBack", + "ReturnsWithinOption": "Days_30", + "Description": "If you are not satisfied, return the book for refund.", + "ShippingCostPaidByOption": "Buyer" + }, + "ShippingDetails": { + "ShippingType": "Flat", + "ShippingServiceOptions": { + "ShippingServicePriority": "1", + "ShippingService": "USPSMedia", + "ShippingServiceCost": "2.50" + } + }, + "Site": "US" + } + } + + print "Trading samples for SDK version %s" % ebaysdk.get_version() + + api.execute('VerifyAddItem', myitem) + + if api.error(): + raise Exception(api.error()) + + print api.warnings() + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] if __name__ == "__main__": (opts, args) = init_options() run(opts) + feedback(opts) + addItem(opts) From efd14ee4c831e30f9cfaa4fb7d1803ef77a4c5a9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 24 Apr 2013 14:39:54 -0700 Subject: [PATCH 086/218] bump version --- Changes | 2 +- ebaysdk/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 0ef7a9f..23a52d4 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Changes for ebaysdk -0.1.9 +0.1.9 Wed Apr 24 14:38:40 PDT 2013 - update error handling - add VerifyAddItem sample call - update license in setup.py diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index 4c0ed09..86c4251 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -5,4 +5,4 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -__version__ = "0.1.9" +__version__ = "0.1.10" From e37c94d4a582c577e50142d39f8984e2c1f9bbf2 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 24 Apr 2013 18:11:25 -0700 Subject: [PATCH 087/218] remove test file --- samples/mytest.py | 88 ----------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 samples/mytest.py diff --git a/samples/mytest.py b/samples/mytest.py deleted file mode 100644 index 86d48b1..0000000 --- a/samples/mytest.py +++ /dev/null @@ -1,88 +0,0 @@ -from ebaysdk.utils import xml2dict - -xml = """ - - - ABC...123 - - en_US - High - - Harry Potter and the Philosopher's Stone - - This is the first book in the Harry Potter series. In excellent condition! - - - 377 - - 1.0 - true - US - USD - 3 - Days_7 - Chinese - PayPal - magicalbookseller@yahoo.com - - http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007 - - 95125 - 1 - - ReturnsAccepted - MoneyBack - Days_30 - If you are not satisfied, return the book for refund. - Buyer - - - Flat - - 1 - USPSMedia - 2.50 - - - US - - -""" - -data = { - "Item": { - "Title": "Harry Potter and the Philosopher's Stone", - "Description": "This is the first book in the Harry Potter series. In excellent condition!", - "PrimaryCategory": { "CategoryID": "377" }, - "StartPrice": "1.0", - "CategoryMappingAllowed": "true", - "Condition": "3000", - "Country": "US", - "Currency": "USD", - "DispatchTimeMax": "3", - "ListingDuration": "Days_7", - "ListingType": "Chinese", - "PaymentMethods": "PayPal", - "PayPalEmailAddress": "magicalbookseller@yahoo.com", - "PictureDetails": { "PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007" }, - "PostalCode": "95125", - "Quantity": "1", - "ReturnPolicy": { - "ReturnsAcceptedOption": "ReturnsAccepted", - "RefundOption": "MoneyBack", - "ReturnsWithinOption": "Days_30", - "Description": "If you are not satisfied, return the book for refund.", - "ShippingCostPaidByOption": "Buyer" - }, - "ShippingDetails": { - "ShippingType": "Flat", - "ShippingServiceOptions": { - "ShippingServicePriority": "1", - "ShippingService": "USPSMedia", - "ShippingServiceCost": "2.50" - } - }, - "Site": "US" - } -} -print "%s" % xml2dict().fromstring(xml) From dbcc8de6fe8d4179bce04a19d102de5620bc93f8 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 25 Apr 2013 14:47:40 -0700 Subject: [PATCH 088/218] update samples, modify SOA class --- Changes | 4 ++ ebaysdk/__init__.py | 2 +- samples/trading.py | 109 ++++++++++++++++++++++++++++++++------------ 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/Changes b/Changes index 23a52d4..76d059c 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Changes for ebaysdk +0.1.10 +- update SOA class default value +- add more trading api samples + 0.1.9 Wed Apr 24 14:38:40 PDT 2013 - update error handling - add VerifyAddItem sample call diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 56fe870..e96ebfc 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1112,7 +1112,7 @@ def __init__(self, app_config=None, site_id='EBAY-US', debug=False): 'request_encoding': 'XML', 'response_encoding': 'XML', 'message_protocol': 'SOAP12', - 'soap_env_str': 'http://www.ebay.com/marketplace/fundraising/v1/services', + 'soap_env_str': '', # http://www.ebay.com/marketplace/fundraising/v1/services', } ph = None diff --git a/samples/trading.py b/samples/trading.py index 8c5f9c2..05ab1fe 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -7,6 +7,8 @@ import os import sys +import datetime +import json from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -39,17 +41,12 @@ def init_options(): return opts, args -def run(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid) +def dump(api, full=False): - api.execute('GetCharities', {'CharityID': 3897}) - - # checkfor errors - if api.error(): - raise Exception(api.error()) + print "\n" - print "Trading samples for SDK version %s" % ebaysdk.get_version() + if api.warnings(): + print "Warnings" + api.warnings() if api.response_content(): print "Call Success: %s in length" % len(api.response_content()) @@ -57,11 +54,26 @@ def run(opts): print "Response code: %s" % api.response_code() print "Response DOM: %s" % api.response_dom() - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] + if full: + print api.response_content() + print(json.dumps(api.response_dict(), indent=2)) + else: + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] + + +def run(opts): + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid) + + api.execute('GetCharities', {'CharityID': 3897}) + + if api.error(): + raise Exception(api.error()) + + dump(api) print api.response_dict().Charity.Name - #print api.response_content() def feedback(opts): @@ -73,21 +85,17 @@ def feedback(opts): if api.error(): raise Exception(api.error()) - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) - - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + dump(api) if int(api.response_dict().FeedbackScore) > 50: print "Doing good!" else: print "Sell more, buy more.." - #import pprint - #pprint.pprint(api.response_dict()) +def verifyAddItem(opts): + """http://www.utilities-online.info/xmltojson/#.UXli2it4avc + """ -def addItem(opts): api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, certid=opts.certid, devid=opts.devid, warnings=False) @@ -128,26 +136,67 @@ def addItem(opts): } } - print "Trading samples for SDK version %s" % ebaysdk.get_version() - api.execute('VerifyAddItem', myitem) if api.error(): raise Exception(api.error()) - print api.warnings() + dump(api) - if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() +def uploadPicture(opts): + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True) + + pictureData = { + "WarningLevel": "High", + "ExternalPictureURL": "http://developer.ebay.com/DevZone/XML/docs/images/hp_book_image.jpg", + "PictureName": "WorldLeaders" + } + + api.execute('UploadSiteHostedPictures', pictureData) + + if api.error(): + raise Exception(api.error()) + + dump(api) + + +def memberMessages(opts): + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True) + + now = datetime.datetime.now() + + memberData = { + "WarningLevel": "High", + "MailMessageType": "All", + # "MessageStatus": "Unanswered", + "StartCreationTime": now - datetime.timedelta(days=60), + "EndCreationTime": now, + "Pagination": { + "EntriesPerPage": "5", + "PageNumber": "1" + } + } + + api.execute('GetMemberMessages', memberData) + + dump(api) + + for m in api.response_dict().MemberMessage.MemberMessageExchange: + print "%s: %s" % (m.CreationDate, m.Question.Subject[:50]) - dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] if __name__ == "__main__": (opts, args) = init_options() + + print "Trading API Samples for version %s" % ebaysdk.get_version() + run(opts) feedback(opts) - addItem(opts) + verifyAddItem(opts) + uploadPicture(opts) + memberMessages(opts) From a979582014439d91293fd839dcf747dfa3b402a7 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 25 Apr 2013 20:02:32 -0700 Subject: [PATCH 089/218] add to shopping samples --- samples/shopping.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/samples/shopping.py b/samples/shopping.py index 5f466f9..d845696 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -7,6 +7,7 @@ import os import sys +import json from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -33,6 +34,26 @@ def init_options(): return opts, args +def dump(api, full=False): + + print "\n" + + if api.warnings(): + print "Warnings" + api.warnings() + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + if full: + print api.response_content() + print(json.dumps(api.response_dict(), indent=2)) + else: + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] + def run(opts): api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) @@ -56,6 +77,49 @@ def run(opts): for item in api.response_dict().ItemArray.Item: print item.Title + +def popularSearches(opts): + + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + + choice = True + + while choice: + choice = raw_input('Search: ') + + if choice == 'quit': + break + + mySearch = { + # "CategoryID": " string ", + # "IncludeChildCategories": " boolean ", + "MaxKeywords": 10, + "QueryKeywords": choice, + } + + api.execute('FindPopularSearches', mySearch) + + #dump(api, full=True) + + print "Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches + + for term in api.response_dict().PopularSearchResult.AlternativeSearches.split(';')[:3]: + api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) + + print "Term: %s" % term + + try: + for item in api.response_dict().ItemArray.Item: + print item.Title + except AttributeError: + pass + + # dump(api) + + print "\n" + if __name__ == "__main__": (opts, args) = init_options() run(opts) + popularSearches(opts) From bf5fa76179772ec7353b626add2372f46de4f885 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 25 Apr 2013 21:31:14 -0700 Subject: [PATCH 090/218] add merchandising API class --- Changes | 1 + ebaysdk/__init__.py | 66 +++++++++++++++++++++++++++++++++++- samples/merchandising.py | 72 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 samples/merchandising.py diff --git a/Changes b/Changes index 76d059c..2835fa8 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ Changes for ebaysdk 0.1.10 +- added Merchandising API class - update SOA class default value - add more trading api samples diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index e96ebfc..c26563a 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -496,6 +496,15 @@ def _build_request_xml(self): return xml + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + def _get_resp_body_errors(self): """Parses the response content to pull errors. @@ -1084,6 +1093,15 @@ def _get_resp_body_errors(self): return [] + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + def error(self): "Builds and returns the api error message." @@ -1101,6 +1119,52 @@ def error(self): return "" +class merchandising(finding): + """Shopping API class + + API documentation: + http://developer.ebay.com/products/merchandising/ + + Supported calls: + getMostWatchedItems + getSimilarItems + getTopSellingProducts + (all others, see API docs) + + Doctests: + >>> s = merchandising(config_file=os.environ.get('EBAY_YAML')) + >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) + >>> print s.response_obj().ack + Success + >>> print s.error() + + """ + + def __init__(self, **kwargs): + """Merchandising class constructor. + + Keyword arguments: + domain -- API endpoint (default: open.api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /MerchandisingService) + appid -- eBay application id + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 799) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ + finding.__init__(self, **kwargs) + + self.api_config['uri'] = '/MerchandisingService' + + class SOAService(ebaybase): "SOAP class." @@ -1112,7 +1176,7 @@ def __init__(self, app_config=None, site_id='EBAY-US', debug=False): 'request_encoding': 'XML', 'response_encoding': 'XML', 'message_protocol': 'SOAP12', - 'soap_env_str': '', # http://www.ebay.com/marketplace/fundraising/v1/services', + 'soap_env_str': '', # http://www.ebay.com/marketplace/fundraising/v1/services', } ph = None diff --git a/samples/merchandising.py b/samples/merchandising.py new file mode 100644 index 0000000..6af9242 --- /dev/null +++ b/samples/merchandising.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import sys +import json +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +import ebaysdk +from ebaysdk import merchandising + + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + + (opts, args) = parser.parse_args() + return opts, args + + +def dump(api, full=False): + + print "\n" + + if api.warnings(): + print "Warnings" + api.warnings() + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + if full: + print api.response_content() + print(json.dumps(api.response_dict(), indent=2)) + else: + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:150] + + +def run(opts): + api = merchandising(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + + api.execute('getMostWatchedItems', {'maxResults': 3}) + + if api.error(): + raise Exception(api.error()) + + dump(api) + + +if __name__ == "__main__": + (opts, args) = init_options() + run(opts) From 243c1aec005812d78c1055911aa1e223ec45c042 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 25 Apr 2013 21:41:23 -0700 Subject: [PATCH 091/218] Update README.rst --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 8222efd..43de879 100644 --- a/README.rst +++ b/README.rst @@ -20,6 +20,7 @@ SDK Classes * `Trading API Class`_ - secure, authenticated access to private eBay data. * `Finding API Class`_ - access eBay's next generation search capabilities. * `Shopping API Class`_ - performance-optimized, lightweight APIs for accessing public eBay data. +* `Merchandising API Class`_ - find items and products on eBay that provide good value or are otherwise popular with eBay buyers. * `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. * `Parallel Class`_ - SDK support for concurrent API calls. @@ -55,6 +56,7 @@ License .. _Trading API Class: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class .. _Finding API Class: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class .. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class +.. _Merchandising API Class: https://github.com/timotheus/ebaysdk-python/wiki/Merchandising-API-Class .. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class .. _eBay Developer Forums: https://www.x.com/developers/ebay/forums From 3d33bbff0803974c8fad5ccf0642f7d93961a992 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 25 Apr 2013 22:04:25 -0700 Subject: [PATCH 092/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 43de879..f4be520 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Welcome to the python ebaysdk ============================= -This SDK is a programmatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, & Trading APIs. +This SDK is a programmatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. Quick Example:: From 1547213d9badeb13bf1d82411ed3df3232add00c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 1 May 2013 16:29:32 -0700 Subject: [PATCH 093/218] add error checking to html() class --- Changes | 2 ++ ebaysdk/__init__.py | 34 +++++++++++++++++++++-- samples/rss.py | 66 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 samples/rss.py diff --git a/Changes b/Changes index 2835fa8..5406af9 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Changes for ebaysdk 0.1.10 +- added error checking for html() class +- all class return response_data regardless of the http status code - added Merchandising API class - update SOA class default value - add more trading api samples diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index c26563a..2ece658 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -362,6 +362,7 @@ def _process_http_request(self): if self._response_code != 200: self._response_error = "%s" % self._response_reason + return response_data #raise Exception('%s' % self._response_reason) else: return response_data @@ -584,7 +585,7 @@ class html(ebaybase): >>> print title.toxml() <![CDATA[mytouch slide]]> >>> print h.error() - None + >>> h = html(method='POST', debug=False) >>> retval = h.execute('http://www.ebay.com/') >>> print h.response_content() != '' @@ -635,6 +636,7 @@ def execute(self, url, call_data=dict()): if self._response_content: self.process() + self.error() return self @@ -687,10 +689,38 @@ def _execute_http_request(self): self._response_error = "Exception: %s" % e raise Exception("%s" % e) + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + if self._response_error: + sys.stderr.write("%s: %s" % (self.url, self._response_error)) + errors.append(self._response_error) + + self._resp_body_errors = errors + + return errors + def error(self): "Builds and returns the api error message." - return self._response_error + error_array = [] + error_array.extend(self._get_resp_body_errors()) + + if len(error_array) > 0: + error_string = "%s: %s" % (self.url, ", ".join(error_array)) + return error_string + + return "" def _build_request_xml(self): "Builds and returns the request XML." diff --git a/samples/rss.py b/samples/rss.py new file mode 100644 index 0000000..40d888f --- /dev/null +++ b/samples/rss.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import sys +import smtplib +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +from ebaysdk import html + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + + (opts, args) = parser.parse_args() + return opts, args + + +def dump(api, full=False): + + print "\n" + + if api.response_content(): + print "Response Content: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + #print "Response soup: %s" % api.response_soup() + + if full: + print api.response_content() + #print(json.dumps(api.response_dict(), indent=2)) + else: + pass + #dictstr = "%s" % api.response_dict() + #print "Response dictionary: %s..." % dictstr[:150] + +def run404(opts): + api = html(debug=opts.debug) + + api.execute('http://feeds2.feedburner.com/feedburnerstatus') + + #if api.error(): + # print Exception(api.error()) + + dump(api) + + api.execute('http://feeds2.feedburner.com/feedburnerstatusd') + + if api.error(): + raise Exception(api.error()) + + dump(api) + +if __name__ == "__main__": + (opts, args) = init_options() + run404(opts) From ab2958847118e895bfc9ff4fccebbe31380d17e5 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 20 May 2013 10:13:11 -0700 Subject: [PATCH 094/218] add filtering to finding sample --- samples/finding.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/samples/finding.py b/samples/finding.py index 233c56c..7c52243 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -39,6 +39,12 @@ def run(opts): api.execute('findItemsAdvanced', { 'keywords': 'python', + 'itemFilter': [ + {'name': 'Condition', + 'value': 'Used'}, + {'name': 'LocatedIn', + 'value': 'GB'}, + ], 'affiliate': {'trackingId': 1}, 'sortOrder': 'CountryDescending', }) From f2b8c5ba9dc21a4fbc9041269eba036d829f637d Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 20 Jul 2013 17:51:13 -0700 Subject: [PATCH 095/218] store response error codes in an array when processing response. The reponse error codes can be accessed by the api.response_codes() accessor. --- Changes | 8 ++++ ebaysdk/__init__.py | 36 ++++++++++++++--- samples/finding.py | 1 + samples/trading.py | 96 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 5406af9..b6dcfc0 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,13 @@ Changes for ebaysdk +- store response error codes in an array when processing response. + The reponse error codes can be accessed by the api.response_codes() + accessor. + + if api.error(): + if 37 in api.response_codes(): + print "Invalid data" + 0.1.10 - added error checking for html() class - all class return response_data regardless of the http status code diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 2ece658..2789e4f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -174,6 +174,7 @@ def _reset(self): self._response_dict = None self._response_error = None self._resp_body_errors = [] + self._resp_codes = [] def do(self, verb, call_data=dict()): return self.execute(verb, call_data) @@ -497,6 +498,9 @@ def _build_request_xml(self): return xml + def response_codes(self): + return self._resp_codes + def warnings(self): warning_string = "" @@ -520,6 +524,7 @@ def _get_resp_body_errors(self): errors = [] warnings = [] + resp_codes = [] if self.verb is None: return errors @@ -543,6 +548,8 @@ def _get_resp_body_errors(self): if e.getElementsByTagName('ErrorCode'): eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) + if int(eCode) not in resp_codes: + resp_codes.append(int(eCode)) if e.getElementsByTagName('ShortMessage'): eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) @@ -560,6 +567,7 @@ def _get_resp_body_errors(self): self._resp_body_warnings = warnings self._resp_body_errors = errors + self._resp_codes = resp_codes if self.api_config['warnings'] and len(warnings) > 0: sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) @@ -724,12 +732,13 @@ def error(self): def _build_request_xml(self): "Builds and returns the request XML." - self.call_xml = self._to_xml(self.call_data) - xml = "" - xml += self.call_xml + if type(self.call_data) is str: + self.call_xml = self.call_data + else: + self.call_xml = urllib.urlencode(self.call_data) - return xml + return self.call_xml class trading(ebaybase): @@ -746,7 +755,7 @@ class trading(ebaybase): Doctests: >>> t = trading(config_file=os.environ.get('EBAY_YAML')) - >>> retval = t.execute('GetCharities', { 'CharityID': 3897 }) + >>> retval = t.execute('GetCharities', {'CharityID': 3897}) >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) @@ -754,6 +763,9 @@ class trading(ebaybase): Sunshine Kids Foundation >>> print t.error() + >>> retval2 = t.execute('VerifyAddItem', {}) + >>> print t.response_codes() + [10009] """ def __init__(self, **kwargs): @@ -859,6 +871,7 @@ def _get_resp_body_errors(self): errors = [] warnings = [] + resp_codes = [] if self.verb is None: return errors @@ -882,6 +895,8 @@ def _get_resp_body_errors(self): if e.getElementsByTagName('ErrorCode'): eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) + if int(eCode) not in resp_codes: + resp_codes.append(int(eCode)) if e.getElementsByTagName('ShortMessage'): eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) @@ -899,6 +914,7 @@ def _get_resp_body_errors(self): self._resp_body_warnings = warnings self._resp_body_errors = errors + self._resp_codes = resp_codes if self.api_config['warnings'] and len(warnings) > 0: sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) @@ -918,6 +934,9 @@ def warnings(self): return warning_string + def response_codes(self): + return self._resp_codes + def error(self): "Builds and returns the api error message." @@ -1072,6 +1091,7 @@ def _get_resp_body_errors(self): errors = [] warnings = [] + resp_codes = [] if self.verb is None: return errors @@ -1094,6 +1114,8 @@ def _get_resp_body_errors(self): if e.getElementsByTagName('errorId'): eId = nodeText(e.getElementsByTagName('errorId')[0]) + if int(eId) not in resp_codes: + resp_codes.append(int(eId)) if e.getElementsByTagName('message'): eMsg = nodeText(e.getElementsByTagName('message')[0]) @@ -1108,6 +1130,7 @@ def _get_resp_body_errors(self): self._resp_body_warnings = warnings self._resp_body_errors = errors + self._resp_codes = resp_codes if self.api_config['warnings'] and len(warnings) > 0: sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) @@ -1123,6 +1146,9 @@ def _get_resp_body_errors(self): return [] + def response_codes(self): + return self._resp_codes + def warnings(self): warning_string = "" diff --git a/samples/finding.py b/samples/finding.py index 7c52243..6fee341 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -61,6 +61,7 @@ def run(opts): dictstr = "%s" % api.response_dict() print "Response dictionary: %s..." % dictstr[:250] + def run2(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsByProduct', '53039031') diff --git a/samples/trading.py b/samples/trading.py index 05ab1fe..0f36fc1 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -103,7 +103,7 @@ def verifyAddItem(opts): "Item": { "Title": "Harry Potter and the Philosopher's Stone", "Description": "This is the first book in the Harry Potter series. In excellent condition!", - "PrimaryCategory": {"CategoryID": "377"}, + "PrimaryCategory": {"CategoryID": "377aaaaaa"}, "StartPrice": "1.0", "CategoryMappingAllowed": "true", "Country": "US", @@ -144,6 +144,62 @@ def verifyAddItem(opts): dump(api) +def verifyAddItemErrorCodes(opts): + """http://www.utilities-online.info/xmltojson/#.UXli2it4avc + """ + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + myitem = { + "Item": { + "Title": "Harry Potter and the Philosopher's Stone", + "Description": "This is the first book in the Harry Potter series. In excellent condition!", + "PrimaryCategory": {"CategoryID": "377aaaaaa"}, + "StartPrice": "1.0", + "CategoryMappingAllowed": "true", + "Country": "US", + "ConditionID": "3000", + "Currency": "USD", + "DispatchTimeMax": "3", + "ListingDuration": "Days_7", + "ListingType": "Chinese", + "PaymentMethods": "PayPal", + "PayPalEmailAddress": "tkeefdddder@gmail.com", + "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, + "PostalCode": "95125", + "Quantity": "1", + "ReturnPolicy": { + "ReturnsAcceptedOption": "ReturnsAccepted", + "RefundOption": "MoneyBack", + "ReturnsWithinOption": "Days_30", + "Description": "If you are not satisfied, return the book for refund.", + "ShippingCostPaidByOption": "Buyer" + }, + "ShippingDetails": { + "ShippingType": "Flat", + "ShippingServiceOptions": { + "ShippingServicePriority": "1", + "ShippingService": "USPSMedia", + "ShippingServiceCost": "2.50" + } + }, + "Site": "US" + } + } + + api.execute('VerifyAddItem', myitem) + + if api.error(): + + # traverse the DOM to look for error codes + for node in api.response_dom().getElementsByTagName('ErrorCode'): + print "error code: %s" % ebaysdk.nodeText(node) + + # check for invalid data - error code 37 + if 37 in api.response_codes(): + print "Invalid data in request" + def uploadPicture(opts): api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, @@ -189,6 +245,40 @@ def memberMessages(opts): for m in api.response_dict().MemberMessage.MemberMessageExchange: print "%s: %s" % (m.CreationDate, m.Question.Subject[:50]) +def getuser(opts): + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) + + api.execute('GetUser', {'UserID': 'biddergoat'}) + + dump(api, full=True) + +def categories(opts): + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) + + now = datetime.datetime.now() + + callData = { + 'DetailLevel': 'ReturnAll', + 'CategorySiteID': 101, + 'LevelLimit': 4, + } + + api.execute('GetCategories', callData) + + dump(api, full=True) + +''' +api = trading(domain='api.sandbox.ebay.com') +api.execute('GetCategories', { + 'DetailLevel': 'ReturnAll', + 'CategorySiteID': 101, + 'LevelLimit': 4, +}) +''' if __name__ == "__main__": (opts, args) = init_options() @@ -198,5 +288,9 @@ def memberMessages(opts): run(opts) feedback(opts) verifyAddItem(opts) + verifyAddItemErrorCodes(opts) uploadPicture(opts) memberMessages(opts) + categories(opts) + getuser(opts) + From 9d5432a885b320417f3010f00c62ae3bc5040b3f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 22 Jul 2013 20:45:32 -0700 Subject: [PATCH 096/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f4be520..ee02103 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Welcome to the python ebaysdk ============================= -This SDK is a programmatic inteface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. +This SDK is a programmatic interface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. Quick Example:: From 87c7f8220d379875de9572794f2407e8cc04209a Mon Sep 17 00:00:00 2001 From: xcash Date: Wed, 24 Jul 2013 10:29:44 +0200 Subject: [PATCH 097/218] added errors flag to api initialization issue #23 --- ebaysdk/__init__.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 2789e4f..717a263 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -403,7 +403,7 @@ def error(self): if len(error_array) > 0: error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - if self.api_config.get('warnings', False): + if self.api_config['errors']: sys.stderr.write(error_string) return error_string @@ -438,7 +438,8 @@ def __init__(self, **kwargs): domain -- API endpoint (default: open.api.ebay.com) config_file -- YAML defaults (default: ebay.yaml) debug -- debugging enabled (default: False) - warnings -- warnings enabled (default: False) + warnings -- warnings enabled (default: True) + errors -- errors enabled (default: True) uri -- API endpoint uri (default: /shopping) appid -- eBay application id siteid -- eBay country site id (default: 0 (US)) @@ -468,6 +469,7 @@ def __init__(self, **kwargs): # override yaml defaults with args sent to the constructor self.set_config('uri', '/shopping') self.set_config('warnings', True) + self.set_config('errors', True) self.set_config('https', False) self.set_config('siteid', 0) self.set_config('response_encoding', 'XML') @@ -573,7 +575,8 @@ def _get_resp_body_errors(self): sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) if self.response_dict().Ack == 'Failure': - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + if self.api_config['errors']: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) return errors return [] @@ -711,7 +714,8 @@ def _get_resp_body_errors(self): errors = [] if self._response_error: - sys.stderr.write("%s: %s" % (self.url, self._response_error)) + if self.api_config['errors']: + sys.stderr.write("%s: %s" % (self.url, self._response_error)) errors.append(self._response_error) self._resp_body_errors = errors @@ -808,6 +812,7 @@ def __init__(self, **kwargs): # override yaml defaults with args sent to the constructor self.set_config('uri', '/ws/api.dll') self.set_config('warnings', True) + self.set_config('errors', True) self.set_config('https', True) self.set_config('siteid', 0) self.set_config('response_encoding', 'XML') @@ -920,7 +925,8 @@ def _get_resp_body_errors(self): sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) if self.response_dict().Ack == 'Failure': - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + if self.api_config['errors']: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) return errors return [] @@ -1021,6 +1027,7 @@ def __init__(self, **kwargs): self.set_config('uri', '/services/search/FindingService/v1') self.set_config('https', False) self.set_config('warnings', True) + self.set_config('errors', True) self.set_config('siteid', 'EBAY-US') self.set_config('response_encoding', 'XML') self.set_config('request_encoding', 'XML') @@ -1136,10 +1143,11 @@ def _get_resp_body_errors(self): sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) try: - if self.response_dict().ack == 'Success' and len(errors) > 0: + if self.response_dict().ack == 'Success' and len(errors) > 0 and self.api_config['errors']: sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) elif len(errors) > 0: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) + if self.api_config['errors']: + sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) return errors except AttributeError: pass From 23471b25a75526202dfa7dfee6ba183b1191a91b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 24 Jul 2013 15:57:11 -0700 Subject: [PATCH 098/218] modify test --- ebaysdk/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 717a263..7454f46 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -767,8 +767,9 @@ class trading(ebaybase): Sunshine Kids Foundation >>> print t.error() - >>> retval2 = t.execute('VerifyAddItem', {}) - >>> print t.response_codes() + >>> t2 = trading(errors=False, config_file=os.environ.get('EBAY_YAML')) + >>> retval2 = t2.execute('VerifyAddItem', {}) + >>> print t2.response_codes() [10009] """ From c02aee60f37f94a0b90d5496f903400f55d3fb41 Mon Sep 17 00:00:00 2001 From: Shalabh Aggarwal Date: Tue, 3 Sep 2013 21:51:33 +0530 Subject: [PATCH 099/218] Call to prepare XML should be done only if data is present fixes #29 execute() should have data initialised with None Hence, if no data is present, we should not call for preparation of XML This would allow making calls like **GetTokenStatus**, which cannot be made with the current implementation. --- ebaysdk/__init__.py | 4 ++-- samples/trading.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 7454f46..e644906 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -193,12 +193,12 @@ def _to_xml(self, data): return xml - def execute(self, verb, data): + def execute(self, verb, data=None): "Executes the HTTP request." self.verb = verb - self.call_xml = self._to_xml(data) + self.call_xml = self._to_xml(data) if data else '' self.prepare() self._reset() diff --git a/samples/trading.py b/samples/trading.py index 0f36fc1..d2b418e 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -92,6 +92,19 @@ def feedback(opts): else: print "Sell more, buy more.." + +def getTokenStatus(opts): + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + api.execute('GetTokenStatus') + + if api.error(): + raise Exception(api.error()) + + dump(api) + + def verifyAddItem(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc """ From 9dc790ce009bd2157ca87bd7a481999ccf0de3ee Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 3 Sep 2013 09:51:23 -0700 Subject: [PATCH 100/218] update samples --- samples/trading.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/samples/trading.py b/samples/trading.py index d2b418e..ab368be 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -116,7 +116,7 @@ def verifyAddItem(opts): "Item": { "Title": "Harry Potter and the Philosopher's Stone", "Description": "This is the first book in the Harry Potter series. In excellent condition!", - "PrimaryCategory": {"CategoryID": "377aaaaaa"}, + "PrimaryCategory": {"CategoryID": "377"}, "StartPrice": "1.0", "CategoryMappingAllowed": "true", "Country": "US", @@ -255,10 +255,11 @@ def memberMessages(opts): dump(api) - for m in api.response_dict().MemberMessage.MemberMessageExchange: - print "%s: %s" % (m.CreationDate, m.Question.Subject[:50]) + if api.response_dict().MemberMessage: + for m in api.response_dict().MemberMessage.MemberMessageExchange: + print "%s: %s" % (m.CreationDate, m.Question.Subject[:50]) -def getuser(opts): +def getUser(opts): api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) @@ -305,5 +306,6 @@ def categories(opts): uploadPicture(opts) memberMessages(opts) categories(opts) - getuser(opts) + getUser(opts) + getTokenStatus(opts) From 1c20e0fcb1583ab17d6937b1f2354b72eeb6dcd8 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 5 Sep 2013 16:26:36 -0700 Subject: [PATCH 101/218] Update __init__.py bump trading version default to 837 --- ebaysdk/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index e644906..89b9526 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -825,8 +825,8 @@ def __init__(self, **kwargs): self.set_config('appid', None) self.set_config('devid', None) self.set_config('certid', None) - self.set_config('version', '648') - self.set_config('compatibility', '648') + self.set_config('version', '837') + self.set_config('compatibility', '837') def _build_request_headers(self): "Builds HTTP headers" From ec35b3dc79018b2e0fbf8dda2baebac8f990f51f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 5 Sep 2013 16:35:49 -0700 Subject: [PATCH 102/218] add test --- ebaysdk/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index e644906..deae697 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -64,6 +64,9 @@ class ebaybase(object): >>> d = { 'list': ['a', 'b', 'c']} >>> print dict2xml(d) abc + >>> l = ['a', 'b', 'c'] + >>> print list2xml(l) + [] """ def __init__(self, debug=False, method='GET', From e24ab79dffd2a987d417f451862debed3419e7bf Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 5 Sep 2013 16:38:59 -0700 Subject: [PATCH 103/218] modify sample yaml file, add getOrders sample --- ebay.yaml | 2 +- ebaysdk/__init__.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ebay.yaml b/ebay.yaml index 3533004..6556abe 100644 --- a/ebay.yaml +++ b/ebay.yaml @@ -12,7 +12,7 @@ api.sandbox.ebay.com: # Trading API - https://www.x.com/developers/ebay/products/trading-api api.ebay.com: - compatability: 719 + version: 719 appid: ENTER_YOUR_APPID_HERE certid: ENTER_YOUR_CERTID_HERE devid: ENTER_YOUR_DEVID_HERE diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 47f5bf8..89b9526 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -64,9 +64,6 @@ class ebaybase(object): >>> d = { 'list': ['a', 'b', 'c']} >>> print dict2xml(d) abc - >>> l = ['a', 'b', 'c'] - >>> print list2xml(l) - [] """ def __init__(self, debug=False, method='GET', From af7459a407bb4ef7c35530e162798a1618bb2205 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 5 Sep 2013 16:40:12 -0700 Subject: [PATCH 104/218] modify sample yaml file, add getOrders sample --- samples/trading.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/samples/trading.py b/samples/trading.py index ab368be..63939d0 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -267,6 +267,16 @@ def getUser(opts): api.execute('GetUser', {'UserID': 'biddergoat'}) dump(api, full=True) + + +def getOrders(opts): + + api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20) + + api.execute('GetOrders', {'NumberOfDays': 30}) + + dump(api, full=True) def categories(opts): @@ -307,5 +317,6 @@ def categories(opts): memberMessages(opts) categories(opts) getUser(opts) + getOrders(opts) getTokenStatus(opts) From 59f016d0c245a6b71fa7aab46c587fa29aa6b7f4 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 24 Oct 2013 11:17:16 -0700 Subject: [PATCH 105/218] fix key issue when errors arg is not defined when using the SOAService class --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 89b9526..b15d7b8 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -403,7 +403,7 @@ def error(self): if len(error_array) > 0: error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - if self.api_config['errors']: + if self.api_config.get('errors', True): sys.stderr.write(error_string) return error_string From f1dfa6de76ef79a392664da384bf86ae49a44865 Mon Sep 17 00:00:00 2001 From: Nikolay Derkach Date: Sun, 27 Oct 2013 16:49:31 +0100 Subject: [PATCH 106/218] ported to python3, multiple fixes --- ebaysdk/__init__.py | 145 +++++++++++++++++++++++---------------- ebaysdk/utils.py | 78 ++++++++++----------- ebaysdk/utils2.py | 18 ++--- samples/finding.py | 18 ++--- samples/merchandising.py | 16 ++--- samples/parallel.py | 8 +-- samples/rss.py | 8 +-- samples/shopping.py | 40 +++++------ samples/trading.py | 30 ++++---- setup.py | 2 +- 10 files changed, 194 insertions(+), 169 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 89b9526..7680828 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,11 +9,10 @@ import sys import re import traceback -import StringIO +import io import yaml import pycurl -import urllib -from types import DictType, ListType +import urllib.request, urllib.parse, urllib.error try: import simplejson as json @@ -21,7 +20,7 @@ import json from xml.dom.minidom import parseString -from BeautifulSoup import BeautifulStoneSoup +from bs4 import BeautifulStoneSoup from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict @@ -62,7 +61,7 @@ class ebaybase(object): Doctests: >>> d = { 'list': ['a', 'b', 'c']} - >>> print dict2xml(d) + >>> print(dict2xml(d, listnames={'': 'list'})) abc """ @@ -119,8 +118,8 @@ def yaml_defaults(self, config_file, domain): if os.path.exists(config_file): try: f = open(config_file, "r") - except IOError, e: - print "unable to open file %s" % e + except IOError as e: + print("unable to open file %s" % e) yData = yaml.load(f.read()) return yData.get(domain, {}) @@ -133,8 +132,8 @@ def yaml_defaults(self, config_file, domain): if os.path.exists(myfile): try: f = open(myfile, "r") - except IOError, e: - print "unable to open file %s" % e + except IOError as e: + print("unable to open file %s" % e) yData = yaml.load(f.read()) domain = self.api_config.get('domain', '') @@ -180,14 +179,14 @@ def do(self, verb, call_data=dict()): return self.execute(verb, call_data) def _to_xml(self, data): - "Converts a list of dictionary to XML and returns it." + "Converts a list or dictionary to XML and returns it." xml = '' - if type(data) == DictType: - xml = dict2xml(data, roottag='TRASHME') - elif type(data) == ListType: - xml = list2xml(data, roottag='TRASHME') + if type(data) == dict: + xml = dict2xml(data) + elif type(data) == list: + xml = list2xml(data) else: xml = data @@ -238,7 +237,7 @@ def response_soup(self): "Returns a BeautifulSoup object of the response." if not self._response_soup: - self._response_soup = BeautifulStoneSoup(unicode(self._response_content, encoding='utf-8')) + self._response_soup = BeautifulStoneSoup(str(self._response_content, encoding='utf-8')) return self._response_soup @@ -289,7 +288,7 @@ def _execute_http_request(self): # construct headers request_headers = self._build_request_headers() self._curl.setopt(pycurl.HTTPHEADER, [ - str('%s: %s' % (k, v)) for k, v in request_headers.items() + str('%s: %s' % (k, v)) for k, v in list(request_headers.items()) ]) # construct URL & post data @@ -310,14 +309,14 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = StringIO.StringIO() - self._response_body = StringIO.StringIO() + self._response_header = io.StringIO() + self._response_body = io.StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) - self._curl.setopt(pycurl.HEADERFUNCTION, self._response_header.write) - self._curl.setopt(pycurl.WRITEFUNCTION, self._response_body.write) + self._curl.setopt(pycurl.HEADERFUNCTION, self._write_header) + self._curl.setopt(pycurl.WRITEFUNCTION, self._write_body) if self.debug: sys.stderr.write("CURL Request: %s\n" % request_url) @@ -333,13 +332,14 @@ def _execute_http_request(self): try: self._curl.perform() return self._process_http_request() - except Exception, e: + except Exception as ee: + e = ee continue break raise Exception(e) - except Exception, e: + except Exception as e: self._response_error = "Exception: %s" % e raise Exception("%s" % e) @@ -410,6 +410,14 @@ def error(self): return "" + def _write_body(self, buf): + "Callback function invoked when body data is ready" + self._response_body.write(buf) + + def _write_header(self, buf): + "Callback function invoked when header data is ready" + self._response_header.write(buf) + class shopping(ebaybase): """Shopping API class @@ -425,9 +433,9 @@ class shopping(ebaybase): Doctests: >>> s = shopping(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - >>> print s.response_obj().Ack + >>> print(s.response_obj().Ack) Success - >>> print s.error() + >>> print(s.error()) """ @@ -480,7 +488,7 @@ def __init__(self, **kwargs): self.set_config('version', '799') if self.api_config['https'] and self.debug: - print "HTTPS is not supported on the Shopping API." + print("HTTPS is not supported on the Shopping API.") def _build_request_headers(self): return { @@ -588,20 +596,20 @@ class html(ebaybase): Doctests: >>> h = html() >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> print h.response_obj().rss.channel.ttl + >>> print(h.response_obj().rss.channel.ttl) 60 >>> title = h.response_dom().getElementsByTagName('title')[0] - >>> print nodeText( title ) + >>> print(nodeText(title)) mytouch slide - >>> print title.toxml() + >>> print(title.toxml()) <![CDATA[mytouch slide]]> - >>> print h.error() + >>> print(h.error()) >>> h = html(method='POST', debug=False) >>> retval = h.execute('http://www.ebay.com/') - >>> print h.response_content() != '' + >>> print(h.response_content() != '') True - >>> print h.response_code() + >>> print(h.response_code()) 200 """ @@ -635,7 +643,7 @@ def response_dict(self): return self._response_dict def execute(self, url, call_data=dict()): - "Excute method for the HTTP request." + "Execute method for the HTTP request." self.url = url self.call_data = call_data @@ -664,7 +672,7 @@ def _execute_http_request(self): request_url = self.url if self.call_data and self.method == 'GET': - request_url = request_url + '?' + urllib.urlencode(self.call_data) + request_url = request_url + '?' + urllib.parse.urlencode(self.call_data) elif self.method == 'POST': request_xml = self._build_request_xml() @@ -675,14 +683,14 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = StringIO.StringIO() - self._response_body = StringIO.StringIO() + self._response_header = io.StringIO() + self._response_body = io.StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) - self._curl.setopt(pycurl.HEADERFUNCTION, self._response_header.write) - self._curl.setopt(pycurl.WRITEFUNCTION, self._response_body.write) + self._curl.setopt(pycurl.HEADERFUNCTION, self._write_header) + self._curl.setopt(pycurl.WRITEFUNCTION, self._write_body) if self.debug: sys.stderr.write("CURL Request: %s\n" % request_url) @@ -696,7 +704,7 @@ def _execute_http_request(self): self._curl.perform() return self._process_http_request() - except Exception, e: + except Exception as e: self._response_error = "Exception: %s" % e raise Exception("%s" % e) @@ -714,7 +722,7 @@ def _get_resp_body_errors(self): errors = [] if self._response_error: - if self.api_config['errors']: + if self.api_config.get('errors', True): sys.stderr.write("%s: %s" % (self.url, self._response_error)) errors.append(self._response_error) @@ -740,7 +748,7 @@ def _build_request_xml(self): if type(self.call_data) is str: self.call_xml = self.call_data else: - self.call_xml = urllib.urlencode(self.call_data) + self.call_xml = urllib.parse.urlencode(self.call_data) return self.call_xml @@ -763,13 +771,13 @@ class trading(ebaybase): >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) - >>> print charity_name + >>> print(charity_name) Sunshine Kids Foundation - >>> print t.error() + >>> print(t.error()) >>> t2 = trading(errors=False, config_file=os.environ.get('EBAY_YAML')) >>> retval2 = t2.execute('VerifyAddItem', {}) - >>> print t2.response_codes() + >>> print(t2.response_codes()) [10009] """ @@ -975,14 +983,14 @@ class finding(ebaybase): >>> f = finding(config_file=os.environ.get('EBAY_YAML')) >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> error = f.error() - >>> print error + >>> print(error) >>> if len( error ) <= 0: - ... print f.response_obj().itemSearchURL != '' + ... print(f.response_obj().itemSearchURL != '') ... items = f.response_obj().searchResult.item - ... print len(items) - ... print f.response_dict().ack + ... print(len(items)) + ... print(f.response_dict().ack) True 100 Success @@ -1037,8 +1045,9 @@ def __init__(self, **kwargs): self.set_config('token', None) self.set_config('iaf_token', None) self.set_config('appid', None) - self.set_config('version', '1.0.0') + self.set_config('version', '1.12.0') self.set_config('compatibility', '1.0.0') + self.set_config('service', 'FindingService') def _build_request_headers(self): return { @@ -1199,9 +1208,9 @@ class merchandising(finding): Doctests: >>> s = merchandising(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) - >>> print s.response_obj().ack + >>> print(s.response_obj().ack) Success - >>> print s.error() + >>> print(s.error()) """ @@ -1228,6 +1237,26 @@ def __init__(self, **kwargs): finding.__init__(self, **kwargs) self.api_config['uri'] = '/MerchandisingService' + self.api_config['service'] = 'MerchandisingService' + + def _build_request_headers(self): + return { + "X-EBAY-API-VERSION": self.api_config.get('version', ''), + "EBAY-SOA-CONSUMER-ID": self.api_config.get('appid', ''), + "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": self.verb, + "X-EBAY-API-REQUEST-ENCODING": "XML", + "X-EBAY-SOA-SERVICE-NAME": self.api_config.get('service', ''), + "Content-Type": "text/xml" + } + + def _build_request_xml(self): + xml = "" + xml += "<" + self.verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" + xml += self.call_xml + xml += "" + + return xml class SOAService(ebaybase): @@ -1280,7 +1309,7 @@ def response_dict(self): verb = self.verb + 'Response' self._response_dict = mydict['Envelope']['Body'][verb] - except Exception, e: + except Exception as e: self._response_dict = mydict self._resp_body_errors.append("Error parsing SOAP response: %s" % e) @@ -1329,7 +1358,7 @@ def soapify(self, xml): xml_type = type(xml) if xml_type == dict: soap = {} - for k, v in xml.items(): + for k, v in list(xml.items()): soap['ser:%s' % (k)] = self.soapify(v) elif xml_type == list: soap = [] @@ -1352,15 +1381,15 @@ class parallel(object): >>> r4 = trading(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r4.execute('GetCharities', { 'CharityID': 3897 }) >>> p.wait() - >>> print p.error() + >>> print(p.error()) - >>> print r1.response_obj().rss.channel.ttl + >>> print(r1.response_obj().rss.channel.ttl) 60 - >>> print r2.response_dict().ack + >>> print(r2.response_dict().ack) Success - >>> print r3.response_obj().Ack + >>> print(r3.response_obj().Ack) Success - >>> print r4.response_obj().Ack + >>> print(r4.response_obj().Ack) Success """ @@ -1393,7 +1422,7 @@ def wait(self, timeout=20): self._errors.append("%s" % self._get_curl_http_error(request._curl)) self._requests = [] - except Exception, e: + except Exception as e: self._errors.append("Exception: %s" % e) traceback.print_exc() raise Exception("%s" % e) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index d30b51c..1fa6646 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET import re -from StringIO import StringIO +from io import StringIO class object_dict(dict): """object view of dict, you can @@ -49,7 +49,7 @@ def getvalue(self, item, value=None): return self.get(item, {}).get('value', value) def __getstate__(self): - return self.items() + return list(self.items()) def __setstate__(self, items): self.update(items) @@ -64,7 +64,7 @@ def _parse_node(self, node): # Save attrs and text, hope there will not be a child with same name if node.text: node_tree.value = node.text - for (k,v) in node.attrib.items(): + for (k,v) in list(node.attrib.items()): k,v = self._namespace_split(k, object_dict({'value':v})) node_tree[k] = v #Save childrens @@ -116,7 +116,7 @@ class Struct(object): """Emulate a cross over between a dict() and an object().""" def __init__(self, entries, default=None, nodefault=False): # ensure all keys are strings and nothing else - entries = dict([(str(x), y) for x, y in entries.items()]) + entries = dict([(str(x), y) for x, y in list(entries.items())]) self.__dict__.update(entries) self.__default = default self.__nodefault = nodefault @@ -192,9 +192,9 @@ def __contains__(self, item): """ return item in self.__dict__ - def __nonzero__(self): + def __bool__(self): """Returns whether the instance evaluates to False""" - return bool(self.items()) + return bool(list(self.items())) def has_key(self, item): """Emulate dict.has_key() functionality. @@ -214,7 +214,7 @@ def items(self): >>> obj.items() [('a', 'b')] """ - return [(k, v) for (k, v) in self.__dict__.items() if not k.startswith('_Struct__')] + return [(k, v) for (k, v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] def keys(self): """Emulate dict.keys() functionality. @@ -223,7 +223,7 @@ def keys(self): >>> obj.keys() ['a'] """ - return [k for (k, _v) in self.__dict__.items() if not k.startswith('_Struct__')] + return [k for (k, _v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] def values(self): """Emulate dict.values() functionality. @@ -232,10 +232,10 @@ def values(self): >>> obj.values() ['b'] """ - return [v for (k, v) in self.__dict__.items() if not k.startswith('_Struct__')] + return [v for (k, v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] def __repr__(self): - return "" % dict(self.items()) + return "" % dict(list(self.items())) def as_dict(self): """Return a dict representing the content of this struct.""" @@ -287,15 +287,13 @@ def make_struct(obj, default=None, nodefault=False): """ if type(obj) == type(Struct): return obj - if (not hasattr(obj, '__dict__')) and hasattr(obj, 'iterkeys'): - # this should be a dict + if type(obj) == dict: struc = Struct(obj, default, nodefault) # handle recursive sub-dicts - for key, val in obj.items(): + for key, val in list(obj.items()): setattr(struc, key, make_struct(val, default, nodefault)) return struc - elif hasattr(obj, '__delslice__') and hasattr(obj, '__getitem__'): - # + elif type(obj) == list: return [make_struct(v, default, nodefault) for v in obj] else: return obj @@ -308,22 +306,20 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): assert not isinstance(dictitem, list) if isinstance(dictitem, dict): - for (tag, child) in sorted(dictitem.iteritems()): + for (tag, child) in sorted(dictitem.items()): if isinstance(child, list): # iterate through the array and convert - listelem = ET.Element('') - listelem2 = ET.Element(tag) - parent.append(listelem) + listparent = ET.Element(tag if tag in listnames.keys() else '') + parent.append(listparent) for listchild in child: - elem = ET.Element(listnames.get(tag, listelem2.tag)) - listelem.append(elem) - _convert_dict_to_xml_recurse(elem, listchild, listnames) + item = ET.SubElement(listparent, listnames.get(tag, tag)) + _convert_dict_to_xml_recurse(item, listchild, listnames) else: elem = ET.Element(tag) parent.append(elem) _convert_dict_to_xml_recurse(elem, child, listnames) elif not dictitem is None: - parent.text = unicode(dictitem) + parent.text = str(dictitem) def dict2et(xmldict, roottag='data', listnames=None): @@ -333,8 +329,8 @@ def dict2et(xmldict, roottag='data', listnames=None): >>> data = {"nr": "xq12", "positionen": [{"m": 12}, {"m": 2}]} >>> root = dict2et(data) - >>> ET.tostring(root) - 'xq12122' + >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') + 'xq12122' Per default ecerything ins put in an enclosing '' element. Also per default lists are converted to collecitons of `` elements. But by provding a mapping between list names and element names, @@ -342,11 +338,11 @@ def dict2et(xmldict, roottag='data', listnames=None): >>> data = {"positionen": [{"m": 12}, {"m": 2}]} >>> root = dict2et(data, roottag='xml') - >>> ET.tostring(root) - '122' + >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') + '122' >>> root = dict2et(data, roottag='xml', listnames={'positionen': 'position'}) - >>> ET.tostring(root) + >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') '122' >>> data = {"kommiauftragsnr":2103839, "anliefertermin":"2009-11-25", "prioritaet": 7, @@ -357,7 +353,8 @@ def dict2et(xmldict, roottag='data', listnames=None): ... ]} >>> print ET.tostring(dict2et(data, 'kommiauftrag', - ... listnames={'positionen': 'position', 'versandeinweisungen': 'versandeinweisung'})) + ... listnames={'positionen': 'position', 'versandeinweisungen': 'versandeinweisung'}), + ... encoding="unicode").replace('<>', '').replace('','') ... # doctest: +SKIP ''' 2009-11-25 @@ -398,17 +395,17 @@ def list2et(xmllist, root, elementname): return basexml.find(root) -def dict2xml(datadict, roottag='TRASHME', listnames=None, pretty=False): +def dict2xml(datadict, roottag='', listnames=None, pretty=False): """ Converts a dictionary to an UTF-8 encoded XML string. See also dict2et() """ root = dict2et(datadict, roottag, listnames) - xml = to_string(root, pretty=pretty) xml = xml.replace('<>', '').replace('','') - return xml.replace('<%s>' % roottag, '').replace('' % roottag, '') - + return xml + + def list2xml(datalist, roottag, elementname, pretty=False): """Converts a list to an UTF-8 encoded XML string. @@ -418,7 +415,7 @@ def list2xml(datalist, roottag, elementname, pretty=False): return to_string(root, pretty=pretty) -def to_string(root, encoding='utf-8', pretty=False): +def to_string(root, pretty=False): """Converts an ElementTree to a string""" if pretty: @@ -426,10 +423,10 @@ def to_string(root, encoding='utf-8', pretty=False): tree = ET.ElementTree(root) fileobj = StringIO() - #fileobj.write('' % encoding) + # fileobj.write('' % encoding) if pretty: fileobj.write('\n') - tree.write(fileobj, 'utf-8') + tree.write(fileobj, 'unicode') return fileobj.getvalue() @@ -467,7 +464,7 @@ def test(): "artnr": "14695", "batchnr": "3104247"} xmlstr = dict2xml(data, roottag='warenzugang') - assert xmlstr == ('14695' + assert xmlstr == ('14695' '31042473104247-77') data = {"kommiauftragsnr": 2103839, @@ -481,10 +478,10 @@ def test(): "name1": "Uwe Zweihaus", "name2": "400424990", "name3": "", - u"strasse": u"Bahnhofstr. 2", + "strasse": "Bahnhofstr. 2", "land": "DE", "plz": "42499", - "ort": u"Hücksenwagen", + "ort": "Hücksenwagen", "positionen": [{"menge": 12, "artnr": "14640/XL", "posnr": 1}, @@ -499,7 +496,7 @@ def test(): "anweisung": "48h vor Anlieferung unter 0900-LOGISTIK avisieren"}, {"guid": "2103839-GuTi", "bezeichner": "abpackern140", - "anweisung": u"Paletten höchstens auf 140 cm Packen"}] + "anweisung": "Paletten höchstens auf 140 cm Packen"}] } xmlstr = dict2xml(data, roottag='kommiauftrag') @@ -529,7 +526,6 @@ def test(): "art": "paket"}]} xmlstr = dict2xml(data, roottag='rueckmeldung') - print xmlstr if __name__ == '__main__': import doctest diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index 020b95e..c48b9d0 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -50,7 +50,7 @@ def getvalue(self, item, value=None): return self.get(item, object_dict()).get('value', value) def __getstate__(self): - return self.items() + return list(self.items()) def __setstate__(self, items): self.update(items) @@ -65,7 +65,7 @@ def _parse_node(self, node): # Save attrs and text, hope there will not be a child with same name if node.text: node_tree.value = node.text - for (k,v) in node.attrib.items(): + for (k,v) in list(node.attrib.items()): k,v = self._namespace_split(k, object_dict({'value':v})) node_tree[k] = v #Save childrens @@ -130,7 +130,7 @@ def tostring(self,d): def dict2xml(self,map): if type(map) == object_dict or type(map) == dict: - for key, value in map.items(): + for key, value in list(map.items()): keyo = key if self.attributes: # FIXME: This assumes the attributes do not require encoding @@ -161,7 +161,7 @@ def dict2xml(self,map): return self.xml def encode(self,str1): - if type(str1) != str and type(str1) != unicode: + if type(str1) != str and type(str1) != str: str1 = str(str1) if self.encoding: str1 = str1.encode(self.encoding) @@ -186,13 +186,13 @@ def dict_to_xml(d, root_node_name, stream): stream.write('\n<' + root_node_name) attributes = StringIO() nodes = StringIO() - for item in d.items(): + for item in list(d.items()): key, value = item if isinstance(value, dict): dict_to_xml(value, key, nodes) elif isinstance(value, list): list_to_xml(key, value, nodes) - elif isinstance(value, str) or isinstance(value, unicode): + elif isinstance(value, str) or isinstance(value, str): attributes.write('\n %s="%s" ' % (key, value)) else: raise TypeError('sorry, we support only dicts, lists and strings') @@ -223,15 +223,15 @@ def list_to_dict(l, ignore_root = True): # like for x in l[2]: d = list_to_dict(x, False) - for k, v in d.iteritems(): - if not inside_dict.has_key(k): + for k, v in d.items(): + if k not in inside_dict: inside_dict[k] = [] inside_dict[k].append(v) ret = root_dict if ignore_root: - ret = root_dict.values()[0] + ret = list(root_dict.values())[0] return ret diff --git a/samples/finding.py b/samples/finding.py index 6fee341..e27de09 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -53,13 +53,13 @@ def run(opts): raise Exception(api.error()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:250] + print("Response dictionary: %s..." % dictstr[:250]) def run2(opts): @@ -70,16 +70,16 @@ def run2(opts): raise Exception(api.error()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] + print("Response dictionary: %s..." % dictstr[:50]) if __name__ == "__main__": - print "Finding samples for SDK version %s" % ebaysdk.get_version() + print("Finding samples for SDK version %s" % ebaysdk.get_version()) (opts, args) = init_options() run(opts) run2(opts) diff --git a/samples/merchandising.py b/samples/merchandising.py index 6af9242..e7b175f 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -36,23 +36,23 @@ def init_options(): def dump(api, full=False): - print "\n" + print("\n") if api.warnings(): - print "Warnings" + api.warnings() + print("Warnings" + api.warnings()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) if full: - print api.response_content() - print(json.dumps(api.response_dict(), indent=2)) + print(api.response_content()) + print((json.dumps(api.response_dict(), indent=2))) else: dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] + print("Response dictionary: %s..." % dictstr[:150]) def run(opts): diff --git a/samples/parallel.py b/samples/parallel.py index d760c54..6cdda9c 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -60,13 +60,13 @@ def run(opts): for api in apis: - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) dictstr = "%s" % api.response_dict() - print "Response dictionary: %s...\n" % dictstr[:50] + print("Response dictionary: %s...\n" % dictstr[:50]) if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/rss.py b/samples/rss.py index 40d888f..323633b 100644 --- a/samples/rss.py +++ b/samples/rss.py @@ -28,16 +28,16 @@ def init_options(): def dump(api, full=False): - print "\n" + print("\n") if api.response_content(): - print "Response Content: %s in length" % len(api.response_content()) + print("Response Content: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() + print("Response code: %s" % api.response_code()) #print "Response soup: %s" % api.response_soup() if full: - print api.response_content() + print(api.response_content()) #print(json.dumps(api.response_dict(), indent=2)) else: pass diff --git a/samples/shopping.py b/samples/shopping.py index d845696..0f777b0 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -36,46 +36,46 @@ def init_options(): def dump(api, full=False): - print "\n" + print("\n") if api.warnings(): - print "Warnings" + api.warnings() + print("Warnings" + api.warnings()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) if full: - print api.response_content() - print(json.dumps(api.response_dict(), indent=2)) + print(api.response_content()) + print((json.dumps(api.response_dict(), indent=2))) else: dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] + print("Response dictionary: %s..." % dictstr[:150]) def run(opts): api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - print "Shopping samples for SDK version %s" % ebaysdk.get_version() + print("Shopping samples for SDK version %s" % ebaysdk.get_version()) if api.error(): raise Exception(api.error()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:50] + print("Response dictionary: %s..." % dictstr[:50]) - print "Matching Titles:" + print("Matching Titles:") for item in api.response_dict().ItemArray.Item: - print item.Title + print(item.Title) def popularSearches(opts): @@ -86,7 +86,7 @@ def popularSearches(opts): choice = True while choice: - choice = raw_input('Search: ') + choice = input('Search: ') if choice == 'quit': break @@ -102,22 +102,22 @@ def popularSearches(opts): #dump(api, full=True) - print "Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches + print("Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches) for term in api.response_dict().PopularSearchResult.AlternativeSearches.split(';')[:3]: api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) - print "Term: %s" % term + print("Term: %s" % term) try: for item in api.response_dict().ItemArray.Item: - print item.Title + print(item.Title) except AttributeError: pass # dump(api) - print "\n" + print("\n") if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/trading.py b/samples/trading.py index 63939d0..5800140 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -43,23 +43,23 @@ def init_options(): def dump(api, full=False): - print "\n" + print("\n") if api.warnings(): - print "Warnings" + api.warnings() + print("Warnings" + api.warnings()) if api.response_content(): - print "Call Success: %s in length" % len(api.response_content()) + print("Call Success: %s in length" % len(api.response_content())) - print "Response code: %s" % api.response_code() - print "Response DOM: %s" % api.response_dom() + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) if full: - print api.response_content() - print(json.dumps(api.response_dict(), indent=2)) + print(api.response_content()) + print((json.dumps(api.response_dict(), indent=2))) else: dictstr = "%s" % api.response_dict() - print "Response dictionary: %s..." % dictstr[:150] + print("Response dictionary: %s..." % dictstr[:150]) def run(opts): @@ -73,7 +73,7 @@ def run(opts): dump(api) - print api.response_dict().Charity.Name + print(api.response_dict().Charity.Name) def feedback(opts): @@ -88,9 +88,9 @@ def feedback(opts): dump(api) if int(api.response_dict().FeedbackScore) > 50: - print "Doing good!" + print("Doing good!") else: - print "Sell more, buy more.." + print("Sell more, buy more..") def getTokenStatus(opts): @@ -207,11 +207,11 @@ def verifyAddItemErrorCodes(opts): # traverse the DOM to look for error codes for node in api.response_dom().getElementsByTagName('ErrorCode'): - print "error code: %s" % ebaysdk.nodeText(node) + print("error code: %s" % ebaysdk.nodeText(node)) # check for invalid data - error code 37 if 37 in api.response_codes(): - print "Invalid data in request" + print("Invalid data in request") def uploadPicture(opts): @@ -257,7 +257,7 @@ def memberMessages(opts): if api.response_dict().MemberMessage: for m in api.response_dict().MemberMessage.MemberMessageExchange: - print "%s: %s" % (m.CreationDate, m.Question.Subject[:50]) + print("%s: %s" % (m.CreationDate, m.Question.Subject[:50])) def getUser(opts): @@ -307,7 +307,7 @@ def categories(opts): if __name__ == "__main__": (opts, args) = init_options() - print "Trading API Samples for version %s" % ebaysdk.get_version() + print("Trading API Samples for version %s" % ebaysdk.get_version()) run(opts) feedback(opts) diff --git a/setup.py b/setup.py index 98bca9a..950ed04 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], - install_requires=['PyYaml', 'pycurl', 'Beautifulsoup'], + install_requires=['PyYaml', 'pycurl', 'beautifulsoup4'], test_suite='tests', long_description=long_desc, classifiers=[ From f1adcfa1029ef2006f8aa43c511f844f600c693c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 29 Oct 2013 15:38:48 -0700 Subject: [PATCH 107/218] fix issues when running with python 2.7 --- ebaysdk/__init__.py | 20 ++++++++++++-------- ebaysdk/utils.py | 9 ++++++--- samples/shopping.py | 6 ++++++ samples/trading.py | 8 +++++++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index d0f6493..0749bc1 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,10 +9,14 @@ import sys import re import traceback -import io import yaml import pycurl -import urllib.request, urllib.parse, urllib.error +from io import BytesIO as StringIO + +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode try: import simplejson as json @@ -309,8 +313,8 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = io.StringIO() - self._response_body = io.StringIO() + self._response_header = StringIO() + self._response_body = StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -672,7 +676,7 @@ def _execute_http_request(self): request_url = self.url if self.call_data and self.method == 'GET': - request_url = request_url + '?' + urllib.parse.urlencode(self.call_data) + request_url = request_url + '?' + urlencode(self.call_data) elif self.method == 'POST': request_xml = self._build_request_xml() @@ -683,8 +687,8 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = io.StringIO() - self._response_body = io.StringIO() + self._response_header = StringIO() + self._response_body = StringIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -748,7 +752,7 @@ def _build_request_xml(self): if type(self.call_data) is str: self.call_xml = self.call_data else: - self.call_xml = urllib.parse.urlencode(self.call_data) + self.call_xml = urlencode(self.call_data) return self.call_xml diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 1fa6646..2e80551 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET import re -from io import StringIO +from io import BytesIO as StringIO class object_dict(dict): """object view of dict, you can @@ -423,10 +423,13 @@ def to_string(root, pretty=False): tree = ET.ElementTree(root) fileobj = StringIO() - # fileobj.write('' % encoding) + + # asdf fileobj.write('' % encoding) + if pretty: fileobj.write('\n') - tree.write(fileobj, 'unicode') + + tree.write(fileobj, 'utf-8') return fileobj.getvalue() diff --git a/samples/shopping.py b/samples/shopping.py index 0f777b0..b111816 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -10,6 +10,11 @@ import json from optparse import OptionParser +try: + input = raw_input +except NameError: + pass + sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) import ebaysdk @@ -86,6 +91,7 @@ def popularSearches(opts): choice = True while choice: + choice = input('Search: ') if choice == 'quit': diff --git a/samples/trading.py b/samples/trading.py index 5800140..3f8f74b 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -9,6 +9,7 @@ import sys import datetime import json +import types from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -256,7 +257,12 @@ def memberMessages(opts): dump(api) if api.response_dict().MemberMessage: - for m in api.response_dict().MemberMessage.MemberMessageExchange: + messages = api.response_dict().MemberMessage.MemberMessageExchange + + if type(messages) != types.ListType: + messages = [ messages ] + + for m in messages: print("%s: %s" % (m.CreationDate, m.Question.Subject[:50])) def getUser(opts): From 2a06b450d0abd0934dadc75e29236c1cc32a141a Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 29 Oct 2013 16:16:54 -0700 Subject: [PATCH 108/218] add depracation warning when not using bs4 --- ebaysdk/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 0749bc1..a72102e 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -24,7 +24,12 @@ import json from xml.dom.minidom import parseString -from bs4 import BeautifulStoneSoup + +try: + from bs4 import BeautifulStoneSoup +except ImportError: + from BeautifulSoup import BeautifulStoneSoup + sys.stderr.write('You are using an old version of BeautifulSoup, upgrade to bs4') from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict From 8bff3b00feed415e8be42d37a35ed27cc22ddb1a Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 29 Oct 2013 16:22:08 -0700 Subject: [PATCH 109/218] update deprecation text --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index a72102e..752d170 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -29,7 +29,7 @@ from bs4 import BeautifulStoneSoup except ImportError: from BeautifulSoup import BeautifulStoneSoup - sys.stderr.write('You are using an old version of BeautifulSoup, upgrade to bs4') + sys.stderr.write('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict From 5fdb41aa1cd1df2345d672fd5fb0b606146dd94c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 30 Oct 2013 15:41:18 -0600 Subject: [PATCH 110/218] rework type check --- samples/trading.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/trading.py b/samples/trading.py index 3f8f74b..a200602 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -9,7 +9,6 @@ import sys import datetime import json -import types from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) @@ -259,7 +258,7 @@ def memberMessages(opts): if api.response_dict().MemberMessage: messages = api.response_dict().MemberMessage.MemberMessageExchange - if type(messages) != types.ListType: + if type(messages) != list: messages = [ messages ] for m in messages: From b56160b66c6ac57c9846cf8f25f920edad23646c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 5 Nov 2013 08:19:54 -0800 Subject: [PATCH 111/218] more py3 fixes --- ebaysdk/__init__.py | 14 +++++++------- ebaysdk/utils.py | 8 +++++--- ebaysdk/utils2.py | 8 +++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 752d170..204394c 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -11,7 +11,8 @@ import traceback import yaml import pycurl -from io import BytesIO as StringIO + +from io import BytesIO try: from urllib.parse import urlencode @@ -318,8 +319,8 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = StringIO() - self._response_body = StringIO() + self._response_header = BytesIO() + self._response_body = BytesIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -692,8 +693,8 @@ def _execute_http_request(self): self._curl.setopt(pycurl.URL, str(request_url)) self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._response_header = StringIO() - self._response_body = StringIO() + self._response_header = BytesIO() + self._response_body = BytesIO() self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) self._curl.setopt(pycurl.TIMEOUT, self.timeout) @@ -994,7 +995,6 @@ class finding(ebaybase): >>> error = f.error() >>> print(error) - >>> if len( error ) <= 0: ... print(f.response_obj().itemSearchURL != '') ... items = f.response_obj().searchResult.item @@ -1388,7 +1388,7 @@ class parallel(object): >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) >>> r4 = trading(parallel=p, config_file=os.environ.get('EBAY_YAML')) - >>> retval = r4.execute('GetCharities', { 'CharityID': 3897 }) + >>> retval = r4.execute('GetUser', {}) >>> p.wait() >>> print(p.error()) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 2e80551..f61d70f 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -8,8 +8,10 @@ import xml.etree.ElementTree as ET import re -from io import BytesIO as StringIO - +import sys + +from io import BytesIO + class object_dict(dict): """object view of dict, you can >>> a = object_dict() @@ -422,7 +424,7 @@ def to_string(root, pretty=False): indent(root) tree = ET.ElementTree(root) - fileobj = StringIO() + fileobj = BytesIO() # asdf fileobj.write('' % encoding) diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py index c48b9d0..c92b47d 100644 --- a/ebaysdk/utils2.py +++ b/ebaysdk/utils2.py @@ -12,7 +12,9 @@ import cElementTree as ET # for 2.4 import re - + +from io import BytesIO + class object_dict(dict): """object view of dict, you can >>> a = object_dict() @@ -184,8 +186,8 @@ def list_to_xml(name, l, stream): def dict_to_xml(d, root_node_name, stream): """ Transform a dict into a XML, writing to a stream """ stream.write('\n<' + root_node_name) - attributes = StringIO() - nodes = StringIO() + attributes = BytesIO() + nodes = BytesIO() for item in list(d.items()): key, value = item if isinstance(value, dict): From 77e33320a7a275228e2a10c7ce1cd18d029ab95b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 5 Nov 2013 16:15:35 -0800 Subject: [PATCH 112/218] fix error handling bug --- ebaysdk/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 204394c..d573a3d 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -634,8 +634,11 @@ def __init__(self, method='GET', **kwargs): timeout -- HTTP request timeout (default: 20) parallel -- ebaysdk parallel object """ - ebaybase.__init__(self, method=method, **kwargs) + + ebaybase.__init__(self, method=method, **kwargs) + self.api_config = dict() + def response_dom(self): "Returns the HTTP response dom." From 01184386c7e5397caaa54bfb52fd9eb88e6a1c0f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 5 Nov 2013 16:24:59 -0800 Subject: [PATCH 113/218] test commit --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index b6dcfc0..d220fb1 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ Changes for ebaysdk +- add py3 support - store response error codes in an array when processing response. The reponse error codes can be accessed by the api.response_codes() accessor. From a0d39a398555ba99af901e3a61a6ce4d8db74196 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 5 Nov 2013 16:36:48 -0800 Subject: [PATCH 114/218] Update Changes --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index d220fb1..7028011 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ Changes for ebaysdk -- add py3 support +0.1.11 +- added py3 support thanks to Nikolay Derkach - store response error codes in an array when processing response. The reponse error codes can be accessed by the api.response_codes() accessor. From 8eae2fc7bd5eecf7a199888e5ef1dadc100224eb Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 5 Nov 2013 16:37:06 -0800 Subject: [PATCH 115/218] Update _version.py --- ebaysdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/_version.py b/ebaysdk/_version.py index 86c4251..8f1a631 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/_version.py @@ -5,4 +5,4 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -__version__ = "0.1.10" +__version__ = "0.1.11" From a6e2438f46c635986c9dd09644560a4972fed411 Mon Sep 17 00:00:00 2001 From: Federico Frenguelli Date: Mon, 2 Dec 2013 15:12:12 +0100 Subject: [PATCH 116/218] Python2 compatibility fix on str() function --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index d573a3d..0726351 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -247,7 +247,7 @@ def response_soup(self): "Returns a BeautifulSoup object of the response." if not self._response_soup: - self._response_soup = BeautifulStoneSoup(str(self._response_content, encoding='utf-8')) + self._response_soup = BeautifulStoneSoup(self._response_content.decode('utf-8')) return self._response_soup From 52975fe9d5ed0024e88271dcad5a2127ae9805f0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 6 Dec 2013 13:44:14 -0800 Subject: [PATCH 117/218] fix error code issue with shopping backend --- Changes | 3 +++ ebaysdk/__init__.py | 17 ++++++++++++----- samples/shopping.py | 10 ++++++++++ samples/trading.py | 6 +++--- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 7028011..6532521 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Changes for ebaysdk 0.1.11 +- https://github.com/timotheus/ebaysdk-python/issues/40 + fix datatype issue with error codes from the shopping API + - added py3 support thanks to Nikolay Derkach - store response error codes in an array when processing response. The reponse error codes can be accessed by the api.response_codes() diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 0726351..b35af6f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -25,6 +25,7 @@ import json from xml.dom.minidom import parseString +from xml.parsers.expat import ExpatError try: from bs4 import BeautifulStoneSoup @@ -258,10 +259,13 @@ def response_dom(self): "Returns the response DOM (xml.dom.minidom)." if not self._response_dom: - dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb)))) - + dom = None try: + dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb)))) self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] + + except ExpatError as e: + raise Exception("Invalid Verb: %s (%s)" % (self.verb, e)) except IndexError: self._response_dom = dom @@ -567,9 +571,12 @@ def _get_resp_body_errors(self): eClass = nodeText(e.getElementsByTagName('ErrorClassification')[0]) if e.getElementsByTagName('ErrorCode'): - eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) - if int(eCode) not in resp_codes: - resp_codes.append(int(eCode)) + eCode = float(nodeText(e.getElementsByTagName('ErrorCode')[0])) + if eCode.is_integer(): + eCode = int(eCode) + + if eCode not in resp_codes: + resp_codes.append(eCode) if e.getElementsByTagName('ShortMessage'): eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) diff --git a/samples/shopping.py b/samples/shopping.py index b111816..f9bb721 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -125,7 +125,17 @@ def popularSearches(opts): print("\n") +def categoryInfo(opts): + + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + + api.execute('getCategoryInfo', {"CategoryID": 3410}) + + dump(api, full=False) + if __name__ == "__main__": (opts, args) = init_options() run(opts) popularSearches(opts) + categoryInfo(opts) diff --git a/samples/trading.py b/samples/trading.py index a200602..197dc8c 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -271,7 +271,7 @@ def getUser(opts): api.execute('GetUser', {'UserID': 'biddergoat'}) - dump(api, full=True) + dump(api, full=False) def getOrders(opts): @@ -281,7 +281,7 @@ def getOrders(opts): api.execute('GetOrders', {'NumberOfDays': 30}) - dump(api, full=True) + dump(api, full=False) def categories(opts): @@ -298,7 +298,7 @@ def categories(opts): api.execute('GetCategories', callData) - dump(api, full=True) + dump(api, full=False) ''' api = trading(domain='api.sandbox.ebay.com') From b2588c43e75557edbb509becd682d5e2a31c22af Mon Sep 17 00:00:00 2001 From: Thomas Durey Date: Fri, 20 Dec 2013 16:02:31 +0100 Subject: [PATCH 118/218] #32 add support for attributes --- ebaysdk/utils.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index f61d70f..203e578 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -13,7 +13,7 @@ from io import BytesIO class object_dict(dict): - """object view of dict, you can + """object view of dict, you can >>> a = object_dict() >>> a.fish = 'fish' >>> a['fish'] @@ -32,7 +32,7 @@ def __init__(self, initd=None): dict.__init__(self, initd) def __getattr__(self, item): - try: + try: d = self.__getitem__(item) except KeyError: return None @@ -41,9 +41,9 @@ def __getattr__(self, item): return d['value'] else: return d - + # if value is the only key in object, you can omit it - + def __setattr__(self, item, value): self.__setitem__(item, value) @@ -78,8 +78,8 @@ def _parse_node(self, node): old = node_tree[tag] if not isinstance(old, list): node_tree.pop(tag) - node_tree[tag] = [old] # multi times, so change old dict to a list - node_tree[tag].append(tree) # add the new one + node_tree[tag] = [old] # multi times, so change old dict to a list + node_tree[tag].append(tree) # add the new one return node_tree @@ -91,14 +91,14 @@ def _namespace_split(self, tag, value): """ result = re.compile("\{(.*)\}(.*)").search(tag) if result: - value.namespace, tag = result.groups() + value.namespace, tag = result.groups() return (tag,value) def parse(self, file): """parse a xml file to a dict""" f = open(file, 'r') - return self.fromstring(f.read()) + return self.fromstring(f.read()) def fromstring(self, s): """parse a string""" @@ -308,6 +308,14 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): assert not isinstance(dictitem, list) if isinstance(dictitem, dict): + # special case of attrs and text + if '@attrs' in dictitem.keys(): + attrs = dictitem.pop('@attrs') + for key, value in attrs.iteritems(): + parent.set(key, value) # TODO: will fail if attrs is not a dict + if '#text' in dictitem.keys(): + text = dictitem.pop('#text') + parent.text = str(text) for (tag, child) in sorted(dictitem.items()): if isinstance(child, list): # iterate through the array and convert @@ -425,12 +433,12 @@ def to_string(root, pretty=False): tree = ET.ElementTree(root) fileobj = BytesIO() - + # asdf fileobj.write('' % encoding) - + if pretty: fileobj.write('\n') - + tree.write(fileobj, 'utf-8') return fileobj.getvalue() From d0c77710ee49394c1cd6163ea1b4f016abed517c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 10 Jan 2014 10:25:34 -0800 Subject: [PATCH 119/218] Add affiliate headers to the Shopping API back-end --- Changes | 1 + ebay.yaml | 5 +++++ ebaysdk/__init__.py | 28 ++++++++++++++++++++++++---- samples/shopping.py | 16 +++++++++++++++- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 6532521..1c17a19 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ Changes for ebaysdk 0.1.11 +- Add affiliate headers to the Shopping API back-end - https://github.com/timotheus/ebaysdk-python/issues/40 fix datatype issue with error codes from the shopping API diff --git a/ebay.yaml b/ebay.yaml index 6556abe..e79658c 100644 --- a/ebay.yaml +++ b/ebay.yaml @@ -27,3 +27,8 @@ svcs.ebay.com: open.api.ebay.com: appid: ENTER_YOUR_APPID_HERE version: 671 + + # Optional affiliate tracking + # http://developer.ebay.com/DevZone/shopping/docs/Concepts/ShoppingAPI_FormatOverview.html#StandardURLParameters + trackingid: ENTER_YOUR_TRACKINGID_HERE + trackingpartnercode: ENTER_YOUR_PARTNERCODE_HERE \ No newline at end of file diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b35af6f..7006272 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -461,7 +461,7 @@ def __init__(self, **kwargs): config_file -- YAML defaults (default: ebay.yaml) debug -- debugging enabled (default: False) warnings -- warnings enabled (default: True) - errors -- errors enabled (default: True) + errors -- errors enabled (default: True) uri -- API endpoint uri (default: /shopping) appid -- eBay application id siteid -- eBay country site id (default: 0 (US)) @@ -471,8 +471,14 @@ def __init__(self, **kwargs): proxy_port -- proxy port number timeout -- HTTP request timeout (default: 20) parallel -- ebaysdk parallel object - response_encoding -- API encoding (default: XML) - request_encoding -- API encoding (default: XML) + trackingid -- ID to identify you to your tracking partner + trackingpartnercode -- third party who is your tracking partner + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + + More affiliate tracking info: + http://developer.ebay.com/DevZone/shopping/docs/Concepts/ShoppingAPI_FormatOverview.html#StandardURLParameters + """ ebaybase.__init__(self, method='POST', **kwargs) @@ -500,12 +506,14 @@ def __init__(self, **kwargs): self.set_config('proxy_port', None) self.set_config('appid', None) self.set_config('version', '799') + self.set_config('trackingid', None) + self.set_config('trackingpartnercode', None) if self.api_config['https'] and self.debug: print("HTTPS is not supported on the Shopping API.") def _build_request_headers(self): - return { + headers = { "X-EBAY-API-VERSION": self.api_config.get('version', ''), "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), @@ -514,6 +522,18 @@ def _build_request_headers(self): "Content-Type": "text/xml" } + if self.api_config.get('trackingid', None): + headers.update({ + "X-EBAY-API-TRACKING-ID": self.api_config.get('trackingid', '') + }) + + if self.api_config.get('trackingpartnercode', None): + headers.update({ + "X-EBAY-API-TRACKING-PARTNER-CODE": self.api_config.get('trackingpartnercode', '') + }) + + return headers + def _build_request_xml(self): xml = "" xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" diff --git a/samples/shopping.py b/samples/shopping.py index f9bb721..5152b45 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -130,7 +130,20 @@ def categoryInfo(opts): api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('getCategoryInfo', {"CategoryID": 3410}) + api.execute('GetCategoryInfo', {"CategoryID": 3410}) + + dump(api, full=False) + +def with_affiliate_info(opts): + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True, trackingid=1234, trackingpartnercode=9) + + mySearch = { + "MaxKeywords": 10, + "QueryKeywords": 'shirt', + } + + api.execute('FindPopularSearches', mySearch) dump(api, full=False) @@ -139,3 +152,4 @@ def categoryInfo(opts): run(opts) popularSearches(opts) categoryInfo(opts) + with_affiliate_info(opts) From 0071eb5d3e78d99f4432a076e592b94cf519dae0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 10 Jan 2014 11:01:40 -0800 Subject: [PATCH 120/218] add attribute example --- samples/shopping.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/samples/shopping.py b/samples/shopping.py index 5152b45..c9aeb0c 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -147,9 +147,18 @@ def with_affiliate_info(opts): dump(api, full=False) +def using_attributes(opts): + api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + + api.execute('FindProducts', {"ProductID": {'@attrs': {'type': 'ISBN'}, '#text': '0596154488'}}) + + dump(api, full=False) + if __name__ == "__main__": (opts, args) = init_options() - run(opts) - popularSearches(opts) - categoryInfo(opts) - with_affiliate_info(opts) + #run(opts) + #popularSearches(opts) + #categoryInfo(opts) + #with_affiliate_info(opts) + using_attributes(opts) From c3d6378e7f6380f9251dbaba0f8e14457f056191 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 10 Jan 2014 11:07:14 -0800 Subject: [PATCH 121/218] pulling in support for attributes in requests --- ebaysdk/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 7006272..ae038ae 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -1417,8 +1417,6 @@ class parallel(object): >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) - >>> r4 = trading(parallel=p, config_file=os.environ.get('EBAY_YAML')) - >>> retval = r4.execute('GetUser', {}) >>> p.wait() >>> print(p.error()) @@ -1428,8 +1426,6 @@ class parallel(object): Success >>> print(r3.response_obj().Ack) Success - >>> print(r4.response_obj().Ack) - Success """ def __init__(self): From 2d1759e2342720fbc7976705c30a9eca4fc1ae69 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 10 Jan 2014 13:08:59 -0800 Subject: [PATCH 122/218] update samples --- samples/shopping.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/samples/shopping.py b/samples/shopping.py index c9aeb0c..39a5ba1 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -151,14 +151,16 @@ def using_attributes(opts): api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('FindProducts', {"ProductID": {'@attrs': {'type': 'ISBN'}, '#text': '0596154488'}}) + api.execute('FindProducts', { + "ProductID": {'@attrs': {'type': 'ISBN'}, + '#text': '0596154488'}}) dump(api, full=False) if __name__ == "__main__": (opts, args) = init_options() - #run(opts) - #popularSearches(opts) - #categoryInfo(opts) - #with_affiliate_info(opts) + run(opts) + popularSearches(opts) + categoryInfo(opts) + with_affiliate_info(opts) using_attributes(opts) From 57228614f702122352730be0d4eb53268e3b7b09 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 14 Jan 2014 13:07:23 -0500 Subject: [PATCH 123/218] shopping API uses X-EBAY-API-SITE-ID not X-EBAY-API-SITEID --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index ae038ae..cadc18b 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -516,7 +516,7 @@ def _build_request_headers(self): headers = { "X-EBAY-API-VERSION": self.api_config.get('version', ''), "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), - "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), + "X-EBAY-API-SITE-ID": self.api_config.get('siteid', ''), "X-EBAY-API-CALL-NAME": self.verb, "X-EBAY-API-REQUEST-ENCODING": "XML", "Content-Type": "text/xml" From 241bd253676aacc9bc79e67a3ae47c4e769681ca Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 24 Jan 2014 11:42:39 -0800 Subject: [PATCH 124/218] first cut at major refactor --- Changes | 3 + ebaysdk/__init__.py | 1570 +------------------------ ebaysdk/config.py | 94 ++ ebaysdk/connection.py | 287 +++++ ebaysdk/{_version.py => exception.py} | 7 +- ebaysdk/finding/__init__.py | 186 +++ ebaysdk/merchandising/__init__.py | 79 ++ ebaysdk/shopping/__init__.py | 198 ++++ ebaysdk/soa/__init__.py | 194 +++ ebaysdk/soa/finditem.py | 118 ++ ebaysdk/trading/__init__.py | 201 ++++ ebaysdk/utils.py | 73 +- ebaysdk/utils2.py | 342 ------ samples/common.py | 28 + samples/finding.py | 63 +- samples/finditem.py | 69 ++ samples/merchandising.py | 41 +- samples/shopping.py | 137 ++- samples/trading.py | 383 +++--- setup.py | 2 +- tests/__init__.py | 19 +- 21 files changed, 1903 insertions(+), 2191 deletions(-) create mode 100644 ebaysdk/config.py create mode 100644 ebaysdk/connection.py rename ebaysdk/{_version.py => exception.py} (55%) create mode 100644 ebaysdk/finding/__init__.py create mode 100644 ebaysdk/merchandising/__init__.py create mode 100644 ebaysdk/shopping/__init__.py create mode 100644 ebaysdk/soa/__init__.py create mode 100644 ebaysdk/soa/finditem.py create mode 100644 ebaysdk/trading/__init__.py delete mode 100644 ebaysdk/utils2.py create mode 100644 samples/common.py create mode 100644 samples/finditem.py diff --git a/Changes b/Changes index 1c17a19..fa7e2db 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +1.0.0 +- Major refactor + 0.1.11 - Add affiliate headers to the Shopping API back-end - https://github.com/timotheus/ebaysdk-python/issues/40 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index ae038ae..05a5ce9 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -5,1517 +5,77 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -import os -import sys -import re -import traceback -import yaml -import pycurl -from io import BytesIO +import platform +import logging -try: - from urllib.parse import urlencode -except ImportError: - from urllib import urlencode +__version__ = '1.0.0' +Version = __version__ # for backware compatibility -try: - import simplejson as json -except ImportError: - import json -from xml.dom.minidom import parseString -from xml.parsers.expat import ExpatError +UserAgent = 'eBaySDK/%s Python/%s %s/%s' % ( + __version__, + platform.python_version(), + platform.system(), + platform.release() +) -try: - from bs4 import BeautifulStoneSoup -except ImportError: - from BeautifulSoup import BeautifulStoneSoup - sys.stderr.write('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') - -from ebaysdk.utils import xml2dict, dict2xml, list2xml, object_dict - - -def get_version(): - "Get the version." - - VERSIONFILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "_version.py") - version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - open(VERSIONFILE, "rt").read(), re.M).group(1) - - return version - -__version__ = get_version() - - -def nodeText(node): - "Returns the node's text string." - - rc = [] - - if hasattr(node, 'childNodes'): - for cn in node.childNodes: - if cn.nodeType == cn.TEXT_NODE: - rc.append(cn.data) - elif cn.nodeType == cn.CDATA_SECTION_NODE: - rc.append(cn.data) - - return ''.join(rc) - - -def tag(name, value): - return "<%s>%s" % (name, value, name) - - -class ebaybase(object): - """Base API class. - - Doctests: - >>> d = { 'list': ['a', 'b', 'c']} - >>> print(dict2xml(d, listnames={'': 'list'})) - abc - """ - - def __init__(self, debug=False, method='GET', - proxy_host=None, timeout=20, proxy_port=80, - parallel=None, **kwargs): - - self.verb = None - self.debug = debug - self.method = method - self.timeout = timeout - self.proxy_host = proxy_host - self.proxy_port = proxy_port - self.parallel = parallel - self._reset() - - def debug_callback(self, debug_type, debug_message): - sys.stderr.write('type: ' + str(debug_type) + ' message'+str(debug_message) + "\n") - - def v(self, *args, **kwargs): - - args_a = [w for w in args] - first = args_a[0] - args_a.remove(first) - - h = kwargs.get('mydict', {}) - if h: - h = h.get(first, {}) - else: - h = self.response_dict().get(first, {}) - - if len(args) == 1: - try: - return h.get('value', None) - except: - return h - - last = args_a.pop() - - for a in args_a: - h = h.get(a, {}) - - h = h.get(last, {}) - - try: - return h.get('value', None) - except: - return h - - def yaml_defaults(self, config_file, domain): - "Returns a dictionary of YAML defaults." - - # check for absolute path - if os.path.exists(config_file): - try: - f = open(config_file, "r") - except IOError as e: - print("unable to open file %s" % e) - - yData = yaml.load(f.read()) - return yData.get(domain, {}) - - # check other directories - dirs = ['.', os.environ.get('HOME'), '/etc'] - for mydir in dirs: - myfile = "%s/%s" % (mydir, config_file) - - if os.path.exists(myfile): - try: - f = open(myfile, "r") - except IOError as e: - print("unable to open file %s" % e) - - yData = yaml.load(f.read()) - domain = self.api_config.get('domain', '') - - return yData.get(domain, {}) - - return {} - - def set_config(self, cKey, defaultValue): - - if cKey in self._kwargs and self._kwargs[cKey] is not None: - self.api_config.update({cKey: self._kwargs[cKey]}) - - # otherwise, use yaml default and then fall back to - # the default set in the __init__() - else: - if not cKey in self.api_config: - self.api_config.update({cKey: defaultValue}) - else: - pass - - def getNodeText(self, nodelist): - rc = "" - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc = rc + node.data - return rc - - def _reset(self): - self._response_reason = None - self._response_status = None - self._response_code = None - self._response_content = None - self._response_dom = None - self._response_obj = None - self._response_soup = None - self._response_dict = None - self._response_error = None - self._resp_body_errors = [] - self._resp_codes = [] - - def do(self, verb, call_data=dict()): - return self.execute(verb, call_data) - - def _to_xml(self, data): - "Converts a list or dictionary to XML and returns it." - - xml = '' - - if type(data) == dict: - xml = dict2xml(data) - elif type(data) == list: - xml = list2xml(data) - else: - xml = data - - return xml - - def execute(self, verb, data=None): - "Executes the HTTP request." - - self.verb = verb - - self.call_xml = self._to_xml(data) if data else '' - self.prepare() - - self._reset() - self._response_content = self._execute_http_request() - - if self._response_content: - self.process() - self.error() - - return self - - def prepare(self): - "Performs any final changes to the request." +class NullHandler(logging.Handler): + def emit(self, record): pass - def process(self): - "Performs any final changes to the response." - - # remove xml namespace - regex = re.compile('xmlns="[^"]+"') - self._response_content = regex.sub('', self._response_content) - - def response_status(self): - "Retuns the HTTP response status string." - - return self._response_status - - def response_code(self): - "Returns the HTTP response status code." - - return self._response_code - - def response_content(self): - return self._response_content - - def response_soup(self): - "Returns a BeautifulSoup object of the response." - - if not self._response_soup: - self._response_soup = BeautifulStoneSoup(self._response_content.decode('utf-8')) - - return self._response_soup - - def response_obj(self): - return self.response_dict() - - def response_dom(self): - "Returns the response DOM (xml.dom.minidom)." - - if not self._response_dom: - dom = None - try: - dom = parseString((self._response_content or ("<%sResponse>" % (self.verb, self.verb)))) - self._response_dom = dom.getElementsByTagName(self.verb+'Response')[0] - - except ExpatError as e: - raise Exception("Invalid Verb: %s (%s)" % (self.verb, e)) - except IndexError: - self._response_dom = dom - - return self._response_dom - - def response_dict(self): - "Returns the response dictionary." - - if not self._response_dict and self._response_content: - mydict = xml2dict().fromstring(self._response_content) - self._response_dict = mydict.get(self.verb+'Response', mydict) - - return self._response_dict - - def response_json(self): - "Returns the response JSON." - - return json.dumps(self.response_dict()) - - def _execute_http_request(self): - "Performs the http request and returns the XML response body." - - try: - self._curl = pycurl.Curl() - - self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - self._curl.setopt(pycurl.SSL_VERIFYHOST, 0) - - if self.proxy_host: - self._curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) - else: - self._curl.setopt(pycurl.PROXY, '') - - # construct headers - request_headers = self._build_request_headers() - self._curl.setopt(pycurl.HTTPHEADER, [ - str('%s: %s' % (k, v)) for k, v in list(request_headers.items()) - ]) - - # construct URL & post data - request_url = self.api_config.get('domain', None) - - if self.api_config.get('uri', None): - request_url = "%s%s" % (request_url, self.api_config.get('uri', None)) - - if self.api_config.get('https', None): - request_url = "https://%s" % request_url - - if self.method == 'POST': - request_xml = self._build_request_xml() - self._curl.setopt(pycurl.POST, True) - self._curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - - self._curl.setopt(pycurl.FOLLOWLOCATION, 1) - self._curl.setopt(pycurl.URL, str(request_url)) - self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - - self._response_header = BytesIO() - self._response_body = BytesIO() - - self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) - self._curl.setopt(pycurl.TIMEOUT, self.timeout) - - self._curl.setopt(pycurl.HEADERFUNCTION, self._write_header) - self._curl.setopt(pycurl.WRITEFUNCTION, self._write_body) - - if self.debug: - sys.stderr.write("CURL Request: %s\n" % request_url) - self._curl.setopt(pycurl.VERBOSE, 1) - self._curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - - if self.parallel: - self.parallel._add_request(self) - return None - else: - e = None - for i in range(3): - try: - self._curl.perform() - return self._process_http_request() - except Exception as ee: - e = ee - continue - break - - raise Exception(e) - - except Exception as e: - self._response_error = "Exception: %s" % e - raise Exception("%s" % e) - - def _process_http_request(self): - """Final processing for the HTTP response. - Returns the response data. - """ - - self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) - - if self._response_code == 0: - return None - - self._response_status = self._response_header.getvalue().splitlines()[0] - self._response_reason = re.match(r'^HTTP.+? +\d+ +(.*) *$', self._response_status).group(1) - response_data = self._response_body.getvalue() - - self._response_header = None - self._response_body = None - self._curl.close() - - if self._response_code != 200: - self._response_error = "%s" % self._response_reason - return response_data - #raise Exception('%s' % self._response_reason) - else: - return response_data - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - - if self.verb is None: - return errors - - dom = self.response_dom() - if dom is None: - return errors - - return [] - - def error(self): - "Builds and returns the api error message." - - error_array = [] - if self._response_error: - error_array.append(self._response_error) - - error_array.extend(self._get_resp_body_errors()) - - if len(error_array) > 0: - error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - - if self.api_config.get('errors', True): - sys.stderr.write(error_string) - - return error_string - - return "" - - def _write_body(self, buf): - "Callback function invoked when body data is ready" - self._response_body.write(buf) - - def _write_header(self, buf): - "Callback function invoked when header data is ready" - self._response_header.write(buf) - - -class shopping(ebaybase): - """Shopping API class - - API documentation: - http://developer.ebay.com/products/shopping/ - - Supported calls: - getSingleItem - getMultipleItems - (all others, see API docs) - - Doctests: - >>> s = shopping(config_file=os.environ.get('EBAY_YAML')) - >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - >>> print(s.response_obj().Ack) - Success - >>> print(s.error()) - - """ - - def __init__(self, **kwargs): - """Shopping class constructor. - - Keyword arguments: - domain -- API endpoint (default: open.api.ebay.com) - config_file -- YAML defaults (default: ebay.yaml) - debug -- debugging enabled (default: False) - warnings -- warnings enabled (default: True) - errors -- errors enabled (default: True) - uri -- API endpoint uri (default: /shopping) - appid -- eBay application id - siteid -- eBay country site id (default: 0 (US)) - compatibility -- version number (default: 799) - https -- execute of https (default: True) - proxy_host -- proxy hostname - proxy_port -- proxy port number - timeout -- HTTP request timeout (default: 20) - parallel -- ebaysdk parallel object - trackingid -- ID to identify you to your tracking partner - trackingpartnercode -- third party who is your tracking partner - response_encoding -- API encoding (default: XML) - request_encoding -- API encoding (default: XML) - - More affiliate tracking info: - http://developer.ebay.com/DevZone/shopping/docs/Concepts/ShoppingAPI_FormatOverview.html#StandardURLParameters - - """ - ebaybase.__init__(self, method='POST', **kwargs) - - self._kwargs = kwargs - - self.api_config = { - 'domain': kwargs.get('domain', 'open.api.ebay.com'), - 'config_file': kwargs.get('config_file', 'ebay.yaml'), - } - - # pull stuff in value yaml defaults - self.api_config.update( - self.yaml_defaults(self.api_config['config_file'], self.api_config['domain']) - ) - - # override yaml defaults with args sent to the constructor - self.set_config('uri', '/shopping') - self.set_config('warnings', True) - self.set_config('errors', True) - self.set_config('https', False) - self.set_config('siteid', 0) - self.set_config('response_encoding', 'XML') - self.set_config('request_encoding', 'XML') - self.set_config('proxy_host', None) - self.set_config('proxy_port', None) - self.set_config('appid', None) - self.set_config('version', '799') - self.set_config('trackingid', None) - self.set_config('trackingpartnercode', None) - - if self.api_config['https'] and self.debug: - print("HTTPS is not supported on the Shopping API.") - - def _build_request_headers(self): - headers = { - "X-EBAY-API-VERSION": self.api_config.get('version', ''), - "X-EBAY-API-APP-ID": self.api_config.get('appid', ''), - "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), - "X-EBAY-API-CALL-NAME": self.verb, - "X-EBAY-API-REQUEST-ENCODING": "XML", - "Content-Type": "text/xml" - } - - if self.api_config.get('trackingid', None): - headers.update({ - "X-EBAY-API-TRACKING-ID": self.api_config.get('trackingid', '') - }) - - if self.api_config.get('trackingpartnercode', None): - headers.update({ - "X-EBAY-API-TRACKING-PARTNER-CODE": self.api_config.get('trackingpartnercode', '') - }) - - return headers - - def _build_request_xml(self): - xml = "" - xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" - xml += self.call_xml - xml += "" - - return xml - - def response_codes(self): - return self._resp_codes - - def warnings(self): - warning_string = "" - - if len(self._resp_body_warnings) > 0: - warning_string = "%s: %s" \ - % (self.verb, ", ".join(self._resp_body_warnings)) - - return warning_string - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - warnings = [] - resp_codes = [] - - if self.verb is None: - return errors - - dom = self.response_dom() - if dom is None: - return errors - - for e in dom.getElementsByTagName("Errors"): - eSeverity = None - eClass = None - eShortMsg = None - eLongMsg = None - eCode = None - - if e.getElementsByTagName('SeverityCode'): - eSeverity = nodeText(e.getElementsByTagName('SeverityCode')[0]) - - if e.getElementsByTagName('ErrorClassification'): - eClass = nodeText(e.getElementsByTagName('ErrorClassification')[0]) - - if e.getElementsByTagName('ErrorCode'): - eCode = float(nodeText(e.getElementsByTagName('ErrorCode')[0])) - if eCode.is_integer(): - eCode = int(eCode) - - if eCode not in resp_codes: - resp_codes.append(eCode) - - if e.getElementsByTagName('ShortMessage'): - eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) - - if e.getElementsByTagName('LongMessage'): - eLongMsg = nodeText(e.getElementsByTagName('LongMessage')[0]) - - msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ - % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) - - if eSeverity == 'Warning': - warnings.append(msg) - else: - errors.append(msg) - - self._resp_body_warnings = warnings - self._resp_body_errors = errors - self._resp_codes = resp_codes - - if self.api_config['warnings'] and len(warnings) > 0: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) - - if self.response_dict().Ack == 'Failure': - if self.api_config['errors']: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) - return errors - - return [] - - -class html(ebaybase): - """HTML class for traditional calls. - - Doctests: - >>> h = html() - >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> print(h.response_obj().rss.channel.ttl) - 60 - >>> title = h.response_dom().getElementsByTagName('title')[0] - >>> print(nodeText(title)) - mytouch slide - >>> print(title.toxml()) - <![CDATA[mytouch slide]]> - >>> print(h.error()) - - >>> h = html(method='POST', debug=False) - >>> retval = h.execute('http://www.ebay.com/') - >>> print(h.response_content() != '') - True - >>> print(h.response_code()) - 200 - """ - - def __init__(self, method='GET', **kwargs): - """HTML class constructor. - - Keyword arguments: - debug -- debugging enabled (default: False) - method -- GET/POST/PUT (default: GET) - proxy_host -- proxy hostname - proxy_port -- proxy port number - timeout -- HTTP request timeout (default: 20) - parallel -- ebaysdk parallel object - """ - - - ebaybase.__init__(self, method=method, **kwargs) - self.api_config = dict() - - def response_dom(self): - "Returns the HTTP response dom." - - if not self._response_dom: - self._response_dom = parseString(self._response_content) - - return self._response_dom - - def response_dict(self): - "Return the HTTP response dictionary." - - if not self._response_dict and self.response_content: - self._response_dict = xml2dict().fromstring(self._response_content) - - return self._response_dict - - def execute(self, url, call_data=dict()): - "Execute method for the HTTP request." - - self.url = url - self.call_data = call_data - - self.prepare() - - self._reset() - self._response_content = self._execute_http_request() - - if self._response_content: - self.process() - self.error() - - return self +log = logging.getLogger('ebaysdk') +perflog = logging.getLogger('ebaysdk.perf') - def _execute_http_request(self): - "Executes and returns the XML response body." +log.addHandler(NullHandler()) +perflog.addHandler(NullHandler()) - try: - self._curl = pycurl.Curl() - - if self.proxy_host: - self._curl.setopt(pycurl.PROXY, str('%s:%d' % (self.proxy_host, self.proxy_port))) - else: - self._curl.setopt(pycurl.PROXY, '') - - request_url = self.url - if self.call_data and self.method == 'GET': - request_url = request_url + '?' + urlencode(self.call_data) - - elif self.method == 'POST': - request_xml = self._build_request_xml() - self._curl.setopt(pycurl.POST, True) - self._curl.setopt(pycurl.POSTFIELDS, str(request_xml)) - - self._curl.setopt(pycurl.FOLLOWLOCATION, 1) - self._curl.setopt(pycurl.URL, str(request_url)) - self._curl.setopt(pycurl.SSL_VERIFYPEER, 0) - - self._response_header = BytesIO() - self._response_body = BytesIO() - - self._curl.setopt(pycurl.CONNECTTIMEOUT, self.timeout) - self._curl.setopt(pycurl.TIMEOUT, self.timeout) - - self._curl.setopt(pycurl.HEADERFUNCTION, self._write_header) - self._curl.setopt(pycurl.WRITEFUNCTION, self._write_body) - - if self.debug: - sys.stderr.write("CURL Request: %s\n" % request_url) - self._curl.setopt(pycurl.VERBOSE, 1) - self._curl.setopt(pycurl.DEBUGFUNCTION, self.debug_callback) - - if self.parallel: - self.parallel._add_request(self) - return None - else: - self._curl.perform() - return self._process_http_request() - - except Exception as e: - self._response_error = "Exception: %s" % e - raise Exception("%s" % e) - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - if self._response_error: - if self.api_config.get('errors', True): - sys.stderr.write("%s: %s" % (self.url, self._response_error)) - errors.append(self._response_error) - - self._resp_body_errors = errors - - return errors - - def error(self): - "Builds and returns the api error message." - - error_array = [] - error_array.extend(self._get_resp_body_errors()) - - if len(error_array) > 0: - error_string = "%s: %s" % (self.url, ", ".join(error_array)) - return error_string - - return "" - - def _build_request_xml(self): - "Builds and returns the request XML." - - if type(self.call_data) is str: - self.call_xml = self.call_data - else: - self.call_xml = urlencode(self.call_data) - - return self.call_xml - - -class trading(ebaybase): - """Trading API class - - API documentation: - https://www.x.com/developers/ebay/products/trading-api - - Supported calls: - AddItem - ReviseItem - GetUser - (all others, see API docs) - - Doctests: - >>> t = trading(config_file=os.environ.get('EBAY_YAML')) - >>> retval = t.execute('GetCharities', {'CharityID': 3897}) - >>> charity_name = '' - >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: - ... charity_name = nodeText(t.response_dom().getElementsByTagName('Name')[0]) - >>> print(charity_name) - Sunshine Kids Foundation - >>> print(t.error()) - - >>> t2 = trading(errors=False, config_file=os.environ.get('EBAY_YAML')) - >>> retval2 = t2.execute('VerifyAddItem', {}) - >>> print(t2.response_codes()) - [10009] - """ - - def __init__(self, **kwargs): - """Trading class constructor. - - Keyword arguments: - domain -- API endpoint (default: api.ebay.com) - config_file -- YAML defaults (default: ebay.yaml) - debug -- debugging enabled (default: False) - warnings -- warnings enabled (default: False) - uri -- API endpoint uri (default: /ws/api.dll) - appid -- eBay application id - devid -- eBay developer id - certid -- eBay cert id - token -- eBay application/user token - siteid -- eBay country site id (default: 0 (US)) - compatibility -- version number (default: 648) - https -- execute of https (default: True) - proxy_host -- proxy hostname - proxy_port -- proxy port number - timeout -- HTTP request timeout (default: 20) - parallel -- ebaysdk parallel object - response_encoding -- API encoding (default: XML) - request_encoding -- API encoding (default: XML) - """ - ebaybase.__init__(self, method='POST', **kwargs) - - self._kwargs = kwargs - - self.api_config = { - 'domain': kwargs.get('domain', 'api.ebay.com'), - 'config_file': kwargs.get('config_file', 'ebay.yaml'), - } - - # pull stuff in value yaml defaults - self.api_config.update( - self.yaml_defaults(self.api_config['config_file'], self.api_config['domain']) - ) - - # override yaml defaults with args sent to the constructor - self.set_config('uri', '/ws/api.dll') - self.set_config('warnings', True) - self.set_config('errors', True) - self.set_config('https', True) - self.set_config('siteid', 0) - self.set_config('response_encoding', 'XML') - self.set_config('request_encoding', 'XML') - self.set_config('proxy_host', None) - self.set_config('proxy_port', None) - self.set_config('token', None) - self.set_config('iaf_token', None) - self.set_config('appid', None) - self.set_config('devid', None) - self.set_config('certid', None) - self.set_config('version', '837') - self.set_config('compatibility', '837') - - def _build_request_headers(self): - "Builds HTTP headers" - - headers = { - "X-EBAY-API-COMPATIBILITY-LEVEL": self.api_config.get('version', ''), - "X-EBAY-API-DEV-NAME": self.api_config.get('devid', ''), - "X-EBAY-API-APP-NAME": self.api_config.get('appid', ''), - "X-EBAY-API-CERT-NAME": self.api_config.get('certid', ''), - "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), - "X-EBAY-API-CALL-NAME": self.verb, - "Content-Type": "text/xml" - } - if self.api_config.get('iaf_token', None): - headers["X-EBAY-API-IAF-TOKEN"] = self.api_config.get('iaf_token') - - return headers - - def _build_request_xml(self): - "Builds the XML request" - - xml = "" - xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" - if not self.api_config.get('iaf_token', None): - xml += "" - if self.api_config.get('token', None): - xml += "%s" % self.api_config.get('token') - elif self.api_config.get('username', None): - xml += "%s" % self.api_config.get('username', '') - if self.api_config.get('password', None): - xml += "%s" % self.api_config.get('password', '') - xml += "" - xml += self.call_xml - xml += "" - return xml - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - warnings = [] - resp_codes = [] - - if self.verb is None: - return errors - - dom = self.response_dom() - if dom is None: - return errors - - for e in dom.getElementsByTagName("Errors"): - eSeverity = None - eClass = None - eShortMsg = None - eLongMsg = None - eCode = None - - if e.getElementsByTagName('SeverityCode'): - eSeverity = nodeText(e.getElementsByTagName('SeverityCode')[0]) - - if e.getElementsByTagName('ErrorClassification'): - eClass = nodeText(e.getElementsByTagName('ErrorClassification')[0]) - - if e.getElementsByTagName('ErrorCode'): - eCode = nodeText(e.getElementsByTagName('ErrorCode')[0]) - if int(eCode) not in resp_codes: - resp_codes.append(int(eCode)) - - if e.getElementsByTagName('ShortMessage'): - eShortMsg = nodeText(e.getElementsByTagName('ShortMessage')[0]) - - if e.getElementsByTagName('LongMessage'): - eLongMsg = nodeText(e.getElementsByTagName('LongMessage')[0]) - - msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ - % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) - - if eSeverity == 'Warning': - warnings.append(msg) - else: - errors.append(msg) - - self._resp_body_warnings = warnings - self._resp_body_errors = errors - self._resp_codes = resp_codes - - if self.api_config['warnings'] and len(warnings) > 0: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) - - if self.response_dict().Ack == 'Failure': - if self.api_config['errors']: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) - return errors - - return [] - - def warnings(self): - warning_string = "" - - if len(self._resp_body_warnings) > 0: - warning_string = "%s: %s" \ - % (self.verb, ", ".join(self._resp_body_warnings)) - - return warning_string - - def response_codes(self): - return self._resp_codes - - def error(self): - "Builds and returns the api error message." - - error_array = [] - if self._response_error: - error_array.append(self._response_error) - - error_array.extend(self._get_resp_body_errors()) - - if len(error_array) > 0: - error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - - return error_string - - return "" - -class finding(ebaybase): - """Finding API class - - API documentation: - https://www.x.com/developers/ebay/products/finding-api - - Supported calls: - findItemsAdvanced - findItemsByCategory - (all others, see API docs) - - Doctests: - >>> f = finding(config_file=os.environ.get('EBAY_YAML')) - >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) - >>> error = f.error() - >>> print(error) - - >>> if len( error ) <= 0: - ... print(f.response_obj().itemSearchURL != '') - ... items = f.response_obj().searchResult.item - ... print(len(items)) - ... print(f.response_dict().ack) - True - 100 - Success - - """ - - def __init__(self, **kwargs): - """Finding class constructor. - - Keyword arguments: - domain -- API endpoint (default: svcs.ebay.com) - config_file -- YAML defaults (default: ebay.yaml) - debug -- debugging enabled (default: False) - warnings -- warnings enabled (default: False) - uri -- API endpoint uri (default: /services/search/FindingService/v1) - appid -- eBay application id - siteid -- eBay country site id (default: EBAY-US) - compatibility -- version number (default: 1.0.0) - https -- execute of https (default: False) - proxy_host -- proxy hostname - proxy_port -- proxy port number - timeout -- HTTP request timeout (default: 20) - parallel -- ebaysdk parallel object - response_encoding -- API encoding (default: XML) - request_encoding -- API encoding (default: XML) - """ - ebaybase.__init__(self, method='POST', **kwargs) - - self._kwargs = kwargs - - self.api_config = { - 'domain': kwargs.get('domain', 'svcs.ebay.com'), - 'config_file': kwargs.get('config_file', 'ebay.yaml'), - } - - # pull stuff in value yaml defaults - self.api_config.update( - self.yaml_defaults(self.api_config['config_file'], - self.api_config['domain']) - ) - - # override yaml defaults with args sent to the constructor - self.set_config('uri', '/services/search/FindingService/v1') - self.set_config('https', False) - self.set_config('warnings', True) - self.set_config('errors', True) - self.set_config('siteid', 'EBAY-US') - self.set_config('response_encoding', 'XML') - self.set_config('request_encoding', 'XML') - self.set_config('proxy_host', None) - self.set_config('proxy_port', None) - self.set_config('token', None) - self.set_config('iaf_token', None) - self.set_config('appid', None) - self.set_config('version', '1.12.0') - self.set_config('compatibility', '1.0.0') - self.set_config('service', 'FindingService') - - def _build_request_headers(self): - return { - "X-EBAY-SOA-SERVICE-NAME": self.api_config.get('service', ''), - "X-EBAY-SOA-SERVICE-VERSION": self.api_config.get('version', ''), - "X-EBAY-SOA-SECURITY-APPNAME": self.api_config.get('appid', ''), - "X-EBAY-SOA-GLOBAL-ID": self.api_config.get('siteid', ''), - "X-EBAY-SOA-OPERATION-NAME": self.verb, - "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.api_config.get('request_encoding', ''), - "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.api_config.get('response_encoding', ''), - "Content-Type": "text/xml" - } - - def _build_request_xml(self): - xml = "" - xml += "<" + self.verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" - xml += self.call_xml - xml += "" - - return xml - - def _process_http_request(self): - """Final processing for the HTTP response. - Returns the response data. - """ - - self._response_code = self._curl.getinfo(pycurl.HTTP_CODE) - - if self._response_code == 0: - return None - - self._response_status = self._response_header.getvalue().splitlines()[0] - self._response_reason = re.match(r'^HTTP.+? +\d+ +(.*) *$', self._response_status).group(1) - response_data = self._response_body.getvalue() - - self._response_header = None - self._response_body = None - self._curl.close() - - if self._response_code != 200: - self._response_error = "%s" % self._response_reason - return response_data - #raise Exception('%s' % self._response_reason) - else: - return response_data - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - warnings = [] - resp_codes = [] - - if self.verb is None: - return errors - - dom = self.response_dom() - if dom is None: - return errors - - for e in dom.getElementsByTagName("error"): - eSeverity = None - eDomain = None - eMsg = None - eId = None - - if e.getElementsByTagName('severity'): - eSeverity = nodeText(e.getElementsByTagName('severity')[0]) - - if e.getElementsByTagName('domain'): - eDomain = nodeText(e.getElementsByTagName('domain')[0]) - - if e.getElementsByTagName('errorId'): - eId = nodeText(e.getElementsByTagName('errorId')[0]) - if int(eId) not in resp_codes: - resp_codes.append(int(eId)) - - if e.getElementsByTagName('message'): - eMsg = nodeText(e.getElementsByTagName('message')[0]) - - msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ - % (eDomain, eSeverity, eId, eMsg) - - if eSeverity == 'Warning': - warnings.append(msg) - else: - errors.append(msg) - - self._resp_body_warnings = warnings - self._resp_body_errors = errors - self._resp_codes = resp_codes - - if self.api_config['warnings'] and len(warnings) > 0: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(warnings))) - - try: - if self.response_dict().ack == 'Success' and len(errors) > 0 and self.api_config['errors']: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) - elif len(errors) > 0: - if self.api_config['errors']: - sys.stderr.write("%s: %s\n\n" % (self.verb, "\n".join(errors))) - return errors - except AttributeError: - pass - - return [] - - def response_codes(self): - return self._resp_codes - - def warnings(self): - warning_string = "" - - if len(self._resp_body_warnings) > 0: - warning_string = "%s: %s" \ - % (self.verb, ", ".join(self._resp_body_warnings)) - - return warning_string - - def error(self): - "Builds and returns the api error message." - - error_array = [] - if self._response_error: - error_array.append(self._response_error) - - error_array.extend(self._get_resp_body_errors()) - - if len(error_array) > 0: - error_string = "%s: %s" % (self.verb, ", ".join(error_array)) - - return error_string - - return "" - - -class merchandising(finding): - """Shopping API class - - API documentation: - http://developer.ebay.com/products/merchandising/ - - Supported calls: - getMostWatchedItems - getSimilarItems - getTopSellingProducts - (all others, see API docs) - - Doctests: - >>> s = merchandising(config_file=os.environ.get('EBAY_YAML')) - >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) - >>> print(s.response_obj().ack) - Success - >>> print(s.error()) - - """ - - def __init__(self, **kwargs): - """Merchandising class constructor. - - Keyword arguments: - domain -- API endpoint (default: open.api.ebay.com) - config_file -- YAML defaults (default: ebay.yaml) - debug -- debugging enabled (default: False) - warnings -- warnings enabled (default: False) - uri -- API endpoint uri (default: /MerchandisingService) - appid -- eBay application id - siteid -- eBay country site id (default: 0 (US)) - compatibility -- version number (default: 799) - https -- execute of https (default: True) - proxy_host -- proxy hostname - proxy_port -- proxy port number - timeout -- HTTP request timeout (default: 20) - parallel -- ebaysdk parallel object - response_encoding -- API encoding (default: XML) - request_encoding -- API encoding (default: XML) - """ - finding.__init__(self, **kwargs) - - self.api_config['uri'] = '/MerchandisingService' - self.api_config['service'] = 'MerchandisingService' - - def _build_request_headers(self): - return { - "X-EBAY-API-VERSION": self.api_config.get('version', ''), - "EBAY-SOA-CONSUMER-ID": self.api_config.get('appid', ''), - "X-EBAY-API-SITEID": self.api_config.get('siteid', ''), - "X-EBAY-SOA-OPERATION-NAME": self.verb, - "X-EBAY-API-REQUEST-ENCODING": "XML", - "X-EBAY-SOA-SERVICE-NAME": self.api_config.get('service', ''), - "Content-Type": "text/xml" - } - - def _build_request_xml(self): - xml = "" - xml += "<" + self.verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" - xml += self.call_xml - xml += "" - - return xml - - -class SOAService(ebaybase): - "SOAP class." - - def __init__(self, app_config=None, site_id='EBAY-US', debug=False): - self.api_config = { - 'https': False, - 'site_id': site_id, - 'content_type': 'text/xml;charset=UTF-8', - 'request_encoding': 'XML', - 'response_encoding': 'XML', - 'message_protocol': 'SOAP12', - 'soap_env_str': '', # http://www.ebay.com/marketplace/fundraising/v1/services', - } - - ph = None - pp = 80 - if app_config: - self.load_from_app_config(app_config) - ph = self.api_config.get('proxy_host', ph) - pp = self.api_config.get('proxy_port', pp) - - ebaybase.__init__( - self, - debug=debug, - method='POST', - proxy_host=ph, - proxy_port=pp, - ) - - # override this method, to provide setup through a config object, which - # should provide a get() method for extracting constants we care about - # this method should then set the .api_config[] dict (e.g. the comment below) - def load_from_app_config(self, app_config): - #self.api_config['domain'] = app_config.get('API_SERVICE_DOMAIN') - #self.api_config['uri'] = app_config.get('API_SERVICE_URI') - pass - - # Note: this method will always return at least an empty object_dict! - # It used to return None in some cases. If you get an empty dict, - # you can use the .error() method to look for the cause. - def response_dict(self): - if self._response_dict: - return self._response_dict - - mydict = object_dict() - try: - mydict = xml2dict().fromstring(self._response_content) - verb = self.verb + 'Response' - self._response_dict = mydict['Envelope']['Body'][verb] - - except Exception as e: - self._response_dict = mydict - self._resp_body_errors.append("Error parsing SOAP response: %s" % e) - - return self._response_dict - - def _build_request_headers(self): - return { - 'Content-Type': self.api_config['content_type'], - 'X-EBAY-SOA-SERVICE-NAME': self.api_config['service'], - 'X-EBAY-SOA-OPERATION-NAME': self.verb, - 'X-EBAY-SOA-GLOBAL-ID': self.api_config['site_id'], - 'X-EBAY-SOA-REQUEST-DATA-FORMAT': self.api_config['request_encoding'], - 'X-EBAY-SOA-RESPONSE-DATA-FORMAT': self.api_config['response_encoding'], - 'X-EBAY-SOA-MESSAGE-PROTOCOL': self.api_config['message_protocol'], - } - - def _build_request_xml(self): - xml = '' - xml += '>> p = parallel() - >>> r1 = html(parallel=p) - >>> retval = r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> r2 = finding(parallel=p, config_file=os.environ.get('EBAY_YAML')) - >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) - >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) - >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) - >>> p.wait() - >>> print(p.error()) - - >>> print(r1.response_obj().rss.channel.ttl) - 60 - >>> print(r2.response_dict().ack) - Success - >>> print(r3.response_obj().Ack) - Success - """ - - def __init__(self): - self._requests = [] - self._errors = [] - - def _add_request(self, request): - self._requests.append(request) - - def wait(self, timeout=20): - "wait for all of the api requests to complete" - - self._errors = [] - try: - if timeout > 0: - creqs = self._requests - for i in range(3): - failed_calls = self.execute_multi(creqs, timeout) - - if failed_calls: - creqs = failed_calls - continue - else: - creqs = [] - - break - - for request in creqs: - self._errors.append("%s" % self._get_curl_http_error(request._curl)) - - self._requests = [] - except Exception as e: - self._errors.append("Exception: %s" % e) - traceback.print_exc() - raise Exception("%s" % e) - - def _get_curl_http_error(self, curl, info=None): - code = curl.getinfo(pycurl.HTTP_CODE) - url = curl.getinfo(pycurl.EFFECTIVE_URL) - if code == 403: - return 'Server refuses to fullfil the request for: %s' % url - else: - if info is None: - msg = '' - else: - msg = ': ' + info - - return '%s : Unable to handle http code %d%s' % (url, code, msg) - - def execute_multi(self, calls, timeout): - - multi = pycurl.CurlMulti() - for request in calls: - multi.add_handle(request._curl) - - while True: - while True: - ret, num = multi.perform() - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - if num == 0: - break - if multi.select(timeout) < 0: - raise pycurl.error(pycurl.E_OPERATION_TIMEOUTED) - - failed_calls = [] - - for request in calls: - multi.remove_handle(request._curl) - - request._response_content = request._process_http_request() - - if request.response_code() == 0: - failed_calls.append(request) - else: - if request._response_content: - request.process() - - error_string = request.error() - if error_string: - self._errors.append(error_string) - - multi.close() - - return failed_calls - - def error(self): - "builds and returns the api error message" - - if len(self._errors) > 0: - return "parallel error:\n%s\n" % ("\n".join(self._errors)) - - return "" +def get_version(): + return __version__ + +def set_file_logger(name, filepath, level=logging.INFO, format_string=None): + global log + log.handlers=[] + + if not format_string: + format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" + logger = logging.getLogger(name) + logger.setLevel(level) + fh = logging.FileHandler(filepath) + fh.setLevel(level) + formatter = logging.Formatter(format_string) + fh.setFormatter(formatter) + logger.addHandler(fh) + log = logger + + +def set_stream_logger(name, level=logging.DEBUG, format_string=None): + global log + log.handlers=[] + + if not format_string: + format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" + logger = logging.getLogger(name) + logger.setLevel(level) + fh = logging.StreamHandler() + fh.setLevel(level) + formatter = logging.Formatter(format_string) + fh.setFormatter(formatter) + logger.addHandler(fh) + log = logger + +def trading(*args, **kwargs): + from ebaysdk.trading import Connection as Trading + return Trading(*args, **kwargs) + +def shopping(*args, **kwargs): + from ebaysdk.shopping import Connection as Shopping + return Shopping(*args, **kwargs) + +def finding(*args, **kwargs): + from ebaysdk.finding import Connection as Finding + return Finding(*args, **kwargs) + +def merchandising(*args, **kwargs): + from ebaysdk.merchandising import Connection as Merchandising + return Merchandising(*args, **kwargs) diff --git a/ebaysdk/config.py b/ebaysdk/config.py new file mode 100644 index 0000000..1e35608 --- /dev/null +++ b/ebaysdk/config.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import yaml + +from ebaysdk import log + +class Config(object): + """Config Class for all APIs connections + + >>> c = Config(domain='api.ebay.com') + >>> print(c.file()) + ebay.yaml + >>> c.set('fname', 'tim') + >>> c.get('fname') + 'tim' + >>> c.get('missingkey', 'defaultvalue') + 'defaultvalue' + >>> c.set('number', 22) + >>> c.get('number') + 22 + """ + + def __init__(self, domain, connection_kwargs=dict(), config_file='ebay.yaml'): + self.config_file=config_file + self.domain=domain + self.values=dict() + self.config_file_used=[] + self.connection_kwargs=connection_kwargs + + # populate defaults + self._populate_yaml_defaults() + + def _populate_yaml_defaults(self): + "Returns a dictionary of YAML defaults." + + # check for absolute path + if self.config_file and os.path.exists(self.config_file): + self.config_file_used=self.config_file + fhandle = open(self.config_file, "r") + dataobj = yaml.load(fhandle.read()) + + for k, val in dataobj.get(self.domain, {}).iteritems(): + self.set(k, val) + + return self + + # check other directories + dirs = ['.', os.environ.get('HOME'), '/etc'] + for mydir in dirs: + myfile = "%s/%s" % (mydir, self.config_file) + + if os.path.exists(myfile): + self.config_file_used=myfile + + fhandle = open(self.config_file, "r") + dataobj = yaml.load(fhandle.read()) + + for k, val in dataobj.get(self.domain, {}).iteritems(): + self.set(k, val) + + return self + + def file(self): + return self.config_file_used + + def get(self, cKey, defaultValue=None): + log.debug('get: %s=%s' % (cKey, self.values.get(cKey, defaultValue))) + return self.values.get(cKey, defaultValue) + + def set(self, cKey, defaultValue, force=False): + + if force: + log.debug('set (force): %s=%s' % (cKey, defaultValue)) + self.values.update({cKey: defaultValue}) + + elif cKey in self.connection_kwargs and self.connection_kwargs[cKey] is not None: + log.debug('set: %s=%s' % (cKey, self.connection_kwargs[cKey])) + self.values.update({cKey: self.connection_kwargs[cKey]}) + + # otherwise, use yaml default and then fall back to + # the default set in the __init__() + else: + if not cKey in self.values: + log.debug('set: %s=%s' % (cKey, defaultValue)) + self.values.update({cKey: defaultValue}) + else: + pass \ No newline at end of file diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py new file mode 100644 index 0000000..a8e406b --- /dev/null +++ b/ebaysdk/connection.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +from ebaysdk import log + +import re +import json +import time +import uuid + +from requests import Request, Session +from requests.adapters import HTTPAdapter + +from xml.dom.minidom import parseString +from xml.parsers.expat import ExpatError + +try: + from bs4 import BeautifulStoneSoup +except ImportError: + from BeautifulSoup import BeautifulStoneSoup + log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') + +from ebaysdk import set_stream_logger, UserAgent +from ebaysdk.utils import getNodeText as getNodeTextUtils +from ebaysdk.utils import dict2xml, xml2dict, getValue +from ebaysdk.exception import ConnectionError, ConnectionResponseError + +HTTP_SSL = { + False: 'http', + True: 'https', +} + +class BaseConnection(object): + """Base Connection Class. + + Doctests: + >>> d = { 'list': ['a', 'b', 'c']} + >>> print(dict2xml(d, listnames={'': 'list'})) + abc + >>> d2 = {'node': {'@attrs': {'a': 'b'}, '#text': 'foo'}} + >>> print(dict2xml(d2)) + foo + """ + + def __init__(self, debug=False, method='GET', + proxy_host=None, timeout=20, proxy_port=80, + parallel=None, **kwargs): + + if debug: + set_stream_logger('ebaysdk') + + self.response = None + self.request = None + self.verb = None + self.debug = debug + self.method = method + self.timeout = timeout + self.proxy_host = proxy_host + self.proxy_port = proxy_port + + self.proxies = dict() + if self.proxy_host: + proxy = 'http://%s:%s' % (self.proxy_host, self.proxy_port) + self.proxies = { + 'http': proxy, + 'https': proxy + } + + self.session = Session() + self.session.mount('http://', HTTPAdapter(max_retries=3)) + self.session.mount('https://', HTTPAdapter(max_retries=3)) + + self._reset() + + def debug_callback(self, debug_type, debug_message): + log.debug('type: ' + str(debug_type) + ' message' + str(debug_message)) + + def v(self, *args, **kwargs): + return getValue(self.response_dict(), *args, **kwargs) + + def getNodeText(self, nodelist): + return getNodeTextUtils(nodelist) + + def _reset(self): + self.response = None + self.request = None + self.verb = None + self._request_id = None + self._time = time.time() + self._response_content = None + self._response_dom = None + self._response_obj = None + self._response_soup = None + self._response_dict = None + self._response_error = None + self._resp_body_errors = [] + self._resp_body_warnings = [] + self._resp_codes = [] + + def do(self, verb, call_data=dict()): + return self.execute(verb, call_data) + + def execute(self, verb, data=None): + "Executes the HTTP request." + log.debug('execute: verb=%s data=%s' % (verb, data)) + + self._reset() + self.build_request(verb, data) + self.execute_request() + self.process_response() + self.error_check() + + log.debug('total time=%s' % (time.time() - self._time)) + + return self + + def build_request(self, verb, data): + + self.verb = verb + self._request_id = uuid.uuid4() + + url = "%s://%s%s" % ( + HTTP_SSL[self.config.get('https', False)], + self.config.get('domain'), + self.config.get('uri') + ) + + headers = self.build_request_headers(verb) + headers.update({'User-Agent': UserAgent, + 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) + + request = Request(self.method, + url, + data=self.build_request_data(verb, data), + headers=headers, + ) + + self.request = request.prepare() + + + def execute_request(self): + + log.debug("REQUEST (%s): %s %s" \ + % (self._request_id, self.request.method, self.request.url)) + log.debug('headers=%s' % self.request.headers) + log.debug('body=%s' % self.request.body) + + self.response = self.session.send(self.request, + verify=False, + proxies=self.proxies, + timeout=self.timeout, + allow_redirects=True + ) + + log.debug('RESPONSE (%s):' % self._request_id) + log.debug('elapsed time=%s' % self.response.elapsed) + log.debug('status code=%s' % self.response.status_code) + log.debug('headers=%s' % self.response.headers) + log.debug('content=%s' % self.response.text) + + def process_response(self): + """Post processing of the response""" + + if self.response.status_code != 200: + self._response_error = self.response.reason + + # remove xml namespace + regex = re.compile('xmlns="[^"]+"') + self._response_content = regex.sub('', self.response.content) + + def error_check(self): + estr = self.error() + + if estr and self.config.get('errors', True): + log.error(estr) + raise ConnectionError(estr) + + def response_codes(self): + return self._resp_codes + + def response_status(self): + "Retuns the HTTP response status string." + + return self.response.reason + + def response_code(self): + "Returns the HTTP response status code." + + return self.response.status_code + + def response_content(self): + return self._response_content + + def response_soup(self): + "Returns a BeautifulSoup object of the response." + + if not self._response_soup: + self._response_soup = BeautifulStoneSoup( + self._response_content.decode('utf-8') + ) + + return self._response_soup + + def response_obj(self): + return self.response_dict() + + def response_dom(self): + "Returns the response DOM (xml.dom.minidom)." + + if not self._response_dom: + dom = None + content = None + + try: + if self._response_content: + content = self._response_content + else: + content = "<%sResponse>" % (self.verb, self.verb) + + dom = parseString(content) + self._response_dom = dom.getElementsByTagName( + self.verb + 'Response')[0] + + except ExpatError as e: + raise ConnectionResponseError("Invalid Verb: %s (%s)" % (self.verb, e)) + except IndexError: + self._response_dom = dom + + return self._response_dom + + def response_dict(self): + "Returns the response dictionary." + + if not self._response_dict and self._response_content: + mydict = xml2dict().fromstring(self._response_content) + self._response_dict = mydict.get(self.verb + 'Response', mydict) + + return self._response_dict + + def response_json(self): + "Returns the response JSON." + + return json.dumps(self.response_dict()) + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + return [] + + def error(self): + "Builds and returns the api error message." + + error_array = [] + if self._response_error: + error_array.append(self._response_error) + + error_array.extend(self._get_resp_body_errors()) + + if len(error_array) > 0: + error_string = "%s: %s" % (self.verb, ", ".join(error_array)) + + return error_string + + return None diff --git a/ebaysdk/_version.py b/ebaysdk/exception.py similarity index 55% rename from ebaysdk/_version.py rename to ebaysdk/exception.py index 8f1a631..0ec63c5 100644 --- a/ebaysdk/_version.py +++ b/ebaysdk/exception.py @@ -5,4 +5,9 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' -__version__ = "0.1.11" + +class ConnectionError(Exception): + pass + +class ConnectionResponseError(Exception): + pass diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py new file mode 100644 index 0000000..a4342a2 --- /dev/null +++ b/ebaysdk/finding/__init__.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk import log +from ebaysdk.connection import BaseConnection +from ebaysdk.config import Config +from ebaysdk.utils import getNodeText, to_xml + +class Connection(BaseConnection): + """Connection class for the Finding service + + API documentation: + https://www.x.com/developers/ebay/products/finding-api + + Supported calls: + findItemsAdvanced + findItemsByCategory + (all others, see API docs) + + Doctests: + >>> f = Connection(config_file=os.environ.get('EBAY_YAML')) + >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> error = f.error() + >>> print(error) + None + >>> if not f.error(): + ... print(f.response_obj().itemSearchURL != '') + ... items = f.response_obj().searchResult.item + ... print(len(items)) + ... print(f.response_dict().ack) + True + 100 + Success + + """ + + def __init__(self, **kwargs): + """Finding class constructor. + + Keyword arguments: + domain -- API endpoint (default: svcs.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /services/search/FindingService/v1) + appid -- eBay application id + siteid -- eBay country site id (default: EBAY-US) + compatibility -- version number (default: 1.0.0) + https -- execute of https (default: False) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ + + super(Connection, self).__init__(method='POST', **kwargs) + + self.config=Config(domain=kwargs.get('domain', 'svcs.ebay.com'), + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + # override yaml defaults with args sent to the constructor + self.config.set('domain', kwargs.get('domain', 'svcs.ebay.com')) + self.config.set('uri', '/services/search/FindingService/v1') + self.config.set('https', False) + self.config.set('warnings', True) + self.config.set('errors', True) + self.config.set('siteid', 'EBAY-US') + self.config.set('response_encoding', 'XML') + self.config.set('request_encoding', 'XML') + self.config.set('proxy_host', None) + self.config.set('proxy_port', None) + self.config.set('token', None) + self.config.set('iaf_token', None) + self.config.set('appid', None) + self.config.set('version', '1.12.0') + self.config.set('compatibility', '1.0.0') + self.config.set('service', 'FindingService') + + def build_request_headers(self, verb): + return { + "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), + "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), + "X-EBAY-SOA-SECURITY-APPNAME": self.config.get('appid', ''), + "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": verb, + "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.config.get('request_encoding', ''), + "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.config.get('response_encoding', ''), + "Content-Type": "text/xml" + } + + def build_request_data(self, verb, data): + xml = "" + xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" + xml += to_xml(data) or '' + xml += "" + + return xml + + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + resp_codes = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("error"): + eSeverity = None + eDomain = None + eMsg = None + eId = None + + if e.getElementsByTagName('severity'): + eSeverity = getNodeText(e.getElementsByTagName('severity')[0]) + + if e.getElementsByTagName('domain'): + eDomain = getNodeText(e.getElementsByTagName('domain')[0]) + + if e.getElementsByTagName('errorId'): + eId = getNodeText(e.getElementsByTagName('errorId')[0]) + if int(eId) not in resp_codes: + resp_codes.append(int(eId)) + + if e.getElementsByTagName('message'): + eMsg = getNodeText(e.getElementsByTagName('message')[0]) + + msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ + % (eDomain, eSeverity, eId, eMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + self._resp_codes = resp_codes + + if self.config.get('warnings') and len(warnings) > 0: + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + try: + if self.response_dict().ack == 'Success' and len(errors) > 0 and self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + elif len(errors) > 0: + if self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + except AttributeError: + pass + + return [] diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py new file mode 100644 index 0000000..75342c7 --- /dev/null +++ b/ebaysdk/merchandising/__init__.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk.finding import Connection as FindingConnection +from ebaysdk.utils import to_xml + +class Connection(FindingConnection): + """Connection class for the Merchandising service + + API documentation: + http://developer.ebay.com/products/merchandising/ + + Supported calls: + getMostWatchedItems + getSimilarItems + getTopSellingProducts + (all others, see API docs) + + Doctests: + >>> s = Connection(config_file=os.environ.get('EBAY_YAML')) + >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) + >>> print(s.response_obj().ack) + Success + >>> print(s.error()) + None + """ + + def __init__(self, **kwargs): + """Merchandising class constructor. + + Keyword arguments: + domain -- API endpoint (default: open.api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /MerchandisingService) + appid -- eBay application id + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 799) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ + + super(Connection, self).__init__(**kwargs) + + self.config.set('uri', '/MerchandisingService', force=True) + self.config.set('service', 'MerchandisingService', force=True) + + def build_request_headers(self, verb): + return { + "X-EBAY-API-VERSION": self.config.get('version', ''), + "EBAY-SOA-CONSUMER-ID": self.config.get('appid', ''), + "X-EBAY-API-SITEID": self.config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": verb, + "X-EBAY-API-REQUEST-ENCODING": "XML", + "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), + "Content-Type": "text/xml" + } + + + def build_request_data(self, verb, data): + xml = "" + xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" + xml += to_xml(data) or '' + xml += "" + + return xml diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py new file mode 100644 index 0000000..3711922 --- /dev/null +++ b/ebaysdk/shopping/__init__.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk import log +from ebaysdk.connection import BaseConnection +from ebaysdk.config import Config +from ebaysdk.utils import getNodeText, to_xml + +class Connection(BaseConnection): + """Shopping API class + + API documentation: + http://developer.ebay.com/products/shopping/ + + Supported calls: + getSingleItem + getMultipleItems + (all others, see API docs) + + Doctests: + >>> s = Connection(config_file=os.environ.get('EBAY_YAML')) + >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + >>> print(s.response_obj().Ack) + Success + >>> print(s.error()) + None + """ + + def __init__(self, **kwargs): + """Shopping class constructor. + + Keyword arguments: + domain -- API endpoint (default: open.api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: True) + errors -- errors enabled (default: True) + uri -- API endpoint uri (default: /shopping) + appid -- eBay application id + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 799) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + trackingid -- ID to identify you to your tracking partner + trackingpartnercode -- third party who is your tracking partner + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + + More affiliate tracking info: + http://developer.ebay.com/DevZone/shopping/docs/Concepts/ShoppingAPI_FormatOverview.html#StandardURLParameters + + """ + super(Connection, self).__init__(method='POST', **kwargs) + + self.config=Config(domain=kwargs.get('domain', 'open.api.ebay.com'), + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + # override yaml defaults with args sent to the constructor + self.config.set('domain', kwargs.get('domain', 'open.api.ebay.com')) + self.config.set('uri', '/shopping') + self.config.set('warnings', True) + self.config.set('errors', True) + self.config.set('https', False) + self.config.set('siteid', 0) + self.config.set('response_encoding', 'XML') + self.config.set('request_encoding', 'XML') + self.config.set('proxy_host', None) + self.config.set('proxy_port', None) + self.config.set('appid', None) + self.config.set('version', '799') + self.config.set('trackingid', None) + self.config.set('trackingpartnercode', None) + + if self.config.get('https') and self.debug: + print("HTTPS is not supported on the Shopping API.") + + def build_request_headers(self, verb): + headers = { + "X-EBAY-API-VERSION": self.config.get('version', ''), + "X-EBAY-API-APP-ID": self.config.get('appid', ''), + "X-EBAY-API-SITEID": self.config.get('siteid', ''), + "X-EBAY-API-CALL-NAME": verb, + "X-EBAY-API-REQUEST-ENCODING": "XML", + "Content-Type": "text/xml" + } + + if self.config.get('trackingid'): + headers.update({ + "X-EBAY-API-TRACKING-ID": self.config.get('trackingid') + }) + + if self.config.get('trackingpartnercode'): + headers.update({ + "X-EBAY-API-TRACKING-PARTNER-CODE": self.config.get('trackingpartnercode') + }) + + return headers + + def build_request_data(self, verb, data): + + xml = "" + xml += "<" + verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" + xml += to_xml(data) or '' + xml += "" + + return xml + + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + resp_codes = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("Errors"): + eSeverity = None + eClass = None + eShortMsg = None + eLongMsg = None + eCode = None + + if e.getElementsByTagName('SeverityCode'): + eSeverity = getNodeText(e.getElementsByTagName('SeverityCode')[0]) + + if e.getElementsByTagName('ErrorClassification'): + eClass = getNodeText(e.getElementsByTagName('ErrorClassification')[0]) + + if e.getElementsByTagName('ErrorCode'): + eCode = float(getNodeText(e.getElementsByTagName('ErrorCode')[0])) + if eCode.is_integer(): + eCode = int(eCode) + + if eCode not in resp_codes: + resp_codes.append(eCode) + + if e.getElementsByTagName('ShortMessage'): + eShortMsg = getNodeText(e.getElementsByTagName('ShortMessage')[0]) + + if e.getElementsByTagName('LongMessage'): + eLongMsg = getNodeText(e.getElementsByTagName('LongMessage')[0]) + + msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ + % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + self._resp_codes = resp_codes + + if self.config.get('warnings') and len(warnings) > 0: + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + if self.response_dict().Ack == 'Failure': + if self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + + return [] diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py new file mode 100644 index 0000000..45f1e2f --- /dev/null +++ b/ebaysdk/soa/__init__.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +from ebaysdk import log +from ebaysdk.connection import BaseConnection +from ebaysdk.config import Config +from ebaysdk.utils import getNodeText, to_xml, xml2dict + +class Connection(BaseConnection): + """Connection class for a base SOA service""" + + def __init__(self, app_config=None, site_id='EBAY-US', debug=False, **kwargs): + """SOA Connection class constructor""" + + super(Connection, self).__init__(method='POST', debug=debug, **kwargs) + + self.config=Config(domain=kwargs.get('domain', ''), + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + self.config.set('https', False) + self.config.set('site_id', site_id) + self.config.set('content_type', 'text/xml;charset=UTF-8') + self.config.set('request_encoding', 'XML') + self.config.set('response_encoding', 'XML') + self.config.set('message_protocol', 'SOAP12') + self.config.set('soap_env_str', '') ## http://www.ebay.com/marketplace/fundraising/v1/services', + + ph = None + pp = 80 + if app_config: + self.load_from_app_config(app_config) + ph = self.config.get('proxy_host', ph) + pp = self.config.get('proxy_port', pp) + + + # override this method, to provide setup through a config object, which + # should provide a get() method for extracting constants we care about + # this method should then set the .api_config[] dict (e.g. the comment below) + def load_from_app_config(self, app_config): + #self.api_config['domain'] = app_config.get('API_SERVICE_DOMAIN') + #self.api_config['uri'] = app_config.get('API_SERVICE_URI') + pass + + # Note: this method will always return at least an empty object_dict! + # It used to return None in some cases. If you get an empty dict, + # you can use the .error() method to look for the cause. + def response_dict(self): + if self._response_dict: + return self._response_dict + + mydict = xml2dict().fromstring(self._response_content) + + try: + verb = self.verb + 'Response' + self._response_dict = mydict['Envelope']['Body'][verb] + + except KeyError: + self._response_dict = mydict.get(self.verb + 'Response', mydict) + + return self._response_dict + + def build_request_headers(self, verb): + return { + 'Content-Type': self.config.get('content_type'), + 'X-EBAY-SOA-SERVICE-NAME': self.config.get('service'), + 'X-EBAY-SOA-OPERATION-NAME': verb, + 'X-EBAY-SOA-GLOBAL-ID': self.config.get('site_id'), + 'X-EBAY-SOA-REQUEST-DATA-FORMAT': self.config.get('request_encoding'), + 'X-EBAY-SOA-RESPONSE-DATA-FORMAT': self.config.get('response_encoding'), + 'X-EBAY-SOA-MESSAGE-PROTOCOL': self.config.get('message_protocol'), + } + + def build_request_data(self, verb, data): + xml = '' + xml += ' 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + + 5014CoreRuntimeErrorSystem + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + resp_codes = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("error"): + eSeverity = None + eDomain = None + eMsg = None + eId = None + + if e.getElementsByTagName('severity'): + eSeverity = getNodeText(e.getElementsByTagName('severity')[0]) + + if e.getElementsByTagName('domain'): + eDomain = getNodeText(e.getElementsByTagName('domain')[0]) + + if e.getElementsByTagName('errorId'): + eId = getNodeText(e.getElementsByTagName('errorId')[0]) + if int(eId) not in resp_codes: + resp_codes.append(int(eId)) + + if e.getElementsByTagName('message'): + eMsg = getNodeText(e.getElementsByTagName('message')[0]) + + msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ + % (eDomain, eSeverity, eId, eMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + self._resp_codes = resp_codes + + if self.config.get('warnings') and len(warnings) > 0: + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + try: + if self.response_dict().ack == 'Success' and len(errors) > 0 and self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + elif len(errors) > 0: + if self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors + except AttributeError: + pass + + return [] diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py new file mode 100644 index 0000000..307bb52 --- /dev/null +++ b/ebaysdk/soa/finditem.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk.soa import Connection as BaseConnection +from ebaysdk.utils import to_xml, getNodeText + +class Connection(BaseConnection): + """ + Not to be confused with Finding service + + Implements FindItemServiceNextGen + + https://wiki.vip.corp.ebay.com/display/apdoc/FindItemServiceNextGen + + This class is a bit hackish, it subclasses SOAService, but removes + SOAP support. FindItemServiceNextGen works fine with standard XML + and lets avoid all of the ugliness associated with SOAP. + + >>> from ebaysdk2.shopping import Connection as Shopping + >>> s = Shopping(config_file=os.environ.get('EBAY_YAML')) + >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + >>> nodes = s.response_dom().getElementsByTagName('ItemID') + >>> itemIds = [getNodeText(n) for n in nodes] + >>> len(itemIds) > 0 + True + >>> f = Connection(debug=False, config_file=os.environ.get('EBAY_YAML')) + >>> records = f.find_items_by_ids(itemIds) + >>> len(records) > 0 + True + """ + + def __init__(self, site_id='EBAY-US', debug=False, consumer_id=None, + **kwargs): + + super(Connection, self).__init__(consumer_id=consumer_id, + domain='apifindingcore.vip.ebay.com', + app_config=None, + site_id=site_id, + debug=debug, **kwargs) + + self.config.set('domain', 'apifindingcore.vip.ebay.com') + self.config.set('service', 'FindItemServiceNextGen', force=True) + self.config.set('https', False) + self.config.set('uri', "/services/search/FindItemServiceNextGen/v1", force=True) + self.config.set('consumer_id', consumer_id) + + self.read_set = None + + def build_request_headers(self, verb): + return { + "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), + "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), + "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": verb, + "X-EBAY-SOA-CONSUMER-ID": self.config.get('consumer_id', ''), + "Content-Type": "text/xml" + } + + def findItemsByIds(self, ebay_item_ids, + read_set=['ITEM_ID', 'TITLE', 'SELLER_NAME', 'ALL_CATS', 'ITEM_CONDITION_NEW']): + + self.read_set = read_set + read_set_node = [] + + for rtype in self.read_set: + read_set_node.append({ + 'member': { + 'namespace': 'ItemDictionary', + 'name': rtype + } + }) + + args = {'id': ebay_item_ids, 'readSet': read_set_node} + self.execute('findItemsByIds', args) + return self.mappedResponse() + + def mappedResponse(self): + records = [] + + for r in self.response_dict().get('record', []): + mydict = dict() + i = 0 + for values_dict in r.value: + for key, value in values_dict.iteritems(): + value_data = None + if type(value) == list: + value_data = [x['value'] for x in value] + else: + value_data = value['value'] + + mydict.update({self.read_set[i]: value_data}) + + i = i+1 + + records.append(mydict) + + return records + + def find_items_by_ids(self, *args, **kwargs): + return self.findItemsByIds(*args, **kwargs) + + def build_request_data(self, verb, data): + xml = "" + xml += "<" + verb + "Request" + xml += ' xmlns="http://www.ebay.com/marketplace/search/v1/services"' + xml += '>' + xml += to_xml(data) or '' + xml += "" + + return xml + diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py new file mode 100644 index 0000000..4a10b94 --- /dev/null +++ b/ebaysdk/trading/__init__.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk import log +from ebaysdk.connection import BaseConnection +from ebaysdk.config import Config +from ebaysdk.utils import getNodeText, to_xml + +class Connection(BaseConnection): + """Trading API class + + API documentation: + https://www.x.com/developers/ebay/products/trading-api + + Supported calls: + AddItem + ReviseItem + GetUser + (all others, see API docs) + + Doctests: + >>> t = Connection(config_file=os.environ.get('EBAY_YAML')) + >>> retval = t.execute('GetCharities', {'CharityID': 3897}) + >>> charity_name = '' + >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: + ... charity_name = getNodeText(t.response_dom().getElementsByTagName('Name')[0]) + >>> print(charity_name) + Sunshine Kids Foundation + >>> print(t.error()) + None + >>> t2 = Connection(errors=False, config_file=os.environ.get('EBAY_YAML')) + >>> retval2 = t2.execute('VerifyAddItem', {}) + >>> print(t2.response_codes()) + [5] + """ + + def __init__(self, **kwargs): + """Trading class constructor. + + Keyword arguments: + domain -- API endpoint (default: api.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /ws/api.dll) + appid -- eBay application id + devid -- eBay developer id + certid -- eBay cert id + token -- eBay application/user token + siteid -- eBay country site id (default: 0 (US)) + compatibility -- version number (default: 648) + https -- execute of https (default: True) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ + super(Connection, self).__init__(method='POST', **kwargs) + + self.config=Config(domain=kwargs.get('domain', 'api.ebay.com'), + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + + # override yaml defaults with args sent to the constructor + self.config.set('domain', kwargs.get('domain', 'api.ebay.com')) + self.config.set('uri', '/ws/api.dll') + self.config.set('warnings', True) + self.config.set('errors', True) + self.config.set('https', True) + self.config.set('siteid', 0) + self.config.set('response_encoding', 'XML') + self.config.set('request_encoding', 'XML') + self.config.set('proxy_host', None) + self.config.set('proxy_port', None) + self.config.set('token', None) + self.config.set('iaf_token', None) + self.config.set('appid', None) + self.config.set('devid', None) + self.config.set('certid', None) + self.config.set('version', '837') + self.config.set('compatibility', '837') + + def build_request_headers(self, verb): + headers = { + "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('version', ''), + "X-EBAY-API-DEV-NAME": self.config.get('devid', ''), + "X-EBAY-API-APP-NAME": self.config.get('appid', ''), + "X-EBAY-API-CERT-NAME": self.config.get('certid', ''), + "X-EBAY-API-SITEID": self.config.get('siteid', ''), + "X-EBAY-API-CALL-NAME": self.verb, + "Content-Type": "text/xml" + } + if self.config.get('iaf_token', None): + headers["X-EBAY-API-IAF-TOKEN"] = self.config.get('iaf_token') + + return headers + + def build_request_data(self, verb, data): + xml = "" + xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" + if not self.config.get('iaf_token', None): + xml += "" + if self.config.get('token', None): + xml += "%s" % self.config.get('token') + elif self.config.get('username', None): + xml += "%s" % self.config.get('username', '') + if self.config.get('password', None): + xml += "%s" % self.config.get('password', '') + xml += "" + xml += to_xml(data) or '' + xml += "" + return xml + + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + resp_codes = [] + + if self.verb is None: + return errors + + dom = self.response_dom() + if dom is None: + return errors + + for e in dom.getElementsByTagName("Errors"): + eSeverity = None + eClass = None + eShortMsg = None + eLongMsg = None + eCode = None + + if e.getElementsByTagName('SeverityCode'): + eSeverity = getNodeText(e.getElementsByTagName('SeverityCode')[0]) + + if e.getElementsByTagName('ErrorClassification'): + eClass = getNodeText(e.getElementsByTagName('ErrorClassification')[0]) + + if e.getElementsByTagName('ErrorCode'): + eCode = getNodeText(e.getElementsByTagName('ErrorCode')[0]) + if int(eCode) not in resp_codes: + resp_codes.append(int(eCode)) + + if e.getElementsByTagName('ShortMessage'): + eShortMsg = getNodeText(e.getElementsByTagName('ShortMessage')[0]) + + if e.getElementsByTagName('LongMessage'): + eLongMsg = getNodeText(e.getElementsByTagName('LongMessage')[0]) + + msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ + % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + self._resp_codes = resp_codes + + if self.config.get('warnings') and len(warnings) > 0: + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + if self.response_dict().Ack == 'Failure': + if self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + + return errors + + return [] diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 203e578..2e7cc93 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -6,12 +6,71 @@ Licensed under CDDL 1.0 ''' -import xml.etree.ElementTree as ET -import re -import sys +try: + import xml.etree.ElementTree as ET +except: + import cElementTree as ET # for 2.4 +import re from io import BytesIO +def to_xml(data): + "Converts a list or dictionary to XML and returns it." + + xml = '' + + if type(data) == dict: + xml = dict2xml(data) + elif type(data) == list: + xml = list2xml(data) + else: + xml = data + + return xml + +def getValue(response_dict, *args, **kwargs): + args_a = [w for w in args] + first = args_a[0] + args_a.remove(first) + + h = kwargs.get('mydict', {}) + if h: + h = h.get(first, {}) + else: + h = response_dict.get(first, {}) + + if len(args) == 1: + try: + return h.get('value', None) + except: + return h + + last = args_a.pop() + + for a in args_a: + h = h.get(a, {}) + + h = h.get(last, {}) + + try: + return h.get('value', None) + except: + return h + +def getNodeText(node): + "Returns the node's text string." + + rc = [] + + if hasattr(node, 'childNodes'): + for cn in node.childNodes: + if cn.nodeType == cn.TEXT_NODE: + rc.append(cn.data) + elif cn.nodeType == cn.CDATA_SECTION_NODE: + rc.append(cn.data) + + return ''.join(rc) + class object_dict(dict): """object view of dict, you can >>> a = object_dict() @@ -339,7 +398,7 @@ def dict2et(xmldict, roottag='data', listnames=None): >>> data = {"nr": "xq12", "positionen": [{"m": 12}, {"m": 2}]} >>> root = dict2et(data) - >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') + >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') 'xq12122' Per default ecerything ins put in an enclosing '' element. Also per default lists are converted @@ -348,11 +407,11 @@ def dict2et(xmldict, roottag='data', listnames=None): >>> data = {"positionen": [{"m": 12}, {"m": 2}]} >>> root = dict2et(data, roottag='xml') - >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') + >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') '122' >>> root = dict2et(data, roottag='xml', listnames={'positionen': 'position'}) - >>> ET.tostring(root, encoding="unicode").replace('<>', '').replace('','') + >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') '122' >>> data = {"kommiauftragsnr":2103839, "anliefertermin":"2009-11-25", "prioritaet": 7, @@ -364,7 +423,7 @@ def dict2et(xmldict, roottag='data', listnames=None): >>> print ET.tostring(dict2et(data, 'kommiauftrag', ... listnames={'positionen': 'position', 'versandeinweisungen': 'versandeinweisung'}), - ... encoding="unicode").replace('<>', '').replace('','') + ... encoding="utf-8").replace('<>', '').replace('','') ... # doctest: +SKIP ''' 2009-11-25 diff --git a/ebaysdk/utils2.py b/ebaysdk/utils2.py deleted file mode 100644 index c92b47d..0000000 --- a/ebaysdk/utils2.py +++ /dev/null @@ -1,342 +0,0 @@ -# -*- coding: utf-8 -*- - -''' -© 2012-2013 eBay Software Foundation -Authored by: Tim Keefer -Licensed under CDDL 1.0 -''' - -try: - import xml.etree.ElementTree as ET -except: - import cElementTree as ET # for 2.4 - -import re - -from io import BytesIO - -class object_dict(dict): - """object view of dict, you can - >>> a = object_dict() - >>> a.fish = 'fish' - >>> a['fish'] - 'fish' - >>> a['water'] = 'water' - >>> a.water - 'water' - >>> a.test = {'value': 1} - >>> a.test2 = object_dict({'name': 'test2', 'value': 2}) - >>> a.test, a.test2.name, a.test2.value - (1, 'test2', 2) - """ - def __init__(self, initd=None): - if initd is None: - initd = {} - dict.__init__(self, initd) - - def __getattr__(self, item): - - d = self.__getitem__(item) - - if isinstance(d, dict) and 'value' in d and len(d) == 1: - return d['value'] - else: - return d - - # if value is the only key in object, you can omit it - - def __setattr__(self, item, value): - self.__setitem__(item, value) - - def getvalue(self, item, value=None): - return self.get(item, object_dict()).get('value', value) - - def __getstate__(self): - return list(self.items()) - - def __setstate__(self, items): - self.update(items) - -class xml2dict(object): - - def __init__(self): - pass - - def _parse_node(self, node): - node_tree = object_dict() - # Save attrs and text, hope there will not be a child with same name - if node.text: - node_tree.value = node.text - for (k,v) in list(node.attrib.items()): - k,v = self._namespace_split(k, object_dict({'value':v})) - node_tree[k] = v - #Save childrens - for child in list(node): - tag, tree = self._namespace_split(child.tag, self._parse_node(child)) - if tag not in node_tree: # the first time, so store it in dict - node_tree[tag] = tree - continue - old = node_tree[tag] - if not isinstance(old, list): - node_tree.pop(tag) - node_tree[tag] = [old] # multi times, so change old dict to a list - node_tree[tag].append(tree) # add the new one - - return node_tree - - def _namespace_split(self, tag, value): - """ - Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients' - ns = http://cs.sfsu.edu/csc867/myscheduler - name = patients - """ - result = re.compile("\{(.*)\}(.*)").search(tag) - if result: - value.namespace, tag = result.groups() - - return (tag,value) - - def parse(self, file): - """parse a xml file to a dict""" - f = open(file, 'r') - return self.fromstring(f.read()) - - def fromstring(self, s): - """parse a string""" - t = ET.fromstring(s) - root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) - return object_dict({root_tag: root_tree}) - - -class dict2xml: - xml = "" - level = 0 - - def __init__(self,encoding=None,attributes=False): - self.xml = "" - self.level = 0 - self.encoding = encoding - self.attributes = attributes - - def __del__(self): - pass - - def setXml(self,Xml): - self.xml = Xml - - def setLevel(self,Level): - self.level = Level - - def tostring(self,d): - return self.dict2xml(d) - - def dict2xml(self,map): - if type(map) == object_dict or type(map) == dict: - for key, value in list(map.items()): - keyo = key - if self.attributes: - # FIXME: This assumes the attributes do not require encoding - keyc = re.sub(r' .+$',r'',key) - else: - keyc = key - if type(value) == object_dict or type(value) == dict: - if(len(value) > 0): - self.xml += " "*self.level - self.xml += "<%s>\n" % (keyo) - self.level += 1 - self.dict2xml(value) - self.level -= 1 - self.xml += " "*self.level - self.xml += "\n" % (keyc) - else: - self.xml += " "*(self.level) - self.xml += "<%s>\n" % (keyo,keyc) - elif type(value) == list: - for v in value: - self.dict2xml({key:v}) - else: - self.xml += " "*(self.level) - self.xml += "<%s>%s\n" % (keyo,self.encode(value),keyc) - else: - self.xml += " "*self.level - self.xml += "<%s>%s\n" % (keyo,self.encode(value),keyc) - return self.xml - - def encode(self,str1): - if type(str1) != str and type(str1) != str: - str1 = str(str1) - if self.encoding: - str1 = str1.encode(self.encoding) - str2 = '' - for c in str1: - if c == '&': - str2 += '&' - elif c == '<': - str2 += '`' - elif c == '>': - str2 += 'b' - else: - str2 += c - return str2 - -def list_to_xml(name, l, stream): - for d in l: - dict_to_xml(d, name, stream) - -def dict_to_xml(d, root_node_name, stream): - """ Transform a dict into a XML, writing to a stream """ - stream.write('\n<' + root_node_name) - attributes = BytesIO() - nodes = BytesIO() - for item in list(d.items()): - key, value = item - if isinstance(value, dict): - dict_to_xml(value, key, nodes) - elif isinstance(value, list): - list_to_xml(key, value, nodes) - elif isinstance(value, str) or isinstance(value, str): - attributes.write('\n %s="%s" ' % (key, value)) - else: - raise TypeError('sorry, we support only dicts, lists and strings') - - stream.write(attributes.getvalue()) - nodes_str = nodes.getvalue() - if len(nodes_str) == 0: - stream.write('/>') - else: - stream.write('>') - stream.write(nodes_str) - stream.write('\n' % root_node_name) - -def dict_from_xml(xml): - """ Load a dict from a XML string """ - - def list_to_dict(l, ignore_root = True): - """ Convert our internal format list to a dict. We need this - because we use a list as a intermediate format during xml load """ - root_dict = {} - inside_dict = {} - # index 0: node name - # index 1: attributes list - # index 2: children node list - root_dict[l[0]] = inside_dict - inside_dict.update(l[1]) - # if it's a node containing lot's of nodes with same name, - # like - for x in l[2]: - d = list_to_dict(x, False) - for k, v in d.items(): - if k not in inside_dict: - inside_dict[k] = [] - - inside_dict[k].append(v) - - ret = root_dict - if ignore_root: - ret = list(root_dict.values())[0] - - return ret - - class M: - """ This is our expat event sink """ - def __init__(self): - self.lists_stack = [] - self.current_list = None - def start_element(self, name, attrs): - l = [] - # root node? - if self.current_list is None: - self.current_list = [name, attrs, l] - else: - self.current_list.append([name, attrs, l]) - - self.lists_stack.append(self.current_list) - self.current_list = l - pass - - def end_element(self, name): - self.current_list = self.lists_stack.pop() - def char_data(self, data): - # We don't write char_data to file (beyond \n and spaces). - # What to do? Raise? - pass - - p = expat.ParserCreate() - m = M() - - p.StartElementHandler = m.start_element - p.EndElementHandler = m.end_element - p.CharacterDataHandler = m.char_data - - p.Parse(xml) - - d = list_to_dict(m.current_list) - - return d - -class ConfigHolder: - def __init__(self, d=None): - """ - Init from dict d - """ - if d is None: - self.d = {} - else: - self.d = d - - def __str__(self): - return self.d.__str__() - - __repr__ = __str__ - - def load_from_xml(self, xml): - self.d = dict_from_xml(xml) - - def load_from_dict(self, d): - self.d = d - - def get_must_exist(self, key): - v = self.get(key) - - if v is None: - raise KeyError('the required config key "%s" was not found' % key) - - return v - - def __getitem__(self, key): - """ - Support for config['path/key'] syntax - """ - return self.get_must_exist(key) - - def get(self, key, default=None): - """ - Get from config using a filesystem-like syntax - - value = 'start/sub/key' will - return config_map['start']['sub']['key'] - """ - try: - d = self.d - - path = key.split('/') - # handle 'key/subkey[2]/value/' - if path[-1] == '' : - path = path[:-1] - - for x in path[:len(path)-1]: - i = x.find('[') - if i: - if x[-1] != ']': - raise Exception('invalid syntax') - index = int(x[i+1:-1]) - - d = d[x[:i]][index] - else: - d = d[x] - - return d[path[-1]] - - except: - return default - diff --git a/samples/common.py b/samples/common.py new file mode 100644 index 0000000..cc2a166 --- /dev/null +++ b/samples/common.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import json + +def dump(api, full=False): + + print("\n") + + if api.warnings(): + print("Warnings" + api.warnings()) + + if api.response_content(): + print("Call Success: %s in length" % len(api.response_content())) + + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) + + if full: + print(api.response_content()) + print((json.dumps(api.response_dict(), indent=2))) + else: + dictstr = "%s" % api.response_dict() + print("Response dictionary: %s..." % dictstr[:150]) diff --git a/samples/finding.py b/samples/finding.py index e27de09..c9cfd6b 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -11,9 +11,11 @@ sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) -import ebaysdk -from ebaysdk import finding +from common import dump +import ebaysdk +from ebaysdk.finding import Connection as finding +from ebaysdk.exception import ConnectionError def init_options(): usage = "usage: %prog [options]" @@ -34,49 +36,40 @@ def init_options(): def run(opts): - api = finding(siteid='EBAY-NLBE', debug=opts.debug, appid=opts.appid, config_file=opts.yaml, - warnings=True) - api.execute('findItemsAdvanced', { - 'keywords': 'python', - 'itemFilter': [ - {'name': 'Condition', - 'value': 'Used'}, - {'name': 'LocatedIn', - 'value': 'GB'}, - ], - 'affiliate': {'trackingId': 1}, - 'sortOrder': 'CountryDescending', - }) + try: + api = finding(siteid='EBAY-NLBE', debug=opts.debug, appid=opts.appid, + config_file=opts.yaml, warnings=True) - if api.error(): - raise Exception(api.error()) + api.execute('findItemsAdvanced', { + 'keywords': 'python', + 'itemFilter': [ + {'name': 'Condition', + 'value': 'Used'}, + {'name': 'LocatedIn', + 'value': 'GB'}, + ], + 'affiliate': {'trackingId': 1}, + 'sortOrder': 'CountryDescending', + }) - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) + dump(api) - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) + except ConnectionError as e: + print e - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:250]) def run2(opts): - api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsByProduct', '53039031') - - if api.error(): - raise Exception(api.error()) - - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) + try: + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api.execute('findItemsByProduct', '53039031') + + dump(api) - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) + except ConnectionError as e: + print e - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:50]) if __name__ == "__main__": print("Finding samples for SDK version %s" % ebaysdk.get_version()) diff --git a/samples/finditem.py b/samples/finditem.py new file mode 100644 index 0000000..314a01c --- /dev/null +++ b/samples/finditem.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import sys +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +from common import dump + +import ebaysdk +from ebaysdk.soa.finditem import Connection as FindItem +from ebaysdk.shopping import Connection as Shopping +from ebaysdk.utils import getNodeText +from ebaysdk.exception import ConnectionError + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + parser.add_option("-y", "--yaml", + dest="yaml", default='ebay.yaml', + help="Specifies the name of the YAML defaults file. [default: %default]") + parser.add_option("-a", "--appid", + dest="appid", default=None, + help="Specifies the eBay application id to use.") + parser.add_option("-c", "--consumer_id", + dest="consumer_id", default=None, + help="Specifies the eBay consumer_id id to use.") + + (opts, args) = parser.parse_args() + return opts, args + +def run(opts): + + try: + + shopping = Shopping(debug=opts.debug, appid=opts.appid, + config_file=opts.yaml, warnings=False) + + shopping.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + nodes = shopping.response_dom().getElementsByTagName('ItemID') + itemIds = [getNodeText(n) for n in nodes] + + api = FindItem(debug=opts.debug, consumer_id=opts.consumer_id, config_file=opts.yaml) + + records = api.find_items_by_ids(itemIds) + + for r in records: + print("ID(%s) TITLE(%s)" % (r['ITEM_ID'], r['TITLE'][:35])) + + dump(api) + + except ConnectionError as e: + print e + + +if __name__ == "__main__": + print("FindItem samples for SDK version %s" % ebaysdk.get_version()) + (opts, args) = init_options() + run(opts) diff --git a/samples/merchandising.py b/samples/merchandising.py index e7b175f..1bf1c5a 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -7,14 +7,15 @@ import os import sys -import json from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) +from common import dump + import ebaysdk from ebaysdk import merchandising - +from ebaysdk.exception import ConnectionError def init_options(): usage = "usage: %prog [options]" @@ -33,38 +34,16 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args - -def dump(api, full=False): - - print("\n") - - if api.warnings(): - print("Warnings" + api.warnings()) - - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) - - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) - - if full: - print(api.response_content()) - print((json.dumps(api.response_dict(), indent=2))) - else: - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:150]) - - def run(opts): - api = merchandising(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, - warnings=True) - - api.execute('getMostWatchedItems', {'maxResults': 3}) + try: + api = merchandising(debug=opts.debug, appid=opts.appid, + config_file=opts.yaml, warnings=True) - if api.error(): - raise Exception(api.error()) + api.execute('getMostWatchedItems', {'maxResults': 3}) - dump(api) + dump(api) + except ConnectionError as e: + print e if __name__ == "__main__": diff --git a/samples/shopping.py b/samples/shopping.py index 39a5ba1..6cc75b4 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -7,7 +7,6 @@ import os import sys -import json from optparse import OptionParser try: @@ -17,8 +16,11 @@ sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) +from common import dump + import ebaysdk -from ebaysdk import shopping +from ebaysdk.exception import ConnectionError +from ebaysdk.shopping import Connection as Shopping def init_options(): @@ -38,56 +40,38 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args - -def dump(api, full=False): - - print("\n") - - if api.warnings(): - print("Warnings" + api.warnings()) - - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) - - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) - - if full: - print(api.response_content()) - print((json.dumps(api.response_dict(), indent=2))) - else: - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:150]) - def run(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) print("Shopping samples for SDK version %s" % ebaysdk.get_version()) - if api.error(): - raise Exception(api.error()) + try: + api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) + if api.response_content(): + print("Call Success: %s in length" % len(api.response_content())) - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) + print("Response code: %s" % api.response_code()) + print("Response DOM: %s" % api.response_dom()) + + dictstr = "%s" % api.response_dict() + print("Response dictionary: %s..." % dictstr[:50]) - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:50]) + print("Matching Titles:") + for item in api.response_dict().ItemArray.Item: + print(item.Title) - print("Matching Titles:") - for item in api.response_dict().ItemArray.Item: - print(item.Title) + except ConnectionError as e: + print e def popularSearches(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) + choice = True while choice: @@ -104,58 +88,73 @@ def popularSearches(opts): "QueryKeywords": choice, } - api.execute('FindPopularSearches', mySearch) + try: + api.execute('FindPopularSearches', mySearch) - #dump(api, full=True) + #dump(api, full=True) - print("Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches) + print("Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches) - for term in api.response_dict().PopularSearchResult.AlternativeSearches.split(';')[:3]: - api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) + for term in api.response_dict().PopularSearchResult.AlternativeSearches.split(';')[:3]: + api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) - print("Term: %s" % term) + print("Term: %s" % term) - try: - for item in api.response_dict().ItemArray.Item: - print(item.Title) - except AttributeError: - pass + try: + for item in api.response_dict().ItemArray.Item: + print(item.Title) + except AttributeError: + pass - # dump(api) + # dump(api) - print("\n") + print("\n") + except ConnectionError as e: + print e def categoryInfo(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, - warnings=True) + try: + api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) - api.execute('GetCategoryInfo', {"CategoryID": 3410}) - - dump(api, full=False) + api.execute('GetCategoryInfo', {"CategoryID": 3410}) + dump(api, full=False) + + except ConnectionError as e: + print e def with_affiliate_info(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, - warnings=True, trackingid=1234, trackingpartnercode=9) + try: + api = Shopping(debug=opts.debug, appid=opts.appid, + config_file=opts.yaml, warnings=True, + trackingid=1234, trackingpartnercode=9) - mySearch = { - "MaxKeywords": 10, - "QueryKeywords": 'shirt', - } + mySearch = { + "MaxKeywords": 10, + "QueryKeywords": 'shirt', + } - api.execute('FindPopularSearches', mySearch) + api.execute('FindPopularSearches', mySearch) + dump(api, full=True) - dump(api, full=False) + except ConnectionError as e: + print e def using_attributes(opts): - api = shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, - warnings=True) - api.execute('FindProducts', { - "ProductID": {'@attrs': {'type': 'ISBN'}, - '#text': '0596154488'}}) + try: + api = Shopping(debug=opts.debug, appid=opts.appid, + config_file=opts.yaml, warnings=True) + + api.execute('FindProducts', { + "ProductID": {'@attrs': {'type': 'ISBN'}, + '#text': '0596154488'}}) + + dump(api, full=False) - dump(api, full=False) + except ConnectionError as e: + print e if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/trading.py b/samples/trading.py index 197dc8c..7a69e96 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -8,13 +8,16 @@ import os import sys import datetime -import json from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) +from common import dump + import ebaysdk -from ebaysdk import trading +from ebaysdk.utils import getNodeText +from ebaysdk.exception import ConnectionError +from ebaysdk.trading import Connection as Trading def init_options(): @@ -40,265 +43,252 @@ def init_options(): (opts, args) = parser.parse_args() return opts, args - -def dump(api, full=False): - - print("\n") - - if api.warnings(): - print("Warnings" + api.warnings()) - - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) - - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) - - if full: - print(api.response_content()) - print((json.dumps(api.response_dict(), indent=2))) - else: - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s..." % dictstr[:150]) - - def run(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid) - api.execute('GetCharities', {'CharityID': 3897}) + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid) - if api.error(): - raise Exception(api.error()) - - dump(api) - - print(api.response_dict().Charity.Name) + api.execute('GetCharities', {'CharityID': 3897}) + dump(api) + print(api.response_dict().Charity.Name) + except ConnectionError as e: + print e def feedback(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=False) - - api.execute('GetFeedback', {'UserID': 'tim0th3us'}) - - if api.error(): - raise Exception(api.error()) + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) - dump(api) + api.execute('GetFeedback', {'UserID': 'tim0th3us'}) + dump(api) - if int(api.response_dict().FeedbackScore) > 50: - print("Doing good!") - else: - print("Sell more, buy more..") + if int(api.response_dict().FeedbackScore) > 50: + print("Doing good!") + else: + print("Sell more, buy more..") + + except ConnectionError as e: + print e def getTokenStatus(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=False) - - api.execute('GetTokenStatus') - if api.error(): - raise Exception(api.error()) + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) - dump(api) + api.execute('GetTokenStatus') + dump(api) + except ConnectionError as e: + print e def verifyAddItem(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc """ - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=False) - - myitem = { - "Item": { - "Title": "Harry Potter and the Philosopher's Stone", - "Description": "This is the first book in the Harry Potter series. In excellent condition!", - "PrimaryCategory": {"CategoryID": "377"}, - "StartPrice": "1.0", - "CategoryMappingAllowed": "true", - "Country": "US", - "ConditionID": "3000", - "Currency": "USD", - "DispatchTimeMax": "3", - "ListingDuration": "Days_7", - "ListingType": "Chinese", - "PaymentMethods": "PayPal", - "PayPalEmailAddress": "tkeefdddder@gmail.com", - "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, - "PostalCode": "95125", - "Quantity": "1", - "ReturnPolicy": { - "ReturnsAcceptedOption": "ReturnsAccepted", - "RefundOption": "MoneyBack", - "ReturnsWithinOption": "Days_30", - "Description": "If you are not satisfied, return the book for refund.", - "ShippingCostPaidByOption": "Buyer" - }, - "ShippingDetails": { - "ShippingType": "Flat", - "ShippingServiceOptions": { - "ShippingServicePriority": "1", - "ShippingService": "USPSMedia", - "ShippingServiceCost": "2.50" - } - }, - "Site": "US" + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + myitem = { + "Item": { + "Title": "Harry Potter and the Philosopher's Stone", + "Description": "This is the first book in the Harry Potter series. In excellent condition!", + "PrimaryCategory": {"CategoryID": "377"}, + "StartPrice": "1.0", + "CategoryMappingAllowed": "true", + "Country": "US", + "ConditionID": "3000", + "Currency": "USD", + "DispatchTimeMax": "3", + "ListingDuration": "Days_7", + "ListingType": "Chinese", + "PaymentMethods": "PayPal", + "PayPalEmailAddress": "tkeefdddder@gmail.com", + "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, + "PostalCode": "95125", + "Quantity": "1", + "ReturnPolicy": { + "ReturnsAcceptedOption": "ReturnsAccepted", + "RefundOption": "MoneyBack", + "ReturnsWithinOption": "Days_30", + "Description": "If you are not satisfied, return the book for refund.", + "ShippingCostPaidByOption": "Buyer" + }, + "ShippingDetails": { + "ShippingType": "Flat", + "ShippingServiceOptions": { + "ShippingServicePriority": "1", + "ShippingService": "USPSMedia", + "ShippingServiceCost": "2.50" + } + }, + "Site": "US" + } } - } - - api.execute('VerifyAddItem', myitem) - - if api.error(): - raise Exception(api.error()) - dump(api) + api.execute('VerifyAddItem', myitem) + dump(api) + except ConnectionError as e: + print e def verifyAddItemErrorCodes(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc """ - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=False) - - myitem = { - "Item": { - "Title": "Harry Potter and the Philosopher's Stone", - "Description": "This is the first book in the Harry Potter series. In excellent condition!", - "PrimaryCategory": {"CategoryID": "377aaaaaa"}, - "StartPrice": "1.0", - "CategoryMappingAllowed": "true", - "Country": "US", - "ConditionID": "3000", - "Currency": "USD", - "DispatchTimeMax": "3", - "ListingDuration": "Days_7", - "ListingType": "Chinese", - "PaymentMethods": "PayPal", - "PayPalEmailAddress": "tkeefdddder@gmail.com", - "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, - "PostalCode": "95125", - "Quantity": "1", - "ReturnPolicy": { - "ReturnsAcceptedOption": "ReturnsAccepted", - "RefundOption": "MoneyBack", - "ReturnsWithinOption": "Days_30", - "Description": "If you are not satisfied, return the book for refund.", - "ShippingCostPaidByOption": "Buyer" - }, - "ShippingDetails": { - "ShippingType": "Flat", - "ShippingServiceOptions": { - "ShippingServicePriority": "1", - "ShippingService": "USPSMedia", - "ShippingServiceCost": "2.50" - } - }, - "Site": "US" + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=False) + + myitem = { + "Item": { + "Title": "Harry Potter and the Philosopher's Stone", + "Description": "This is the first book in the Harry Potter series. In excellent condition!", + "PrimaryCategory": {"CategoryID": "377aaaaaa"}, + "StartPrice": "1.0", + "CategoryMappingAllowed": "true", + "Country": "US", + "ConditionID": "3000", + "Currency": "USD", + "DispatchTimeMax": "3", + "ListingDuration": "Days_7", + "ListingType": "Chinese", + "PaymentMethods": "PayPal", + "PayPalEmailAddress": "tkeefdddder@gmail.com", + "PictureDetails": {"PictureURL": "http://i1.sandbox.ebayimg.com/03/i/00/30/07/20_1.JPG?set_id=8800005007"}, + "PostalCode": "95125", + "Quantity": "1", + "ReturnPolicy": { + "ReturnsAcceptedOption": "ReturnsAccepted", + "RefundOption": "MoneyBack", + "ReturnsWithinOption": "Days_30", + "Description": "If you are not satisfied, return the book for refund.", + "ShippingCostPaidByOption": "Buyer" + }, + "ShippingDetails": { + "ShippingType": "Flat", + "ShippingServiceOptions": { + "ShippingServicePriority": "1", + "ShippingService": "USPSMedia", + "ShippingServiceCost": "2.50" + } + }, + "Site": "US" + } } - } - - api.execute('VerifyAddItem', myitem) - - if api.error(): + api.execute('VerifyAddItem', myitem) + + except ConnectionError as e: # traverse the DOM to look for error codes for node in api.response_dom().getElementsByTagName('ErrorCode'): - print("error code: %s" % ebaysdk.nodeText(node)) + print("error code: %s" % getNodeText(node)) # check for invalid data - error code 37 if 37 in api.response_codes(): print("Invalid data in request") -def uploadPicture(opts): - - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=True) + print e - pictureData = { - "WarningLevel": "High", - "ExternalPictureURL": "http://developer.ebay.com/DevZone/XML/docs/images/hp_book_image.jpg", - "PictureName": "WorldLeaders" - } +def uploadPicture(opts): - api.execute('UploadSiteHostedPictures', pictureData) + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True) - if api.error(): - raise Exception(api.error()) + pictureData = { + "WarningLevel": "High", + "ExternalPictureURL": "http://developer.ebay.com/DevZone/XML/docs/images/hp_book_image.jpg", + "PictureName": "WorldLeaders" + } - dump(api) + api.execute('UploadSiteHostedPictures', pictureData) + dump(api) + except ConnectionError as e: + print e def memberMessages(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=True) - - now = datetime.datetime.now() - - memberData = { - "WarningLevel": "High", - "MailMessageType": "All", - # "MessageStatus": "Unanswered", - "StartCreationTime": now - datetime.timedelta(days=60), - "EndCreationTime": now, - "Pagination": { - "EntriesPerPage": "5", - "PageNumber": "1" + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True) + + now = datetime.datetime.now() + + memberData = { + "WarningLevel": "High", + "MailMessageType": "All", + # "MessageStatus": "Unanswered", + "StartCreationTime": now - datetime.timedelta(days=60), + "EndCreationTime": now, + "Pagination": { + "EntriesPerPage": "5", + "PageNumber": "1" + } } - } - api.execute('GetMemberMessages', memberData) + api.execute('GetMemberMessages', memberData) - dump(api) + dump(api) - if api.response_dict().MemberMessage: - messages = api.response_dict().MemberMessage.MemberMessageExchange + if api.response_dict().MemberMessage: + messages = api.response_dict().MemberMessage.MemberMessageExchange - if type(messages) != list: - messages = [ messages ] + if type(messages) != list: + messages = [ messages ] - for m in messages: - print("%s: %s" % (m.CreationDate, m.Question.Subject[:50])) + for m in messages: + print("%s: %s" % (m.CreationDate, m.Question.Subject[:50])) -def getUser(opts): + except ConnectionError as e: + print e - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) +def getUser(opts): + try: - api.execute('GetUser', {'UserID': 'biddergoat'}) + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) - dump(api, full=False) + api.execute('GetUser', {'UserID': 'biddergoat'}) + dump(api, full=False) + except ConnectionError as e: + print e def getOrders(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=True, timeout=20) + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20) - api.execute('GetOrders', {'NumberOfDays': 30}) + api.execute('GetOrders', {'NumberOfDays': 30}) + dump(api, full=False) - dump(api, full=False) + except ConnectionError as e: + print e def categories(opts): - api = trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, - certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) - - now = datetime.datetime.now() + try: + api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid, + certid=opts.certid, devid=opts.devid, warnings=True, timeout=20, siteid=101) - callData = { - 'DetailLevel': 'ReturnAll', - 'CategorySiteID': 101, - 'LevelLimit': 4, - } + callData = { + 'DetailLevel': 'ReturnAll', + 'CategorySiteID': 101, + 'LevelLimit': 4, + } - api.execute('GetCategories', callData) + api.execute('GetCategories', callData) + dump(api, full=False) - dump(api, full=False) + except ConnectionError as e: + print e ''' api = trading(domain='api.sandbox.ebay.com') @@ -317,11 +307,10 @@ def categories(opts): run(opts) feedback(opts) verifyAddItem(opts) + getTokenStatus(opts) verifyAddItemErrorCodes(opts) uploadPicture(opts) memberMessages(opts) categories(opts) getUser(opts) getOrders(opts) - getTokenStatus(opts) - diff --git a/setup.py b/setup.py index 950ed04..4f301c9 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ PKG = 'ebaysdk' # Get the version -VERSIONFILE = os.path.join(PKG, "_version.py") +VERSIONFILE = os.path.join(PKG, "__init__.py") version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", open(VERSIONFILE, "rt").read(), re.M).group(1) diff --git a/tests/__init__.py b/tests/__init__.py index d942bd1..5d0f9ac 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,13 +8,26 @@ import unittest import doctest -import ebaysdk -import os +import ebaysdk2.utils +import ebaysdk2.config +import ebaysdk2.connection +import ebaysdk2.finding +import ebaysdk2.shopping +import ebaysdk2.trading +import ebaysdk2.merchandising +import ebaysdk2.soa.finditem def getTestSuite(): suite = unittest.TestSuite() - suite.addTest(doctest.DocTestSuite(ebaysdk)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.connection)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.config)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.utils)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.finding)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.shopping)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.trading)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.merchandising)) + suite.addTest(doctest.DocTestSuite(ebaysdk2.soa.finditem)) return suite runner = unittest.TextTestRunner() From e6c33cc11a74322c7ae1eb1b6d48155139564e8c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 24 Jan 2014 11:48:18 -0800 Subject: [PATCH 125/218] fix tests --- ebaysdk/soa/finditem.py | 2 +- tests/__init__.py | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index 307bb52..173d36b 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -23,7 +23,7 @@ class Connection(BaseConnection): SOAP support. FindItemServiceNextGen works fine with standard XML and lets avoid all of the ugliness associated with SOAP. - >>> from ebaysdk2.shopping import Connection as Shopping + >>> from ebaysdk.shopping import Connection as Shopping >>> s = Shopping(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) >>> nodes = s.response_dom().getElementsByTagName('ItemID') diff --git a/tests/__init__.py b/tests/__init__.py index 5d0f9ac..5bebb3b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,26 +8,26 @@ import unittest import doctest -import ebaysdk2.utils -import ebaysdk2.config -import ebaysdk2.connection -import ebaysdk2.finding -import ebaysdk2.shopping -import ebaysdk2.trading -import ebaysdk2.merchandising -import ebaysdk2.soa.finditem +import ebaysdk.utils +import ebaysdk.config +import ebaysdk.connection +import ebaysdk.finding +import ebaysdk.shopping +import ebaysdk.trading +import ebaysdk.merchandising +import ebaysdk.soa.finditem def getTestSuite(): suite = unittest.TestSuite() - suite.addTest(doctest.DocTestSuite(ebaysdk2.connection)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.config)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.utils)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.finding)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.shopping)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.trading)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.merchandising)) - suite.addTest(doctest.DocTestSuite(ebaysdk2.soa.finditem)) + suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) + suite.addTest(doctest.DocTestSuite(ebaysdk.config)) + suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) + suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) + suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) + suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) + suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) + suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) return suite runner = unittest.TextTestRunner() From f94a02dfa5546ba2cff32d47e6e293addbad3d72 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 24 Jan 2014 15:42:19 -0800 Subject: [PATCH 126/218] add http (html) back-end --- ebaysdk/__init__.py | 7 +- ebaysdk/http/__init__.py | 136 +++++++++++++++++++++++++++++++++++++++ samples/http.py | 47 ++++++++++++++ tests/__init__.py | 2 + 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 ebaysdk/http/__init__.py create mode 100644 samples/http.py diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 05a5ce9..2560e82 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -78,4 +78,9 @@ def finding(*args, **kwargs): def merchandising(*args, **kwargs): from ebaysdk.merchandising import Connection as Merchandising - return Merchandising(*args, **kwargs) + return Merchandising(*args, **kwargs) + +def html(*args, **kwargs): + from ebaysdk.http import Connection as HTTP + return HTTP(*args, **kwargs) + diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py new file mode 100644 index 0000000..3bcfeb1 --- /dev/null +++ b/ebaysdk/http/__init__.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import uuid +import time + +from xml.parsers.expat import ExpatError +from xml.dom.minidom import parseString +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + +from requests import Request + +from ebaysdk import log, UserAgent +from ebaysdk.connection import BaseConnection +from ebaysdk.exception import ConnectionResponseError +from ebaysdk.config import Config +from ebaysdk.utils import getNodeText, to_xml, xml2dict + +class Connection(BaseConnection): + """HTML class for traditional calls. + + Doctests: + >>> h = Connection() + >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> print(h.response_obj().rss.channel.ttl) + 60 + >>> title = h.response_dom().getElementsByTagName('title')[0] + >>> print(getNodeText(title)) + mytouch slide + >>> print(title.toxml()) + <![CDATA[mytouch slide]]> + >>> print(h.error()) + None + >>> h = Connection(method='POST', debug=False) + >>> retval = h.execute('http://www.ebay.com/') + >>> print(h.response_content() != '') + True + >>> print(h.response_code()) + 200 + """ + + def __init__(self, method='GET', **kwargs): + """HTML class constructor. + + Keyword arguments: + debug -- debugging enabled (default: False) + method -- GET/POST/PUT (default: GET) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + """ + + super(Connection, self).__init__(method=method, **kwargs) + + self.config=Config(domain=None, + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + + def response_dom(self): + "Returns the HTTP response dom." + + try: + if not self._response_dom: + self._response_dom = parseString(self._response_content) + + return self._response_dom + except ExpatError: + raise ConnectionResponseError('response is not well-formed') + + def response_dict(self): + "Return the HTTP response dictionary." + + try: + if not self._response_dict and self.response_content: + self._response_dict = xml2dict().fromstring(self._response_content) + + return self._response_dict + except ExpatError: + raise ConnectionResponseError('response is not well-formed') + + def execute(self, url, data=None, headers=dict(), method=None): + "Executes the HTTP request." + log.debug('execute: url=%s data=%s' % (url, data)) + + if method: + self.method=method + + self._reset() + self.build_request(url, data, headers) + self.execute_request() + self.process_response() + self.error_check() + + log.debug('total time=%s' % (time.time() - self._time)) + + return self + + def build_request(self, url, data, headers): + + self._request_id = uuid.uuid4() + + headers.update({'User-Agent': UserAgent, + 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) + + request = Request(self.method, + url, + data=self.build_request_data(data), + headers=headers, + ) + + self.request = request.prepare() + + def warnings(self): + return '' + + def build_request_data(self, data): + "Builds and returns the request XML." + + if not data: + return None + if type(data) is str: + return data + else: + return urlencode(data) + diff --git a/samples/http.py b/samples/http.py new file mode 100644 index 0000000..992c9ba --- /dev/null +++ b/samples/http.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import sys + +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +from common import dump + +import ebaysdk +from ebaysdk.http import Connection as HTTP +from ebaysdk.exception import ConnectionError + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + + (opts, args) = parser.parse_args() + return opts, args + +def run(opts): + + try: + api = HTTP(debug=opts.debug, method='GET') + + api.execute('http://feeds.wired.com/wired/index') + + dump(api) + + except ConnectionError as e: + print e + +if __name__ == "__main__": + print("HTTP samples for SDK version %s" % ebaysdk.get_version()) + (opts, args) = init_options() + run(opts) diff --git a/tests/__init__.py b/tests/__init__.py index 5bebb3b..32ad010 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,6 +10,7 @@ import doctest import ebaysdk.utils import ebaysdk.config +import ebaysdk.http import ebaysdk.connection import ebaysdk.finding import ebaysdk.shopping @@ -24,6 +25,7 @@ def getTestSuite(): suite.addTest(doctest.DocTestSuite(ebaysdk.config)) suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) + suite.addTest(doctest.DocTestSuite(ebaysdk.http)) suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) From 6f4a1c2d3f3dc7bdf194ae69ac4fccb6dd5323f3 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 24 Jan 2014 15:42:42 -0800 Subject: [PATCH 127/218] remove rss sample --- samples/rss.py | 66 -------------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 samples/rss.py diff --git a/samples/rss.py b/samples/rss.py deleted file mode 100644 index 323633b..0000000 --- a/samples/rss.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -''' -© 2012-2013 eBay Software Foundation -Authored by: Tim Keefer -Licensed under CDDL 1.0 -''' - -import os -import sys -import smtplib -from optparse import OptionParser - -sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) - -from ebaysdk import html - -def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - - (opts, args) = parser.parse_args() - return opts, args - - -def dump(api, full=False): - - print("\n") - - if api.response_content(): - print("Response Content: %s in length" % len(api.response_content())) - - print("Response code: %s" % api.response_code()) - #print "Response soup: %s" % api.response_soup() - - if full: - print(api.response_content()) - #print(json.dumps(api.response_dict(), indent=2)) - else: - pass - #dictstr = "%s" % api.response_dict() - #print "Response dictionary: %s..." % dictstr[:150] - -def run404(opts): - api = html(debug=opts.debug) - - api.execute('http://feeds2.feedburner.com/feedburnerstatus') - - #if api.error(): - # print Exception(api.error()) - - dump(api) - - api.execute('http://feeds2.feedburner.com/feedburnerstatusd') - - if api.error(): - raise Exception(api.error()) - - dump(api) - -if __name__ == "__main__": - (opts, args) = init_options() - run404(opts) From 6c17fcdf00394af8681368b7672ec4412c708966 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 27 Jan 2014 12:09:17 -0800 Subject: [PATCH 128/218] add parallel support using grequests --- ebaysdk/__init__.py | 3 +++ ebaysdk/connection.py | 13 +++++++--- ebaysdk/http/__init__.py | 10 +++++--- samples/parallel.py | 53 ++++++++++++++++++++-------------------- tests/__init__.py | 2 ++ 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 2560e82..2428651 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -84,3 +84,6 @@ def html(*args, **kwargs): from ebaysdk.http import Connection as HTTP return HTTP(*args, **kwargs) +def parallel(*args, **kwargs): + from ebaysdk.parallel import Parallel + return Parallel(*args, **kwargs) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index a8e406b..d8f3d4a 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -75,6 +75,8 @@ def __init__(self, debug=False, method='GET', self.session.mount('http://', HTTPAdapter(max_retries=3)) self.session.mount('https://', HTTPAdapter(max_retries=3)) + self.parallel = parallel + self._reset() def debug_callback(self, debug_type, debug_message): @@ -112,8 +114,10 @@ def execute(self, verb, data=None): self._reset() self.build_request(verb, data) self.execute_request() - self.process_response() - self.error_check() + + if self.response: + self.process_response() + self.error_check() log.debug('total time=%s' % (time.time() - self._time)) @@ -142,7 +146,6 @@ def build_request(self, verb, data): self.request = request.prepare() - def execute_request(self): log.debug("REQUEST (%s): %s %s" \ @@ -150,6 +153,10 @@ def execute_request(self): log.debug('headers=%s' % self.request.headers) log.debug('body=%s' % self.request.body) + if self.parallel: + self.parallel._add_request(self) + return None + self.response = self.session.send(self.request, verify=False, proxies=self.proxies, diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 3bcfeb1..75b3f2e 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -6,7 +6,6 @@ Licensed under CDDL 1.0 ''' -import os import uuid import time @@ -23,7 +22,7 @@ from ebaysdk.connection import BaseConnection from ebaysdk.exception import ConnectionResponseError from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml, xml2dict +from ebaysdk.utils import getNodeText, xml2dict class Connection(BaseConnection): """HTML class for traditional calls. @@ -98,7 +97,12 @@ def execute(self, url, data=None, headers=dict(), method=None): self._reset() self.build_request(url, data, headers) - self.execute_request() + self.execute_request() + + if self.parallel: + self.parallel._add_request(self) + return None + self.process_response() self.error_check() diff --git a/samples/parallel.py b/samples/parallel.py index 6cdda9c..cf30b6d 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -11,8 +11,10 @@ sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) -from ebaysdk import finding, html, parallel - +from common import dump +from ebaysdk import finding, html +from ebaysdk.parallel import Parallel +from ebaysdk.exception import ConnectionError def init_options(): usage = "usage: %prog [options]" @@ -34,39 +36,36 @@ def init_options(): def run(opts): - p = parallel() - apis = [] - - api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api1.execute('findItemsAdvanced', {'keywords': 'python'}) - apis.append(api1) - - api4 = html(parallel=p) - api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') - apis.append(api4) + try: + p = Parallel() + apis = [] - api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api2.execute('findItemsAdvanced', {'keywords': 'perl'}) - apis.append(api2) + api1 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api1.execute('findItemsAdvanced', {'keywords': 'python'}) + apis.append(api1) - api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api3.execute('findItemsAdvanced', {'keywords': 'php'}) - apis.append(api3) + api4 = html(parallel=p) + api4.execute('http://www.ebay.com/sch/i.html?_nkw=Shirt&_rss=1') + apis.append(api4) - p.wait() + api2 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api2.execute('findItemsAdvanced', {'keywords': 'perl'}) + apis.append(api2) - if p.error(): - raise Exception(p.error()) + api3 = finding(parallel=p, debug=opts.debug, appid=opts.appid, config_file=opts.yaml) + api3.execute('findItemsAdvanced', {'keywords': 'php'}) + apis.append(api3) - for api in apis: + p.wait() - print("Call Success: %s in length" % len(api.response_content())) + if p.error(): + print p.error() - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) + for api in apis: + dump(api) - dictstr = "%s" % api.response_dict() - print("Response dictionary: %s...\n" % dictstr[:50]) + except ConnectionError as e: + print e if __name__ == "__main__": (opts, args) = init_options() diff --git a/tests/__init__.py b/tests/__init__.py index 32ad010..f0994a4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,10 +17,12 @@ import ebaysdk.trading import ebaysdk.merchandising import ebaysdk.soa.finditem +import ebaysdk.parallel def getTestSuite(): suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) suite.addTest(doctest.DocTestSuite(ebaysdk.config)) suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) From 2bb5e698cb8bcce283448f1e8eaa56110993bdc8 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 27 Jan 2014 13:24:41 -0800 Subject: [PATCH 129/218] add parallel class --- ebaysdk/parallel.py | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 ebaysdk/parallel.py diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py new file mode 100644 index 0000000..f23c365 --- /dev/null +++ b/ebaysdk/parallel.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import grequests +from ebaysdk.exception import ConnectionError + +class Parallel(object): + """ + >>> from ebaysdk.finding import Connection as finding + >>> from ebaysdk.shopping import Connection as shopping + >>> from ebaysdk import html + >>> import os + >>> p = Parallel() + >>> r1 = html(parallel=p) + >>> retval = r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> r2 = finding(parallel=p, config_file=os.environ.get('EBAY_YAML')) + >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) + >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) + >>> p.wait() + >>> print(p.error()) + None + >>> print(r1.response_obj().rss.channel.ttl) + 60 + >>> print(r2.response_dict().ack) + Success + >>> print(r3.response_obj().Ack) + Success + """ + + def __init__(self): + self._grequests = [] + self._requests = [] + self._errors = [] + + def _add_request(self, request): + self._requests.append(request) + + def wait(self, timeout=20): + "wait for all of the api requests to complete" + + self._errors = [] + self._grequests = [] + + try: + for r in self._requests: + req = grequests.request(r.request.method, + r.request.url, + data=r.request.body, + headers=r.request.headers, + verify=False, + proxies=r.proxies, + timeout=r.timeout, + allow_redirects=True) + + self._grequests.append(req) + + gresponses = grequests.map(self._grequests) + + for idx, r in enumerate(self._requests): + r.response = gresponses[idx] + r.process_response() + r.error_check() + + if r.error(): + self._errors.append(r.error()) + + except ConnectionError as e: + self._errors.append("%s" % e) + + self._requests = [] + + def error(self): + "builds and returns the api error message" + + if len(self._errors) > 0: + return "parallel error:\n%s\n" % ("\n".join(self._errors)) + + return None From 15ef800d1c2199750d824d2b7bf8a2b2207cfac9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 27 Jan 2014 14:46:42 -0800 Subject: [PATCH 130/218] modify deps --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4f301c9..ad85e1e 100644 --- a/setup.py +++ b/setup.py @@ -41,11 +41,11 @@ license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], - install_requires=['PyYaml', 'pycurl', 'beautifulsoup4'], + install_requires=['PyYaml', 'requests', 'beautifulsoup4'], test_suite='tests', long_description=long_desc, classifiers=[ 'Topic :: Internet :: WWW/HTTP', - 'Intended Audience :: Developers', + 'Intended Audience :: Developere', ] ) From a1077f870a58e279f6e8133f72d43dce2aa45a65 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 29 Jan 2014 16:24:35 -0800 Subject: [PATCH 131/218] yaml file fix --- ebaysdk/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/config.py b/ebaysdk/config.py index 1e35608..eb4a5d6 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -59,7 +59,7 @@ def _populate_yaml_defaults(self): if os.path.exists(myfile): self.config_file_used=myfile - fhandle = open(self.config_file, "r") + fhandle = open(myfile, "r") dataobj = yaml.load(fhandle.read()) for k, val in dataobj.get(self.domain, {}).iteritems(): @@ -91,4 +91,4 @@ def set(self, cKey, defaultValue, force=False): log.debug('set: %s=%s' % (cKey, defaultValue)) self.values.update({cKey: defaultValue}) else: - pass \ No newline at end of file + pass From 5e94135c47577d4db1acab97b3b154e45134c1d3 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 30 Jan 2014 10:48:52 -0800 Subject: [PATCH 132/218] remove some debugging --- ebaysdk/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ebaysdk/config.py b/ebaysdk/config.py index eb4a5d6..7c3cf5d 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -71,24 +71,24 @@ def file(self): return self.config_file_used def get(self, cKey, defaultValue=None): - log.debug('get: %s=%s' % (cKey, self.values.get(cKey, defaultValue))) + #log.debug('get: %s=%s' % (cKey, self.values.get(cKey, defaultValue))) return self.values.get(cKey, defaultValue) def set(self, cKey, defaultValue, force=False): if force: - log.debug('set (force): %s=%s' % (cKey, defaultValue)) + #log.debug('set (force): %s=%s' % (cKey, defaultValue)) self.values.update({cKey: defaultValue}) elif cKey in self.connection_kwargs and self.connection_kwargs[cKey] is not None: - log.debug('set: %s=%s' % (cKey, self.connection_kwargs[cKey])) + #log.debug('set: %s=%s' % (cKey, self.connection_kwargs[cKey])) self.values.update({cKey: self.connection_kwargs[cKey]}) # otherwise, use yaml default and then fall back to # the default set in the __init__() else: if not cKey in self.values: - log.debug('set: %s=%s' % (cKey, defaultValue)) + #log.debug('set: %s=%s' % (cKey, defaultValue)) self.values.update({cKey: defaultValue}) else: pass From b98beac075db3d1c967834e1973105ca7667c680 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 31 Jan 2014 11:24:40 -0800 Subject: [PATCH 133/218] doc updates --- Changes | 6 ++++++ INSTALL | 47 ++++------------------------------------------- MANIFEST.in | 1 + README.rst | 4 ++-- setup.py | 6 +++--- 5 files changed, 16 insertions(+), 48 deletions(-) diff --git a/Changes b/Changes index fa7e2db..5e29c68 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,12 @@ Changes for ebaysdk 1.0.0 - Major refactor + + changes are backward compatible unless your subclassing any of the + SDK classes + + moved from PyCurl to Python Requests + + code organization + + standard python logging + + add exceptions 0.1.11 - Add affiliate headers to the Shopping API back-end diff --git a/INSTALL b/INSTALL index 61787f5..8896770 100644 --- a/INSTALL +++ b/INSTALL @@ -3,9 +3,7 @@ Running the tests: Installing ebaysdk on Mac: -1) Install Xcode from the app center -2) Download and install the Xcode Command Line Tools from https://developer.apple.com/downloads/ -3) Install the SDK +1) Install the SDK with easy_install sudo easy_install ebaysdk @@ -15,7 +13,7 @@ Installing ebaysdk on Mac: Installing ebaysdk on Windows: -1) Download and install the latest release of Python 2.7: +1) Download and install the latest release of Python 2.7+ http://python.org/download/ @@ -23,7 +21,7 @@ Choose either "Python 3.3.0 Windows x86 MSI Installer" or "Python 3.3.0 Windows X86-64 MSI Installer". To use the latter, you must be running a 64-bit version of Windows. -2) Install setuptools: +2) Install setuptools First, visit http://pypi.python.org/pypi/setuptools @@ -43,23 +41,7 @@ setuptools, then run it from the command prompt as follows: The last step assumes that Python was installed to its default location. -3) Install pycurl: - -This could be complicated because pycurl requires libcurl to be -installed. Since this is a native library, this can't be installed -using pip or easy_install. Luckily Christoph Gohlke has pre-complied -many common libraries for Python on Windows, including 32- and 64-bit -versions. - -Simply visit this site: - -http://www.lfd.uci.edu/~gohlke/pythonlibs/ - -Then download and install the appropriate version of pycurl for Python -2.7. Use the amd64 version if you are running 64-bit -Python. Otherwise, use the win32 version. - -4) Install ebaysdk: +3) Install ebaysdk Download and extract the zipball, or clone the ebaysdk-python repository. Then open the Command Prompt and change to the root @@ -69,24 +51,3 @@ directory of the distribution and execute: If there were no errors, ebaysdk should be ready to use! - - -Legacy Install on Mac: - -Dependency: pycurl - - How to install pycurl on Mac - - 1) Install Fink - http://www.finkproject.org/download/ - - 2) Install libssh2, libcurl4-shlibs - sudo /sw/bin/apt-get install libssh2 - sudo /sw/bin/apt-get -q0 -f install libcurl4-shlibs - - 3) Download pycurl from http://pycurl.sourceforge.net/download/ - - 4) Extract and install pycurl - tar -zxvf pycurl-7.16.4.tar.gz - cd pycurl-7.16.4 - sudo env ARCHFLAGS="-arch i386" python setup.py install --curl-config=/sw/bin/curl-config diff --git a/MANIFEST.in b/MANIFEST.in index 79c2b36..cd79363 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ recursive-include ebaysdk *.py recursive-include tests *.py +recursive-include samples *.py include ebay.yaml include Changes include INSTALL diff --git a/README.rst b/README.rst index ee02103..b701b1a 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,9 @@ This SDK is a programmatic interface into the eBay APIs. It simplifies developme Quick Example:: - from ebaysdk import finding + from ebaysdk.finding import Connection - api = finding(appid='YOUR_APPID_HERE') + api = Connection(appid='YOUR_APPID_HERE') api.execute('findItemsAdvanced', {'keywords': 'shoes'}) print api.response_dict() diff --git a/setup.py b/setup.py index ad85e1e..4f4b58f 100644 --- a/setup.py +++ b/setup.py @@ -34,18 +34,18 @@ setup( name=PKG, version=version, - description="Simple and Extensible eBay SDK for Python", + description="eBay SDK for Python", author="Tim Keefer", author_email="tkeefer@gmail.com", url="https://github.com/timotheus/ebaysdk-python", license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], - install_requires=['PyYaml', 'requests', 'beautifulsoup4'], + install_requires=['PyYaml', 'requests', 'grequests', 'beautifulsoup4'], test_suite='tests', long_description=long_desc, classifiers=[ 'Topic :: Internet :: WWW/HTTP', - 'Intended Audience :: Developere', + 'Intended Audience :: Developer', ] ) From b923a1642242aa0ea8fc0af0aee65326520880fc Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 31 Jan 2014 12:19:49 -0800 Subject: [PATCH 134/218] Update README.rst --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index b701b1a..884a62a 100644 --- a/README.rst +++ b/README.rst @@ -6,11 +6,13 @@ This SDK is a programmatic interface into the eBay APIs. It simplifies developme Quick Example:: from ebaysdk.finding import Connection + try: + api = Connection(appid='YOUR_APPID_HERE') + api.execute('findItemsAdvanced', {'keywords': 'shoes'}) - api = Connection(appid='YOUR_APPID_HERE') - api.execute('findItemsAdvanced', {'keywords': 'shoes'}) - - print api.response_dict() + print api.response_dict() + except ConnectionError as e: + raise e Getting Started --------------- From 35e7ed122025619e68fc7ceef0af5f5123c4ef48 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 5 Feb 2014 13:10:33 -0800 Subject: [PATCH 135/218] fix http backend bug --- Changes | 2 ++ ebaysdk/http/__init__.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index 5e29c68..7304a28 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Changes for ebaysdk +- fix http back-end + 1.0.0 - Major refactor + changes are backward compatible unless your subclassing any of the diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 75b3f2e..296b102 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -117,10 +117,16 @@ def build_request(self, url, data, headers): headers.update({'User-Agent': UserAgent, 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) + kw = dict() + if self.method == 'POST': + kw['data'] = data + else: + kw['params'] = data + request = Request(self.method, url, - data=self.build_request_data(data), headers=headers, + **kw ) self.request = request.prepare() @@ -128,13 +134,4 @@ def build_request(self, url, data, headers): def warnings(self): return '' - def build_request_data(self, data): - "Builds and returns the request XML." - - if not data: - return None - if type(data) is str: - return data - else: - return urlencode(data) From e733967ad7c69c354961ab114cfc6844fa6d7bdd Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 5 Feb 2014 13:14:26 -0800 Subject: [PATCH 136/218] bump version --- ebaysdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 09ec6a9..7484d72 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,7 +9,7 @@ import platform import logging -__version__ = '1.0.0' +__version__ = '1.0.1' Version = __version__ # for backware compatibility From b9cb7b28197e8f880ca189c7225a3e0035a490f3 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 5 Feb 2014 13:17:20 -0800 Subject: [PATCH 137/218] fix pypi classifier --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4f4b58f..fe1557f 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,6 @@ long_description=long_desc, classifiers=[ 'Topic :: Internet :: WWW/HTTP', - 'Intended Audience :: Developer', + 'Intended Audience :: Developers', ] ) From e55d2cc157818f259642bbe08a4529834231dbe1 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 8 Feb 2014 20:20:42 -0800 Subject: [PATCH 138/218] fix unicode issue --- ebaysdk/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 2e7cc93..4fe7257 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -374,7 +374,7 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): parent.set(key, value) # TODO: will fail if attrs is not a dict if '#text' in dictitem.keys(): text = dictitem.pop('#text') - parent.text = str(text) + parent.text = str(text).decode('utf-8') for (tag, child) in sorted(dictitem.items()): if isinstance(child, list): # iterate through the array and convert @@ -388,7 +388,7 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): parent.append(elem) _convert_dict_to_xml_recurse(elem, child, listnames) elif not dictitem is None: - parent.text = str(dictitem) + parent.text = str(dictitem).decode('utf-8') def dict2et(xmldict, roottag='data', listnames=None): From 7dbb827f66e558db7d7f95617e485721d268f751 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Sat, 8 Feb 2014 20:44:44 -0800 Subject: [PATCH 139/218] fix unicode issue in dict to xml routine --- Changes | 3 +++ ebaysdk/__init__.py | 2 +- ebaysdk/finding/__init__.py | 2 +- samples/finding.py | 2 +- tests/__init__.py | 4 +++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 7304a28..29340ab 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +1.0.2 Sat Feb 8 20:44:00 PST 2014 +- fix unicode issue is dict to xml routine +- add a unicode test - fix http back-end 1.0.0 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 7484d72..86626cf 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,7 +9,7 @@ import platform import logging -__version__ = '1.0.1' +__version__ = '1.0.2' Version = __version__ # for backware compatibility diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index a4342a2..83e7cd0 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -26,7 +26,7 @@ class Connection(BaseConnection): Doctests: >>> f = Connection(config_file=os.environ.get('EBAY_YAML')) - >>> retval = f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + >>> retval = f.execute('findItemsAdvanced', {'keywords': 'niño'}) >>> error = f.error() >>> print(error) None diff --git a/samples/finding.py b/samples/finding.py index c9cfd6b..fa7327c 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -42,7 +42,7 @@ def run(opts): config_file=opts.yaml, warnings=True) api.execute('findItemsAdvanced', { - 'keywords': 'python', + 'keywords': 'niño', 'itemFilter': [ {'name': 'Condition', 'value': 'Used'}, diff --git a/tests/__init__.py b/tests/__init__.py index f0994a4..a9a945a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -31,7 +31,9 @@ def getTestSuite(): suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) - suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) + + # Internal Only Service + #uite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) return suite runner = unittest.TextTestRunner() From e3619d1d15d3285df9a6e922918c14af6905b1e5 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:24:34 -0800 Subject: [PATCH 140/218] move import into method call --- ebaysdk/connection.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index d8f3d4a..c2797e7 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -19,12 +19,6 @@ from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError -try: - from bs4 import BeautifulStoneSoup -except ImportError: - from BeautifulSoup import BeautifulStoneSoup - log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') - from ebaysdk import set_stream_logger, UserAgent from ebaysdk.utils import getNodeText as getNodeTextUtils from ebaysdk.utils import dict2xml, xml2dict, getValue @@ -205,6 +199,11 @@ def response_content(self): def response_soup(self): "Returns a BeautifulSoup object of the response." + try: + from bs4 import BeautifulStoneSoup + except ImportError: + from BeautifulSoup import BeautifulStoneSoup + log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') if not self._response_soup: self._response_soup = BeautifulStoneSoup( From 6566c4fa1048e4f26d61b1a0027a2df039f8b900 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:25:54 -0800 Subject: [PATCH 141/218] py3 fixes --- samples/finding.py | 9 ++++++--- samples/t_http.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 samples/t_http.py diff --git a/samples/finding.py b/samples/finding.py index fa7327c..380cf30 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -10,6 +10,9 @@ from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) +#sys.path.append('%s/../' % os.path.dirname(__file__)) + +print(sys.path) from common import dump @@ -42,7 +45,7 @@ def run(opts): config_file=opts.yaml, warnings=True) api.execute('findItemsAdvanced', { - 'keywords': 'niño', + 'keywords': u'niño', 'itemFilter': [ {'name': 'Condition', 'value': 'Used'}, @@ -56,7 +59,7 @@ def run(opts): dump(api) except ConnectionError as e: - print e + print(e) @@ -68,7 +71,7 @@ def run2(opts): dump(api) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": diff --git a/samples/t_http.py b/samples/t_http.py new file mode 100644 index 0000000..992c9ba --- /dev/null +++ b/samples/t_http.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os +import sys + +from optparse import OptionParser + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +from common import dump + +import ebaysdk +from ebaysdk.http import Connection as HTTP +from ebaysdk.exception import ConnectionError + +def init_options(): + usage = "usage: %prog [options]" + parser = OptionParser(usage=usage) + + parser.add_option("-d", "--debug", + action="store_true", dest="debug", default=False, + help="Enabled debugging [default: %default]") + + (opts, args) = parser.parse_args() + return opts, args + +def run(opts): + + try: + api = HTTP(debug=opts.debug, method='GET') + + api.execute('http://feeds.wired.com/wired/index') + + dump(api) + + except ConnectionError as e: + print e + +if __name__ == "__main__": + print("HTTP samples for SDK version %s" % ebaysdk.get_version()) + (opts, args) = init_options() + run(opts) From 564fad2efacc340963d2c0b3834a51c802cbafd1 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:26:17 -0800 Subject: [PATCH 142/218] py3 fixes --- ebaysdk/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/config.py b/ebaysdk/config.py index 7c3cf5d..c3661a7 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -46,7 +46,7 @@ def _populate_yaml_defaults(self): fhandle = open(self.config_file, "r") dataobj = yaml.load(fhandle.read()) - for k, val in dataobj.get(self.domain, {}).iteritems(): + for k, val in dataobj.get(self.domain, {}).items(): self.set(k, val) return self @@ -62,7 +62,7 @@ def _populate_yaml_defaults(self): fhandle = open(myfile, "r") dataobj = yaml.load(fhandle.read()) - for k, val in dataobj.get(self.domain, {}).iteritems(): + for k, val in dataobj.get(self.domain, {}).items(): self.set(k, val) return self From 05fe17f19a55bd4bbe0ecf05a7fe81af5fbb6ce5 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:28:02 -0800 Subject: [PATCH 143/218] py3 fixes --- samples/finding.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/samples/finding.py b/samples/finding.py index 380cf30..d461bdf 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -10,9 +10,6 @@ from optparse import OptionParser sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) -#sys.path.append('%s/../' % os.path.dirname(__file__)) - -print(sys.path) from common import dump From 3b510b9402cd7cc28a8acfd965975935d79db835 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:28:27 -0800 Subject: [PATCH 144/218] fix encode issue --- ebaysdk/utils.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 4fe7257..1fd011a 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -12,6 +12,7 @@ import cElementTree as ET # for 2.4 import re +import sys from io import BytesIO def to_xml(data): @@ -374,7 +375,10 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): parent.set(key, value) # TODO: will fail if attrs is not a dict if '#text' in dictitem.keys(): text = dictitem.pop('#text') - parent.text = str(text).decode('utf-8') + if sys.version_info[0] < 3: + parent.text = unicode(text) + else: + parent.text = str(text) for (tag, child) in sorted(dictitem.items()): if isinstance(child, list): # iterate through the array and convert @@ -388,7 +392,10 @@ def _convert_dict_to_xml_recurse(parent, dictitem, listnames): parent.append(elem) _convert_dict_to_xml_recurse(elem, child, listnames) elif not dictitem is None: - parent.text = str(dictitem).decode('utf-8') + if sys.version_info[0] < 3: + parent.text = unicode(dictitem) + else: + parent.text = str(dictitem) def dict2et(xmldict, roottag='data', listnames=None): @@ -471,7 +478,7 @@ def dict2xml(datadict, roottag='', listnames=None, pretty=False): """ root = dict2et(datadict, roottag, listnames) xml = to_string(root, pretty=pretty) - xml = xml.replace('<>', '').replace('','') + xml = xml.replace('<>', '').replace('', '') return xml From 3e3f03c0903807de16af7a05f58dc352927b9e4f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:33:21 -0800 Subject: [PATCH 145/218] fix doctest --- ebaysdk/finding/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 83e7cd0..bd24337 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -26,17 +26,17 @@ class Connection(BaseConnection): Doctests: >>> f = Connection(config_file=os.environ.get('EBAY_YAML')) - >>> retval = f.execute('findItemsAdvanced', {'keywords': 'niño'}) + >>> retval = f.execute('findItemsAdvanced', {'keywords': u'niño'}) >>> error = f.error() >>> print(error) None >>> if not f.error(): ... print(f.response_obj().itemSearchURL != '') ... items = f.response_obj().searchResult.item - ... print(len(items)) + ... print(len(items) > 2) ... print(f.response_dict().ack) True - 100 + True Success """ From 9ddca24317485d6a3d0f10d4630ac11ec43dd78f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:33:57 -0800 Subject: [PATCH 146/218] del file that messes with imports --- samples/http.py | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 samples/http.py diff --git a/samples/http.py b/samples/http.py deleted file mode 100644 index 992c9ba..0000000 --- a/samples/http.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -''' -© 2012-2013 eBay Software Foundation -Authored by: Tim Keefer -Licensed under CDDL 1.0 -''' - -import os -import sys - -from optparse import OptionParser - -sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) - -from common import dump - -import ebaysdk -from ebaysdk.http import Connection as HTTP -from ebaysdk.exception import ConnectionError - -def init_options(): - usage = "usage: %prog [options]" - parser = OptionParser(usage=usage) - - parser.add_option("-d", "--debug", - action="store_true", dest="debug", default=False, - help="Enabled debugging [default: %default]") - - (opts, args) = parser.parse_args() - return opts, args - -def run(opts): - - try: - api = HTTP(debug=opts.debug, method='GET') - - api.execute('http://feeds.wired.com/wired/index') - - dump(api) - - except ConnectionError as e: - print e - -if __name__ == "__main__": - print("HTTP samples for SDK version %s" % ebaysdk.get_version()) - (opts, args) = init_options() - run(opts) From 9c48f26012ff6450987dc528e6f3a28181ecb8f0 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 10 Feb 2014 11:35:27 -0800 Subject: [PATCH 147/218] bump version --- Changes | 3 +++ ebaysdk/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 29340ab..c04d45a 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ Changes for ebaysdk +1.0.3 +- rework unicode fix + 1.0.2 Sat Feb 8 20:44:00 PST 2014 - fix unicode issue is dict to xml routine - add a unicode test diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 86626cf..807c9a5 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,7 +9,7 @@ import platform import logging -__version__ = '1.0.2' +__version__ = '1.0.3' Version = __version__ # for backware compatibility From 0a3063621c496bf3cb56640baf41c74d0f976bc6 Mon Sep 17 00:00:00 2001 From: jweigel Date: Tue, 11 Feb 2014 14:18:16 -0800 Subject: [PATCH 148/218] Fix dict2xml so it doesn't return an empty element for a empty dict. --- ebaysdk/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 1fd011a..0f5bbf0 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -476,10 +476,13 @@ def dict2xml(datadict, roottag='', listnames=None, pretty=False): Converts a dictionary to an UTF-8 encoded XML string. See also dict2et() """ - root = dict2et(datadict, roottag, listnames) - xml = to_string(root, pretty=pretty) - xml = xml.replace('<>', '').replace('', '') - return xml + if len(datadict): + root = dict2et(datadict, roottag, listnames) + xml = to_string(root, pretty=pretty) + xml = xml.replace('<>', '').replace('', '') + return xml + else: + return '' def list2xml(datalist, roottag, elementname, pretty=False): From cdf0126b1c287c2856b10f86b2be0ca382ddb7ef Mon Sep 17 00:00:00 2001 From: jweigel Date: Tue, 11 Feb 2014 14:30:09 -0800 Subject: [PATCH 149/218] Perform type check on dict2xml parameter. --- ebaysdk/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 0f5bbf0..a6ab39e 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -476,7 +476,7 @@ def dict2xml(datadict, roottag='', listnames=None, pretty=False): Converts a dictionary to an UTF-8 encoded XML string. See also dict2et() """ - if len(datadict): + if isinstance(datadict, dict) and len(datadict): root = dict2et(datadict, roottag, listnames) xml = to_string(root, pretty=pretty) xml = xml.replace('<>', '').replace('', '') From 35ff21322761f301c9feda60c1774b01ba74eb00 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 11 Feb 2014 14:53:27 -0800 Subject: [PATCH 150/218] update doctest --- ebaysdk/trading/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 4a10b94..309a9e0 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -35,10 +35,10 @@ class Connection(BaseConnection): Sunshine Kids Foundation >>> print(t.error()) None - >>> t2 = Connection(errors=False, config_file=os.environ.get('EBAY_YAML')) + >>> t2 = Connection(errors=False, debug=False, config_file=os.environ.get('EBAY_YAML')) >>> retval2 = t2.execute('VerifyAddItem', {}) >>> print(t2.response_codes()) - [5] + [10009] """ def __init__(self, **kwargs): From b7ebe107abd99bac786ffee31294d55a9952f44c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 11 Feb 2014 15:02:40 -0800 Subject: [PATCH 151/218] response work --- ebaysdk/connection.py | 57 ++-- ebaysdk/http/__init__.py | 9 +- ebaysdk/response.py | 199 ++++++++++++ ebaysdk/soa/__init__.py | 2 +- ebaysdk/utils.py | 661 +++++++-------------------------------- samples/finding.py | 19 +- tests/__init__.py | 10 +- 7 files changed, 361 insertions(+), 596 deletions(-) create mode 100644 ebaysdk/response.py diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index c2797e7..3efb6cd 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -19,9 +19,16 @@ from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError +try: + from bs4 import BeautifulStoneSoup +except ImportError: + from BeautifulSoup import BeautifulStoneSoup + log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') + from ebaysdk import set_stream_logger, UserAgent from ebaysdk.utils import getNodeText as getNodeTextUtils -from ebaysdk.utils import dict2xml, xml2dict, getValue +from ebaysdk.utils import getValue +from ebaysdk.response import Response from ebaysdk.exception import ConnectionError, ConnectionResponseError HTTP_SSL = { @@ -30,16 +37,7 @@ } class BaseConnection(object): - """Base Connection Class. - - Doctests: - >>> d = { 'list': ['a', 'b', 'c']} - >>> print(dict2xml(d, listnames={'': 'list'})) - abc - >>> d2 = {'node': {'@attrs': {'a': 'b'}, '#text': 'foo'}} - >>> print(dict2xml(d2)) - foo - """ + """Base Connection Class.""" def __init__(self, debug=False, method='GET', proxy_host=None, timeout=20, proxy_port=80, @@ -151,13 +149,15 @@ def execute_request(self): self.parallel._add_request(self) return None - self.response = self.session.send(self.request, + response = self.session.send(self.request, verify=False, proxies=self.proxies, timeout=self.timeout, allow_redirects=True ) + self.response = Response(response) + log.debug('RESPONSE (%s):' % self._request_id) log.debug('elapsed time=%s' % self.response.elapsed) log.debug('status code=%s' % self.response.status_code) @@ -166,13 +166,16 @@ def execute_request(self): def process_response(self): """Post processing of the response""" - + if self.response.status_code != 200: self._response_error = self.response.reason + #self.response_obj=Response(self.response.content) + + # leave?? # remove xml namespace - regex = re.compile('xmlns="[^"]+"') - self._response_content = regex.sub('', self.response.content) + #regex = re.compile('xmlns="[^"]+"') + #self._response_content = regex.sub('', self.response.content) def error_check(self): estr = self.error() @@ -195,19 +198,14 @@ def response_code(self): return self.response.status_code def response_content(self): - return self._response_content + return self.response.content def response_soup(self): "Returns a BeautifulSoup object of the response." - try: - from bs4 import BeautifulStoneSoup - except ImportError: - from BeautifulSoup import BeautifulStoneSoup - log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') if not self._response_soup: self._response_soup = BeautifulStoneSoup( - self._response_content.decode('utf-8') + self.response_content.decode('utf-8') ) return self._response_soup @@ -223,8 +221,10 @@ def response_dom(self): content = None try: - if self._response_content: - content = self._response_content + if self.response.content: + regex = re.compile('xmlns="[^"]+"') + content = regex.sub('', self.response.content) + else: content = "<%sResponse>" % (self.verb, self.verb) @@ -242,9 +242,12 @@ def response_dom(self): def response_dict(self): "Returns the response dictionary." - if not self._response_dict and self._response_content: - mydict = xml2dict().fromstring(self._response_content) - self._response_dict = mydict.get(self.verb + 'Response', mydict) + return self.response.asdict() + + #if not self._response_dict and self._response_content: + # mydict = xml2dict().fromstring(self._response_content) + # self._response_dict = mydict.get(self.verb + 'Response', mydict) + return self._response_dict diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 296b102..7ba7ac1 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -22,7 +22,7 @@ from ebaysdk.connection import BaseConnection from ebaysdk.exception import ConnectionResponseError from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, xml2dict +from ebaysdk.utils import getNodeText class Connection(BaseConnection): """HTML class for traditional calls. @@ -81,10 +81,11 @@ def response_dict(self): "Return the HTTP response dictionary." try: - if not self._response_dict and self.response_content: - self._response_dict = xml2dict().fromstring(self._response_content) + #if not self._response_dict and self.response_content: + # self._response_dict = xml2dict().fromstring(self._response_content) + + return self._response_obj.asdict() - return self._response_dict except ExpatError: raise ConnectionResponseError('response is not well-formed') diff --git a/ebaysdk/response.py b/ebaysdk/response.py new file mode 100644 index 0000000..a126c62 --- /dev/null +++ b/ebaysdk/response.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' +from ebaysdk.utils import get_dom_tree + +class ResponseDataObject(object): + + def __init__(self, mydict={}): + self._load_dict(mydict) + + def __repr__(self): + return str(self) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return str(self.__dict__) + + def _load_dict(self, mydict): + + for a in mydict.items(): + + if isinstance(a[1], dict): + o = ResponseDataObject(a[1]) + setattr(self, a[0], o) + + elif isinstance(a[1], list): + objs = [] + for i in a[1]: + objs.append(ResponseDataObject(i)) + + setattr(self, a[0], objs) + else: + setattr(self, a[0], a[1]) + + +class Response(object): + ''' + + + Success + 1.12.0 + 2014-02-07T23:31:13.941Z + + + + + + 1 + 2 + 90 + 179 + + http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1 + + + Doctests: + >>> xml = 'Success1.12.02014-02-07T23:31:13.941Z1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' + >>> o = ResponseDataObject({'content': xml}) + >>> r = Response(o) + + >>> xml + + >>> r.asdict() + + >>> r.reply + + ''' + rdict = dict() + rdom = None + + def __init__(self, obj): + # requests response object + self.obj = obj + self.tree = self._parse_xml(obj.content) + + res = [] + self.xmltodict(self.tree, res) + + mydict = dict() + self._build_dict(res, mydict) + self.rdict=mydict + + self.reply = ResponseDataObject(self.asdict()) + + def _build_dict(self, ndict, mydict): + + if isinstance(ndict, list): + for i in ndict: + self._build_dict(i, mydict) + elif isinstance(ndict, dict): + if isinstance(ndict[ndict.keys()[0]], list): + if isinstance(mydict.get(ndict.keys()[0], {}), dict) \ + and mydict.has_key(ndict.keys()[0]): + mydict[ndict.keys()[0]] = [ mydict[ndict.keys()[0]] ] + elif mydict.has_key(ndict.keys()[0]) and isinstance(mydict[ndict.keys()[0]], list): + pass + else: + mydict[ndict.keys()[0]] = {} + + for i in ndict[ndict.keys()[0]]: + self._build_dict(i, mydict[ndict.keys()[0]]) + else: + if isinstance(mydict, list): + mydict.append(ndict) + else: + mydict.update(ndict) + + def __getattr__(self, name): + return getattr(self.obj, name) + + def _parse_xml(self, xml): + return get_dom_tree(xml) + + def _get_node_tag(self, node): + return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '') + + def xmltodict(self, node, res): + rep = {} + + if len(node): + for n in list(node): + #print self._get_node_tag(node) + #rep[self._get_node_tag(node)] = [] + rep[self._get_node_tag(node)] = [] + + self.xmltodict(n, rep[self._get_node_tag(node)]) + + if len(n): + #print "len=%s" % len(n) + value = None + if len(n.attrib) > 0: + value = rep[self._get_node_tag(node)] + #value = {'value':rep[self._get_node_tag(node)], + # '_attrs': n.attrib} + else: + value = rep[self._get_node_tag(node)] + + res.append({self._get_node_tag(n):value}) + else: + res.append(rep[ self._get_node_tag(node)][0]) + #print "else >> %s (%s)" % (self._get_node_tag(node), rep[ self._get_node_tag(node)][0]) + #print "res >> %s" % ' '.join(res) + else: + value = None + if len(node.attrib) > 0: + value = {'value': node.text, '_attrs': node.attrib} + else: + value = node.text + + #print "tag=%s" % self._get_node_tag(node) + #print "value=%s" % value or '' + #print "before=" + ' '.join(res) + res.append({self._get_node_tag(node):value or ''}) + #print "after=" + ' '.join(res) + + return + + def orig_xmltodict(self, node, res): + rep = {} + + if len(node): + for n in list(node): + print self._get_node_tag(node) + rep[self._get_node_tag(node)] = [] + self.xmltodict(n, rep[self._get_node_tag(node)]) + + if len(n): + print "len=%s" % len(n) + value = None + if len(n.attrib) > 0: + value = {'value':rep[self._get_node_tag(node)], + '_attrs': n.attrib} + else: + value = rep[self._get_node_tag(node)] + + res.append({self._get_node_tag(n):value}) + else: + res.append(rep[self._get_node_tag(node)][0]) + print "else >> %s (%s)" % (self._get_node_tag(node), res) + #print "res >> %s" % ' '.join(res) + else: + value = None + if len(node.attrib) > 0: + value = {'value': node.text, '_attrs': node.attrib} + else: + value = node.text + + res.append({self._get_node_tag(node):value or ''}) + + return + + def asdict(self): + return self.rdict \ No newline at end of file diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index 45f1e2f..bdb94b0 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -9,7 +9,7 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml, xml2dict +from ebaysdk.utils import getNodeText, to_xml class Connection(BaseConnection): """Connection class for a base SOA service""" diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index a6ab39e..966c7ae 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -7,25 +7,118 @@ ''' try: - import xml.etree.ElementTree as ET -except: - import cElementTree as ET # for 2.4 - -import re -import sys -from io import BytesIO + from lxml import etree as ET + print("running with lxml.etree") +except ImportError: + try: + # Python 2.5 + import xml.etree.cElementTree as ET + print("running with cElementTree on Python 2.5+") + except ImportError: + + try: + # Python 2.5 + import xml.etree.ElementTree as ET + print("running with ElementTree on Python 2.5+") + except ImportError: + try: + # normal cElementTree install + import cElementTree as ET + print("running with cElementTree") + except ImportError: + try: + # normal ElementTree install + import elementtree.ElementTree as ET + print("running with ElementTree") + except ImportError: + print("Failed to import ElementTree from any known place") def to_xml(data): "Converts a list or dictionary to XML and returns it." + if isinstance(data, str): + return data + else: + return dicttoxml(data) + +def get_dom_tree(xml): + tree = ET.fromstring(xml) + return tree.getroottree().getroot() + +def attribute_check(root): + attrs = [] + value = None + + if isinstance(root, dict): + if '#text' in root: + value = root['#text'] + if '@attrs' in root: + for ak, av in root.pop('@attrs').iteritems(): + attrs.append('%s="%s"' % (ak, av)) + + return attrs, value + +def dicttoxml(root): + ''' + Doctests: + >>> dict1 = {'Items': [{'ItemId': 1234}, {'ItemId': 2222}]} + >>> dicttoxml(dict1) + '12342222' + >>> dict2 = { + ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, + ... 'paginationInput': [ + ... {'pageNumber': '1'}, + ... {'pageSize': '25'} + ... ], + ... 'sortOrder': 'StartTimeNewest' + ... } + >>> dicttoxml(dict2) + '125StartTimeNewest222' + >>> dict3 = { + ... 'parent': {'child': {'#text': 222, '@attrs': {'site': 'US', 'id': 1234}}} + ... } + >>> dicttoxml(dict3) + '222' + ''' + xml = '' + if isinstance(root, dict): + for key in root.keys(): + + if isinstance(root[key], dict): + attrs, value = attribute_check(root[key]) + + if not value: + value = dicttoxml(root[key]) + + attrs_sp = '' + if len(attrs) > 0: + attrs_sp = ' ' + + xml = '%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ + {'tag': key, 'xml': xml, 'attrs': ' '.join(attrs), + 'value': value, 'attrs_sp': attrs_sp} + + elif isinstance(root[key], list): + xml = '%s<%s>' % (xml, key) + + for item in root[key]: + attrs, value = attribute_check(item) + + if not value: + value = dicttoxml(item) + + xml = '%s%s' % (xml, value) + + xml = '%s' % (xml, key) + + else: + value = root[key] + xml = '%(xml)s<%(tag)s>%(value)s' % \ + {'xml': xml, 'tag': key, 'value': value} - if type(data) == dict: - xml = dict2xml(data) - elif type(data) == list: - xml = list2xml(data) else: - xml = data + raise Exception('Unable to serialize node of type %s (%s)' % (type(root), root)) return xml @@ -72,553 +165,9 @@ def getNodeText(node): return ''.join(rc) -class object_dict(dict): - """object view of dict, you can - >>> a = object_dict() - >>> a.fish = 'fish' - >>> a['fish'] - 'fish' - >>> a['water'] = 'water' - >>> a.water - 'water' - >>> a.test = {'value': 1} - >>> a.test2 = object_dict({'name': 'test2', 'value': 2}) - >>> a.test, a.test2.name, a.test2.value - (1, 'test2', 2) - """ - def __init__(self, initd=None): - if initd is None: - initd = {} - dict.__init__(self, initd) - - def __getattr__(self, item): - try: - d = self.__getitem__(item) - except KeyError: - return None - - if isinstance(d, dict) and 'value' in d and len(d) == 1: - return d['value'] - else: - return d - - # if value is the only key in object, you can omit it - - def __setattr__(self, item, value): - self.__setitem__(item, value) - - def getvalue(self, item, value=None): - return self.get(item, {}).get('value', value) - - def __getstate__(self): - return list(self.items()) - - def __setstate__(self, items): - self.update(items) - -class xml2dict(object): - - def __init__(self): - pass - - def _parse_node(self, node): - node_tree = object_dict() - # Save attrs and text, hope there will not be a child with same name - if node.text: - node_tree.value = node.text - for (k,v) in list(node.attrib.items()): - k,v = self._namespace_split(k, object_dict({'value':v})) - node_tree[k] = v - #Save childrens - for child in list(node): - tag, tree = self._namespace_split(child.tag, self._parse_node(child)) - if tag not in node_tree: # the first time, so store it in dict - node_tree[tag] = tree - continue - old = node_tree[tag] - if not isinstance(old, list): - node_tree.pop(tag) - node_tree[tag] = [old] # multi times, so change old dict to a list - node_tree[tag].append(tree) # add the new one - - return node_tree - - def _namespace_split(self, tag, value): - """ - Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients' - ns = http://cs.sfsu.edu/csc867/myscheduler - name = patients - """ - result = re.compile("\{(.*)\}(.*)").search(tag) - if result: - value.namespace, tag = result.groups() - - return (tag,value) - - def parse(self, file): - """parse a xml file to a dict""" - f = open(file, 'r') - return self.fromstring(f.read()) - - def fromstring(self, s): - """parse a string""" - t = ET.fromstring(s) - root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) - return object_dict({root_tag: root_tree}) - - -# Basic conversation goal here is converting a dict to an object allowing -# more comfortable access. `Struct()` and `make_struct()` are used to archive -# this goal. -# See http://stackoverflow.com/questions/1305532/convert-python-dict-to-object for the inital Idea -# -# The reasoning for this is the observation that we ferry arround hundreds of dicts via JSON -# and accessing them as `obj['key']` is tiresome after some time. `obj.key` is much nicer. -class Struct(object): - """Emulate a cross over between a dict() and an object().""" - def __init__(self, entries, default=None, nodefault=False): - # ensure all keys are strings and nothing else - entries = dict([(str(x), y) for x, y in list(entries.items())]) - self.__dict__.update(entries) - self.__default = default - self.__nodefault = nodefault - - def __getattr__(self, name): - """Emulate Object access. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.a - 'b' - >>> obj.foobar - 'c' - - `hasattr` results in strange behaviour if you give a default value. This might change in the future. - >>> hasattr(obj, 'a') - True - >>> hasattr(obj, 'foobar') - True - """ - if name.startswith('_'): - # copy expects __deepcopy__, __getnewargs__ to raise AttributeError - # see http://groups.google.com/group/comp.lang.python/browse_thread/thread/6ac8a11de4e2526f/ - # e76b9fbb1b2ee171?#e76b9fbb1b2ee171 - raise AttributeError("'' object has no attribute '%s'" % name) - if self.__nodefault: - raise AttributeError("'' object has no attribute '%s'" % name) - return self.__default - - def __getitem__(self, key): - """Emulate dict like access. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj['a'] - 'b' - - While the standard dict access via [key] uses the default given when creating the struct, - access via get(), results in None for keys not set. This might be considered a bug and - should change in the future. - >>> obj['foobar'] - 'c' - >>> obj.get('foobar') - 'c' - """ - # warnings.warn("dict_accss[foo] on a Struct, use object_access.foo instead", - # DeprecationWarning, stacklevel=2) - if self.__nodefault: - return self.__dict__[key] - return self.__dict__.get(key, self.__default) - - def get(self, key, default=None): - """Emulate dictionary access. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.get('a') - 'b' - >>> obj.get('foobar') - 'c' - """ - if key in self.__dict__: - return self.__dict__[key] - if not self.__nodefault: - return self.__default - return default - - def __contains__(self, item): - """Emulate dict 'in' functionality. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> 'a' in obj - True - >>> 'foobar' in obj - False - """ - return item in self.__dict__ - - def __bool__(self): - """Returns whether the instance evaluates to False""" - return bool(list(self.items())) - - def has_key(self, item): - """Emulate dict.has_key() functionality. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.has_key('a') - True - >>> obj.has_key('foobar') - False - """ - return item in self - - def items(self): - """Emulate dict.items() functionality. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.items() - [('a', 'b')] - """ - return [(k, v) for (k, v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] - - def keys(self): - """Emulate dict.keys() functionality. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.keys() - ['a'] - """ - return [k for (k, _v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] - - def values(self): - """Emulate dict.values() functionality. - - >>> obj = Struct({'a': 'b'}, default='c') - >>> obj.values() - ['b'] - """ - return [v for (k, v) in list(self.__dict__.items()) if not k.startswith('_Struct__')] - - def __repr__(self): - return "" % dict(list(self.items())) - - def as_dict(self): - """Return a dict representing the content of this struct.""" - return self.__dict__ - - -def make_struct(obj, default=None, nodefault=False): - """Converts a dict to an object, leaves objects untouched. - - Someting like obj.vars() = dict() - Read Only! - - >>> obj = make_struct(dict(foo='bar')) - >>> obj.foo - 'bar' - - `make_struct` leaves objects alone. - >>> class MyObj(object): pass - >>> data = MyObj() - >>> data.foo = 'bar' - >>> obj = make_struct(data) - >>> obj.foo - 'bar' - - `make_struct` also is idempotent - >>> obj = make_struct(make_struct(dict(foo='bar'))) - >>> obj.foo - 'bar' - - `make_struct` recursively handles dicts and lists of dicts - >>> obj = make_struct(dict(foo=dict(bar='baz'))) - >>> obj.foo.bar - 'baz' - - >>> obj = make_struct([dict(foo='baz')]) - >>> obj - [] - >>> obj[0].foo - 'baz' - - >>> obj = make_struct(dict(foo=dict(bar=dict(baz='end')))) - >>> obj.foo.bar.baz - 'end' - - >>> obj = make_struct(dict(foo=[dict(bar='baz')])) - >>> obj.foo[0].bar - 'baz' - >>> obj.items() - [('foo', [])] - """ - if type(obj) == type(Struct): - return obj - if type(obj) == dict: - struc = Struct(obj, default, nodefault) - # handle recursive sub-dicts - for key, val in list(obj.items()): - setattr(struc, key, make_struct(val, default, nodefault)) - return struc - elif type(obj) == list: - return [make_struct(v, default, nodefault) for v in obj] - else: - return obj - - -# Code is based on http://code.activestate.com/recipes/573463/ -def _convert_dict_to_xml_recurse(parent, dictitem, listnames): - """Helper Function for XML conversion.""" - # we can't convert bare lists - assert not isinstance(dictitem, list) - - if isinstance(dictitem, dict): - # special case of attrs and text - if '@attrs' in dictitem.keys(): - attrs = dictitem.pop('@attrs') - for key, value in attrs.iteritems(): - parent.set(key, value) # TODO: will fail if attrs is not a dict - if '#text' in dictitem.keys(): - text = dictitem.pop('#text') - if sys.version_info[0] < 3: - parent.text = unicode(text) - else: - parent.text = str(text) - for (tag, child) in sorted(dictitem.items()): - if isinstance(child, list): - # iterate through the array and convert - listparent = ET.Element(tag if tag in listnames.keys() else '') - parent.append(listparent) - for listchild in child: - item = ET.SubElement(listparent, listnames.get(tag, tag)) - _convert_dict_to_xml_recurse(item, listchild, listnames) - else: - elem = ET.Element(tag) - parent.append(elem) - _convert_dict_to_xml_recurse(elem, child, listnames) - elif not dictitem is None: - if sys.version_info[0] < 3: - parent.text = unicode(dictitem) - else: - parent.text = str(dictitem) - - -def dict2et(xmldict, roottag='data', listnames=None): - """Converts a dict to an ElementTree. - - Converts a dictionary to an XML ElementTree Element:: - - >>> data = {"nr": "xq12", "positionen": [{"m": 12}, {"m": 2}]} - >>> root = dict2et(data) - >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') - 'xq12122' - - Per default ecerything ins put in an enclosing '' element. Also per default lists are converted - to collecitons of `` elements. But by provding a mapping between list names and element names, - you van generate different elements:: - - >>> data = {"positionen": [{"m": 12}, {"m": 2}]} - >>> root = dict2et(data, roottag='xml') - >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') - '122' - - >>> root = dict2et(data, roottag='xml', listnames={'positionen': 'position'}) - >>> ET.tostring(root, encoding="utf-8").replace('<>', '').replace('','') - '122' - - >>> data = {"kommiauftragsnr":2103839, "anliefertermin":"2009-11-25", "prioritaet": 7, - ... "ort": u"Hücksenwagen", - ... "positionen": [{"menge": 12, "artnr": "14640/XL", "posnr": 1},], - ... "versandeinweisungen": [{"guid": "2103839-XalE", "bezeichner": "avisierung48h", - ... "anweisung": "48h vor Anlieferung unter 0900-LOGISTIK avisieren"}, - ... ]} - - >>> print ET.tostring(dict2et(data, 'kommiauftrag', - ... listnames={'positionen': 'position', 'versandeinweisungen': 'versandeinweisung'}), - ... encoding="utf-8").replace('<>', '').replace('','') - ... # doctest: +SKIP - ''' - 2009-11-25 - - - 1 - 12 - 14640/XL - - - Hücksenwagen - - - avisierung48h - 48h vor Anlieferung unter 0900-LOGISTIK avisieren - 2103839-XalE - - - 7 - 2103839 - ''' - """ - - if not listnames: - listnames = {} - root = ET.Element(roottag) - _convert_dict_to_xml_recurse(root, xmldict, listnames) - return root - - -def list2et(xmllist, root, elementname): - """Converts a list to an ElementTree. - - See also dict2et() - """ - - basexml = dict2et({root: xmllist}, 'xml', listnames={root: elementname}) - return basexml.find(root) - - -def dict2xml(datadict, roottag='', listnames=None, pretty=False): - """ - Converts a dictionary to an UTF-8 encoded XML string. - See also dict2et() - """ - if isinstance(datadict, dict) and len(datadict): - root = dict2et(datadict, roottag, listnames) - xml = to_string(root, pretty=pretty) - xml = xml.replace('<>', '').replace('', '') - return xml - else: - return '' - - -def list2xml(datalist, roottag, elementname, pretty=False): - """Converts a list to an UTF-8 encoded XML string. - - See also dict2et() - """ - root = list2et(datalist, roottag, elementname) - return to_string(root, pretty=pretty) - - -def to_string(root, pretty=False): - """Converts an ElementTree to a string""" - - if pretty: - indent(root) - - tree = ET.ElementTree(root) - fileobj = BytesIO() - - # asdf fileobj.write('' % encoding) - - if pretty: - fileobj.write('\n') - - tree.write(fileobj, 'utf-8') - return fileobj.getvalue() - - -# From http://effbot.org/zone/element-lib.htm -# prettyprint: Prints a tree with each node indented according to its depth. This is -# done by first indenting the tree (see below), and then serializing it as usual. -# indent: Adds whitespace to the tree, so that saving it as usual results in a prettyprinted tree. -# in-place prettyprint formatter - -def indent(elem, level=0): - """XML prettyprint: Prints a tree with each node indented according to its depth.""" - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for child in elem: - indent(child, level + 1) - if child: - if not child.tail or not child.tail.strip(): - child.tail = i - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -def test(): - """Simple selftest.""" - - data = {"guid": "3104247-7", - "menge": 7, - "artnr": "14695", - "batchnr": "3104247"} - xmlstr = dict2xml(data, roottag='warenzugang') - assert xmlstr == ('14695' - '31042473104247-77') - - data = {"kommiauftragsnr": 2103839, - "anliefertermin": "2009-11-25", - "fixtermin": True, - "prioritaet": 7, - "info_kunde": "Besuch H. Gerlach", - "auftragsnr": 1025575, - "kundenname": "Ute Zweihaus 400424990", - "kundennr": "21548", - "name1": "Uwe Zweihaus", - "name2": "400424990", - "name3": "", - "strasse": "Bahnhofstr. 2", - "land": "DE", - "plz": "42499", - "ort": "Hücksenwagen", - "positionen": [{"menge": 12, - "artnr": "14640/XL", - "posnr": 1}, - {"menge": 4, - "artnr": "14640/03", - "posnr": 2}, - {"menge": 2, - "artnr": "10105", - "posnr": 3}], - "versandeinweisungen": [{"guid": "2103839-XalE", - "bezeichner": "avisierung48h", - "anweisung": "48h vor Anlieferung unter 0900-LOGISTIK avisieren"}, - {"guid": "2103839-GuTi", - "bezeichner": "abpackern140", - "anweisung": "Paletten höchstens auf 140 cm Packen"}] - } - - xmlstr = dict2xml(data, roottag='kommiauftrag') - - data = {"kommiauftragsnr": 2103839, - "positionen": [{"menge": 4, - "artnr": "14640/XL", - "posnr": 1, - "nve": "23455326543222553"}, - {"menge": 8, - "artnr": "14640/XL", - "posnr": 1, - "nve": "43255634634653546"}, - {"menge": 4, - "artnr": "14640/03", - "posnr": 2, - "nve": "43255634634653546"}, - {"menge": 2, - "artnr": "10105", - "posnr": 3, - "nve": "23455326543222553"}], - "nves": [{"nve": "23455326543222553", - "gewicht": 28256, - "art": "paket"}, - {"nve": "43255634634653546", - "gewicht": 28256, - "art": "paket"}]} - - xmlstr = dict2xml(data, roottag='rueckmeldung') - if __name__ == '__main__': import doctest import sys failure_count, test_count = doctest.testmod() - d = make_struct({ - 'item1': 'string', - 'item2': ['dies', 'ist', 'eine', 'liste'], - 'item3': dict(dies=1, ist=2, ein=3, dict=4), - 'item4': 10, - 'item5': [dict(dict=1, in_einer=2, liste=3)]}) - test() sys.exit(failure_count) diff --git a/samples/finding.py b/samples/finding.py index d461bdf..1dab964 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -42,7 +42,7 @@ def run(opts): config_file=opts.yaml, warnings=True) api.execute('findItemsAdvanced', { - 'keywords': u'niño', + 'keywords': 'python', 'itemFilter': [ {'name': 'Condition', 'value': 'Used'}, @@ -56,19 +56,28 @@ def run(opts): dump(api) except ConnectionError as e: - print(e) + print e def run2(opts): try: api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsByProduct', '53039031') - + api.execute('findItemsByProduct', '530390312') dump(api) + from IPython import embed; embed() + + ''' + import suds.sudsobject + r = suds.sudsobject.Factory.object('reply', dict={'a': 'b', 'price': {'value': '8.99', '_currency': 'USD'}}) + +from ebaysuds import ShoppingAPI +c = ShoppingAPI(site_id='US', app_id='TimKeefe-b919-4dc3-b4a9-b7c7280e2493') +r = c.GetSingleItem(ItemID='261389277941') + ''' except ConnectionError as e: - print(e) + print e if __name__ == "__main__": diff --git a/tests/__init__.py b/tests/__init__.py index a9a945a..61f2cbe 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,6 +9,8 @@ import unittest import doctest import ebaysdk.utils +import ebaysdk.utils2 +import ebaysdk.response import ebaysdk.config import ebaysdk.http import ebaysdk.connection @@ -22,6 +24,9 @@ def getTestSuite(): suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(ebaysdk.response)) + ''' + suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) suite.addTest(doctest.DocTestSuite(ebaysdk.config)) @@ -31,9 +36,8 @@ def getTestSuite(): suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) - - # Internal Only Service - #uite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) + suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) + ''' return suite runner = unittest.TextTestRunner() From 303800669ff7a097681abfa7a0938241a2b757a4 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 12 Feb 2014 10:51:51 -0800 Subject: [PATCH 152/218] response milestone! --- ebaysdk/connection.py | 32 +++----- ebaysdk/response.py | 165 +++++++++++++----------------------------- samples/finding.py | 1 - 3 files changed, 59 insertions(+), 139 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 3efb6cd..ebafe07 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -75,7 +75,7 @@ def debug_callback(self, debug_type, debug_message): log.debug('type: ' + str(debug_type) + ' message' + str(debug_message)) def v(self, *args, **kwargs): - return getValue(self.response_dict(), *args, **kwargs) + return getValue(self.response.dict(), *args, **kwargs) def getNodeText(self, nodelist): return getNodeTextUtils(nodelist) @@ -149,15 +149,13 @@ def execute_request(self): self.parallel._add_request(self) return None - response = self.session.send(self.request, + self.response = self.session.send(self.request, verify=False, proxies=self.proxies, timeout=self.timeout, allow_redirects=True ) - self.response = Response(response) - log.debug('RESPONSE (%s):' % self._request_id) log.debug('elapsed time=%s' % self.response.elapsed) log.debug('status code=%s' % self.response.status_code) @@ -167,16 +165,11 @@ def execute_request(self): def process_response(self): """Post processing of the response""" + self.response = Response(self.response, verb=self.verb) + if self.response.status_code != 200: self._response_error = self.response.reason - #self.response_obj=Response(self.response.content) - - # leave?? - # remove xml namespace - #regex = re.compile('xmlns="[^"]+"') - #self._response_content = regex.sub('', self.response.content) - def error_check(self): estr = self.error() @@ -211,10 +204,12 @@ def response_soup(self): return self._response_soup def response_obj(self): - return self.response_dict() + return self.response.dict() def response_dom(self): - "Returns the response DOM (xml.dom.minidom)." + """ Deprecated: use self.response.dom() instead + Returns the response DOM (xml.dom.minidom). + """ if not self._response_dom: dom = None @@ -242,19 +237,12 @@ def response_dom(self): def response_dict(self): "Returns the response dictionary." - return self.response.asdict() - - #if not self._response_dict and self._response_content: - # mydict = xml2dict().fromstring(self._response_content) - # self._response_dict = mydict.get(self.verb + 'Response', mydict) - - - return self._response_dict + return self.response.dict() def response_json(self): "Returns the response JSON." - return json.dumps(self.response_dict()) + return json.dumps(self.response.dict()) def _get_resp_body_errors(self): """Parses the response content to pull errors. diff --git a/ebaysdk/response.py b/ebaysdk/response.py index a126c62..10e5b2c 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -5,6 +5,9 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' +from collections import defaultdict +import json + from ebaysdk.utils import get_dom_tree class ResponseDataObject(object): @@ -32,13 +35,15 @@ def _load_dict(self, mydict): elif isinstance(a[1], list): objs = [] for i in a[1]: - objs.append(ResponseDataObject(i)) + if isinstance(i, str): + objs.append(i) + else: + objs.append(ResponseDataObject(i)) setattr(self, a[0], objs) else: setattr(self, a[0], a[1]) - class Response(object): ''' @@ -71,48 +76,45 @@ class Response(object): >>> r.reply ''' - rdict = dict() - rdom = None - - def __init__(self, obj): - # requests response object - self.obj = obj - self.tree = self._parse_xml(obj.content) - - res = [] - self.xmltodict(self.tree, res) - - mydict = dict() - self._build_dict(res, mydict) - self.rdict=mydict - - self.reply = ResponseDataObject(self.asdict()) - - def _build_dict(self, ndict, mydict): - - if isinstance(ndict, list): - for i in ndict: - self._build_dict(i, mydict) - elif isinstance(ndict, dict): - if isinstance(ndict[ndict.keys()[0]], list): - if isinstance(mydict.get(ndict.keys()[0], {}), dict) \ - and mydict.has_key(ndict.keys()[0]): - mydict[ndict.keys()[0]] = [ mydict[ndict.keys()[0]] ] - elif mydict.has_key(ndict.keys()[0]) and isinstance(mydict[ndict.keys()[0]], list): - pass - else: - mydict[ndict.keys()[0]] = {} - - for i in ndict[ndict.keys()[0]]: - self._build_dict(i, mydict[ndict.keys()[0]]) + _dict = dict() + _dom = None + + def __init__(self, obj, verb=None): + + self._obj = obj + self._dom = self._parse_xml(obj.content) + self._dict = self._etree_to_dict(self._dom) + + if verb: + self._dict = self._dict.get('%sResponse' % verb, self._dict) + + self.reply = ResponseDataObject(self._dict) + + def _etree_to_dict(self, t): + # remove xmlns from nodes, I find them meaningless + t.tag = self._get_node_tag(t) + + d = {t.tag: {} if t.attrib else None} + children = list(t) + if children: + dd = defaultdict(list) + for dc in map(self._etree_to_dict, children): + for k, v in dc.iteritems(): + dd[k].append(v) + d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} + if t.attrib: + d[t.tag].update(('_' + k, v) for k, v in t.attrib.iteritems()) + if t.text: + text = t.text.strip() + if children or t.attrib: + if text: + d[t.tag]['value'] = text else: - if isinstance(mydict, list): - mydict.append(ndict) - else: - mydict.update(ndict) + d[t.tag] = text + return d def __getattr__(self, name): - return getattr(self.obj, name) + return getattr(self._obj, name) def _parse_xml(self, xml): return get_dom_tree(xml) @@ -120,80 +122,11 @@ def _parse_xml(self, xml): def _get_node_tag(self, node): return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '') - def xmltodict(self, node, res): - rep = {} - - if len(node): - for n in list(node): - #print self._get_node_tag(node) - #rep[self._get_node_tag(node)] = [] - rep[self._get_node_tag(node)] = [] - - self.xmltodict(n, rep[self._get_node_tag(node)]) - - if len(n): - #print "len=%s" % len(n) - value = None - if len(n.attrib) > 0: - value = rep[self._get_node_tag(node)] - #value = {'value':rep[self._get_node_tag(node)], - # '_attrs': n.attrib} - else: - value = rep[self._get_node_tag(node)] - - res.append({self._get_node_tag(n):value}) - else: - res.append(rep[ self._get_node_tag(node)][0]) - #print "else >> %s (%s)" % (self._get_node_tag(node), rep[ self._get_node_tag(node)][0]) - #print "res >> %s" % ' '.join(res) - else: - value = None - if len(node.attrib) > 0: - value = {'value': node.text, '_attrs': node.attrib} - else: - value = node.text - - #print "tag=%s" % self._get_node_tag(node) - #print "value=%s" % value or '' - #print "before=" + ' '.join(res) - res.append({self._get_node_tag(node):value or ''}) - #print "after=" + ' '.join(res) - - return - - def orig_xmltodict(self, node, res): - rep = {} - - if len(node): - for n in list(node): - print self._get_node_tag(node) - rep[self._get_node_tag(node)] = [] - self.xmltodict(n, rep[self._get_node_tag(node)]) - - if len(n): - print "len=%s" % len(n) - value = None - if len(n.attrib) > 0: - value = {'value':rep[self._get_node_tag(node)], - '_attrs': n.attrib} - else: - value = rep[self._get_node_tag(node)] - - res.append({self._get_node_tag(n):value}) - else: - res.append(rep[self._get_node_tag(node)][0]) - print "else >> %s (%s)" % (self._get_node_tag(node), res) - #print "res >> %s" % ' '.join(res) - else: - value = None - if len(node.attrib) > 0: - value = {'value': node.text, '_attrs': node.attrib} - else: - value = node.text + def dom(self): + return self._dom - res.append({self._get_node_tag(node):value or ''}) - - return + def dict(self): + return self._dict - def asdict(self): - return self.rdict \ No newline at end of file + def json(self): + json.dumps(self.dict()) \ No newline at end of file diff --git a/samples/finding.py b/samples/finding.py index 1dab964..ad08ada 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -65,7 +65,6 @@ def run2(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsByProduct', '530390312') dump(api) - from IPython import embed; embed() ''' import suds.sudsobject From 3750bab2279b11cffeb567909c15dbcb1bc56253 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 13 Feb 2014 12:10:23 -0800 Subject: [PATCH 153/218] finial version of Response object --- ebaysdk/connection.py | 6 ++-- ebaysdk/response.py | 80 +++++++++++++++++++++++++++++++++++-------- samples/finding.py | 15 +++----- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index ebafe07..e9b8f52 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -84,6 +84,7 @@ def _reset(self): self.response = None self.request = None self.verb = None + self.listnodes = [] self._request_id = None self._time = time.time() self._response_content = None @@ -99,11 +100,12 @@ def _reset(self): def do(self, verb, call_data=dict()): return self.execute(verb, call_data) - def execute(self, verb, data=None): + def execute(self, verb, data=None, listnodes=[]): "Executes the HTTP request." log.debug('execute: verb=%s data=%s' % (verb, data)) self._reset() + self.listnodes=listnodes self.build_request(verb, data) self.execute_request() @@ -165,7 +167,7 @@ def execute_request(self): def process_response(self): """Post processing of the response""" - self.response = Response(self.response, verb=self.verb) + self.response = Response(self.response, verb=self.verb, listnodes=self.listnodes) if self.response.status_code != 200: self._response_error = self.response.reason diff --git a/ebaysdk/response.py b/ebaysdk/response.py index 10e5b2c..d549d8a 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -65,31 +65,71 @@ class Response(object): Doctests: - >>> xml = 'Success1.12.02014-02-07T23:31:13.941Z1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' + >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem Two1190179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' >>> o = ResponseDataObject({'content': xml}) - >>> r = Response(o) - - >>> xml - - >>> r.asdict() - + >>> r = Response(o, verb='findItemsByProduct', listnodes=['searchResult.item', 'findItemsByProductResponse.paginationOutput.pageNumber']) + >>> len(r.dom().getchildren()) > 2 + True + >>> r.reply.searchResult._count == '1' + True + >>> r.reply.searchResult + {'item': [{'name': 'Item Two'}], '_count': '1'} + >>> len(r.reply.paginationOutput.pageNumber) == 1 + True + >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem TwoUSMXItem One1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' + >>> o = ResponseDataObject({'content': xml}) + >>> r = Response(o, verb='findItemsByProduct', listnodes=['searchResult.item']) + >>> len(r.dom().getchildren()) > 2 + True + >>> r.json() + '{"itemSearchURL": "http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1", "paginationOutput": {"totalPages": "90", "entriesPerPage": "2", "pageNumber": "1", "totalEntries": "179"}, "ack": "Success", "timestamp": "2014-02-07T23:31:13.941Z", "searchResult": {"item": [{"name": "Item Two", "shipping": {"c": ["US", "MX"]}}, {"name": "Item One"}], "_count": "2"}, "version": "1.12.0"}' + >>> r.dict() + {'itemSearchURL': 'http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1', 'paginationOutput': {'totalPages': '90', 'entriesPerPage': '2', 'pageNumber': '1', 'totalEntries': '179'}, 'ack': 'Success', 'timestamp': '2014-02-07T23:31:13.941Z', 'searchResult': {'item': [{'name': 'Item Two', 'shipping': {'c': ['US', 'MX']}}, {'name': 'Item One'}], '_count': '2'}, 'version': '1.12.0'} >>> r.reply - + {'itemSearchURL': 'http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1', 'paginationOutput': {'totalPages': '90', 'entriesPerPage': '2', 'pageNumber': '1', 'totalEntries': '179'}, 'ack': 'Success', 'timestamp': '2014-02-07T23:31:13.941Z', 'searchResult': {'item': [{'name': 'Item Two', 'shipping': {'c': ['US', 'MX']}}, {'name': 'Item One'}], '_count': '2'}, 'version': '1.12.0'} + >>> len(r.reply.searchResult.item) == 2 + True + >>> r.reply.searchResult._count == '2' + True + >>> item = r.reply.searchResult.item[0] + >>> item.name == 'Item Two' + True + >>> len(item.shipping.c) == 2 + True ''' - _dict = dict() - _dom = None - - def __init__(self, obj, verb=None): + + def __init__(self, obj, verb=None, listnodes=[]): + self._listnodes=listnodes + self._add_prefix(self._listnodes, verb) self._obj = obj self._dom = self._parse_xml(obj.content) self._dict = self._etree_to_dict(self._dom) - + if verb: self._dict = self._dict.get('%sResponse' % verb, self._dict) self.reply = ResponseDataObject(self._dict) + def _add_prefix(self, nodes, verb): + if verb: + for i, v in enumerate(nodes): + if not nodes[i].startswith(verb): + nodes[i] = "%sResponse.%s" % (verb, nodes[i]) + + def _get_node_path(self, t): + i = t + path = [] + path.insert(0, i.tag) + while 1: + try: + path.insert(0, i.getparent().tag) + i = i.getparent() + except AttributeError: + break + + return '.'.join(path) + def _etree_to_dict(self, t): # remove xmlns from nodes, I find them meaningless t.tag = self._get_node_tag(t) @@ -101,7 +141,17 @@ def _etree_to_dict(self, t): for dc in map(self._etree_to_dict, children): for k, v in dc.iteritems(): dd[k].append(v) - d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} + + d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} + + # TODO: Optimizations? Forces a node to type list + parent_path = self._get_node_path(t) + for k in d[t.tag].keys(): + path = "%s.%s" % (parent_path, k) + if path in self._listnodes: + if not isinstance(d[t.tag][k], list): + d[t.tag][k] = [ d[t.tag][k] ] + if t.attrib: d[t.tag].update(('_' + k, v) for k, v in t.attrib.iteritems()) if t.text: @@ -129,4 +179,4 @@ def dict(self): return self._dict def json(self): - json.dumps(self.dict()) \ No newline at end of file + return json.dumps(self.dict()) \ No newline at end of file diff --git a/samples/finding.py b/samples/finding.py index ad08ada..8faf81c 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -63,18 +63,11 @@ def run(opts): def run2(opts): try: api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsByProduct', '530390312') + api.execute('findItemsByProduct', + '530390311', + listnodes=['searchResult.item']) dump(api) - - ''' - import suds.sudsobject - r = suds.sudsobject.Factory.object('reply', dict={'a': 'b', 'price': {'value': '8.99', '_currency': 'USD'}}) - -from ebaysuds import ShoppingAPI -c = ShoppingAPI(site_id='US', app_id='TimKeefe-b919-4dc3-b4a9-b7c7280e2493') -r = c.GetSingleItem(ItemID='261389277941') - ''' - + except ConnectionError as e: print e From d9411f206ce2e57e99cca2f46bf779956b69669f Mon Sep 17 00:00:00 2001 From: Andy Raines Date: Wed, 19 Feb 2014 22:27:20 +0000 Subject: [PATCH 154/218] Bug fix to shopping api header so that siteid can be set --- ebaysdk/shopping/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 3711922..91ae08a 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -89,7 +89,7 @@ def build_request_headers(self, verb): headers = { "X-EBAY-API-VERSION": self.config.get('version', ''), "X-EBAY-API-APP-ID": self.config.get('appid', ''), - "X-EBAY-API-SITEID": self.config.get('siteid', ''), + "X-EBAY-API-SITE-ID": self.config.get('siteid', ''), "X-EBAY-API-CALL-NAME": verb, "X-EBAY-API-REQUEST-ENCODING": "XML", "Content-Type": "text/xml" From 3c2a31ffd09aeb0b9d427851506206be5d6640d1 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 19 Feb 2014 16:38:03 -0800 Subject: [PATCH 155/218] unicode fix, soa fix, more work on dict2xml --- ebaysdk/connection.py | 6 +-- ebaysdk/finding/__init__.py | 8 ++-- ebaysdk/shopping/__init__.py | 2 +- ebaysdk/soa/__init__.py | 3 +- ebaysdk/soa/finditem.py | 13 +++--- ebaysdk/utils.py | 88 +++++++++++++++++++++++++++--------- samples/common.py | 2 +- samples/finding.py | 4 +- samples/finditem.py | 7 +++ samples/shopping.py | 2 +- tests/__init__.py | 10 ++-- 11 files changed, 98 insertions(+), 47 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index e9b8f52..0aeaaf5 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -103,7 +103,7 @@ def do(self, verb, call_data=dict()): def execute(self, verb, data=None, listnodes=[]): "Executes the HTTP request." log.debug('execute: verb=%s data=%s' % (verb, data)) - + self._reset() self.listnodes=listnodes self.build_request(verb, data) @@ -206,7 +206,7 @@ def response_soup(self): return self._response_soup def response_obj(self): - return self.response.dict() + return self.response.reply def response_dom(self): """ Deprecated: use self.response.dom() instead @@ -239,7 +239,7 @@ def response_dom(self): def response_dict(self): "Returns the response dictionary." - return self.response.dict() + return self.response.reply def response_json(self): "Returns the response JSON." diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index bd24337..bde5a4f 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -25,16 +25,16 @@ class Connection(BaseConnection): (all others, see API docs) Doctests: - >>> f = Connection(config_file=os.environ.get('EBAY_YAML')) + >>> f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) >>> retval = f.execute('findItemsAdvanced', {'keywords': u'niño'}) >>> error = f.error() >>> print(error) None >>> if not f.error(): - ... print(f.response_obj().itemSearchURL != '') - ... items = f.response_obj().searchResult.item + ... print(f.response.reply.itemSearchURL != '') + ... items = f.response.reply.searchResult.item ... print(len(items) > 2) - ... print(f.response_dict().ack) + ... print(f.response.reply.ack) True True Success diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 3711922..8eb38a3 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -190,7 +190,7 @@ def _get_resp_body_errors(self): if self.config.get('warnings') and len(warnings) > 0: log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) - if self.response_dict().Ack == 'Failure': + if self.response.reply.Ack == 'Failure': if self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) return errors diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index bdb94b0..f895405 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -54,7 +54,8 @@ def response_dict(self): if self._response_dict: return self._response_dict - mydict = xml2dict().fromstring(self._response_content) + + mydict = self.response.dict() try: verb = self.verb + 'Response' diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index 173d36b..9f8d42f 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -78,22 +78,23 @@ def findItemsByIds(self, ebay_item_ids, }) args = {'id': ebay_item_ids, 'readSet': read_set_node} - self.execute('findItemsByIds', args) + self.execute('findItemsByIds', args, listnodes=['record']) return self.mappedResponse() def mappedResponse(self): records = [] - for r in self.response_dict().get('record', []): + for r in self.response.dict().get('record', []): mydict = dict() i = 0 - for values_dict in r.value: - for key, value in values_dict.iteritems(): + + for values_dict in r.get('value', {}): + for key, value in values_dict.items(): value_data = None if type(value) == list: - value_data = [x['value'] for x in value] + value_data = [x for x in value] else: - value_data = value['value'] + value_data = value mydict.update({self.read_set[i]: value_data}) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 966c7ae..e4a6878 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -5,6 +5,7 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' +import sys try: from lxml import etree as ET @@ -15,7 +16,6 @@ import xml.etree.cElementTree as ET print("running with cElementTree on Python 2.5+") except ImportError: - try: # Python 2.5 import xml.etree.ElementTree as ET @@ -39,7 +39,7 @@ def to_xml(data): if isinstance(data, str): return data else: - return dicttoxml(data) + return dict2xml(data) def get_dom_tree(xml): tree = ET.fromstring(xml) @@ -58,27 +58,49 @@ def attribute_check(root): return attrs, value -def dicttoxml(root): +def smart_encode(value): + if sys.version_info[0] < 3: + return unicode(value).encode('utf-8') + else: + return str(value) + +def dict2xml(root): ''' Doctests: - >>> dict1 = {'Items': [{'ItemId': 1234}, {'ItemId': 2222}]} - >>> dicttoxml(dict1) + >>> dict1 = {'Items': {'ItemId': ['1234', '2222']}} + >>> dict2xml(dict1) '12342222' >>> dict2 = { ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, - ... 'paginationInput': [ - ... {'pageNumber': '1'}, - ... {'pageSize': '25'} - ... ], + ... 'paginationInput': { + ... 'pageNumber': '1', + ... 'pageSize': '25' + ... }, ... 'sortOrder': 'StartTimeNewest' ... } - >>> dicttoxml(dict2) + >>> dict2xml(dict2) '125StartTimeNewest222' >>> dict3 = { ... 'parent': {'child': {'#text': 222, '@attrs': {'site': 'US', 'id': 1234}}} ... } - >>> dicttoxml(dict3) + >>> dict2xml(dict3) '222' + >>> dict4 = { + ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, + ... 'paginationInput': { + ... 'pageNumber': '1', + ... 'pageSize': '25' + ... }, + ... 'itemFilter': [ + ... {'name': 'Condition', + ... 'value': 'Used'}, + ... {'name': 'LocatedIn', + ... 'value': 'GB'}, + ... ], + ... 'sortOrder': 'StartTimeNewest' + ... } + >>> dict2xml(dict4) + 'ConditionUsedLocatedInGB125StartTimeNewest222' ''' xml = '' @@ -89,7 +111,7 @@ def dicttoxml(root): attrs, value = attribute_check(root[key]) if not value: - value = dicttoxml(root[key]) + value = dict2xml(root[key]) attrs_sp = '' if len(attrs) > 0: @@ -97,26 +119,25 @@ def dicttoxml(root): xml = '%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ {'tag': key, 'xml': xml, 'attrs': ' '.join(attrs), - 'value': value, 'attrs_sp': attrs_sp} + 'value': smart_encode(value), 'attrs_sp': attrs_sp} elif isinstance(root[key], list): - xml = '%s<%s>' % (xml, key) - for item in root[key]: attrs, value = attribute_check(item) if not value: - value = dicttoxml(item) - - xml = '%s%s' % (xml, value) - - xml = '%s' % (xml, key) + value = dict2xml(item) + + xml = '%(xml)s<%(tag)s>%(value)s' % {'xml': xml, 'tag': key, 'value': value} + else: value = root[key] xml = '%(xml)s<%(tag)s>%(value)s' % \ - {'xml': xml, 'tag': key, 'value': value} + {'xml': xml, 'tag': key, 'value': smart_encode(value)} + elif isinstance(root, str) or isinstance(root, int) or isinstance(root, unicode): + xml = '%s%s' % (xml, root) else: raise Exception('Unable to serialize node of type %s (%s)' % (type(root), root)) @@ -165,9 +186,32 @@ def getNodeText(node): return ''.join(rc) +def perftest_dict2xml(): + sample_dict = { + 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, + 'paginationInput': { + 'pageNumber': '1', + 'pageSize': '25' + }, + 'itemFilter': [ + {'name': 'Condition', + 'value': 'Used'}, + {'name': 'LocatedIn', + 'value': 'GB'}, + ], + 'sortOrder': 'StartTimeNewest' + } + + xml = dict2xml(sample_dict) + if __name__ == '__main__': + + import timeit + print("perftest_dict2xml() %s" % \ + timeit.timeit("perftest_dict2xml()", number=50000, + setup="from __main__ import perftest_dict2xml")) + import doctest - import sys failure_count, test_count = doctest.testmod() sys.exit(failure_count) diff --git a/samples/common.py b/samples/common.py index cc2a166..54d0f3d 100644 --- a/samples/common.py +++ b/samples/common.py @@ -22,7 +22,7 @@ def dump(api, full=False): if full: print(api.response_content()) - print((json.dumps(api.response_dict(), indent=2))) + print((json.dumps(api.response.dict(), indent=2))) else: dictstr = "%s" % api.response_dict() print("Response dictionary: %s..." % dictstr[:150]) diff --git a/samples/finding.py b/samples/finding.py index 8faf81c..1ee4383 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -38,11 +38,11 @@ def init_options(): def run(opts): try: - api = finding(siteid='EBAY-NLBE', debug=opts.debug, appid=opts.appid, + api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) api.execute('findItemsAdvanced', { - 'keywords': 'python', + 'keywords': u'niño', 'itemFilter': [ {'name': 'Condition', 'value': 'Used'}, diff --git a/samples/finditem.py b/samples/finditem.py index 314a01c..6cd873a 100644 --- a/samples/finditem.py +++ b/samples/finditem.py @@ -52,6 +52,13 @@ def run(opts): api = FindItem(debug=opts.debug, consumer_id=opts.consumer_id, config_file=opts.yaml) + records = api.find_items_by_ids([itemIds[0]]) + + for r in records: + print("ID(%s) TITLE(%s)" % (r['ITEM_ID'], r['TITLE'][:35])) + + dump(api) + records = api.find_items_by_ids(itemIds) for r in records: diff --git a/samples/shopping.py b/samples/shopping.py index 6cc75b4..f99dd10 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -136,7 +136,7 @@ def with_affiliate_info(opts): } api.execute('FindPopularSearches', mySearch) - dump(api, full=True) + dump(api, full=False) except ConnectionError as e: print e diff --git a/tests/__init__.py b/tests/__init__.py index 61f2cbe..125480d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,7 +9,6 @@ import unittest import doctest import ebaysdk.utils -import ebaysdk.utils2 import ebaysdk.response import ebaysdk.config import ebaysdk.http @@ -25,20 +24,19 @@ def getTestSuite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(ebaysdk.response)) - ''' - suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) - suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) + #suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) suite.addTest(doctest.DocTestSuite(ebaysdk.config)) suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) - suite.addTest(doctest.DocTestSuite(ebaysdk.http)) + #suite.addTest(doctest.DocTestSuite(ebaysdk.http)) suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) - ''' + return suite runner = unittest.TextTestRunner() runner.run(getTestSuite()) + From 8b6670b2288682405e2449290a742b7373919a12 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 19 Feb 2014 23:12:43 -0800 Subject: [PATCH 156/218] python 3 fixes and then some --- .gitignore | 1 + ebaysdk/connection.py | 17 ++++--- ebaysdk/finding/__init__.py | 4 +- ebaysdk/http/__init__.py | 13 ++--- ebaysdk/merchandising/__init__.py | 4 +- ebaysdk/parallel.py | 1 + ebaysdk/response.py | 44 ++++++++++++----- ebaysdk/shopping/__init__.py | 4 +- ebaysdk/soa/__init__.py | 4 +- ebaysdk/soa/finditem.py | 4 +- ebaysdk/trading/__init__.py | 4 +- ebaysdk/utils.py | 79 +++++++++++++++---------------- samples/finding.py | 8 ++-- samples/finditem.py | 2 +- samples/merchandising.py | 2 +- samples/parallel.py | 4 +- samples/shopping.py | 29 ++++++------ samples/t_http.py | 3 +- samples/trading.py | 28 +++++------ setup.py | 2 +- tests/__init__.py | 8 ++-- 21 files changed, 142 insertions(+), 123 deletions(-) diff --git a/.gitignore b/.gitignore index 7ef237d..638e9ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +timebay.yaml build/ dist/ tkebay.yaml diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 0aeaaf5..9fe7a8a 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -19,12 +19,6 @@ from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError -try: - from bs4 import BeautifulStoneSoup -except ImportError: - from BeautifulSoup import BeautifulStoneSoup - log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') - from ebaysdk import set_stream_logger, UserAgent from ebaysdk.utils import getNodeText as getNodeTextUtils from ebaysdk.utils import getValue @@ -199,6 +193,12 @@ def response_soup(self): "Returns a BeautifulSoup object of the response." if not self._response_soup: + try: + from bs4 import BeautifulStoneSoup + except ImportError: + from BeautifulSoup import BeautifulStoneSoup + log.warn('DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') + self._response_soup = BeautifulStoneSoup( self.response_content.decode('utf-8') ) @@ -219,9 +219,8 @@ def response_dom(self): try: if self.response.content: - regex = re.compile('xmlns="[^"]+"') - content = regex.sub('', self.response.content) - + regex = re.compile(b'xmlns="[^"]+"') + content = regex.sub(b'', self.response.content) else: content = "<%sResponse>" % (self.verb, self.verb) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index bde5a4f..0d8aab4 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -11,7 +11,7 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml +from ebaysdk.utils import getNodeText, dict2xml class Connection(BaseConnection): """Connection class for the Finding service @@ -101,7 +101,7 @@ def build_request_headers(self, verb): def build_request_data(self, verb, data): xml = "" xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" - xml += to_xml(data) or '' + xml += dict2xml(data) xml += "" return xml diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 7ba7ac1..edfdb15 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -23,6 +23,7 @@ from ebaysdk.exception import ConnectionResponseError from ebaysdk.config import Config from ebaysdk.utils import getNodeText +from ebaysdk.response import Response class Connection(BaseConnection): """HTML class for traditional calls. @@ -30,7 +31,7 @@ class Connection(BaseConnection): Doctests: >>> h = Connection() >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') - >>> print(h.response_obj().rss.channel.ttl) + >>> print(h.response.reply.rss.channel.ttl) 60 >>> title = h.response_dom().getElementsByTagName('title')[0] >>> print(getNodeText(title)) @@ -41,10 +42,12 @@ class Connection(BaseConnection): None >>> h = Connection(method='POST', debug=False) >>> retval = h.execute('http://www.ebay.com/') - >>> print(h.response_content() != '') + >>> print(h.response.content != '') True >>> print(h.response_code()) 200 + >>> h.response.reply + {} """ def __init__(self, method='GET', **kwargs): @@ -71,7 +74,7 @@ def response_dom(self): try: if not self._response_dom: - self._response_dom = parseString(self._response_content) + self._response_dom = parseString(self.response.content) return self._response_dom except ExpatError: @@ -81,10 +84,8 @@ def response_dict(self): "Return the HTTP response dictionary." try: - #if not self._response_dict and self.response_content: - # self._response_dict = xml2dict().fromstring(self._response_content) - return self._response_obj.asdict() + return self.response.dict() except ExpatError: raise ConnectionResponseError('response is not well-formed') diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index 75342c7..4a58848 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -9,7 +9,7 @@ import os from ebaysdk.finding import Connection as FindingConnection -from ebaysdk.utils import to_xml +from ebaysdk.utils import dict2xml class Connection(FindingConnection): """Connection class for the Merchandising service @@ -73,7 +73,7 @@ def build_request_headers(self, verb): def build_request_data(self, verb, data): xml = "" xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" - xml += to_xml(data) or '' + xml += dict2xml(data) xml += "" return xml diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index f23c365..b4aeece 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -64,6 +64,7 @@ def wait(self, timeout=20): for idx, r in enumerate(self._requests): r.response = gresponses[idx] + #from IPython import embed; embed() r.process_response() r.error_check() diff --git a/ebaysdk/response.py b/ebaysdk/response.py index d549d8a..3d60760 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -5,11 +5,14 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' +import sys +import lxml from collections import defaultdict import json -from ebaysdk.utils import get_dom_tree +from ebaysdk.utils import get_dom_tree, python_2_unicode_compatible +@python_2_unicode_compatible class ResponseDataObject(object): def __init__(self, mydict={}): @@ -19,10 +22,20 @@ def __repr__(self): return str(self) def __str__(self): - return unicode(self).encode('utf-8') + return "%s" % self.__dict__ - def __unicode__(self): - return str(self.__dict__) + def has_key(self, name): + try: + getattr(self, name) + return True + except AttributeError: + return False + + def get(self, name, default=None): + try: + return getattr(self, name) + except AttributeError: + return default def _load_dict(self, mydict): @@ -103,13 +116,17 @@ def __init__(self, obj, verb=None, listnodes=[]): self._add_prefix(self._listnodes, verb) self._obj = obj - self._dom = self._parse_xml(obj.content) - self._dict = self._etree_to_dict(self._dom) + + try: + self._dom = self._parse_xml(obj.content) + self._dict = self._etree_to_dict(self._dom) - if verb: - self._dict = self._dict.get('%sResponse' % verb, self._dict) + if verb: + self._dict = self._dict.get('%sResponse' % verb, self._dict) - self.reply = ResponseDataObject(self._dict) + self.reply = ResponseDataObject(self._dict) + except lxml.etree.XMLSyntaxError: + self.reply = ResponseDataObject({}) def _add_prefix(self, nodes, verb): if verb: @@ -132,6 +149,9 @@ def _get_node_path(self, t): def _etree_to_dict(self, t): # remove xmlns from nodes, I find them meaningless + if type(t) == lxml.etree._Comment: + return {} + t.tag = self._get_node_tag(t) d = {t.tag: {} if t.attrib else None} @@ -139,10 +159,10 @@ def _etree_to_dict(self, t): if children: dd = defaultdict(list) for dc in map(self._etree_to_dict, children): - for k, v in dc.iteritems(): + for k, v in dc.items(): dd[k].append(v) - d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} + d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}} # TODO: Optimizations? Forces a node to type list parent_path = self._get_node_path(t) @@ -153,7 +173,7 @@ def _etree_to_dict(self, t): d[t.tag][k] = [ d[t.tag][k] ] if t.attrib: - d[t.tag].update(('_' + k, v) for k, v in t.attrib.iteritems()) + d[t.tag].update(('_' + k, v) for k, v in t.attrib.items()) if t.text: text = t.text.strip() if children or t.attrib: diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 8eb38a3..17f1b68 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -11,7 +11,7 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml +from ebaysdk.utils import getNodeText, dict2xml class Connection(BaseConnection): """Shopping API class @@ -111,7 +111,7 @@ def build_request_data(self, verb, data): xml = "" xml += "<" + verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" - xml += to_xml(data) or '' + xml += dict2xml(data) xml += "" return xml diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index f895405..4248b1f 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -9,7 +9,7 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml +from ebaysdk.utils import getNodeText, dict2xml class Connection(BaseConnection): """Connection class for a base SOA service""" @@ -85,7 +85,7 @@ def build_request_data(self, verb, data): xml += ' xmlns:ser="%s" >' % self.config.get('soap_env_str') xml += '' xml += '' % verb - xml += to_xml(self.soapify(data)) or '' + xml += dict2xml(self.soapify(data)) xml += '' % verb xml += '' xml += '' diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index 9f8d42f..ed33ea5 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -9,7 +9,7 @@ import os from ebaysdk.soa import Connection as BaseConnection -from ebaysdk.utils import to_xml, getNodeText +from ebaysdk.utils import dict2xml, getNodeText class Connection(BaseConnection): """ @@ -112,7 +112,7 @@ def build_request_data(self, verb, data): xml += "<" + verb + "Request" xml += ' xmlns="http://www.ebay.com/marketplace/search/v1/services"' xml += '>' - xml += to_xml(data) or '' + xml += dict2xml(data) xml += "" return xml diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 309a9e0..3faac2b 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -11,7 +11,7 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, to_xml +from ebaysdk.utils import getNodeText, dict2xml class Connection(BaseConnection): """Trading API class @@ -117,7 +117,7 @@ def build_request_data(self, verb, data): if self.config.get('password', None): xml += "%s" % self.config.get('password', '') xml += "" - xml += to_xml(data) or '' + xml += dict2xml(data) xml += "" return xml diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index e4a6878..a70b251 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -7,43 +7,31 @@ ''' import sys -try: - from lxml import etree as ET - print("running with lxml.etree") -except ImportError: - try: - # Python 2.5 - import xml.etree.cElementTree as ET - print("running with cElementTree on Python 2.5+") - except ImportError: - try: - # Python 2.5 - import xml.etree.ElementTree as ET - print("running with ElementTree on Python 2.5+") - except ImportError: - try: - # normal cElementTree install - import cElementTree as ET - print("running with cElementTree") - except ImportError: - try: - # normal ElementTree install - import elementtree.ElementTree as ET - print("running with ElementTree") - except ImportError: - print("Failed to import ElementTree from any known place") - -def to_xml(data): - "Converts a list or dictionary to XML and returns it." - - if isinstance(data, str): - return data - else: - return dict2xml(data) +from lxml import etree as ET + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if sys.version_info[0] < 3: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass def get_dom_tree(xml): tree = ET.fromstring(xml) + #try: return tree.getroottree().getroot() + #except AttributeError: + # return tree def attribute_check(root): attrs = [] @@ -53,7 +41,7 @@ def attribute_check(root): if '#text' in root: value = root['#text'] if '@attrs' in root: - for ak, av in root.pop('@attrs').iteritems(): + for ak, av in sorted(root.pop('@attrs').items()): attrs.append('%s="%s"' % (ak, av)) return attrs, value @@ -79,12 +67,12 @@ def dict2xml(root): ... 'sortOrder': 'StartTimeNewest' ... } >>> dict2xml(dict2) - '125StartTimeNewest222' + '125222StartTimeNewest' >>> dict3 = { ... 'parent': {'child': {'#text': 222, '@attrs': {'site': 'US', 'id': 1234}}} ... } >>> dict2xml(dict3) - '222' + '222' >>> dict4 = { ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, ... 'paginationInput': { @@ -100,12 +88,20 @@ def dict2xml(root): ... 'sortOrder': 'StartTimeNewest' ... } >>> dict2xml(dict4) - 'ConditionUsedLocatedInGB125StartTimeNewest222' + 'ConditionUsedLocatedInGB125222StartTimeNewest' + >>> dict2xml({}) + '' + >>> dict2xml('b') + 'b' + >>> dict2xml(None) + '' ''' xml = '' + if root is None: + return xml if isinstance(root, dict): - for key in root.keys(): + for key in sorted(root.keys()): if isinstance(root[key], dict): attrs, value = attribute_check(root[key]) @@ -128,8 +124,8 @@ def dict2xml(root): if not value: value = dict2xml(item) - - xml = '%(xml)s<%(tag)s>%(value)s' % {'xml': xml, 'tag': key, 'value': value} + xml = '%(xml)s<%(tag)s>%(value)s' % \ + {'xml': xml, 'tag': key, 'value': value} else: value = root[key] @@ -139,7 +135,8 @@ def dict2xml(root): elif isinstance(root, str) or isinstance(root, int) or isinstance(root, unicode): xml = '%s%s' % (xml, root) else: - raise Exception('Unable to serialize node of type %s (%s)' % (type(root), root)) + raise Exception('Unable to serialize node of type %s (%s)' % \ + (type(root), root)) return xml diff --git a/samples/finding.py b/samples/finding.py index 1ee4383..c6c3e1c 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -56,9 +56,7 @@ def run(opts): dump(api) except ConnectionError as e: - print e - - + print(e) def run2(opts): try: @@ -67,9 +65,9 @@ def run2(opts): '530390311', listnodes=['searchResult.item']) dump(api) - + except ConnectionError as e: - print e + print(e) if __name__ == "__main__": diff --git a/samples/finditem.py b/samples/finditem.py index 6cd873a..9ea25fc 100644 --- a/samples/finditem.py +++ b/samples/finditem.py @@ -67,7 +67,7 @@ def run(opts): dump(api) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": diff --git a/samples/merchandising.py b/samples/merchandising.py index 1bf1c5a..2d6809d 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -43,7 +43,7 @@ def run(opts): dump(api) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": diff --git a/samples/parallel.py b/samples/parallel.py index cf30b6d..e660694 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -59,13 +59,13 @@ def run(opts): p.wait() if p.error(): - print p.error() + print(p.error()) for api in apis: dump(api) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/shopping.py b/samples/shopping.py index f99dd10..f3971d8 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -47,23 +47,24 @@ def run(opts): print("Shopping samples for SDK version %s" % ebaysdk.get_version()) try: - api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + api.execute('FindPopularItems', {'QueryKeywords': 'Python'}, + listnodes=['ItemArray.Item']) - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) + if api.response.content: + print("Call Success: %s in length" % len(api.response.content)) print("Response code: %s" % api.response_code()) print("Response DOM: %s" % api.response_dom()) - dictstr = "%s" % api.response_dict() + dictstr = "%s" % api.response.dict() print("Response dictionary: %s..." % dictstr[:50]) print("Matching Titles:") - for item in api.response_dict().ItemArray.Item: + for item in api.response.reply.ItemArray.Item: print(item.Title) except ConnectionError as e: - print e + print(e) def popularSearches(opts): @@ -89,19 +90,19 @@ def popularSearches(opts): } try: - api.execute('FindPopularSearches', mySearch) + api.execute('FindPopularSearches', mySearch, listnodes=['ItemArray.Item']) #dump(api, full=True) - print("Related: %s" % api.response_dict().PopularSearchResult.RelatedSearches) + print("Related: %s" % api.response.reply.PopularSearchResult.RelatedSearches) - for term in api.response_dict().PopularSearchResult.AlternativeSearches.split(';')[:3]: + for term in api.response.reply.PopularSearchResult.AlternativeSearches.split(';')[:3]: api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) print("Term: %s" % term) try: - for item in api.response_dict().ItemArray.Item: + for item in api.response.reply.ItemArray.Item: print(item.Title) except AttributeError: pass @@ -110,7 +111,7 @@ def popularSearches(opts): print("\n") except ConnectionError as e: - print e + print(e) def categoryInfo(opts): @@ -122,7 +123,7 @@ def categoryInfo(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) def with_affiliate_info(opts): try: @@ -139,7 +140,7 @@ def with_affiliate_info(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) def using_attributes(opts): @@ -154,7 +155,7 @@ def using_attributes(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/t_http.py b/samples/t_http.py index 992c9ba..698299d 100644 --- a/samples/t_http.py +++ b/samples/t_http.py @@ -35,11 +35,10 @@ def run(opts): api = HTTP(debug=opts.debug, method='GET') api.execute('http://feeds.wired.com/wired/index') - dump(api) except ConnectionError as e: - print e + print(e) if __name__ == "__main__": print("HTTP samples for SDK version %s" % ebaysdk.get_version()) diff --git a/samples/trading.py b/samples/trading.py index 7a69e96..0f852cc 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -51,10 +51,10 @@ def run(opts): api.execute('GetCharities', {'CharityID': 3897}) dump(api) - print(api.response_dict().Charity.Name) + print(api.response.reply.Charity.Name) except ConnectionError as e: - print e + print(e) def feedback(opts): try: @@ -64,13 +64,13 @@ def feedback(opts): api.execute('GetFeedback', {'UserID': 'tim0th3us'}) dump(api) - if int(api.response_dict().FeedbackScore) > 50: + if int(api.response.reply.FeedbackScore) > 50: print("Doing good!") else: print("Sell more, buy more..") except ConnectionError as e: - print e + print(e) def getTokenStatus(opts): @@ -83,7 +83,7 @@ def getTokenStatus(opts): dump(api) except ConnectionError as e: - print e + print(e) def verifyAddItem(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc @@ -134,7 +134,7 @@ def verifyAddItem(opts): dump(api) except ConnectionError as e: - print e + print(e) def verifyAddItemErrorCodes(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc @@ -192,7 +192,7 @@ def verifyAddItemErrorCodes(opts): if 37 in api.response_codes(): print("Invalid data in request") - print e + print(e) def uploadPicture(opts): @@ -210,7 +210,7 @@ def uploadPicture(opts): dump(api) except ConnectionError as e: - print e + print(e) def memberMessages(opts): @@ -236,8 +236,8 @@ def memberMessages(opts): dump(api) - if api.response_dict().MemberMessage: - messages = api.response_dict().MemberMessage.MemberMessageExchange + if api.response.reply.has_key('MemberMessage'): + messages = api.response.reply.MemberMessage.MemberMessageExchange if type(messages) != list: messages = [ messages ] @@ -246,7 +246,7 @@ def memberMessages(opts): print("%s: %s" % (m.CreationDate, m.Question.Subject[:50])) except ConnectionError as e: - print e + print(e) def getUser(opts): try: @@ -258,7 +258,7 @@ def getUser(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) def getOrders(opts): @@ -270,7 +270,7 @@ def getOrders(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) def categories(opts): @@ -288,7 +288,7 @@ def categories(opts): dump(api, full=False) except ConnectionError as e: - print e + print(e) ''' api = trading(domain='api.sandbox.ebay.com') diff --git a/setup.py b/setup.py index fe1557f..6418009 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], - install_requires=['PyYaml', 'requests', 'grequests', 'beautifulsoup4'], + install_requires=['PyYaml', 'lxml', 'requests', 'grequests'], test_suite='tests', long_description=long_desc, classifiers=[ diff --git a/tests/__init__.py b/tests/__init__.py index 125480d..d021c9b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,16 +24,18 @@ def getTestSuite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(ebaysdk.response)) - #suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) + suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) suite.addTest(doctest.DocTestSuite(ebaysdk.config)) suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) - #suite.addTest(doctest.DocTestSuite(ebaysdk.http)) + suite.addTest(doctest.DocTestSuite(ebaysdk.http)) suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) - suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) + + # inside only + #suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) return suite From 43c241f6dfdd47d643836f965959ddbecbd94fc9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 19 Feb 2014 23:13:41 -0800 Subject: [PATCH 157/218] python 3 fixes and then some --- ebaysdk/parallel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index b4aeece..f23c365 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -64,7 +64,6 @@ def wait(self, timeout=20): for idx, r in enumerate(self._requests): r.response = gresponses[idx] - #from IPython import embed; embed() r.process_response() r.error_check() From 38d693ef74f2f4ae647e970ba445f4d36ccee2f1 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 20 Feb 2014 22:10:21 -0800 Subject: [PATCH 158/218] add base listnodes --- ebaysdk/connection.py | 20 +- ebaysdk/finding/__init__.py | 85 +++++++ ebaysdk/response.py | 17 +- ebaysdk/shopping/__init__.py | 34 +++ ebaysdk/trading/__init__.py | 477 +++++++++++++++++++++++++++++++++++ samples/finding.py | 5 +- samples/shopping.py | 12 +- 7 files changed, 624 insertions(+), 26 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 9fe7a8a..b0233ff 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -12,6 +12,7 @@ import json import time import uuid +import copy from requests import Request, Session from requests.adapters import HTTPAdapter @@ -78,7 +79,7 @@ def _reset(self): self.response = None self.request = None self.verb = None - self.listnodes = [] + self._listnodes = [] self._request_id = None self._time = time.time() self._response_content = None @@ -91,15 +92,24 @@ def _reset(self): self._resp_body_warnings = [] self._resp_codes = [] - def do(self, verb, call_data=dict()): - return self.execute(verb, call_data) + def _add_prefix(self, nodes, verb): + if verb: + for i, v in enumerate(nodes): + if not nodes[i].startswith(verb): + nodes[i] = "%sResponse.%s" % (verb, nodes[i]) def execute(self, verb, data=None, listnodes=[]): "Executes the HTTP request." log.debug('execute: verb=%s data=%s' % (verb, data)) self._reset() - self.listnodes=listnodes + + self._listnodes += listnodes + self._add_prefix(self._listnodes, verb) + + if getattr(self, 'base_listnodes'): + self._listnodes += self.base_listnodes + self.build_request(verb, data) self.execute_request() @@ -161,7 +171,7 @@ def execute_request(self): def process_response(self): """Post processing of the response""" - self.response = Response(self.response, verb=self.verb, listnodes=self.listnodes) + self.response = Response(self.response, verb=self.verb, listnodes=self._listnodes) if self.response.status_code != 200: self._response_error = self.response.reason diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 0d8aab4..7e53a7a 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -86,6 +86,91 @@ def __init__(self, **kwargs): self.config.set('compatibility', '1.0.0') self.config.set('service', 'FindingService') + self.base_listnodes=[ + 'findcompleteditemsresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsadvancedresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsbycategoryresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsbyimageresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsbykeywordsresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsbyproductresponse.categoryhistogramcontainer.categoryhistogram', + 'finditemsinebaystoresresponse.categoryhistogramcontainer.categoryhistogram', + 'findcompleteditemsresponse.aspecthistogramcontainer.aspect', + 'finditemsadvancedresponse.aspecthistogramcontainer.aspect', + 'finditemsbycategoryresponse.aspecthistogramcontainer.aspect', + 'finditemsbyimageresponse.aspecthistogramcontainer.aspect', + 'finditemsbykeywordsresponse.aspecthistogramcontainer.aspect', + 'finditemsbyproductresponse.aspecthistogramcontainer.aspect', + 'finditemsinebaystoresresponse.aspecthistogramcontainer.aspect', + 'findcompleteditemsresponse.aspect.valuehistogram', + 'finditemsadvancedresponse.aspect.valuehistogram', + 'finditemsbycategoryresponse.aspect.valuehistogram', + 'finditemsbyimageresponse.aspect.valuehistogram', + 'finditemsbykeywordsresponse.aspect.valuehistogram', + 'finditemsbyproductresponse.aspect.valuehistogram', + 'finditemsinebaystoresresponse.aspect.valuehistogram', + 'findcompleteditemsresponse.aspectfilter.aspectvaluename', + 'finditemsadvancedresponse.aspectfilter.aspectvaluename', + 'finditemsbycategoryresponse.aspectfilter.aspectvaluename', + 'finditemsbyimageresponse.aspectfilter.aspectvaluename', + 'finditemsbykeywordsresponse.aspectfilter.aspectvaluename', + 'finditemsbyproductresponse.aspectfilter.aspectvaluename', + 'finditemsinebaystoresresponse.aspectfilter.aspectvaluename', + 'findcompleteditemsresponse.searchresult.item', + 'finditemsadvancedresponse.searchresult.item', + 'finditemsbycategoryresponse.searchresult.item', + 'finditemsbyimageresponse.searchresult.item', + 'finditemsbykeywordsresponse.searchresult.item', + 'finditemsbyproductresponse.searchresult.item', + 'finditemsinebaystoresresponse.searchresult.item', + 'findcompleteditemsresponse.domainfilter.domainname', + 'finditemsadvancedresponse.domainfilter.domainname', + 'finditemsbycategoryresponse.domainfilter.domainname', + 'finditemsbyimageresponse.domainfilter.domainname', + 'finditemsbykeywordsresponse.domainfilter.domainname', + 'finditemsinebaystoresresponse.domainfilter.domainname', + 'findcompleteditemsresponse.itemfilter.value', + 'finditemsadvancedresponse.itemfilter.value', + 'finditemsbycategoryresponse.itemfilter.value', + 'finditemsbyimageresponse.itemfilter.value', + 'finditemsbykeywordsresponse.itemfilter.value', + 'finditemsbyproductresponse.itemfilter.value', + 'finditemsinebaystoresresponse.itemfilter.value', + 'findcompleteditemsresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsadvancedresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsbycategoryresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsbyimageresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsbykeywordsresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsinebaystoresresponse.conditionhistogramcontainer.conditionhistogram', + 'finditemsbyproductresponse.conditionhistogramcontainer.conditionhistogram', + 'findcompleteditemsresponse.searchitem.paymentmethod', + 'finditemsadvancedresponse.searchitem.paymentmethod', + 'finditemsbycategoryresponse.searchitem.paymentmethod', + 'finditemsbyimageresponse.searchitem.paymentmethod', + 'finditemsbykeywordsresponse.searchitem.paymentmethod', + 'finditemsbyproductresponse.searchitem.paymentmethod', + 'finditemsinebaystoresresponse.searchitem.paymentmethod', + 'findcompleteditemsresponse.searchitem.gallerypluspictureurl', + 'finditemsadvancedresponse.searchitem.gallerypluspictureurl', + 'finditemsbycategoryresponse.searchitem.gallerypluspictureurl', + 'finditemsbyimageresponse.searchitem.gallerypluspictureurl', + 'finditemsbykeywordsresponse.searchitem.gallerypluspictureurl', + 'finditemsbyproductresponse.searchitem.gallerypluspictureurl', + 'finditemsinebaystoresresponse.searchitem.gallerypluspictureurl', + 'finditemsbycategoryresponse.searchitem.attribute', + 'finditemsadvancedresponse.searchitem.attribute', + 'finditemsbykeywordsresponse.searchitem.attribute', + 'finditemsinebaystoresresponse.searchitem.attribute', + 'finditemsbyproductresponse.searchitem.attribute', + 'findcompleteditemsresponse.searchitem.attribute', + 'findcompleteditemsresponse.shippinginfo.shiptolocations', + 'finditemsadvancedresponse.shippinginfo.shiptolocations', + 'finditemsbycategoryresponse.shippinginfo.shiptolocations', + 'finditemsbyimageresponse.shippinginfo.shiptolocations', + 'finditemsbykeywordsresponse.shippinginfo.shiptolocations', + 'finditemsbyproductresponse.shippinginfo.shiptolocations', + 'finditemsinebaystoresresponse.shippinginfo.shiptolocations', + ] + def build_request_headers(self, verb): return { "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), diff --git a/ebaysdk/response.py b/ebaysdk/response.py index 3d60760..ae02be4 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -7,6 +7,7 @@ ''' import sys import lxml +import copy from collections import defaultdict import json @@ -112,11 +113,9 @@ class Response(object): ''' def __init__(self, obj, verb=None, listnodes=[]): - self._listnodes=listnodes - self._add_prefix(self._listnodes, verb) - + self._listnodes=copy.copy(listnodes) self._obj = obj - + try: self._dom = self._parse_xml(obj.content) self._dict = self._etree_to_dict(self._dom) @@ -128,12 +127,6 @@ def __init__(self, obj, verb=None, listnodes=[]): except lxml.etree.XMLSyntaxError: self.reply = ResponseDataObject({}) - def _add_prefix(self, nodes, verb): - if verb: - for i, v in enumerate(nodes): - if not nodes[i].startswith(verb): - nodes[i] = "%sResponse.%s" % (verb, nodes[i]) - def _get_node_path(self, t): i = t path = [] @@ -148,10 +141,10 @@ def _get_node_path(self, t): return '.'.join(path) def _etree_to_dict(self, t): - # remove xmlns from nodes, I find them meaningless if type(t) == lxml.etree._Comment: return {} + # remove xmlns from nodes, I find them meaningless t.tag = self._get_node_tag(t) d = {t.tag: {} if t.attrib else None} @@ -168,7 +161,7 @@ def _etree_to_dict(self, t): parent_path = self._get_node_path(t) for k in d[t.tag].keys(): path = "%s.%s" % (parent_path, k) - if path in self._listnodes: + if path.lower() in self._listnodes: if not isinstance(d[t.tag][k], list): d[t.tag][k] = [ d[t.tag][k] ] diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 17f1b68..cf87cd1 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -85,6 +85,40 @@ def __init__(self, **kwargs): if self.config.get('https') and self.debug: print("HTTPS is not supported on the Shopping API.") + self.base_listnodes=[ + 'findhalfproductsresponse.halfcatalogproducttype.productid', + 'findhalfproductsresponse.halfproductstype.product', + 'getshippingcostsresponse.internationalshippingserviceoptiontype.shipsto', + 'getsingleitemresponse.itemcompatibility.compatibility', + 'getsingleitemresponse.itemcompatibility.namevaluelist', + 'getsingleitemresponse.variationspecifics.namevaluelist', + 'getsingleitemresponse.namevaluelist.value', + 'getsingleitemresponse.pictures.variationspecificpictureset', + 'getmultipleitemsresponse.pictures.variationspecificpictureset', + 'findreviewsandguidesresponse.reviewdetailstype.review', + 'getshippingcostsresponse.shippingdetails.internationalshippingserviceoption', + 'getshippingcostsresponse.shippingdetails.shippingserviceoption', + 'getshippingcostsresponse.shippingdetails.excludeshiptolocation', + 'getshippingcostsresponse.shippingserviceoption.shipsto', + 'findpopularitemsresponse.itemarray.item', + 'getsingleitemresponse.item.paymentmethods', + 'getmultipleitemsresponse.item.pictureurl', + 'getsingleitemresponse.item.pictureurl', + 'findproductsresponse.item.shiptolocations', + 'getmultipleitemsresponse.item.shiptolocations', + 'getsingleitemresponse.item.shiptolocations', + 'getmultipleitemsresponse.item.paymentallowedsite', + 'getsingleitemresponse.item.paymentallowedsite', + 'getsingleitemresponse.item.excludeshiptolocation', + 'getshippingcostsresponse.taxtable.taxjurisdiction', + 'getsingleitemresponse.variationspecificpictureset.pictureurl', + 'getmultipleitemsresponse.variationspecificpictureset.pictureurl', + 'getsingleitemresponse.variations.variation', + 'getmultipleitemsresponse.variations.variation', + 'getsingleitemresponse.variations.pictures', + 'getmultipleitemsresponse.variations.pictures', + ] + def build_request_headers(self, verb): headers = { "X-EBAY-API-VERSION": self.config.get('version', ''), diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 3faac2b..ad1d3e0 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -90,6 +90,483 @@ def __init__(self, **kwargs): self.config.set('version', '837') self.config.set('compatibility', '837') + self.base_listnodes = [ + 'getmymessagesresponse.abstractrequesttype.detaillevel', + 'getaccountresponse.abstractrequesttype.outputselector', + 'getadformatleadsresponse.abstractrequesttype.outputselector', + 'getallbiddersresponse.abstractrequesttype.outputselector', + 'getbestoffersresponse.abstractrequesttype.outputselector', + 'getbidderlistresponse.abstractrequesttype.outputselector', + 'getcategoriesresponse.abstractrequesttype.outputselector', + 'getcategoryfeaturesresponse.abstractrequesttype.outputselector', + 'getcategorylistingsresponse.abstractrequesttype.outputselector', + 'getcrosspromotionsresponse.abstractrequesttype.outputselector', + 'getfeedbackresponse.abstractrequesttype.outputselector', + 'gethighbiddersresponse.abstractrequesttype.outputselector', + 'getitemresponse.abstractrequesttype.outputselector', + 'getitemsawaitingfeedbackresponse.abstractrequesttype.outputselector', + 'getitemshippingresponse.abstractrequesttype.outputselector', + 'getitemtransactionsresponse.abstractrequesttype.outputselector', + 'getmembermessagesresponse.abstractrequesttype.outputselector', + 'getmyebaybuyingresponse.abstractrequesttype.outputselector', + 'getmyebaysellingresponse.abstractrequesttype.outputselector', + 'getmymessagesresponse.abstractrequesttype.outputselector', + 'getnotificationpreferencesresponse.abstractrequesttype.outputselector', + 'getordersresponse.abstractrequesttype.outputselector', + 'getordertransactionsresponse.abstractrequesttype.outputselector', + 'getproductsresponse.abstractrequesttype.outputselector', + 'getsearchresultsresponse.abstractrequesttype.outputselector', + 'getsellereventsresponse.abstractrequesttype.outputselector', + 'getsellerlistresponse.abstractrequesttype.outputselector', + 'getsellerpaymentsresponse.abstractrequesttype.outputselector', + 'getsellertransactionsresponse.abstractrequesttype.outputselector', + 'getmessagepreferencesresponse.asqpreferencestype.subject', + 'getaccountresponse.accountentriestype.accountentry', + 'getaccountresponse.accountsummarytype.additionalaccount', + 'additemresponse.additemresponsecontainertype.discountreason', + 'additemsresponse.additemresponsecontainertype.discountreason', + 'setnotificationpreferencesresponse.applicationdeliverypreferencestype.deliveryurldetails', + 'additemresponse.attributearraytype.attribute', + 'additemsresponse.attributearraytype.attribute', + 'verifyadditemresponse.attributearraytype.attribute', + 'additemresponse.attributetype.value', + 'additemsresponse.attributetype.value', + 'addsellingmanagertemplateresponse.attributetype.value', + 'addliveauctionitemresponse.attributetype.value', + 'getitemrecommendationsresponse.attributetype.value', + 'verifyadditemresponse.attributetype.value', + 'addfixedpriceitemresponse.attributetype.value', + 'relistfixedpriceitemresponse.attributetype.value', + 'revisefixedpriceitemresponse.attributetype.value', + 'getfeedbackresponse.averageratingdetailarraytype.averageratingdetails', + 'getfeedbackresponse.averageratingsummarytype.averageratingdetails', + 'respondtobestofferresponse.bestofferarraytype.bestoffer', + 'getliveauctionbiddersresponse.bidderdetailarraytype.bidderdetail', + 'getallbiddersresponse.biddingsummarytype.itembiddetails', + 'getsellerdashboardresponse.buyersatisfactiondashboardtype.alert', + 'getshippingdiscountprofilesresponse.calculatedshippingdiscounttype.discountprofile', + 'getcategoriesresponse.categoryarraytype.category', + 'getcategoryfeaturesresponse.categoryfeaturetype.listingduration', + 'getcategoryfeaturesresponse.categoryfeaturetype.paymentmethod', + 'getcategoriesresponse.categorytype.categoryparentid', + 'getsuggestedcategoriesresponse.categorytype.categoryparentname', + 'getcategory2csresponse.categorytype.productfinderids', + 'getcategory2csresponse.categorytype.characteristicssets', + 'getproductfamilymembersresponse.characteristicssettype.characteristics', + 'getproductsearchpageresponse.characteristicssettype.characteristics', + 'getproductsearchresultsresponse.characteristicssettype.characteristics', + 'getuserresponse.charityaffiliationdetailstype.charityaffiliationdetail', + 'getbidderlistresponse.charityaffiliationstype.charityid', + 'setcharitiesresponse.charityinfotype.nonprofitaddress', + 'setcharitiesresponse.charityinfotype.nonprofitsocialaddress', + 'getcategoryfeaturesresponse.conditionvaluestype.condition', + 'getbidderlistresponse.crosspromotionstype.promoteditem', + 'getuserdisputesresponse.disputearraytype.dispute', + 'getuserdisputesresponse.disputetype.disputeresolution', + 'getdisputeresponse.disputetype.disputemessage', + 'setsellingmanagerfeedbackoptionsresponse.feedbackcommentarraytype.storedcommenttext', + 'getfeedbackresponse.feedbackdetailarraytype.feedbackdetail', + 'getfeedbackresponse.feedbackperiodarraytype.feedbackperiod', + 'addfixedpriceitemresponse.feestype.fee', + 'additemresponse.feestype.fee', + 'additemsresponse.feestype.fee', + 'addliveauctionitemresponse.feestype.fee', + 'relistfixedpriceitemresponse.feestype.fee', + 'relistitemresponse.feestype.fee', + 'revisefixedpriceitemresponse.feestype.fee', + 'reviseitemresponse.feestype.fee', + 'reviseliveauctionitemresponse.feestype.fee', + 'verifyaddfixedpriceitemresponse.feestype.fee', + 'verifyadditemresponse.feestype.fee', + 'reviseinventorystatusresponse.feestype.fee', + 'verifyrelistitemresponse.feestype.fee', + 'getshippingdiscountprofilesresponse.flatshippingdiscounttype.discountprofile', + 'getitemrecommendationsresponse.getrecommendationsrequestcontainertype.recommendationengine', + 'getitemrecommendationsresponse.getrecommendationsrequestcontainertype.deletedfield', + 'getuserresponse.integratedmerchantcreditcardinfotype.supportedsite', + 'sendinvoiceresponse.internationalshippingserviceoptionstype.shiptolocation', + 'reviseinventorystatusresponse.inventoryfeestype.fee', + 'getbidderlistresponse.itemarraytype.item', + 'getbestoffersresponse.itembestoffersarraytype.itembestoffers', + 'addfixedpriceitemresponse.itemcompatibilitylisttype.compatibility', + 'additemresponse.itemcompatibilitylisttype.compatibility', + 'additemfromsellingmanagertemplateresponse.itemcompatibilitylisttype.compatibility', + 'additemsresponse.itemcompatibilitylisttype.compatibility', + 'addsellingmanagertemplateresponse.itemcompatibilitylisttype.compatibility', + 'relistfixedpriceitemresponse.itemcompatibilitylisttype.compatibility', + 'relistitemresponse.itemcompatibilitylisttype.compatibility', + 'revisefixedpriceitemresponse.itemcompatibilitylisttype.compatibility', + 'reviseitemresponse.itemcompatibilitylisttype.compatibility', + 'revisesellingmanagertemplateresponse.itemcompatibilitylisttype.compatibility', + 'verifyaddfixedpriceitemresponse.itemcompatibilitylisttype.compatibility', + 'verifyadditemresponse.itemcompatibilitylisttype.compatibility', + 'verifyrelistitemresponse.itemcompatibilitylisttype.compatibility', + 'addfixedpriceitemresponse.itemcompatibilitytype.namevaluelist', + 'additemresponse.itemcompatibilitytype.namevaluelist', + 'additemfromsellingmanagertemplateresponse.itemcompatibilitytype.namevaluelist', + 'additemsresponse.itemcompatibilitytype.namevaluelist', + 'addsellingmanagertemplateresponse.itemcompatibilitytype.namevaluelist', + 'relistfixedpriceitemresponse.itemcompatibilitytype.namevaluelist', + 'relistitemresponse.itemcompatibilitytype.namevaluelist', + 'revisefixedpriceitemresponse.itemcompatibilitytype.namevaluelist', + 'reviseitemresponse.itemcompatibilitytype.namevaluelist', + 'revisesellingmanagertemplateresponse.itemcompatibilitytype.namevaluelist', + 'verifyadditemresponse.itemcompatibilitytype.namevaluelist', + 'verifyrelistitemresponse.itemcompatibilitytype.namevaluelist', + 'getpromotionalsaledetailsresponse.itemidarraytype.itemid', + 'leavefeedbackresponse.itemratingdetailarraytype.itemratingdetails', + 'getordertransactionsresponse.itemtransactionidarraytype.itemtransactionid', + 'addfixedpriceitemresponse.itemtype.giftservices', + 'additemresponse.itemtype.giftservices', + 'additemsresponse.itemtype.giftservices', + 'addsellingmanagertemplateresponse.itemtype.giftservices', + 'getitemrecommendationsresponse.itemtype.giftservices', + 'relistfixedpriceitemresponse.itemtype.giftservices', + 'relistitemresponse.itemtype.giftservices', + 'revisefixedpriceitemresponse.itemtype.giftservices', + 'reviseitemresponse.itemtype.giftservices', + 'revisesellingmanagertemplateresponse.itemtype.giftservices', + 'verifyadditemresponse.itemtype.giftservices', + 'verifyrelistitemresponse.itemtype.giftservices', + 'addfixedpriceitemresponse.itemtype.listingenhancement', + 'additemresponse.itemtype.listingenhancement', + 'additemsresponse.itemtype.listingenhancement', + 'addsellingmanagertemplateresponse.itemtype.listingenhancement', + 'getitemrecommendationsresponse.itemtype.listingenhancement', + 'relistfixedpriceitemresponse.itemtype.listingenhancement', + 'relistitemresponse.itemtype.listingenhancement', + 'revisefixedpriceitemresponse.itemtype.listingenhancement', + 'reviseitemresponse.itemtype.listingenhancement', + 'revisesellingmanagertemplateresponse.itemtype.listingenhancement', + 'verifyadditemresponse.itemtype.listingenhancement', + 'verifyrelistitemresponse.itemtype.listingenhancement', + 'addfixedpriceitemresponse.itemtype.paymentmethods', + 'additemresponse.itemtype.paymentmethods', + 'additemfromsellingmanagertemplateresponse.itemtype.paymentmethods', + 'additemsresponse.itemtype.paymentmethods', + 'addsellingmanagertemplateresponse.itemtype.paymentmethods', + 'relistfixedpriceitemresponse.itemtype.paymentmethods', + 'relistitemresponse.itemtype.paymentmethods', + 'revisefixedpriceitemresponse.itemtype.paymentmethods', + 'reviseitemresponse.itemtype.paymentmethods', + 'verifyadditemresponse.itemtype.paymentmethods', + 'verifyrelistitemresponse.itemtype.paymentmethods', + 'addfixedpriceitemresponse.itemtype.shiptolocations', + 'additemresponse.itemtype.shiptolocations', + 'additemsresponse.itemtype.shiptolocations', + 'addsellingmanagertemplateresponse.itemtype.shiptolocations', + 'getitemrecommendationsresponse.itemtype.shiptolocations', + 'relistfixedpriceitemresponse.itemtype.shiptolocations', + 'relistitemresponse.itemtype.shiptolocations', + 'revisefixedpriceitemresponse.itemtype.shiptolocations', + 'reviseitemresponse.itemtype.shiptolocations', + 'revisesellingmanagertemplateresponse.itemtype.shiptolocations', + 'verifyadditemresponse.itemtype.shiptolocations', + 'verifyrelistitemresponse.itemtype.shiptolocations', + 'addfixedpriceitemresponse.itemtype.skypecontactoption', + 'additemresponse.itemtype.skypecontactoption', + 'additemsresponse.itemtype.skypecontactoption', + 'addsellingmanagertemplateresponse.itemtype.skypecontactoption', + 'relistfixedpriceitemresponse.itemtype.skypecontactoption', + 'relistitemresponse.itemtype.skypecontactoption', + 'revisefixedpriceitemresponse.itemtype.skypecontactoption', + 'reviseitemresponse.itemtype.skypecontactoption', + 'revisesellingmanagertemplateresponse.itemtype.skypecontactoption', + 'verifyadditemresponse.itemtype.skypecontactoption', + 'verifyrelistitemresponse.itemtype.skypecontactoption', + 'addfixedpriceitemresponse.itemtype.crossbordertrade', + 'additemresponse.itemtype.crossbordertrade', + 'additemsresponse.itemtype.crossbordertrade', + 'addsellingmanagertemplateresponse.itemtype.crossbordertrade', + 'relistfixedpriceitemresponse.itemtype.crossbordertrade', + 'relistitemresponse.itemtype.crossbordertrade', + 'revisefixedpriceitemresponse.itemtype.crossbordertrade', + 'reviseitemresponse.itemtype.crossbordertrade', + 'revisesellingmanagertemplateresponse.itemtype.crossbordertrade', + 'verifyadditemresponse.itemtype.crossbordertrade', + 'verifyrelistitemresponse.itemtype.crossbordertrade', + 'getitemresponse.itemtype.paymentallowedsite', + 'getsellingmanagertemplatesresponse.itemtype.paymentallowedsite', + 'getcategoryfeaturesresponse.listingdurationdefinitiontype.duration', + 'getcategoryfeaturesresponse.listingdurationdefinitionstype.listingduration', + 'getcategoryfeaturesresponse.listingenhancementdurationreferencetype.duration', + 'addfixedpriceitemresponse.listingrecommendationtype.value', + 'additemresponse.listingrecommendationtype.value', + 'additemsresponse.listingrecommendationtype.value', + 'relistfixedpriceitemresponse.listingrecommendationtype.value', + 'relistitemresponse.listingrecommendationtype.value', + 'revisefixedpriceitemresponse.listingrecommendationtype.value', + 'reviseitemresponse.listingrecommendationtype.value', + 'verifyadditemresponse.listingrecommendationtype.value', + 'verifyaddfixedpriceitemresponse.listingrecommendationtype.value', + 'verifyrelistitemresponse.listingrecommendationtype.value', + 'addfixedpriceitemresponse.listingrecommendationstype.recommendation', + 'additemresponse.listingrecommendationstype.recommendation', + 'additemsresponse.listingrecommendationstype.recommendation', + 'relistfixedpriceitemresponse.listingrecommendationstype.recommendation', + 'relistitemresponse.listingrecommendationstype.recommendation', + 'revisefixedpriceitemresponse.listingrecommendationstype.recommendation', + 'reviseitemresponse.listingrecommendationstype.recommendation', + 'verifyadditemresponse.listingrecommendationstype.recommendation', + 'verifyaddfixedpriceitemresponse.listingrecommendationstype.recommendation', + 'verifyrelistitemresponse.listingrecommendationstype.recommendation', + 'getitemrecommendationsresponse.listingtiparraytype.listingtip', + 'getnotificationsusageresponse.markupmarkdownhistorytype.markupmarkdownevent', + 'getebaydetailsresponse.maximumbuyerpolicyviolationsdetailstype.policyviolationduration', + 'getebaydetailsresponse.maximumitemrequirementsdetailstype.maximumitemcount', + 'getebaydetailsresponse.maximumitemrequirementsdetailstype.minimumfeedbackscore', + 'getebaydetailsresponse.maximumunpaiditemstrikescountdetailstype.count', + 'getebaydetailsresponse.maximumunpaiditemstrikesinfodetailstype.maximumunpaiditemstrikesduration', + 'getadformatleadsresponse.membermessageexchangearraytype.membermessageexchange', + 'getadformatleadsresponse.membermessageexchangetype.response', + 'getmembermessagesresponse.membermessageexchangetype.messagemedia', + 'addmembermessageaaqtopartnerresponse.membermessagetype.recipientid', + 'addmembermessagertqresponse.membermessagetype.recipientid', + 'addmembermessagesaaqtobidderresponse.membermessagetype.recipientid', + 'addmembermessageaaqtopartnerresponse.membermessagetype.messagemedia', + 'addmembermessagertqresponse.membermessagetype.messagemedia', + 'addmembermessagecemresponse.membermessagetype.messagemedia', + 'addmembermessageaaqtosellerresponse.membermessagetype.messagemedia', + 'getebaydetailsresponse.minimumfeedbackscoredetailstype.feedbackscore', + 'relistfixedpriceitemresponse.modifynamearraytype.modifyname', + 'revisefixedpriceitemresponse.modifynamearraytype.modifyname', + 'getmymessagesresponse.mymessagesexternalmessageidarraytype.externalmessageid', + 'getmymessagesresponse.mymessagesmessagearraytype.message', + 'deletemymessagesresponse.mymessagesmessageidarraytype.messageid', + 'getmymessagesresponse.mymessagesmessagetype.messagemedia', + 'getmymessagesresponse.mymessagessummarytype.foldersummary', + 'getmyebaybuyingresponse.myebayfavoritesearchlisttype.favoritesearch', + 'getmyebaybuyingresponse.myebayfavoritesearchtype.searchflag', + 'getmyebaybuyingresponse.myebayfavoritesearchtype.sellerid', + 'getmyebaybuyingresponse.myebayfavoritesearchtype.selleridexclude', + 'getmyebaybuyingresponse.myebayfavoritesellerlisttype.favoriteseller', + 'getcategoryspecificsresponse.namerecommendationtype.valuerecommendation', + 'getitemrecommendationsresponse.namerecommendationtype.valuerecommendation', + 'addfixedpriceitemresponse.namevaluelistarraytype.namevaluelist', + 'additemresponse.namevaluelistarraytype.namevaluelist', + 'additemsresponse.namevaluelistarraytype.namevaluelist', + 'addsellingmanagertemplateresponse.namevaluelistarraytype.namevaluelist', + 'addliveauctionitemresponse.namevaluelistarraytype.namevaluelist', + 'relistfixedpriceitemresponse.namevaluelistarraytype.namevaluelist', + 'relistitemresponse.namevaluelistarraytype.namevaluelist', + 'revisefixedpriceitemresponse.namevaluelistarraytype.namevaluelist', + 'reviseitemresponse.namevaluelistarraytype.namevaluelist', + 'revisesellingmanagertemplateresponse.namevaluelistarraytype.namevaluelist', + 'reviseliveauctionitemresponse.namevaluelistarraytype.namevaluelist', + 'verifyaddfixedpriceitemresponse.namevaluelistarraytype.namevaluelist', + 'verifyadditemresponse.namevaluelistarraytype.namevaluelist', + 'verifyrelistitemresponse.namevaluelistarraytype.namevaluelist', + 'additemresponse.namevaluelisttype.value', + 'additemfromsellingmanagertemplateresponse.namevaluelisttype.value', + 'additemsresponse.namevaluelisttype.value', + 'addsellingmanagertemplateresponse.namevaluelisttype.value', + 'addliveauctionitemresponse.namevaluelisttype.value', + 'relistitemresponse.namevaluelisttype.value', + 'reviseitemresponse.namevaluelisttype.value', + 'revisesellingmanagertemplateresponse.namevaluelisttype.value', + 'reviseliveauctionitemresponse.namevaluelisttype.value', + 'verifyadditemresponse.namevaluelisttype.value', + 'verifyrelistitemresponse.namevaluelisttype.value', + 'getnotificationsusageresponse.notificationdetailsarraytype.notificationdetails', + 'setnotificationpreferencesresponse.notificationenablearraytype.notificationenable', + 'setnotificationpreferencesresponse.notificationuserdatatype.summaryschedule', + 'getebaydetailsresponse.numberofpolicyviolationsdetailstype.count', + 'getallbiddersresponse.offerarraytype.offer', + 'gethighbiddersresponse.offerarraytype.offer', + 'getordersresponse.orderarraytype.order', + 'getordersresponse.orderidarraytype.orderid', + 'getmyebaybuyingresponse.ordertransactionarraytype.ordertransaction', + 'addorderresponse.ordertype.paymentmethods', + 'getordertransactionsresponse.ordertype.externaltransaction', + 'getordersresponse.ordertype.externaltransaction', + 'getordersresponse.paymentinformationcodetype.payment', + 'getordersresponse.paymentinformationtype.payment', + 'getordersresponse.paymenttransactioncodetype.paymentreferenceid', + 'getordersresponse.paymenttransactiontype.paymentreferenceid', + 'getsellerdashboardresponse.performancedashboardtype.site', + 'getordersresponse.pickupdetailstype.pickupoptions', + 'additemresponse.picturedetailstype.pictureurl', + 'additemsresponse.picturedetailstype.pictureurl', + 'addsellingmanagertemplateresponse.picturedetailstype.pictureurl', + 'getitemrecommendationsresponse.picturedetailstype.pictureurl', + 'relistitemresponse.picturedetailstype.pictureurl', + 'reviseitemresponse.picturedetailstype.pictureurl', + 'revisesellingmanagertemplateresponse.picturedetailstype.pictureurl', + 'verifyadditemresponse.picturedetailstype.pictureurl', + 'verifyrelistitemresponse.picturedetailstype.pictureurl', + 'getitemresponse.picturedetailstype.externalpictureurl', + 'addfixedpriceitemresponse.picturestype.variationspecificpictureset', + 'verifyaddfixedpriceitemresponse.picturestype.variationspecificpictureset', + 'relistfixedpriceitemresponse.picturestype.variationspecificpictureset', + 'revisefixedpriceitemresponse.picturestype.variationspecificpictureset', + 'getsellerdashboardresponse.powersellerdashboardtype.alert', + 'getbidderlistresponse.productlistingdetailstype.copyright', + 'getitemrecommendationsresponse.productrecommendationstype.product', + 'addfixedpriceitemresponse.productsuggestionstype.productsuggestion', + 'additemresponse.productsuggestionstype.productsuggestion', + 'relistfixedpriceitemresponse.productsuggestionstype.productsuggestion', + 'relistitemresponse.productsuggestionstype.productsuggestion', + 'revisefixedpriceitemresponse.productsuggestionstype.productsuggestion', + 'reviseitemresponse.productsuggestionstype.productsuggestion', + 'verifyadditemresponse.productsuggestionstype.productsuggestion', + 'verifyrelistitemresponse.productsuggestionstype.productsuggestion', + 'getpromotionalsaledetailsresponse.promotionalsalearraytype.promotionalsale', + 'addfixedpriceitemresponse.recommendationtype.recommendedvalue', + 'additemresponse.recommendationtype.recommendedvalue', + 'additemsresponse.recommendationtype.recommendedvalue', + 'relistfixedpriceitemresponse.recommendationtype.recommendedvalue', + 'relistitemresponse.recommendationtype.recommendedvalue', + 'revisefixedpriceitemresponse.recommendationtype.recommendedvalue', + 'reviseitemresponse.recommendationtype.recommendedvalue', + 'verifyadditemresponse.recommendationtype.recommendedvalue', + 'verifyaddfixedpriceitemresponse.recommendationtype.recommendedvalue', + 'verifyrelistitemresponse.recommendationtype.recommendedvalue', + 'getcategoryspecificsresponse.recommendationvalidationrulestype.relationship', + 'getitemrecommendationsresponse.recommendationvalidationrulestype.relationship', + 'getcategoryspecificsresponse.recommendationstype.namerecommendation', + 'getitemrecommendationsresponse.recommendationstype.namerecommendation', + 'getuserresponse.recoupmentpolicyconsenttype.site', + 'getordersresponse.refundarraytype.refund', + 'getordersresponse.refundfundingsourcearraytype.refundfundingsource', + 'getitemtransactionsresponse.refundfundingsourcearraytype.refundfundingsource', + 'getordertransactionsresponse.refundfundingsourcearraytype.refundfundingsource', + 'getsellertransactionsresponse.refundfundingsourcearraytype.refundfundingsource', + 'getordersresponse.refundinformationtype.refund', + 'getordersresponse.refundlinearraytype.refundline', + 'getitemtransactionsresponse.refundlinearraytype.refundline', + 'getordertransactionsresponse.refundlinearraytype.refundline', + 'getsellertransactionsresponse.refundlinearraytype.refundline', + 'getordersresponse.refundtransactionarraytype.refundtransaction', + 'getitemtransactionsresponse.refundtransactionarraytype.refundtransaction', + 'getordertransactionsresponse.refundtransactionarraytype.refundtransaction', + 'getsellertransactionsresponse.refundtransactionarraytype.refundtransaction', + 'getordersresponse.requiredselleractionarraytype.requiredselleraction', + 'getebaydetailsresponse.returnpolicydetailstype.refund', + 'getebaydetailsresponse.returnpolicydetailstype.returnswithin', + 'getebaydetailsresponse.returnpolicydetailstype.returnsaccepted', + 'getebaydetailsresponse.returnpolicydetailstype.warrantyoffered', + 'getebaydetailsresponse.returnpolicydetailstype.warrantytype', + 'getebaydetailsresponse.returnpolicydetailstype.warrantyduration', + 'getebaydetailsresponse.returnpolicydetailstype.shippingcostpaidby', + 'getebaydetailsresponse.returnpolicydetailstype.restockingfeevalue', + 'getsellertransactionsresponse.skuarraytype.sku', + 'getsellerlistresponse.skuarraytype.sku', + 'getsellerdashboardresponse.selleraccountdashboardtype.alert', + 'getitemtransactionsresponse.sellerdiscountstype.sellerdiscount', + 'getordersresponse.sellerdiscountstype.sellerdiscount', + 'getordertransactionsresponse.sellerdiscountstype.sellerdiscount', + 'getsellertransactionsresponse.sellerdiscountstype.sellerdiscount', + 'getuserpreferencesresponse.sellerexcludeshiptolocationpreferencestype.excludeshiptolocation', + 'getuserpreferencesresponse.sellerfavoriteitempreferencestype.favoriteitemid', + 'getfeedbackresponse.sellerratingsummaryarraytype.averageratingsummary', + 'getbidderlistresponse.sellerebaypaymentprocessconsentcodetype.useragreementinfo', + 'getsellingmanagertemplateautomationruleresponse.sellingmanagerautolistaccordingtoscheduletype.dayofweek', + 'getsellingmanagerinventoryfolderresponse.sellingmanagerfolderdetailstype.childfolder', + 'revisesellingmanagerinventoryfolderresponse.sellingmanagerfolderdetailstype.childfolder', + 'getsellingmanagersalerecordresponse.sellingmanagersoldordertype.sellingmanagersoldtransaction', + 'getsellingmanagersoldlistingsresponse.sellingmanagersoldordertype.sellingmanagersoldtransaction', + 'getsellingmanagersalerecordresponse.sellingmanagersoldordertype.vatrate', + 'getsellingmanagersoldlistingsresponse.sellingmanagersoldtransactiontype.listedon', + 'getsellingmanagertemplatesresponse.sellingmanagertemplatedetailsarraytype.sellingmanagertemplatedetails', + 'completesaleresponse.shipmentlineitemtype.lineitem', + 'addshipmentresponse.shipmentlineitemtype.lineitem', + 'reviseshipmentresponse.shipmentlineitemtype.lineitem', + 'revisesellingmanagersalerecordresponse.shipmentlineitemtype.lineitem', + 'setshipmenttrackinginforesponse.shipmentlineitemtype.lineitem', + 'completesaleresponse.shipmenttype.shipmenttrackingdetails', + 'getitemresponse.shippingdetailstype.shippingserviceoptions', + 'getsellingmanagertemplatesresponse.shippingdetailstype.shippingserviceoptions', + 'addfixedpriceitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'additemresponse.shippingdetailstype.internationalshippingserviceoption', + 'additemsresponse.shippingdetailstype.internationalshippingserviceoption', + 'addsellingmanagertemplateresponse.shippingdetailstype.internationalshippingserviceoption', + 'addorderresponse.shippingdetailstype.internationalshippingserviceoption', + 'getitemrecommendationsresponse.shippingdetailstype.internationalshippingserviceoption', + 'relistfixedpriceitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'relistitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'revisefixedpriceitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'reviseitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'revisesellingmanagertemplateresponse.shippingdetailstype.internationalshippingserviceoption', + 'verifyadditemresponse.shippingdetailstype.internationalshippingserviceoption', + 'verifyrelistitemresponse.shippingdetailstype.internationalshippingserviceoption', + 'getsellerlistresponse.shippingdetailstype.excludeshiptolocation', + 'getitemtransactionsresponse.shippingdetailstype.shipmenttrackingdetails', + 'getsellertransactionsresponse.shippingdetailstype.shipmenttrackingdetails', + 'getshippingdiscountprofilesresponse.shippinginsurancetype.flatrateinsurancerangecost', + 'addfixedpriceitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'additemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'additemsresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'verifyadditemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'verifyaddfixedpriceitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'verifyrelistitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'relistfixedpriceitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'relistitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'revisefixedpriceitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'reviseitemresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'addsellingmanagertemplateresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'revisesellingmanagertemplateresponse.shippingservicecostoverridelisttype.shippingservicecostoverride', + 'getebaydetailsresponse.shippingservicedetailstype.servicetype', + 'getebaydetailsresponse.shippingservicedetailstype.shippingpackage', + 'getebaydetailsresponse.shippingservicedetailstype.shippingcarrier', + 'getebaydetailsresponse.shippingservicedetailstype.deprecationdetails', + 'getebaydetailsresponse.shippingservicedetailstype.shippingservicepackagedetails', + 'getordersresponse.shippingserviceoptionstype.shippingpackageinfo', + 'getcategoryfeaturesresponse.sitedefaultstype.listingduration', + 'getcategoryfeaturesresponse.sitedefaultstype.paymentmethod', + 'uploadsitehostedpicturesresponse.sitehostedpicturedetailstype.picturesetmember', + 'getcategory2csresponse.sitewidecharacteristicstype.excludecategoryid', + 'getstoreoptionsresponse.storecolorschemearraytype.colorscheme', + 'getstoreresponse.storecustomcategoryarraytype.customcategory', + 'getstoreresponse.storecustomcategorytype.childcategory', + 'setstorecategoriesresponse.storecustomcategorytype.childcategory', + 'setstoreresponse.storecustomlistingheadertype.linktoinclude', + 'getstorecustompageresponse.storecustompagearraytype.custompage', + 'getstoreoptionsresponse.storelogoarraytype.logo', + 'getcategoryfeaturesresponse.storeownerextendedlistingdurationstype.duration', + 'getstoreoptionsresponse.storesubscriptionarraytype.subscription', + 'getstoreoptionsresponse.storethemearraytype.theme', + 'getsuggestedcategoriesresponse.suggestedcategoryarraytype.suggestedcategory', + 'getuserpreferencesresponse.supportedsellerprofilestype.supportedsellerprofile', + 'settaxtableresponse.taxtabletype.taxjurisdiction', + 'getitemtransactionsresponse.taxestype.taxdetails', + 'getordersresponse.taxestype.taxdetails', + 'getordertransactionsresponse.taxestype.taxdetails', + 'getsellertransactionsresponse.taxestype.taxdetails', + 'getdescriptiontemplatesresponse.themegrouptype.themeid', + 'getuserresponse.topratedsellerdetailstype.topratedprogram', + 'getordersresponse.transactionarraytype.transaction', + 'getitemtransactionsresponse.transactiontype.externaltransaction', + 'getsellertransactionsresponse.transactiontype.externaltransaction', + 'getebaydetailsresponse.unitofmeasurementdetailstype.unitofmeasurement', + 'getebaydetailsresponse.unitofmeasurementtype.alternatetext', + 'getuserpreferencesresponse.unpaiditemassistancepreferencestype.excludeduser', + 'getsellerlistresponse.useridarraytype.userid', + 'getuserresponse.usertype.usersubscription', + 'getuserresponse.usertype.skypeid', + 'addfixedpriceitemresponse.variationspecificpicturesettype.pictureurl', + 'revisefixedpriceitemresponse.variationspecificpicturesettype.pictureurl', + 'relistfixedpriceitemresponse.variationspecificpicturesettype.pictureurl', + 'verifyaddfixedpriceitemresponse.variationspecificpicturesettype.pictureurl', + 'getitemresponse.variationspecificpicturesettype.externalpictureurl', + 'getitemsresponse.variationspecificpicturesettype.externalpictureurl', + 'addfixedpriceitemresponse.variationstype.variation', + 'revisefixedpriceitemresponse.variationstype.variation', + 'relistfixedpriceitemresponse.variationstype.variation', + 'verifyaddfixedpriceitemresponse.variationstype.variation', + 'addfixedpriceitemresponse.variationstype.pictures', + 'revisefixedpriceitemresponse.variationstype.pictures', + 'relistfixedpriceitemresponse.variationstype.pictures', + 'verifyaddfixedpriceitemresponse.variationstype.pictures', + 'getveroreasoncodedetailsresponse.veroreasoncodedetailstype.verositedetail', + 'veroreportitemsresponse.veroreportitemtype.region', + 'veroreportitemsresponse.veroreportitemtype.country', + 'veroreportitemsresponse.veroreportitemstype.reportitem', + 'getveroreportstatusresponse.veroreporteditemdetailstype.reporteditem', + 'getveroreasoncodedetailsresponse.verositedetailtype.reasoncodedetail', + 'getebaydetailsresponse.verifieduserrequirementsdetailstype.feedbackscore', + 'getwantitnowsearchresultsresponse.wantitnowpostarraytype.wantitnowpost', + ] + def build_request_headers(self, verb): headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('version', ''), diff --git a/samples/finding.py b/samples/finding.py index c6c3e1c..014529a 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -54,7 +54,7 @@ def run(opts): }) dump(api) - + #from IPython import embed; embed() except ConnectionError as e: print(e) @@ -62,8 +62,7 @@ def run2(opts): try: api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) api.execute('findItemsByProduct', - '530390311', - listnodes=['searchResult.item']) + '530390311') dump(api) except ConnectionError as e: diff --git a/samples/shopping.py b/samples/shopping.py index f3971d8..4da3ef7 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -90,9 +90,9 @@ def popularSearches(opts): } try: - api.execute('FindPopularSearches', mySearch, listnodes=['ItemArray.Item']) + api.execute('FindPopularSearches', mySearch) #, listnodes=['ItemArray.Item']) - #dump(api, full=True) + dump(api, full=False) print("Related: %s" % api.response.reply.PopularSearchResult.RelatedSearches) @@ -107,7 +107,7 @@ def popularSearches(opts): except AttributeError: pass - # dump(api) + dump(api) print("\n") except ConnectionError as e: @@ -120,7 +120,7 @@ def categoryInfo(opts): warnings=True) api.execute('GetCategoryInfo', {"CategoryID": 3410}) - dump(api, full=False) + #dump(api, full=False) except ConnectionError as e: print(e) @@ -137,7 +137,7 @@ def with_affiliate_info(opts): } api.execute('FindPopularSearches', mySearch) - dump(api, full=False) + #dump(api, full=False) except ConnectionError as e: print(e) @@ -152,7 +152,7 @@ def using_attributes(opts): "ProductID": {'@attrs': {'type': 'ISBN'}, '#text': '0596154488'}}) - dump(api, full=False) + #dump(api, full=False) except ConnectionError as e: print(e) From ff04e95ea7e8e417680d7d3ffd9d94cf7778647e Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 20 Feb 2014 22:27:23 -0800 Subject: [PATCH 159/218] base nodelist changes --- ebaysdk/shopping/__init__.py | 1 + samples/shopping.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index cf87cd1..e13dd93 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -101,6 +101,7 @@ def __init__(self, **kwargs): 'getshippingcostsresponse.shippingdetails.excludeshiptolocation', 'getshippingcostsresponse.shippingserviceoption.shipsto', 'findpopularitemsresponse.itemarray.item', + 'findproductsresponse.itemarray.item', 'getsingleitemresponse.item.paymentmethods', 'getmultipleitemsresponse.item.pictureurl', 'getsingleitemresponse.item.pictureurl', diff --git a/samples/shopping.py b/samples/shopping.py index 4da3ef7..3fb76a5 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -120,7 +120,7 @@ def categoryInfo(opts): warnings=True) api.execute('GetCategoryInfo', {"CategoryID": 3410}) - #dump(api, full=False) + dump(api, full=False) except ConnectionError as e: print(e) @@ -137,7 +137,7 @@ def with_affiliate_info(opts): } api.execute('FindPopularSearches', mySearch) - #dump(api, full=False) + dump(api, full=False) except ConnectionError as e: print(e) @@ -152,7 +152,7 @@ def using_attributes(opts): "ProductID": {'@attrs': {'type': 'ISBN'}, '#text': '0596154488'}}) - #dump(api, full=False) + dump(api, full=False) except ConnectionError as e: print(e) From 3eb02244b628b8936f3438f51d91bf4d9a1d9472 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 21 Feb 2014 14:28:18 -0800 Subject: [PATCH 160/218] add datetime conversion and list nodes --- README.rst | 10 ++- ebaysdk/connection.py | 29 ++++---- ebaysdk/finding/__init__.py | 4 +- ebaysdk/merchandising/__init__.py | 11 ++++ ebaysdk/response.py | 46 ++++++++----- ebaysdk/shopping/__init__.py | 7 +- ebaysdk/soa/finditem.py | 5 +- ebaysdk/trading/__init__.py | 106 ++++++++++++++++++++++++++++-- samples/common.py | 15 ++--- samples/finding.py | 8 ++- samples/finditem.py | 5 +- samples/merchandising.py | 2 +- samples/shopping.py | 32 ++++----- 13 files changed, 208 insertions(+), 72 deletions(-) diff --git a/README.rst b/README.rst index 884a62a..7daad90 100644 --- a/README.rst +++ b/README.rst @@ -8,9 +8,15 @@ Quick Example:: from ebaysdk.finding import Connection try: api = Connection(appid='YOUR_APPID_HERE') - api.execute('findItemsAdvanced', {'keywords': 'shoes'}) + response = api.execute('findItemsAdvanced', {'keywords': 'shoes'}) + + print(response.reply.timestamp) + print(response.reply.version) + + print(response.dict()) + print(response.json()) + print(response.dom()) - print api.response_dict() except ConnectionError as e: raise e diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index b0233ff..d714a8a 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -12,7 +12,6 @@ import json import time import uuid -import copy from requests import Request, Session from requests.adapters import HTTPAdapter @@ -49,6 +48,8 @@ def __init__(self, debug=False, method='GET', self.timeout = timeout self.proxy_host = proxy_host self.proxy_port = proxy_port + self.datetime_nodes = [] + self._list_nodes = [] self.proxies = dict() if self.proxy_host: @@ -64,6 +65,9 @@ def __init__(self, debug=False, method='GET', self.parallel = parallel + self.base_list_nodes = [] + self.datetime_nodes = [] + self._reset() def debug_callback(self, debug_type, debug_message): @@ -79,7 +83,7 @@ def _reset(self): self.response = None self.request = None self.verb = None - self._listnodes = [] + self._list_nodes = [] self._request_id = None self._time = time.time() self._response_content = None @@ -95,20 +99,20 @@ def _reset(self): def _add_prefix(self, nodes, verb): if verb: for i, v in enumerate(nodes): - if not nodes[i].startswith(verb): - nodes[i] = "%sResponse.%s" % (verb, nodes[i]) + if not nodes[i].startswith(verb.lower()): + nodes[i] = "%sresponse.%s" % (verb.lower(), nodes[i].lower()) - def execute(self, verb, data=None, listnodes=[]): + def execute(self, verb, data=None, list_nodes=[]): "Executes the HTTP request." log.debug('execute: verb=%s data=%s' % (verb, data)) self._reset() - self._listnodes += listnodes - self._add_prefix(self._listnodes, verb) + self._list_nodes += list_nodes + self._add_prefix(self._list_nodes, verb) - if getattr(self, 'base_listnodes'): - self._listnodes += self.base_listnodes + if hasattr(self, 'base_list_nodes'): + self._list_nodes += self.base_list_nodes self.build_request(verb, data) self.execute_request() @@ -118,8 +122,8 @@ def execute(self, verb, data=None, listnodes=[]): self.error_check() log.debug('total time=%s' % (time.time() - self._time)) - - return self + + return self.response def build_request(self, verb, data): @@ -171,7 +175,8 @@ def execute_request(self): def process_response(self): """Post processing of the response""" - self.response = Response(self.response, verb=self.verb, listnodes=self._listnodes) + self.response = Response(self.response, verb=self.verb, + list_nodes=self._list_nodes, datetime_nodes=self.datetime_nodes) if self.response.status_code != 200: self._response_error = self.response.reason diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 7e53a7a..c9c7d3a 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -86,7 +86,9 @@ def __init__(self, **kwargs): self.config.set('compatibility', '1.0.0') self.config.set('service', 'FindingService') - self.base_listnodes=[ + self.datetime_nodes = ['starttimefrom', 'timestamp', 'starttime', + 'endtime'] + self.base_list_nodes = [ 'findcompleteditemsresponse.categoryhistogramcontainer.categoryhistogram', 'finditemsadvancedresponse.categoryhistogramcontainer.categoryhistogram', 'finditemsbycategoryresponse.categoryhistogramcontainer.categoryhistogram', diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index 4a58848..e2a62e8 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -58,6 +58,17 @@ def __init__(self, **kwargs): self.config.set('uri', '/MerchandisingService', force=True) self.config.set('service', 'MerchandisingService', force=True) + self.datetime_nodes = ['endtimeto', 'endtimefrom', 'timestamp'] + self.base_list_nodes = [ + 'getdealsresponse.itemrecommendations.item', + 'getmostwatcheditemsresponse.itemrecommendations.item', + 'getrelatedcategoryitemsresponse.itemrecommendations.item', + 'getsimilaritemsresponse.itemrecommendations.item', + 'gettopsellingproductsresponse.productrecommendations.product', + 'getrelatedcategoryitemsresponse.itemfilter.value', + 'getsimilaritemsresponse.itemfilter.value', + ] + def build_request_headers(self, verb): return { "X-EBAY-API-VERSION": self.config.get('version', ''), diff --git a/ebaysdk/response.py b/ebaysdk/response.py index ae02be4..fa66293 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -8,6 +8,8 @@ import sys import lxml import copy +import datetime + from collections import defaultdict import json @@ -16,8 +18,8 @@ @python_2_unicode_compatible class ResponseDataObject(object): - def __init__(self, mydict={}): - self._load_dict(mydict) + def __init__(self, mydict, datetime_nodes): + self._load_dict(mydict, datetime_nodes) def __repr__(self): return str(self) @@ -38,12 +40,23 @@ def get(self, name, default=None): except AttributeError: return default - def _load_dict(self, mydict): + def _setattr(self, name, value, datetime_nodes): + if name.lower() in datetime_nodes: + try: + ts = "%s %s" % (value.partition('T')[0], value.partition('T')[2].partition('.')[0]) + value = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S') + except ValueError: + print "Error: %s" % value + pass + + setattr(self, name, value) + + def _load_dict(self, mydict, datetime_nodes): for a in mydict.items(): if isinstance(a[1], dict): - o = ResponseDataObject(a[1]) + o = ResponseDataObject(a[1], datetime_nodes) setattr(self, a[0], o) elif isinstance(a[1], list): @@ -52,12 +65,12 @@ def _load_dict(self, mydict): if isinstance(i, str): objs.append(i) else: - objs.append(ResponseDataObject(i)) + objs.append(ResponseDataObject(i, datetime_nodes)) setattr(self, a[0], objs) else: - setattr(self, a[0], a[1]) - + self._setattr(a[0], a[1], datetime_nodes) + class Response(object): ''' @@ -80,8 +93,8 @@ class Response(object): Doctests: >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem Two1190179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' - >>> o = ResponseDataObject({'content': xml}) - >>> r = Response(o, verb='findItemsByProduct', listnodes=['searchResult.item', 'findItemsByProductResponse.paginationOutput.pageNumber']) + >>> o = ResponseDataObject({'content': xml}, []) + >>> r = Response(o, verb='findItemsByProduct', list_nodes=['finditemsbyproductresponse.searchresult.item', 'finditemsbyproductresponse.paginationoutput.pagenumber']) >>> len(r.dom().getchildren()) > 2 True >>> r.reply.searchResult._count == '1' @@ -91,8 +104,8 @@ class Response(object): >>> len(r.reply.paginationOutput.pageNumber) == 1 True >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem TwoUSMXItem One1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' - >>> o = ResponseDataObject({'content': xml}) - >>> r = Response(o, verb='findItemsByProduct', listnodes=['searchResult.item']) + >>> o = ResponseDataObject({'content': xml}, []) + >>> r = Response(o, verb='findItemsByProduct', list_nodes=['searchResult.item']) >>> len(r.dom().getchildren()) > 2 True >>> r.json() @@ -112,8 +125,8 @@ class Response(object): True ''' - def __init__(self, obj, verb=None, listnodes=[]): - self._listnodes=copy.copy(listnodes) + def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): + self._list_nodes=copy.copy(list_nodes) self._obj = obj try: @@ -123,9 +136,10 @@ def __init__(self, obj, verb=None, listnodes=[]): if verb: self._dict = self._dict.get('%sResponse' % verb, self._dict) - self.reply = ResponseDataObject(self._dict) + self.reply = ResponseDataObject(self._dict, + datetime_nodes=copy.copy(datetime_nodes)) except lxml.etree.XMLSyntaxError: - self.reply = ResponseDataObject({}) + self.reply = ResponseDataObject({}, []) def _get_node_path(self, t): i = t @@ -161,7 +175,7 @@ def _etree_to_dict(self, t): parent_path = self._get_node_path(t) for k in d[t.tag].keys(): path = "%s.%s" % (parent_path, k) - if path.lower() in self._listnodes: + if path.lower() in self._list_nodes: if not isinstance(d[t.tag][k], list): d[t.tag][k] = [ d[t.tag][k] ] diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index e13dd93..395355f 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -85,7 +85,12 @@ def __init__(self, **kwargs): if self.config.get('https') and self.debug: print("HTTPS is not supported on the Shopping API.") - self.base_listnodes=[ + self.datetime_nodes = ['timestamp', 'registrationdate', 'creationtime', + 'commenttime', 'updatetime', 'estimateddeliverymintime', + 'estimateddeliverymaxtime', 'creationtime', 'estimateddeliverymintime', + 'estimateddeliverymaxtime', 'endtime', 'starttime'] + + self.base_list_nodes=[ 'findhalfproductsresponse.halfcatalogproducttype.productid', 'findhalfproductsresponse.halfproductstype.product', 'getshippingcostsresponse.internationalshippingserviceoptiontype.shipsto', diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index ed33ea5..fc3bd03 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -53,6 +53,9 @@ def __init__(self, site_id='EBAY-US', debug=False, consumer_id=None, self.read_set = None + self.datetime_nodes += ['lastupdatetime', 'timestamp'] + self.base_list_nodes += ['finditemsbyidsresponse.record'] + def build_request_headers(self, verb): return { "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), @@ -78,7 +81,7 @@ def findItemsByIds(self, ebay_item_ids, }) args = {'id': ebay_item_ids, 'readSet': read_set_node} - self.execute('findItemsByIds', args, listnodes=['record']) + self.execute('findItemsByIds', args) return self.mappedResponse() def mappedResponse(self): diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index ad1d3e0..091cce9 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -26,17 +26,20 @@ class Connection(BaseConnection): (all others, see API docs) Doctests: + >>> import datetime >>> t = Connection(config_file=os.environ.get('EBAY_YAML')) - >>> retval = t.execute('GetCharities', {'CharityID': 3897}) + >>> response = t.execute('GetCharities', {'CharityID': 3897}) >>> charity_name = '' >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: ... charity_name = getNodeText(t.response_dom().getElementsByTagName('Name')[0]) >>> print(charity_name) Sunshine Kids Foundation + >>> isinstance(response.reply.Timestamp, datetime.datetime) + True >>> print(t.error()) None >>> t2 = Connection(errors=False, debug=False, config_file=os.environ.get('EBAY_YAML')) - >>> retval2 = t2.execute('VerifyAddItem', {}) + >>> response = t2.execute('VerifyAddItem', {}) >>> print(t2.response_codes()) [10009] """ @@ -90,7 +93,102 @@ def __init__(self, **kwargs): self.config.set('version', '837') self.config.set('compatibility', '837') - self.base_listnodes = [ + self.datetime_nodes = [ + 'shippingtime', + 'starttime', + 'endtime', + 'scheduletime', + 'createdtime', + 'hardexpirationtime', + 'invoicedate', + 'begindate', + 'enddate', + 'startcreationtime', + 'endcreationtime', + 'endtimefrom', + 'endtimeto', + 'updatetime', + 'lastupdatetime', + 'lastmodifiedtime', + 'modtimefrom', + 'modtimeto', + 'createtimefrom', + 'createtimeto', + 'starttimefrom', + 'starttimeto', + 'timeto', + 'paymenttimefrom', + 'paymenttimeto', + 'inventorycountlastcalculateddate', + 'registrationdate', + 'timefrom', + 'timestamp', + 'messagecreationtime', + 'resolutiontime', + 'date', + 'bankmodifydate', + 'creditcardexpiration', + 'creditcardmodifydate', + 'lastpaymentdate', + 'submittedtime', + 'announcementstarttime', + 'eventtime', + 'periodicstartdate', + 'modtime', + 'expirationtime', + 'creationtime', + 'lastusedtime', + 'disputecreatedtime', + 'disputemodifiedtime', + 'externaltransactiontime', + 'commenttime', + 'lastbidtime', + 'time', + 'creationdate', + 'lastmodifieddate', + 'receivedate', + 'expirationdate', + 'resolutiondate', + 'lastreaddate', + 'userforwarddate', + 'itemendtime', + 'userresponsedate', + 'nextretrytime', + 'deliverytime', + 'timebid', + 'paidtime', + 'shippedtime', + 'expectedreleasedate', + 'paymenttime', + 'promotionalsalestarttime', + 'promotionalsaleendtime', + 'refundtime', + 'refundrequestedtime', + 'refundcompletiontime', + 'estimatedrefundcompletiontime', + 'lastemailsenttime', + 'sellerinvoicetime', + 'estimateddeliverydate', + 'printedtime', + 'deliverydate', + 'refundgrantedtime', + 'scheduleddeliverytimemin', + 'scheduleddeliverytimemax', + 'actualdeliverytime', + 'usebydate', + 'lastopenedtime', + 'returndate', + 'revocationtime', + 'lasttimemodified', + 'createddate', + 'invoicesenttime', + 'acceptedtime', + 'sellerebaypaymentprocessenabletime', + 'useridlastchanged', + 'actionrequiredby', + ] + + self.base_list_nodes = [ 'getmymessagesresponse.abstractrequesttype.detaillevel', 'getaccountresponse.abstractrequesttype.outputselector', 'getadformatleadsresponse.abstractrequesttype.outputselector', @@ -566,7 +664,7 @@ def __init__(self, **kwargs): 'getebaydetailsresponse.verifieduserrequirementsdetailstype.feedbackscore', 'getwantitnowsearchresultsresponse.wantitnowpostarraytype.wantitnowpost', ] - + def build_request_headers(self, verb): headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('version', ''), diff --git a/samples/common.py b/samples/common.py index 54d0f3d..f33f621 100644 --- a/samples/common.py +++ b/samples/common.py @@ -5,8 +5,6 @@ Licensed under CDDL 1.0 ''' -import json - def dump(api, full=False): print("\n") @@ -14,15 +12,16 @@ def dump(api, full=False): if api.warnings(): print("Warnings" + api.warnings()) - if api.response_content(): - print("Call Success: %s in length" % len(api.response_content())) + if api.response.content: + print("Call Success: %s in length" % len(api.response.content)) print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) + print("Response DOM1: %s" % api.response_dom()) + print("Response ETREE: %s" % api.response.dom()) if full: - print(api.response_content()) - print((json.dumps(api.response.dict(), indent=2))) + print(api.response.content) + print(api.response.json()) else: - dictstr = "%s" % api.response_dict() + dictstr = "%s" % api.response.dict() print("Response dictionary: %s..." % dictstr[:150]) diff --git a/samples/finding.py b/samples/finding.py index 014529a..86fcf65 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -41,7 +41,7 @@ def run(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('findItemsAdvanced', { + response = api.execute('findItemsAdvanced', { 'keywords': u'niño', 'itemFilter': [ {'name': 'Condition', @@ -54,15 +54,17 @@ def run(opts): }) dump(api) - #from IPython import embed; embed() + except ConnectionError as e: print(e) def run2(opts): try: api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml) - api.execute('findItemsByProduct', + + response = api.execute('findItemsByProduct', '530390311') + dump(api) except ConnectionError as e: diff --git a/samples/finditem.py b/samples/finditem.py index 9ea25fc..ccb7278 100644 --- a/samples/finditem.py +++ b/samples/finditem.py @@ -46,7 +46,8 @@ def run(opts): shopping = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=False) - shopping.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + response = shopping.execute('FindPopularItems', {'QueryKeywords': 'Python'}) + nodes = shopping.response_dom().getElementsByTagName('ItemID') itemIds = [getNodeText(n) for n in nodes] @@ -65,7 +66,7 @@ def run(opts): print("ID(%s) TITLE(%s)" % (r['ITEM_ID'], r['TITLE'][:35])) dump(api) - + from IPython import embed; embed() except ConnectionError as e: print(e) diff --git a/samples/merchandising.py b/samples/merchandising.py index 2d6809d..fafb6f3 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -39,7 +39,7 @@ def run(opts): api = merchandising(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('getMostWatchedItems', {'maxResults': 3}) + response = api.execute('getMostWatchedItems', {'maxResults': 4}) dump(api) except ConnectionError as e: diff --git a/samples/shopping.py b/samples/shopping.py index 3fb76a5..a14a01c 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -47,20 +47,12 @@ def run(opts): print("Shopping samples for SDK version %s" % ebaysdk.get_version()) try: - api.execute('FindPopularItems', {'QueryKeywords': 'Python'}, - listnodes=['ItemArray.Item']) + response = api.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - if api.response.content: - print("Call Success: %s in length" % len(api.response.content)) - - print("Response code: %s" % api.response_code()) - print("Response DOM: %s" % api.response_dom()) - - dictstr = "%s" % api.response.dict() - print("Response dictionary: %s..." % dictstr[:50]) + dump(api) print("Matching Titles:") - for item in api.response.reply.ItemArray.Item: + for item in response.reply.ItemArray.Item: print(item.Title) except ConnectionError as e: @@ -72,7 +64,6 @@ def popularSearches(opts): api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - choice = True while choice: @@ -83,26 +74,24 @@ def popularSearches(opts): break mySearch = { - # "CategoryID": " string ", - # "IncludeChildCategories": " boolean ", "MaxKeywords": 10, "QueryKeywords": choice, } try: - api.execute('FindPopularSearches', mySearch) #, listnodes=['ItemArray.Item']) + response = api.execute('FindPopularSearches', mySearch) dump(api, full=False) - print("Related: %s" % api.response.reply.PopularSearchResult.RelatedSearches) + print("Related: %s" % response.reply.PopularSearchResult.RelatedSearches) - for term in api.response.reply.PopularSearchResult.AlternativeSearches.split(';')[:3]: + for term in response.reply.PopularSearchResult.AlternativeSearches.split(';')[:3]: api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3}) print("Term: %s" % term) try: - for item in api.response.reply.ItemArray.Item: + for item in response.reply.ItemArray.Item: print(item.Title) except AttributeError: pass @@ -110,6 +99,7 @@ def popularSearches(opts): dump(api) print("\n") + except ConnectionError as e: print(e) @@ -119,7 +109,7 @@ def categoryInfo(opts): api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('GetCategoryInfo', {"CategoryID": 3410}) + response = api.execute('GetCategoryInfo', {"CategoryID": 3410}) dump(api, full=False) except ConnectionError as e: @@ -136,7 +126,7 @@ def with_affiliate_info(opts): "QueryKeywords": 'shirt', } - api.execute('FindPopularSearches', mySearch) + response = api.execute('FindPopularSearches', mySearch) dump(api, full=False) except ConnectionError as e: @@ -148,7 +138,7 @@ def using_attributes(opts): api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - api.execute('FindProducts', { + response = api.execute('FindProducts', { "ProductID": {'@attrs': {'type': 'ISBN'}, '#text': '0596154488'}}) From b4839728c031a7095631e8809a97401d4479b72b Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 21 Feb 2014 14:34:37 -0800 Subject: [PATCH 161/218] remove debugging --- ebaysdk/response.py | 1 - samples/finditem.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index fa66293..71bfd7f 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -46,7 +46,6 @@ def _setattr(self, name, value, datetime_nodes): ts = "%s %s" % (value.partition('T')[0], value.partition('T')[2].partition('.')[0]) value = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S') except ValueError: - print "Error: %s" % value pass setattr(self, name, value) diff --git a/samples/finditem.py b/samples/finditem.py index ccb7278..d6c0652 100644 --- a/samples/finditem.py +++ b/samples/finditem.py @@ -66,7 +66,7 @@ def run(opts): print("ID(%s) TITLE(%s)" % (r['ITEM_ID'], r['TITLE'][:35])) dump(api) - from IPython import embed; embed() + except ConnectionError as e: print(e) From 6eea4d6a80be81f3ef992aa666ce6e4825ca7aa4 Mon Sep 17 00:00:00 2001 From: Reuben Cummings Date: Sat, 15 Mar 2014 12:49:01 +0300 Subject: [PATCH 162/218] Make home directory search cross platform --- ebaysdk/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/config.py b/ebaysdk/config.py index c3661a7..0e95721 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -52,7 +52,7 @@ def _populate_yaml_defaults(self): return self # check other directories - dirs = ['.', os.environ.get('HOME'), '/etc'] + dirs = ['.', os.path.expanduser('~'), '/etc'] for mydir in dirs: myfile = "%s/%s" % (mydir, self.config_file) From d738abb11d85d3bf8924924a2fe13b48592f6fd6 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Tue, 18 Mar 2014 13:29:16 -0700 Subject: [PATCH 163/218] Fixed an exception with an empty response while trying to get the SOA error messages --- ebaysdk/soa/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index 45f1e2f..582224b 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -54,14 +54,15 @@ def response_dict(self): if self._response_dict: return self._response_dict - mydict = xml2dict().fromstring(self._response_content) - - try: - verb = self.verb + 'Response' - self._response_dict = mydict['Envelope']['Body'][verb] + if self._response_content: + mydict = xml2dict().fromstring(self._response_content) + + try: + verb = self.verb + 'Response' + self._response_dict = mydict['Envelope']['Body'][verb] - except KeyError: - self._response_dict = mydict.get(self.verb + 'Response', mydict) + except KeyError: + self._response_dict = mydict.get(self.verb + 'Response', mydict) return self._response_dict From 1cdbdb11ebc444c14418517d87ee37e64c46f675 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Thu, 20 Mar 2014 10:53:31 -0700 Subject: [PATCH 164/218] Raising exceptions when trying to use old style imports --- docs/index.rst | 7 +++--- ebaysdk/__init__.py | 53 ++++++++++++++++++++++++++++------------ ebaysdk/parallel.py | 2 +- samples/merchandising.py | 2 +- samples/parallel.py | 3 ++- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e1f1ad8..de7a392 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,10 +7,11 @@ In order to use eBay aspects of this utility you must first register with eBay t Example:: - from ebaysdk import finding, tag, nodeText + from ebaysdk import getNodeText + from ebaysdk.finding import Connection as finding f = finding() - f.execute('findItemsAdvanced', tag('keywords', 'shoes')) + f.execute('findItemsAdvanced', {'keywords': 'shoes'}) dom = f.response_dom() mydict = f.response_dict() @@ -22,7 +23,7 @@ Example:: items = dom.getElementsByTagName('item') for item in items: - print nodeText(item.getElementsByTagName('title')[0]) + print getNodeText(item.getElementsByTagName('title')[0]) .. _eBay Developer Site: http://developer.ebay.com/ diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 807c9a5..b3bc07f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -35,7 +35,7 @@ def get_version(): def set_file_logger(name, filepath, level=logging.INFO, format_string=None): global log - log.handlers=[] + log.handlers = [] if not format_string: format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" @@ -51,7 +51,7 @@ def set_file_logger(name, filepath, level=logging.INFO, format_string=None): def set_stream_logger(name, level=logging.DEBUG, format_string=None): global log - log.handlers=[] + log.handlers = [] if not format_string: format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" @@ -65,26 +65,49 @@ def set_stream_logger(name, level=logging.DEBUG, format_string=None): log = logger def trading(*args, **kwargs): - from ebaysdk.trading import Connection as Trading - return Trading(*args, **kwargs) + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import trading', + 'from ebaysdk.trading import Connection as trading', + ) + ) def shopping(*args, **kwargs): - from ebaysdk.shopping import Connection as Shopping - return Shopping(*args, **kwargs) + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import shopping', + 'from ebaysdk.shopping import Connection as shopping', + ) + ) def finding(*args, **kwargs): - from ebaysdk.finding import Connection as Finding - return Finding(*args, **kwargs) + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import finding', + 'from ebaysdk.finding import Connection as finding', + ) + ) def merchandising(*args, **kwargs): - from ebaysdk.merchandising import Connection as Merchandising - return Merchandising(*args, **kwargs) + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import merchandising', + 'from ebaysdk.merchandising import Connection as merchandising', + ) + ) def html(*args, **kwargs): - from ebaysdk.http import Connection as HTTP - return HTTP(*args, **kwargs) + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import html', + 'from ebaysdk.http import Connection as html', + ) + ) def parallel(*args, **kwargs): - from ebaysdk.parallel import Parallel - return Parallel(*args, **kwargs) - + raise ImportError( + 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( + 'from ebaysdk import parallel', + 'from ebaysdk.parallel import Parallel as parallel', + ) + ) diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index f23c365..ff01c82 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -13,7 +13,7 @@ class Parallel(object): """ >>> from ebaysdk.finding import Connection as finding >>> from ebaysdk.shopping import Connection as shopping - >>> from ebaysdk import html + >>> from ebaysdk.http import Connection as html >>> import os >>> p = Parallel() >>> r1 = html(parallel=p) diff --git a/samples/merchandising.py b/samples/merchandising.py index 1bf1c5a..d82e524 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -14,7 +14,7 @@ from common import dump import ebaysdk -from ebaysdk import merchandising +from ebaysdk.merchandising import Connection as merchandising from ebaysdk.exception import ConnectionError def init_options(): diff --git a/samples/parallel.py b/samples/parallel.py index cf30b6d..78dadcf 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -12,7 +12,8 @@ sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) from common import dump -from ebaysdk import finding, html +from ebaysdk.finding import Connection as finding +from ebaysdk.http import Connection as html from ebaysdk.parallel import Parallel from ebaysdk.exception import ConnectionError From 324eab49aea5d5ceea5abaa59886c870481f62c5 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 14 Apr 2014 15:40:14 -0700 Subject: [PATCH 165/218] rebase with master --- docs/index.rst | 3 --- ebaysdk/__init__.py | 15 --------------- ebaysdk/config.py | 1 - ebaysdk/parallel.py | 1 - ebaysdk/shopping/__init__.py | 1 - samples/merchandising.py | 1 - samples/parallel.py | 1 - 7 files changed, 23 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 600fb7f..de7a392 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,12 +7,10 @@ In order to use eBay aspects of this utility you must first register with eBay t Example:: - from ebaysdk import finding, tag, nodeText from ebaysdk import getNodeText from ebaysdk.finding import Connection as finding f = finding() - f.execute('findItemsAdvanced', tag('keywords', 'shoes')) f.execute('findItemsAdvanced', {'keywords': 'shoes'}) dom = f.response_dom() @@ -25,7 +23,6 @@ Example:: items = dom.getElementsByTagName('item') for item in items: - print nodeText(item.getElementsByTagName('title')[0]) print getNodeText(item.getElementsByTagName('title')[0]) .. _eBay Developer Site: http://developer.ebay.com/ diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 8a3fcfc..b3bc07f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -35,7 +35,6 @@ def get_version(): def set_file_logger(name, filepath, level=logging.INFO, format_string=None): global log - log.handlers=[] log.handlers = [] if not format_string: @@ -52,7 +51,6 @@ def set_file_logger(name, filepath, level=logging.INFO, format_string=None): def set_stream_logger(name, level=logging.DEBUG, format_string=None): global log - log.handlers=[] log.handlers = [] if not format_string: @@ -67,8 +65,6 @@ def set_stream_logger(name, level=logging.DEBUG, format_string=None): log = logger def trading(*args, **kwargs): - from ebaysdk.trading import Connection as Trading - return Trading(*args, **kwargs) raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import trading', @@ -77,8 +73,6 @@ def trading(*args, **kwargs): ) def shopping(*args, **kwargs): - from ebaysdk.shopping import Connection as Shopping - return Shopping(*args, **kwargs) raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import shopping', @@ -87,8 +81,6 @@ def shopping(*args, **kwargs): ) def finding(*args, **kwargs): - from ebaysdk.finding import Connection as Finding - return Finding(*args, **kwargs) raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import finding', @@ -97,8 +89,6 @@ def finding(*args, **kwargs): ) def merchandising(*args, **kwargs): - from ebaysdk.merchandising import Connection as Merchandising - return Merchandising(*args, **kwargs) raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import merchandising', @@ -107,8 +97,6 @@ def merchandising(*args, **kwargs): ) def html(*args, **kwargs): - from ebaysdk.http import Connection as HTTP - return HTTP(*args, **kwargs) raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import html', @@ -117,9 +105,6 @@ def html(*args, **kwargs): ) def parallel(*args, **kwargs): - from ebaysdk.parallel import Parallel - return Parallel(*args, **kwargs) - raise ImportError( 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 'from ebaysdk import parallel', diff --git a/ebaysdk/config.py b/ebaysdk/config.py index 3ab5ae8..0e95721 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -52,7 +52,6 @@ def _populate_yaml_defaults(self): return self # check other directories - dirs = ['.', os.environ.get('HOME'), '/etc'] dirs = ['.', os.path.expanduser('~'), '/etc'] for mydir in dirs: myfile = "%s/%s" % (mydir, self.config_file) diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index 06ee5a4..ff01c82 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -13,7 +13,6 @@ class Parallel(object): """ >>> from ebaysdk.finding import Connection as finding >>> from ebaysdk.shopping import Connection as shopping - >>> from ebaysdk import html >>> from ebaysdk.http import Connection as html >>> import os >>> p = Parallel() diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 64d9577..23b93d0 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -129,7 +129,6 @@ def build_request_headers(self, verb): headers = { "X-EBAY-API-VERSION": self.config.get('version', ''), "X-EBAY-API-APP-ID": self.config.get('appid', ''), - "X-EBAY-API-SITEID": self.config.get('siteid', ''), "X-EBAY-API-SITE-ID": self.config.get('siteid', ''), "X-EBAY-API-CALL-NAME": verb, "X-EBAY-API-REQUEST-ENCODING": "XML", diff --git a/samples/merchandising.py b/samples/merchandising.py index af89789..b416c69 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -14,7 +14,6 @@ from common import dump import ebaysdk -from ebaysdk import merchandising from ebaysdk.merchandising import Connection as merchandising from ebaysdk.exception import ConnectionError diff --git a/samples/parallel.py b/samples/parallel.py index 317d883..c3d960d 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -12,7 +12,6 @@ sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) from common import dump -from ebaysdk import finding, html from ebaysdk.finding import Connection as finding from ebaysdk.http import Connection as html from ebaysdk.parallel import Parallel From 5b0c8b1d5aa8b469d2d89a3564284372e1219e53 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 14 Apr 2014 15:43:18 -0700 Subject: [PATCH 166/218] merge conflicts --- ebaysdk/soa/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index 2f2a76a..ba265a2 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -54,21 +54,13 @@ def response_dict(self): if self._response_dict: return self._response_dict -<<<<<<< HEAD - - mydict = self.response.dict() - - try: - verb = self.verb + 'Response' - self._response_dict = mydict['Envelope']['Body'][verb] -======= if self._response_content: - mydict = xml2dict().fromstring(self._response_content) + mydict = self.response.dict() + try: verb = self.verb + 'Response' self._response_dict = mydict['Envelope']['Body'][verb] ->>>>>>> master except KeyError: self._response_dict = mydict.get(self.verb + 'Response', mydict) From 4330aaf095ac57eb24a27e292386d887fb5a81b6 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 17 Apr 2014 11:21:33 -0700 Subject: [PATCH 167/218] fix logging hander --- ebaysdk/__init__.py | 41 ++++++++++++----------------------------- ebaysdk/connection.py | 2 +- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index b3bc07f..91a3d85 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -12,6 +12,12 @@ __version__ = '1.0.3' Version = __version__ # for backware compatibility +try: + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass UserAgent = 'eBaySDK/%s Python/%s %s/%s' % ( __version__, @@ -20,49 +26,26 @@ platform.release() ) -class NullHandler(logging.Handler): - def emit(self, record): - pass - log = logging.getLogger('ebaysdk') -perflog = logging.getLogger('ebaysdk.perf') -log.addHandler(NullHandler()) -perflog.addHandler(NullHandler()) +if not log.handlers: + log.addHandler(NullHandler()) def get_version(): return __version__ -def set_file_logger(name, filepath, level=logging.INFO, format_string=None): - global log - log.handlers = [] +def set_stream_logger(level=logging.DEBUG, format_string=None): + log.handlers=[] if not format_string: format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" - logger = logging.getLogger(name) - logger.setLevel(level) - fh = logging.FileHandler(filepath) - fh.setLevel(level) - formatter = logging.Formatter(format_string) - fh.setFormatter(formatter) - logger.addHandler(fh) - log = logger - -def set_stream_logger(name, level=logging.DEBUG, format_string=None): - global log - log.handlers = [] - - if not format_string: - format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" - logger = logging.getLogger(name) - logger.setLevel(level) + log.setLevel(level) fh = logging.StreamHandler() fh.setLevel(level) formatter = logging.Formatter(format_string) fh.setFormatter(formatter) - logger.addHandler(fh) - log = logger + log.addHandler(fh) def trading(*args, **kwargs): raise ImportError( diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index c2797e7..7feb99c 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -46,7 +46,7 @@ def __init__(self, debug=False, method='GET', parallel=None, **kwargs): if debug: - set_stream_logger('ebaysdk') + set_stream_logger() self.response = None self.request = None From 2b81f97020c0c3aeac12cdec1d7f392d07643178 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 17 Apr 2014 11:22:03 -0700 Subject: [PATCH 168/218] update Changes --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index c04d45a..75e5bc4 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,7 @@ Changes for ebaysdk 1.0.3 - rework unicode fix +- fix logging handler 1.0.2 Sat Feb 8 20:44:00 PST 2014 - fix unicode issue is dict to xml routine From 005064b8bd28711fb4b52b87543d04596bd245b9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 2 May 2014 14:18:13 -0700 Subject: [PATCH 169/218] add doc helper method, response work, modify error functions to work with new DOM object --- ebaysdk/connection.py | 16 ++++++++-- ebaysdk/exception.py | 6 ++++ ebaysdk/finding/__init__.py | 50 +++++++++++++++++++++++------ ebaysdk/merchandising/__init__.py | 1 + ebaysdk/response.py | 6 +++- ebaysdk/shopping/__init__.py | 48 ++++++++++++++++++---------- ebaysdk/soa/__init__.py | 35 ++++++++++++++------- ebaysdk/trading/__init__.py | 52 ++++++++++++++++++++----------- 8 files changed, 155 insertions(+), 59 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index d714a8a..1619387 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -9,9 +9,9 @@ from ebaysdk import log import re -import json import time import uuid +import webbrowser from requests import Request, Session from requests.adapters import HTTPAdapter @@ -85,6 +85,7 @@ def _reset(self): self.verb = None self._list_nodes = [] self._request_id = None + self._request_dict = {} self._time = time.time() self._response_content = None self._response_dom = None @@ -128,6 +129,7 @@ def execute(self, verb, data=None, list_nodes=[]): def build_request(self, verb, data): self.verb = verb + self._request_dict = data self._request_id = uuid.uuid4() url = "%s://%s%s" % ( @@ -221,12 +223,14 @@ def response_soup(self): return self._response_soup def response_obj(self): + log.warn('response_obj() DEPRECATED, use response.reply instead') return self.response.reply def response_dom(self): """ Deprecated: use self.response.dom() instead Returns the response DOM (xml.dom.minidom). """ + log.warn('response_dom() DEPRECATED, use response.dom instead') if not self._response_dom: dom = None @@ -252,13 +256,15 @@ def response_dom(self): def response_dict(self): "Returns the response dictionary." + log.warn('response_dict() DEPRECATED, use response.dict() or response.reply instead') return self.response.reply def response_json(self): "Returns the response JSON." + log.warn('response_json() DEPRECATED, use response.json() instead') - return json.dumps(self.response.dict()) + return self.response.json() def _get_resp_body_errors(self): """Parses the response content to pull errors. @@ -277,7 +283,7 @@ def _get_resp_body_errors(self): if self.verb is None: return errors - dom = self.response_dom() + dom = self.response.dom() if dom is None: return errors @@ -298,3 +304,7 @@ def error(self): return error_string return None + + def opendoc(self): + webbrowser.open(self.config.get('doc_url')) + diff --git a/ebaysdk/exception.py b/ebaysdk/exception.py index 0ec63c5..399ec29 100644 --- a/ebaysdk/exception.py +++ b/ebaysdk/exception.py @@ -11,3 +11,9 @@ class ConnectionError(Exception): class ConnectionResponseError(Exception): pass + +class RequestPaginationError(Exception): + pass + +class PaginationLimit(Exception): + pass \ No newline at end of file diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index c9c7d3a..995b128 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -10,8 +10,9 @@ from ebaysdk import log from ebaysdk.connection import BaseConnection +from ebaysdk.exception import RequestPaginationError, PaginationLimit from ebaysdk.config import Config -from ebaysdk.utils import getNodeText, dict2xml +from ebaysdk.utils import dict2xml class Connection(BaseConnection): """Connection class for the Finding service @@ -85,6 +86,7 @@ def __init__(self, **kwargs): self.config.set('version', '1.12.0') self.config.set('compatibility', '1.0.0') self.config.set('service', 'FindingService') + self.config.set('doc_url', 'http://developer.ebay.com/DevZone/finding/CallRef/index.html') self.datetime_nodes = ['starttimefrom', 'timestamp', 'starttime', 'endtime'] @@ -231,19 +233,27 @@ def _get_resp_body_errors(self): eMsg = None eId = None - if e.getElementsByTagName('severity'): - eSeverity = getNodeText(e.getElementsByTagName('severity')[0]) + try: + eSeverity = e.findall('severity')[0].text + except IndexError: + pass - if e.getElementsByTagName('domain'): - eDomain = getNodeText(e.getElementsByTagName('domain')[0]) + try: + eDomain = e.findall('domain')[0].text + except IndexError: + pass - if e.getElementsByTagName('errorId'): - eId = getNodeText(e.getElementsByTagName('errorId')[0]) + try: + eId = e.findall('errorId')[0].text if int(eId) not in resp_codes: resp_codes.append(int(eId)) + except IndexError: + pass - if e.getElementsByTagName('message'): - eMsg = getNodeText(e.getElementsByTagName('message')[0]) + try: + eMsg = e.findall('message')[0].text + except IndexError: + pass msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ % (eDomain, eSeverity, eId, eMsg) @@ -261,7 +271,7 @@ def _get_resp_body_errors(self): log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) try: - if self.response_dict().ack == 'Success' and len(errors) > 0 and self.config.get('errors'): + if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) elif len(errors) > 0: if self.config.get('errors'): @@ -271,3 +281,23 @@ def _get_resp_body_errors(self): pass return [] + + def next_page(self): + if type(self._request_dict) is not dict: + raise RequestPaginationError("request data is not of type dict") + + epp = self._request_dict.get('paginationInput', {}).get('enteriesPerPage', None) + num = int(self.response.reply.paginationOutput.pageNumber) + + if num >= int(self.response.reply.paginationOutput.totalPages): + raise PaginationLimit("no more pages to process") + return None + + self._request_dict['paginationInput'] = {} + + if epp: + self._request_dict['paginationInput']['enteriesPerPage'] = epp + + self._request_dict['paginationInput']['pageNumber'] = int(num) + 1 + + self.execute(self.verb, self._request_dict) diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index e2a62e8..5173f81 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -57,6 +57,7 @@ def __init__(self, **kwargs): self.config.set('uri', '/MerchandisingService', force=True) self.config.set('service', 'MerchandisingService', force=True) + self.config.set('doc_url', 'http://developer.ebay.com/Devzone/merchandising/docs/CallRef/index.html') self.datetime_nodes = ['endtimeto', 'endtimefrom', 'timestamp'] self.base_list_nodes = [ diff --git a/ebaysdk/response.py b/ebaysdk/response.py index 71bfd7f..d53d3d4 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -132,7 +132,11 @@ def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): self._dom = self._parse_xml(obj.content) self._dict = self._etree_to_dict(self._dom) - if verb: + if verb and 'Envelope' in self._dict.keys(): + self._dom = self._dom.find('Body').find('%sResponse' % verb) + self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) + elif verb: + self._dom = self._dom.find('%sResponse' % verb) self._dict = self._dict.get('%sResponse' % verb, self._dict) self.reply = ResponseDataObject(self._dict, diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 23b93d0..c002aed 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -81,6 +81,7 @@ def __init__(self, **kwargs): self.config.set('version', '799') self.config.set('trackingid', None) self.config.set('trackingpartnercode', None) + self.config.set('doc_url', 'http://developer.ebay.com/DevZone/Shopping/docs/CallRef/index.html') if self.config.get('https') and self.debug: print("HTTPS is not supported on the Shopping API.") @@ -184,36 +185,51 @@ def _get_resp_body_errors(self): if self.verb is None: return errors - dom = self.response_dom() + dom = self.response.dom() if dom is None: return errors - for e in dom.getElementsByTagName("Errors"): + for e in dom.findall('Errors'): eSeverity = None eClass = None eShortMsg = None eLongMsg = None eCode = None - if e.getElementsByTagName('SeverityCode'): - eSeverity = getNodeText(e.getElementsByTagName('SeverityCode')[0]) - - if e.getElementsByTagName('ErrorClassification'): - eClass = getNodeText(e.getElementsByTagName('ErrorClassification')[0]) - - if e.getElementsByTagName('ErrorCode'): - eCode = float(getNodeText(e.getElementsByTagName('ErrorCode')[0])) + try: + eSeverity = e.findall('SeverityCode')[0].text + except IndexError: + pass + + try: + eClass = e.findall('ErrorClassification')[0].text + except IndexError: + pass + + try: + eCode = e.findall('ErrorCode')[0].text + except IndexError: + pass + + try: + eShortMsg = e.findall('ShortMessage')[0].text + except IndexError: + pass + + try: + eLongMsg = e.findall('LongMessage')[0].text + except IndexError: + pass + + try: + eCode = float(e.findall('ErrorCode')[0]) if eCode.is_integer(): eCode = int(eCode) if eCode not in resp_codes: resp_codes.append(eCode) - - if e.getElementsByTagName('ShortMessage'): - eShortMsg = getNodeText(e.getElementsByTagName('ShortMessage')[0]) - - if e.getElementsByTagName('LongMessage'): - eLongMsg = getNodeText(e.getElementsByTagName('LongMessage')[0]) + except IndexError: + pass msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index ba265a2..91ce53d 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -51,6 +51,9 @@ def load_from_app_config(self, app_config): # It used to return None in some cases. If you get an empty dict, # you can use the .error() method to look for the cause. def response_dict(self): + return self.response.dict() + + ''' if self._response_dict: return self._response_dict @@ -66,6 +69,7 @@ def response_dict(self): self._response_dict = mydict.get(self.verb + 'Response', mydict) return self._response_dict + ''' def build_request_headers(self, verb): return { @@ -144,29 +148,38 @@ def _get_resp_body_errors(self): if self.verb is None: return errors - dom = self.response_dom() + dom = self.response.dom() if dom is None: return errors - for e in dom.getElementsByTagName("error"): + for e in dom.findall('error'): + eSeverity = None eDomain = None eMsg = None eId = None - if e.getElementsByTagName('severity'): - eSeverity = getNodeText(e.getElementsByTagName('severity')[0]) + try: + eSeverity = e.findall('severity')[0].text + except IndexError: + pass - if e.getElementsByTagName('domain'): - eDomain = getNodeText(e.getElementsByTagName('domain')[0]) + try: + eDomain = e.findall('domain')[0].text + except IndexError: + pass - if e.getElementsByTagName('errorId'): - eId = getNodeText(e.getElementsByTagName('errorId')[0]) + try: + eId = e.findall('errorId')[0].text if int(eId) not in resp_codes: resp_codes.append(int(eId)) + except IndexError: + pass - if e.getElementsByTagName('message'): - eMsg = getNodeText(e.getElementsByTagName('message')[0]) + try: + eMsg = e.findall('message')[0].text + except IndexError: + pass msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ % (eDomain, eSeverity, eId, eMsg) @@ -184,7 +197,7 @@ def _get_resp_body_errors(self): log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) try: - if self.response_dict().ack == 'Success' and len(errors) > 0 and self.config.get('errors'): + if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) elif len(errors) > 0: if self.config.get('errors'): diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 091cce9..36886e8 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -92,6 +92,7 @@ def __init__(self, **kwargs): self.config.set('certid', None) self.config.set('version', '837') self.config.set('compatibility', '837') + self.config.set('doc_url', 'http://developer.ebay.com/devzone/xml/docs/reference/ebay/index.html') self.datetime_nodes = [ 'shippingtime', @@ -724,33 +725,48 @@ def _get_resp_body_errors(self): if self.verb is None: return errors - dom = self.response_dom() + dom = self.response.dom() if dom is None: return errors - for e in dom.getElementsByTagName("Errors"): + for e in dom.findall('Errors'): eSeverity = None eClass = None eShortMsg = None eLongMsg = None eCode = None - if e.getElementsByTagName('SeverityCode'): - eSeverity = getNodeText(e.getElementsByTagName('SeverityCode')[0]) - - if e.getElementsByTagName('ErrorClassification'): - eClass = getNodeText(e.getElementsByTagName('ErrorClassification')[0]) - - if e.getElementsByTagName('ErrorCode'): - eCode = getNodeText(e.getElementsByTagName('ErrorCode')[0]) + try: + eSeverity = e.findall('SeverityCode')[0].text + except IndexError: + pass + + try: + eClass = e.findall('ErrorClassification')[0].text + except IndexError: + pass + + try: + eCode = e.findall('ErrorCode')[0].text + except IndexError: + pass + + try: + eShortMsg = e.findall('ShortMessage')[0].text + except IndexError: + pass + + try: + eLongMsg = e.findall('LongMessage')[0].text + except IndexError: + pass + + try: + eCode = e.findall('ErrorCode')[0].text if int(eCode) not in resp_codes: - resp_codes.append(int(eCode)) - - if e.getElementsByTagName('ShortMessage'): - eShortMsg = getNodeText(e.getElementsByTagName('ShortMessage')[0]) - - if e.getElementsByTagName('LongMessage'): - eLongMsg = getNodeText(e.getElementsByTagName('LongMessage')[0]) + resp_codes.append(int(eCode)) + except IndexError: + pass msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) @@ -767,7 +783,7 @@ def _get_resp_body_errors(self): if self.config.get('warnings') and len(warnings) > 0: log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) - if self.response_dict().Ack == 'Failure': + if self.response.reply.Ack == 'Failure': if self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) From 30ce5e988bd025ecd620709803ffcdcfae470d11 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 5 May 2014 12:55:36 -0700 Subject: [PATCH 170/218] modify dict2xml processing --- ebaysdk/utils.py | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index a70b251..805373a 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -28,10 +28,7 @@ def python_2_unicode_compatible(klass): def get_dom_tree(xml): tree = ET.fromstring(xml) - #try: return tree.getroottree().getroot() - #except AttributeError: - # return tree def attribute_check(root): attrs = [] @@ -52,6 +49,9 @@ def smart_encode(value): else: return str(value) +def to_xml(root): + return dict2xml(root) + def dict2xml(root): ''' Doctests: @@ -73,6 +73,11 @@ def dict2xml(root): ... } >>> dict2xml(dict3) '222' + >>> dict5 = { + ... 'parent': {'child': {'@attrs': {'site': 'US', 'id': 1234}, }} + ... } + >>> dict2xml(dict5) + '' >>> dict4 = { ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, ... 'paginationInput': { @@ -95,11 +100,36 @@ def dict2xml(root): 'b' >>> dict2xml(None) '' + >>> common_attrs = {'xmlns:xs': 'http://www.w3.org/2001/XMLSchema', 'xsi:type': 'xs:string'} + >>> attrdict = { 'attributeAssertion': [ + ... {'@attrs': {'Name': 'DevId', 'NameFormat': 'String', 'FriendlyName': 'DeveloperID'}, + ... 'urn:AttributeValue': { + ... '@attrs': common_attrs, + ... '#text': 'mydevid' + ... }, + ... }, + ... {'@attrs': {'Name': 'AppId', 'NameFormat': 'String', 'FriendlyName': 'ApplicationID'}, + ... 'urn:AttributeValue': { + ... '@attrs': common_attrs, + ... '#text': 'myappid', + ... }, + ... }, + ... {'@attrs': {'Name': 'CertId', 'NameFormat': 'String', 'FriendlyName': 'Certificate'}, + ... 'urn:AttributeValue': { + ... '@attrs': common_attrs, + ... '#text': 'mycertid', + ... }, + ... }, + ... ], + ... } + >>> print dict2xml(attrdict) + mydevidmyappidmycertid ''' xml = '' if root is None: return xml + if isinstance(root, dict): for key in sorted(root.keys()): @@ -118,14 +148,20 @@ def dict2xml(root): 'value': smart_encode(value), 'attrs_sp': attrs_sp} elif isinstance(root[key], list): + for item in root[key]: attrs, value = attribute_check(item) if not value: value = dict2xml(item) - xml = '%(xml)s<%(tag)s>%(value)s' % \ - {'xml': xml, 'tag': key, 'value': value} + attrs_sp = '' + if len(attrs) > 0: + attrs_sp = ' ' + + xml = '%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ + {'xml': xml, 'tag': key, 'attrs': ' '.join(attrs), + 'value': smart_encode(value), 'attrs_sp': attrs_sp} else: value = root[key] From 7f6756a9530882c177755d6181ebf42d28c014da Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 5 May 2014 15:07:11 -0700 Subject: [PATCH 171/218] fix response handling when errors occur --- ebaysdk/connection.py | 2 +- ebaysdk/finding/__init__.py | 10 ++++++---- ebaysdk/response.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 63a7ba0..0689aa8 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -118,7 +118,7 @@ def execute(self, verb, data=None, list_nodes=[]): self.build_request(verb, data) self.execute_request() - if self.response: + if self.response.content: self.process_response() self.error_check() diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 995b128..67ce977 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -223,11 +223,11 @@ def _get_resp_body_errors(self): if self.verb is None: return errors - dom = self.response_dom() + dom = self.response.dom() if dom is None: return errors - for e in dom.getElementsByTagName("error"): + for e in dom.findall("error"): eSeverity = None eDomain = None eMsg = None @@ -273,12 +273,14 @@ def _get_resp_body_errors(self): try: if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + elif len(errors) > 0: if self.config.get('errors'): log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + return errors - except AttributeError: - pass + except AttributeError as e: + return errors return [] diff --git a/ebaysdk/response.py b/ebaysdk/response.py index d53d3d4..f01c117 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -131,12 +131,12 @@ def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): try: self._dom = self._parse_xml(obj.content) self._dict = self._etree_to_dict(self._dom) - + if verb and 'Envelope' in self._dict.keys(): self._dom = self._dom.find('Body').find('%sResponse' % verb) self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) elif verb: - self._dom = self._dom.find('%sResponse' % verb) + self._dom = self._dom.find('%sResponse' % verb) or self._dom self._dict = self._dict.get('%sResponse' % verb, self._dict) self.reply = ResponseDataObject(self._dict, From 93c67085da47141162b435a2f14815574b5d4cfc Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 5 May 2014 15:45:14 -0700 Subject: [PATCH 172/218] update doc tests --- ebaysdk/__init__.py | 2 +- ebaysdk/connection.py | 2 +- ebaysdk/http/__init__.py | 6 ++---- ebaysdk/merchandising/__init__.py | 2 +- ebaysdk/parallel.py | 15 ++++++++++++--- ebaysdk/shopping/__init__.py | 2 +- ebaysdk/trading/__init__.py | 4 ++-- samples/common.py | 5 ++++- samples/shopping.py | 1 + samples/trading.py | 4 ++-- 10 files changed, 27 insertions(+), 16 deletions(-) diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 91a3d85..3fdc46f 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,7 +9,7 @@ import platform import logging -__version__ = '1.0.3' +__version__ = '2.0.0' Version = __version__ # for backware compatibility try: diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 0689aa8..74fcbb3 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -118,7 +118,7 @@ def execute(self, verb, data=None, list_nodes=[]): self.build_request(verb, data) self.execute_request() - if self.response.content: + if hasattr(self.response, 'content'): self.process_response() self.error_check() diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index edfdb15..7dd8aa3 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -33,11 +33,9 @@ class Connection(BaseConnection): >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') >>> print(h.response.reply.rss.channel.ttl) 60 - >>> title = h.response_dom().getElementsByTagName('title')[0] - >>> print(getNodeText(title)) + >>> title = h.response.dom().xpath('//title')[0] + >>> print(title.text) mytouch slide - >>> print(title.toxml()) - <![CDATA[mytouch slide]]> >>> print(h.error()) None >>> h = Connection(method='POST', debug=False) diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index 5173f81..17b7d75 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -26,7 +26,7 @@ class Connection(FindingConnection): Doctests: >>> s = Connection(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) - >>> print(s.response_obj().ack) + >>> print(s.response.reply.ack) Success >>> print(s.error()) None diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index ff01c82..f239419 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -25,11 +25,11 @@ class Parallel(object): >>> p.wait() >>> print(p.error()) None - >>> print(r1.response_obj().rss.channel.ttl) + >>> print(r1.response.reply.rss.channel.ttl) 60 - >>> print(r2.response_dict().ack) + >>> print(r2.response.dict()['ack']) Success - >>> print(r3.response_obj().Ack) + >>> print(r3.response.reply.Ack) Success """ @@ -82,3 +82,12 @@ def error(self): return "parallel error:\n%s\n" % ("\n".join(self._errors)) return None + + +if __name__ == '__main__': + + import doctest + import sys + + failure_count, test_count = doctest.testmod() + sys.exit(failure_count) \ No newline at end of file diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index c002aed..42cf2bf 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -222,7 +222,7 @@ def _get_resp_body_errors(self): pass try: - eCode = float(e.findall('ErrorCode')[0]) + eCode = float(e.findall('ErrorCode')[0].text) if eCode.is_integer(): eCode = int(eCode) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 36886e8..8f11d0b 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -30,8 +30,8 @@ class Connection(BaseConnection): >>> t = Connection(config_file=os.environ.get('EBAY_YAML')) >>> response = t.execute('GetCharities', {'CharityID': 3897}) >>> charity_name = '' - >>> if len( t.response_dom().getElementsByTagName('Name') ) > 0: - ... charity_name = getNodeText(t.response_dom().getElementsByTagName('Name')[0]) + >>> if len( t.response.dom().xpath('//Name') ) > 0: + ... charity_name = t.response.dom().xpath('//Name')[0].text >>> print(charity_name) Sunshine Kids Foundation >>> isinstance(response.reply.Timestamp, datetime.datetime) diff --git a/samples/common.py b/samples/common.py index f33f621..83f00e4 100644 --- a/samples/common.py +++ b/samples/common.py @@ -16,12 +16,15 @@ def dump(api, full=False): print("Call Success: %s in length" % len(api.response.content)) print("Response code: %s" % api.response_code()) - print("Response DOM1: %s" % api.response_dom()) + print("Response DOM1: %s" % api.response_dom()) # deprecated print("Response ETREE: %s" % api.response.dom()) if full: print(api.response.content) print(api.response.json()) + print("Response Reply: %s" % api.response.reply) else: dictstr = "%s" % api.response.dict() print("Response dictionary: %s..." % dictstr[:150]) + replystr = "%s" % api.response.reply + print("Response Reply: %s" % replystr[:150]) \ No newline at end of file diff --git a/samples/shopping.py b/samples/shopping.py index a14a01c..4fff2e9 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -110,6 +110,7 @@ def categoryInfo(opts): warnings=True) response = api.execute('GetCategoryInfo', {"CategoryID": 3410}) + dump(api, full=False) except ConnectionError as e: diff --git a/samples/trading.py b/samples/trading.py index 0f852cc..0450c0e 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -185,8 +185,8 @@ def verifyAddItemErrorCodes(opts): except ConnectionError as e: # traverse the DOM to look for error codes - for node in api.response_dom().getElementsByTagName('ErrorCode'): - print("error code: %s" % getNodeText(node)) + for node in api.response.dom().findall('ErrorCode'): + print("error code: %s" % node.text) # check for invalid data - error code 37 if 37 in api.response_codes(): From 03c823e3130b45c22cc9bbad18fb394464ae59ef Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 6 May 2014 14:50:08 -0700 Subject: [PATCH 173/218] fix some dom processing --- ebaysdk/response.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index f01c117..f956b06 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -18,8 +18,8 @@ @python_2_unicode_compatible class ResponseDataObject(object): - def __init__(self, mydict, datetime_nodes): - self._load_dict(mydict, datetime_nodes) + def __init__(self, mydict, datetime_nodes=[]): + self._load_dict(mydict, list(datetime_nodes)) def __repr__(self): return str(self) @@ -127,16 +127,22 @@ class Response(object): def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): self._list_nodes=copy.copy(list_nodes) self._obj = obj - + try: self._dom = self._parse_xml(obj.content) self._dict = self._etree_to_dict(self._dom) if verb and 'Envelope' in self._dict.keys(): - self._dom = self._dom.find('Body').find('%sResponse' % verb) + elem = self._dom.find('Body').find('%sResponse' % verb) + if elem is not None: + self._dom = elem + self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) elif verb: - self._dom = self._dom.find('%sResponse' % verb) or self._dom + elem = self._dom.find('%sResponse' % verb) + if elem is not None: + self._dom = elem + self._dict = self._dict.get('%sResponse' % verb, self._dict) self.reply = ResponseDataObject(self._dict, From 1fc46e242174229a0d37869a6de888ac92008111 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 9 May 2014 16:13:10 -0700 Subject: [PATCH 174/218] fix python 2.6 compat issue --- Changes | 11 +++++ README.rst | 22 ++++++---- docs/index.rst | 99 +++++++++++++++++++++++++++++++++++-------- ebaysdk/config.py | 4 ++ ebaysdk/connection.py | 3 ++ ebaysdk/parallel.py | 3 ++ ebaysdk/response.py | 54 +++++++++++++++++------ ebaysdk/utils.py | 3 +- samples/finding.py | 2 +- tests/__init__.py | 25 +++++++---- 10 files changed, 176 insertions(+), 50 deletions(-) diff --git a/Changes b/Changes index 75e5bc4..826df0f 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,16 @@ Changes for ebaysdk +2.0.0 +- WARNING!! This release breaks some backward compatibility + Read https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 +- imports: Modified package structure +- execute(): Modified return value +- Switched to lxml.etree +- Added a new response class + response.reply, response.dom(), response.dict(), response.json() +- Added ebaysdk exception classes +- Modified utils.py (dict2xml, xml2dict) + 1.0.3 - rework unicode fix - fix logging handler diff --git a/README.rst b/README.rst index 7daad90..9af7bf6 100644 --- a/README.rst +++ b/README.rst @@ -4,18 +4,22 @@ Welcome to the python ebaysdk This SDK is a programmatic interface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. Quick Example:: - + import datetime + from lxml.etree import _Element from ebaysdk.finding import Connection + try: api = Connection(appid='YOUR_APPID_HERE') - response = api.execute('findItemsAdvanced', {'keywords': 'shoes'}) - - print(response.reply.timestamp) - print(response.reply.version) - - print(response.dict()) - print(response.json()) - print(response.dom()) + response = api.execute('findItemsAdvanced', {'keywords': 'legos'}) + + assert(response.reply.ack == 'Success') + assert(type(response.reply.timestamp) == datetime.datetime) + assert(type(response.reply.searchResult.item) == list) + + item = response.reply.searchResult.item[0] + assert(type(item.listingInfo.endTime) == datetime.datetime) + assert(type(response.dict()) == dict) + assert(type(response.dom() == _Element) except ConnectionError as e: raise e diff --git a/docs/index.rst b/docs/index.rst index de7a392..3c4f831 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,29 +1,94 @@ -Welcome to the ebaysdk for Python -================================= +Welcome to the python ebaysdk +============================= -Welcome to the eBay SDK for Python. This SDK cuts development time and simplifies tasks like error handling and enables you to make Finding, Shopping, Merchandising, and Trading API calls. In Addition, the SDK comes with RSS and HTML back-end libraries. +This SDK is a programmatic interface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. -In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Developer Site`_ (see the ebay.yaml for a way to easily tie eBay credentials to the SDK) Finding Services. +Quick Start:: + import datetime + from lxml.etree import _Element + from ebaysdk.finding import Connection -Example:: + try: + api = Connection(appid='YOUR_APPID_HERE') + response = api.execute('findItemsAdvanced', {'keywords': 'legos'}) - from ebaysdk import getNodeText - from ebaysdk.finding import Connection as finding + assert(response.reply.ack == 'Success') + assert(type(response.reply.timestamp) == datetime.datetime) + assert(type(response.reply.searchResult.item) == list) + + item = response.reply.searchResult.item[0] + assert(type(item.listingInfo.endTime) == datetime.datetime) + assert(type(response.dict()) == dict) + assert(type(response.dom() == _Element) - f = finding() - f.execute('findItemsAdvanced', {'keywords': 'shoes'}) + except ConnectionError as e: + raise e - dom = f.response_dom() - mydict = f.response_dict() - # shortcut to response data - print f.v('itemSearchURL') +Migrating from v1 to v2 +----------------------- - # process the response via DOM - items = dom.getElementsByTagName('item') +After lots of planning and beating my head against the wall, version 2 of this SDK has come to contain a few changes that break backward compatibility. These changes were necessary to optimize this project and help keep it moving forward. Below I’ll highlight the changes and point out code changes that you may need to make. - for item in items: - print getNodeText(item.getElementsByTagName('title')[0]) +Changes +* Import: Modified package structure +* execute(): Modified return value +* Switched to lxml.etree +* Added a new response class (response.reply, response.dom(), response.dict(), response.json()) +* Added ebaysdk exception classes +* Modified utils.py (dict2xml, xml2dict) + +Please read the full doc on `Migrating from ebaysdk v1 to v2`_ + +Getting Started +--------------- + +SDK Classes + +* `Trading API Class`_ - secure, authenticated access to private eBay data. +* `Finding API Class`_ - access eBay's next generation search capabilities. +* `Shopping API Class`_ - performance-optimized, lightweight APIs for accessing public eBay data. +* `Merchandising API Class`_ - find items and products on eBay that provide good value or are otherwise popular with eBay buyers. +* `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. +* `Parallel Class`_ - SDK support for concurrent API calls. + +SDK Configuration + +* `YAML Configuration`_ +* `Understanding eBay Credentials`_ + + +Support +------- + +For developer support regarding the SDK code base please use this project's `Github issue tracking`_. + +For developer support regarding the eBay APIs please use the `eBay Developer Forums`_. + +Install +------- + +Installation instructions for *nix and windows can be found in the `INSTALL file`_. + +License +------- + +`COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0) + + +.. _INSTALL file: https://github.com/timotheus/ebaysdk-python/blob/master/INSTALL +.. _COMMON DEVELOPMENT AND DISTRIBUTION LICENSE: http://opensource.org/licenses/CDDL-1.0 +.. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials .. _eBay Developer Site: http://developer.ebay.com/ +.. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration +.. _Trading API Class: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class +.. _Finding API Class: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class +.. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class +.. _Merchandising API Class: https://github.com/timotheus/ebaysdk-python/wiki/Merchandising-API-Class +.. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class +.. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class +.. _eBay Developer Forums: https://www.x.com/developers/ebay/forums +.. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues +.. _Migrating from ebaysdk v1 to v2: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 diff --git a/ebaysdk/config.py b/ebaysdk/config.py index 0e95721..9722b2c 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -10,6 +10,7 @@ import yaml from ebaysdk import log +from ebaysdk.exception import ConnectionError class Config(object): """Config Class for all APIs connections @@ -67,6 +68,9 @@ def _populate_yaml_defaults(self): return self + if self.config_file: + raise ConnectionError('config file %s not found' % self.config_file) + def file(self): return self.config_file_used diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 74fcbb3..62ce07e 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -180,6 +180,9 @@ def process_response(self): self.response = Response(self.response, verb=self.verb, list_nodes=self._list_nodes, datetime_nodes=self.datetime_nodes) + # set for backward compatibility + self._response_content = self.response.content + if self.response.status_code != 200: self._response_error = self.response.reason diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index f239419..79e1ebc 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -5,6 +5,9 @@ Authored by: Tim Keefer Licensed under CDDL 1.0 ''' +import sys +if sys.version_info[0] >= 3: + raise ImportError('grequests does not work with python3+') import grequests from ebaysdk.exception import ConnectionError diff --git a/ebaysdk/response.py b/ebaysdk/response.py index f956b06..740212c 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -14,6 +14,7 @@ import json from ebaysdk.utils import get_dom_tree, python_2_unicode_compatible +from ebaysdk import log @python_2_unicode_compatible class ResponseDataObject(object): @@ -91,28 +92,28 @@ class Response(object): Doctests: - >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem Two1190179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' + >>> xml = b'Success1.12.02014-02-07T23:31:13.941ZItem Two1190179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' >>> o = ResponseDataObject({'content': xml}, []) >>> r = Response(o, verb='findItemsByProduct', list_nodes=['finditemsbyproductresponse.searchresult.item', 'finditemsbyproductresponse.paginationoutput.pagenumber']) >>> len(r.dom().getchildren()) > 2 True >>> r.reply.searchResult._count == '1' True - >>> r.reply.searchResult - {'item': [{'name': 'Item Two'}], '_count': '1'} + >>> type(r.reply.searchResult.item)==list + True >>> len(r.reply.paginationOutput.pageNumber) == 1 True - >>> xml = 'Success1.12.02014-02-07T23:31:13.941ZItem TwoUSMXItem One1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' + >>> xml = b'Success1.12.02014-02-07T23:31:13.941ZItem TwoUSMXItem One1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' >>> o = ResponseDataObject({'content': xml}, []) >>> r = Response(o, verb='findItemsByProduct', list_nodes=['searchResult.item']) >>> len(r.dom().getchildren()) > 2 True - >>> r.json() - '{"itemSearchURL": "http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1", "paginationOutput": {"totalPages": "90", "entriesPerPage": "2", "pageNumber": "1", "totalEntries": "179"}, "ack": "Success", "timestamp": "2014-02-07T23:31:13.941Z", "searchResult": {"item": [{"name": "Item Two", "shipping": {"c": ["US", "MX"]}}, {"name": "Item One"}], "_count": "2"}, "version": "1.12.0"}' - >>> r.dict() - {'itemSearchURL': 'http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1', 'paginationOutput': {'totalPages': '90', 'entriesPerPage': '2', 'pageNumber': '1', 'totalEntries': '179'}, 'ack': 'Success', 'timestamp': '2014-02-07T23:31:13.941Z', 'searchResult': {'item': [{'name': 'Item Two', 'shipping': {'c': ['US', 'MX']}}, {'name': 'Item One'}], '_count': '2'}, 'version': '1.12.0'} - >>> r.reply - {'itemSearchURL': 'http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1', 'paginationOutput': {'totalPages': '90', 'entriesPerPage': '2', 'pageNumber': '1', 'totalEntries': '179'}, 'ack': 'Success', 'timestamp': '2014-02-07T23:31:13.941Z', 'searchResult': {'item': [{'name': 'Item Two', 'shipping': {'c': ['US', 'MX']}}, {'name': 'Item One'}], '_count': '2'}, 'version': '1.12.0'} + >>> import json + >>> j = json.loads(r.json(), 'utf8') + >>> json.dumps(j, sort_keys=True) + '{"ack": "Success", "itemSearchURL": "http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1", "paginationOutput": {"entriesPerPage": "2", "pageNumber": "1", "totalEntries": "179", "totalPages": "90"}, "searchResult": {"_count": "2", "item": [{"name": "Item Two", "shipping": {"c": ["US", "MX"]}}, {"name": "Item One"}]}, "timestamp": "2014-02-07T23:31:13.941Z", "version": "1.12.0"}' + >>> sorted(r.dict().keys()) + ['ack', 'itemSearchURL', 'paginationOutput', 'searchResult', 'timestamp', 'version'] >>> len(r.reply.searchResult.item) == 2 True >>> r.reply.searchResult._count == '2' @@ -147,7 +148,8 @@ def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): self.reply = ResponseDataObject(self._dict, datetime_nodes=copy.copy(datetime_nodes)) - except lxml.etree.XMLSyntaxError: + except lxml.etree.XMLSyntaxError as e: + log.error('response syntax error: %s' % e) self.reply = ResponseDataObject({}, []) def _get_node_path(self, t): @@ -163,6 +165,13 @@ def _get_node_path(self, t): return '.'.join(path) + @staticmethod + def _pullval(v): + if len(v) == 1: + return v[0] + else: + return v + def _etree_to_dict(self, t): if type(t) == lxml.etree._Comment: return {} @@ -178,7 +187,8 @@ def _etree_to_dict(self, t): for k, v in dc.items(): dd[k].append(v) - d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}} + d = {t.tag: dict((k, self._pullval(v)) for k, v in dd.items())} + #d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}} # TODO: Optimizations? Forces a node to type list parent_path = self._get_node_path(t) @@ -208,11 +218,27 @@ def _parse_xml(self, xml): def _get_node_tag(self, node): return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '') - def dom(self): + def dom(self, lxml=True): + if not lxml: + # create and return a cElementTree DOM + pass + return self._dom def dict(self): return self._dict def json(self): - return json.dumps(self.dict()) \ No newline at end of file + return json.dumps(self.dict()) + + +if __name__ == '__main__': + + import os + import sys + + sys.path.insert(0, '%s/' % os.path.dirname(__file__)) + + import doctest + failure_count, test_count = doctest.testmod() + sys.exit(failure_count) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 805373a..08a3922 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -47,6 +47,7 @@ def smart_encode(value): if sys.version_info[0] < 3: return unicode(value).encode('utf-8') else: + return value return str(value) def to_xml(root): @@ -122,7 +123,7 @@ def dict2xml(root): ... }, ... ], ... } - >>> print dict2xml(attrdict) + >>> print(dict2xml(attrdict)) mydevidmyappidmycertid ''' diff --git a/samples/finding.py b/samples/finding.py index 86fcf65..888bd22 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -54,7 +54,7 @@ def run(opts): }) dump(api) - + from IPython import embed; embed() except ConnectionError as e: print(e) diff --git a/tests/__init__.py b/tests/__init__.py index d021c9b..f62df34 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,33 +6,42 @@ Licensed under CDDL 1.0 ''' +import sys import unittest import doctest import ebaysdk.utils -import ebaysdk.response import ebaysdk.config -import ebaysdk.http +import ebaysdk.response import ebaysdk.connection -import ebaysdk.finding +import ebaysdk.http import ebaysdk.shopping import ebaysdk.trading import ebaysdk.merchandising import ebaysdk.soa.finditem -import ebaysdk.parallel +import ebaysdk.finding + +# does not pass with python3.3 +try: + import ebaysdk.parallel +except ImportError: + pass def getTestSuite(): suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) + suite.addTest(doctest.DocTestSuite(ebaysdk.config)) suite.addTest(doctest.DocTestSuite(ebaysdk.response)) - suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) suite.addTest(doctest.DocTestSuite(ebaysdk.connection)) - suite.addTest(doctest.DocTestSuite(ebaysdk.config)) - suite.addTest(doctest.DocTestSuite(ebaysdk.utils)) - suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) suite.addTest(doctest.DocTestSuite(ebaysdk.http)) suite.addTest(doctest.DocTestSuite(ebaysdk.shopping)) suite.addTest(doctest.DocTestSuite(ebaysdk.trading)) suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) + suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) + + if not sys.version_info[0] >= 3: + suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) + # inside only #suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem)) From e7f1a98c2b79a886258e894bdaf76fc318467ec2 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 19 May 2014 08:48:38 -0700 Subject: [PATCH 175/218] Update README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 9af7bf6..2571380 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,7 @@ Welcome to the python ebaysdk This SDK is a programmatic interface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. Quick Example:: + import datetime from lxml.etree import _Element from ebaysdk.finding import Connection From 66a59486772d7174b0d53e9599b5ad8c27aebbfe Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 19 May 2014 15:51:33 -0700 Subject: [PATCH 176/218] add node types to serialize --- ebaysdk/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 08a3922..ca237d7 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -169,7 +169,9 @@ def dict2xml(root): xml = '%(xml)s<%(tag)s>%(value)s' % \ {'xml': xml, 'tag': key, 'value': smart_encode(value)} - elif isinstance(root, str) or isinstance(root, int) or isinstance(root, unicode): + elif isinstance(root, str) or isinstance(root, int) \ + or isinstance(root, unicode) or isinstance(root, long) \ + or isinstance(root, float): xml = '%s%s' % (xml, root) else: raise Exception('Unable to serialize node of type %s (%s)' % \ From c9abb9f651af6792bd629eb29cf9b6a115205823 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 21 May 2014 11:37:16 -0500 Subject: [PATCH 177/218] catch unicode error --- ebaysdk/utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index ca237d7..51d23db 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -44,11 +44,15 @@ def attribute_check(root): return attrs, value def smart_encode(value): - if sys.version_info[0] < 3: - return unicode(value).encode('utf-8') - else: + try: + if sys.version_info[0] < 3: + return unicode(value).encode('utf-8') + else: + return value + #return str(value) + + except UnicodeDecodeError: return value - return str(value) def to_xml(root): return dict2xml(root) From b7bfbc6d04330782738ea1439b899bf7b177c960 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 21 May 2014 11:37:51 -0500 Subject: [PATCH 178/218] modify sample --- samples/finding.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/finding.py b/samples/finding.py index 888bd22..c932e43 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -41,8 +41,9 @@ def run(opts): api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=True) - response = api.execute('findItemsAdvanced', { - 'keywords': u'niño', + api_request = { + #'keywords': u'niño', + 'keywords': u'GRAMMY Foundation®', 'itemFilter': [ {'name': 'Condition', 'value': 'Used'}, @@ -51,10 +52,11 @@ def run(opts): ], 'affiliate': {'trackingId': 1}, 'sortOrder': 'CountryDescending', - }) + } + + response = api.execute('findItemsAdvanced', api_request) dump(api) - from IPython import embed; embed() except ConnectionError as e: print(e) From bc50dd3e09c66d23eb91a4957e2e9efbf0d685bf Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 21 May 2014 13:03:12 -0500 Subject: [PATCH 179/218] modify http backend so that it skips response parsing --- ebaysdk/http/__init__.py | 11 +++++++++ ebaysdk/response.py | 50 ++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 7dd8aa3..0973366 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -88,6 +88,17 @@ def response_dict(self): except ExpatError: raise ConnectionResponseError('response is not well-formed') + def process_response(self): + """Post processing of the response""" + + self.response = Response(self.response, parse_response=False) + + # set for backward compatibility + self._response_content = self.response.content + + if self.response.status_code != 200: + self._response_error = self.response.reason + def execute(self, url, data=None, headers=dict(), method=None): "Executes the HTTP request." log.debug('execute: url=%s data=%s' % (url, data)) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index 740212c..bf74fe0 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -125,33 +125,37 @@ class Response(object): True ''' - def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): + def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[], parse_response=True): self._list_nodes=copy.copy(list_nodes) self._obj = obj - try: - self._dom = self._parse_xml(obj.content) - self._dict = self._etree_to_dict(self._dom) - - if verb and 'Envelope' in self._dict.keys(): - elem = self._dom.find('Body').find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) - elif verb: - elem = self._dom.find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict.get('%sResponse' % verb, self._dict) - - self.reply = ResponseDataObject(self._dict, - datetime_nodes=copy.copy(datetime_nodes)) - except lxml.etree.XMLSyntaxError as e: - log.error('response syntax error: %s' % e) - self.reply = ResponseDataObject({}, []) + if parse_response: + try: + self._dom = self._parse_xml(obj.content) + self._dict = self._etree_to_dict(self._dom) + + if verb and 'Envelope' in self._dict.keys(): + elem = self._dom.find('Body').find('%sResponse' % verb) + if elem is not None: + self._dom = elem + + self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) + elif verb: + elem = self._dom.find('%sResponse' % verb) + if elem is not None: + self._dom = elem + self._dict = self._dict.get('%sResponse' % verb, self._dict) + + self.reply = ResponseDataObject(self._dict, + datetime_nodes=copy.copy(datetime_nodes)) + except lxml.etree.XMLSyntaxError as e: + log.error('response syntax error: %s' % e) + self.reply = ResponseDataObject({}, []) + + else: + self.reply = ResponseDataObject({}, []) + def _get_node_path(self, t): i = t path = [] From 9af46e8eb6b073c706e1f49a9b11ca120a2c60c7 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 21 May 2014 13:21:11 -0500 Subject: [PATCH 180/218] revert parsing changes --- ebaysdk/http/__init__.py | 11 --------- ebaysdk/response.py | 50 ++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 0973366..7dd8aa3 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -88,17 +88,6 @@ def response_dict(self): except ExpatError: raise ConnectionResponseError('response is not well-formed') - def process_response(self): - """Post processing of the response""" - - self.response = Response(self.response, parse_response=False) - - # set for backward compatibility - self._response_content = self.response.content - - if self.response.status_code != 200: - self._response_error = self.response.reason - def execute(self, url, data=None, headers=dict(), method=None): "Executes the HTTP request." log.debug('execute: url=%s data=%s' % (url, data)) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index bf74fe0..b2fd3aa 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -125,37 +125,33 @@ class Response(object): True ''' - def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[], parse_response=True): + def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): self._list_nodes=copy.copy(list_nodes) self._obj = obj - if parse_response: - try: - self._dom = self._parse_xml(obj.content) - self._dict = self._etree_to_dict(self._dom) - - if verb and 'Envelope' in self._dict.keys(): - elem = self._dom.find('Body').find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) - elif verb: - elem = self._dom.find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict.get('%sResponse' % verb, self._dict) - - self.reply = ResponseDataObject(self._dict, - datetime_nodes=copy.copy(datetime_nodes)) - except lxml.etree.XMLSyntaxError as e: - log.error('response syntax error: %s' % e) - self.reply = ResponseDataObject({}, []) - - else: + try: + self._dom = self._parse_xml(obj.content) + self._dict = self._etree_to_dict(self._dom) + + if verb and 'Envelope' in self._dict.keys(): + elem = self._dom.find('Body').find('%sResponse' % verb) + if elem is not None: + self._dom = elem + + self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) + elif verb: + elem = self._dom.find('%sResponse' % verb) + if elem is not None: + self._dom = elem + + self._dict = self._dict.get('%sResponse' % verb, self._dict) + + self.reply = ResponseDataObject(self._dict, + datetime_nodes=copy.copy(datetime_nodes)) + except lxml.etree.XMLSyntaxError as e: + log.debug('response parse failed: %s' % e) self.reply = ResponseDataObject({}, []) - + def _get_node_path(self, t): i = t path = [] From 19389eb87723511cd1e75271ffdcb7815df3b9b9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 22 May 2014 10:41:11 -0500 Subject: [PATCH 181/218] fix the mixup between compatibility and version --- ebaysdk/finding/__init__.py | 3 +-- ebaysdk/merchandising/__init__.py | 2 +- ebaysdk/trading/__init__.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 67ce977..1aba9cf 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -53,7 +53,7 @@ def __init__(self, **kwargs): uri -- API endpoint uri (default: /services/search/FindingService/v1) appid -- eBay application id siteid -- eBay country site id (default: EBAY-US) - compatibility -- version number (default: 1.0.0) + version -- version number (default: 1.0.0) https -- execute of https (default: False) proxy_host -- proxy hostname proxy_port -- proxy port number @@ -84,7 +84,6 @@ def __init__(self, **kwargs): self.config.set('iaf_token', None) self.config.set('appid', None) self.config.set('version', '1.12.0') - self.config.set('compatibility', '1.0.0') self.config.set('service', 'FindingService') self.config.set('doc_url', 'http://developer.ebay.com/DevZone/finding/CallRef/index.html') diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index 17b7d75..b0cc3f7 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -43,7 +43,7 @@ def __init__(self, **kwargs): uri -- API endpoint uri (default: /MerchandisingService) appid -- eBay application id siteid -- eBay country site id (default: 0 (US)) - compatibility -- version number (default: 799) + version -- version number (default: 799) https -- execute of https (default: True) proxy_host -- proxy hostname proxy_port -- proxy port number diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 8f11d0b..69a5c8a 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -90,7 +90,6 @@ def __init__(self, **kwargs): self.config.set('appid', None) self.config.set('devid', None) self.config.set('certid', None) - self.config.set('version', '837') self.config.set('compatibility', '837') self.config.set('doc_url', 'http://developer.ebay.com/devzone/xml/docs/reference/ebay/index.html') @@ -668,7 +667,7 @@ def __init__(self, **kwargs): def build_request_headers(self, verb): headers = { - "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('version', ''), + "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('compatibility', ''), "X-EBAY-API-DEV-NAME": self.config.get('devid', ''), "X-EBAY-API-APP-NAME": self.config.get('appid', ''), "X-EBAY-API-CERT-NAME": self.config.get('certid', ''), From 11fecabff8006b5162e399071ec53bee7cad0501 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 22 May 2014 16:05:30 -0500 Subject: [PATCH 182/218] add parameter to disable response parsing on http backend --- ebaysdk/connection.py | 9 +++++--- ebaysdk/http/__init__.py | 4 ++-- ebaysdk/response.py | 47 +++++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 62ce07e..35eeeb1 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -174,11 +174,14 @@ def execute_request(self): log.debug('headers=%s' % self.response.headers) log.debug('content=%s' % self.response.text) - def process_response(self): + def process_response(self, parse_response=True): """Post processing of the response""" - self.response = Response(self.response, verb=self.verb, - list_nodes=self._list_nodes, datetime_nodes=self.datetime_nodes) + self.response = Response(self.response, + verb=self.verb, + list_nodes=self._list_nodes, + datetime_nodes=self.datetime_nodes, + parse_response=parse_response) # set for backward compatibility self._response_content = self.response.content diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 7dd8aa3..cdc783a 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -88,7 +88,7 @@ def response_dict(self): except ExpatError: raise ConnectionResponseError('response is not well-formed') - def execute(self, url, data=None, headers=dict(), method=None): + def execute(self, url, data=None, headers=dict(), method=None, parse_response=True): "Executes the HTTP request." log.debug('execute: url=%s data=%s' % (url, data)) @@ -103,7 +103,7 @@ def execute(self, url, data=None, headers=dict(), method=None): self.parallel._add_request(self) return None - self.process_response() + self.process_response(parse_response) self.error_check() log.debug('total time=%s' % (time.time() - self._time)) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index b2fd3aa..6272cbf 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -125,31 +125,34 @@ class Response(object): True ''' - def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[]): + def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[], parse_response=True): self._list_nodes=copy.copy(list_nodes) self._obj = obj - try: - self._dom = self._parse_xml(obj.content) - self._dict = self._etree_to_dict(self._dom) - - if verb and 'Envelope' in self._dict.keys(): - elem = self._dom.find('Body').find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) - elif verb: - elem = self._dom.find('%sResponse' % verb) - if elem is not None: - self._dom = elem - - self._dict = self._dict.get('%sResponse' % verb, self._dict) - - self.reply = ResponseDataObject(self._dict, - datetime_nodes=copy.copy(datetime_nodes)) - except lxml.etree.XMLSyntaxError as e: - log.debug('response parse failed: %s' % e) + if parse_response: + try: + self._dom = self._parse_xml(obj.content) + self._dict = self._etree_to_dict(self._dom) + + if verb and 'Envelope' in self._dict.keys(): + elem = self._dom.find('Body').find('%sResponse' % verb) + if elem is not None: + self._dom = elem + + self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict) + elif verb: + elem = self._dom.find('%sResponse' % verb) + if elem is not None: + self._dom = elem + + self._dict = self._dict.get('%sResponse' % verb, self._dict) + + self.reply = ResponseDataObject(self._dict, + datetime_nodes=copy.copy(datetime_nodes)) + except lxml.etree.XMLSyntaxError as e: + log.debug('response parse failed: %s' % e) + self.reply = ResponseDataObject({}, []) + else: self.reply = ResponseDataObject({}, []) def _get_node_path(self, t): From 82336ba17b5ceaf44bd0e3b69273c3f67fd2a617 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 22 May 2014 19:25:35 -0500 Subject: [PATCH 183/218] fix function arg --- ebaysdk/http/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index cdc783a..48ee113 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -103,7 +103,7 @@ def execute(self, url, data=None, headers=dict(), method=None, parse_response=Tr self.parallel._add_request(self) return None - self.process_response(parse_response) + self.process_response(parse_response=parse_response) self.error_check() log.debug('total time=%s' % (time.time() - self._time)) From 9b0aa77c9d889a14e96064d3b8dd64494996532c Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 6 Jun 2014 15:31:13 -0700 Subject: [PATCH 184/218] add verb_attrs to build_request_data method to allow for attributes on the top level node --- ebaysdk/connection.py | 8 ++++---- ebaysdk/finding/__init__.py | 2 +- ebaysdk/merchandising/__init__.py | 2 +- ebaysdk/shopping/__init__.py | 2 +- ebaysdk/soa/__init__.py | 2 +- ebaysdk/soa/finditem.py | 2 +- ebaysdk/trading/__init__.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 35eeeb1..54392ac 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -103,7 +103,7 @@ def _add_prefix(self, nodes, verb): if not nodes[i].startswith(verb.lower()): nodes[i] = "%sresponse.%s" % (verb.lower(), nodes[i].lower()) - def execute(self, verb, data=None, list_nodes=[]): + def execute(self, verb, data=None, list_nodes=[], verb_attrs=None): "Executes the HTTP request." log.debug('execute: verb=%s data=%s' % (verb, data)) @@ -115,7 +115,7 @@ def execute(self, verb, data=None, list_nodes=[]): if hasattr(self, 'base_list_nodes'): self._list_nodes += self.base_list_nodes - self.build_request(verb, data) + self.build_request(verb, data, verb_attrs) self.execute_request() if hasattr(self.response, 'content'): @@ -126,7 +126,7 @@ def execute(self, verb, data=None, list_nodes=[]): return self.response - def build_request(self, verb, data): + def build_request(self, verb, data, verb_attrs): self.verb = verb self._request_dict = data @@ -144,7 +144,7 @@ def build_request(self, verb, data): request = Request(self.method, url, - data=self.build_request_data(verb, data), + data=self.build_request_data(verb, data, verb_attrs), headers=headers, ) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 1aba9cf..5a6fbd5 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -186,7 +186,7 @@ def build_request_headers(self, verb): "Content-Type": "text/xml" } - def build_request_data(self, verb, data): + def build_request_data(self, verb, data, verb_attrs): xml = "" xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" xml += dict2xml(data) diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py index b0cc3f7..a858967 100644 --- a/ebaysdk/merchandising/__init__.py +++ b/ebaysdk/merchandising/__init__.py @@ -82,7 +82,7 @@ def build_request_headers(self, verb): } - def build_request_data(self, verb, data): + def build_request_data(self, verb, data, verb_attrs): xml = "" xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" xml += dict2xml(data) diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 42cf2bf..11a4cc8 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -148,7 +148,7 @@ def build_request_headers(self, verb): return headers - def build_request_data(self, verb, data): + def build_request_data(self, verb, data, verb_attrs): xml = "" xml += "<" + verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" diff --git a/ebaysdk/soa/__init__.py b/ebaysdk/soa/__init__.py index 91ce53d..ef01121 100644 --- a/ebaysdk/soa/__init__.py +++ b/ebaysdk/soa/__init__.py @@ -82,7 +82,7 @@ def build_request_headers(self, verb): 'X-EBAY-SOA-MESSAGE-PROTOCOL': self.config.get('message_protocol'), } - def build_request_data(self, verb, data): + def build_request_data(self, verb, data, verb_attrs): xml = '' xml += '" if not self.config.get('iaf_token', None): From 8e9d77dcc91b00672689ff93b1e0d01a43d1ad56 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 13:25:09 -0700 Subject: [PATCH 185/218] add response to exception classes --- README.rst | 12 ++++++++++-- ebaysdk/connection.py | 6 +++--- ebaysdk/exception.py | 32 ++++++++++++++++++++++++++++---- samples/finding.py | 2 ++ samples/finditem.py | 13 ++++++++----- samples/merchandising.py | 1 + samples/parallel.py | 1 + samples/shopping.py | 8 ++++++-- samples/trading.py | 12 +++++++++++- 9 files changed, 70 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 2571380..a63d137 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,15 @@ Quick Example:: assert(type(response.dom() == _Element) except ConnectionError as e: - raise e + print(e) + print(e.response.dict()) + + +Migrating from v1 to v2 +----------------------- + +For a complete guide on migrating from ebaysdk v1 to v2 and see an overview of the additional features in v2 please read the `v1 to v2 guide`_ + Getting Started --------------- @@ -74,5 +82,5 @@ License .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class .. _eBay Developer Forums: https://www.x.com/developers/ebay/forums .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues - +.. _v1 to v2 guide: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 54392ac..6e6036a 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -194,7 +194,7 @@ def error_check(self): if estr and self.config.get('errors', True): log.error(estr) - raise ConnectionError(estr) + raise ConnectionError(estr, self.response) def response_codes(self): return self._resp_codes @@ -254,7 +254,7 @@ def response_dom(self): self.verb + 'Response')[0] except ExpatError as e: - raise ConnectionResponseError("Invalid Verb: %s (%s)" % (self.verb, e)) + raise ConnectionResponseError("Invalid Verb: %s (%s)" % (self.verb, e), self.response) except IndexError: self._response_dom = dom @@ -305,7 +305,7 @@ def error(self): error_array.extend(self._get_resp_body_errors()) if len(error_array) > 0: - error_string = "%s: %s" % (self.verb, ", ".join(error_array)) + error_string = u"%s: %s" % (self.verb, u", ".join(error_array)) return error_string diff --git a/ebaysdk/exception.py b/ebaysdk/exception.py index 399ec29..37ab4f1 100644 --- a/ebaysdk/exception.py +++ b/ebaysdk/exception.py @@ -7,13 +7,37 @@ ''' class ConnectionError(Exception): - pass + def __init__(self, msg, response): + super(ConnectionError, self).__init__(u'%s' % msg) + self.message = u'%s' % msg + self.response = response + + def __str__(self): + return repr(self.message) class ConnectionResponseError(Exception): - pass + def __init__(self, msg, response): + super(ConnectionError, self).__init__(u'%s' % msg) + self.message = u'%s' % msg + self.response = response + + def __str__(self): + return repr(self.message) class RequestPaginationError(Exception): - pass + def __init__(self, msg, response): + super(ConnectionError, self).__init__(u'%s' % msg) + self.message = u'%s' % msg + self.response = response + + def __str__(self): + return repr(self.message) class PaginationLimit(Exception): - pass \ No newline at end of file + def __init__(self, msg, response): + super(ConnectionError, self).__init__(u'%s' % msg) + self.message = u'%s' % msg + self.response = response + + def __str__(self): + return repr(self.message) diff --git a/samples/finding.py b/samples/finding.py index c932e43..a44bd26 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -59,6 +59,7 @@ def run(opts): dump(api) except ConnectionError as e: print(e) + print(e.response.dict()) def run2(opts): try: @@ -71,6 +72,7 @@ def run2(opts): except ConnectionError as e: print(e) + print(e.response.dict()) if __name__ == "__main__": diff --git a/samples/finditem.py b/samples/finditem.py index d6c0652..9af3400 100644 --- a/samples/finditem.py +++ b/samples/finditem.py @@ -46,12 +46,14 @@ def run(opts): shopping = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml, warnings=False) - response = shopping.execute('FindPopularItems', {'QueryKeywords': 'Python'}) - - nodes = shopping.response_dom().getElementsByTagName('ItemID') - itemIds = [getNodeText(n) for n in nodes] + response = shopping.execute('FindPopularItems', + {'QueryKeywords': 'Python'}) + + nodes = response.dom().xpath('//ItemID') + itemIds = [n.text for n in nodes] - api = FindItem(debug=opts.debug, consumer_id=opts.consumer_id, config_file=opts.yaml) + api = FindItem(debug=opts.debug, + consumer_id=opts.consumer_id, config_file=opts.yaml) records = api.find_items_by_ids([itemIds[0]]) @@ -69,6 +71,7 @@ def run(opts): except ConnectionError as e: print(e) + print(e.response.dict()) if __name__ == "__main__": diff --git a/samples/merchandising.py b/samples/merchandising.py index b416c69..c5cefa4 100644 --- a/samples/merchandising.py +++ b/samples/merchandising.py @@ -44,6 +44,7 @@ def run(opts): dump(api) except ConnectionError as e: print(e) + print(e.response.dict()) if __name__ == "__main__": diff --git a/samples/parallel.py b/samples/parallel.py index c3d960d..788b06b 100644 --- a/samples/parallel.py +++ b/samples/parallel.py @@ -67,6 +67,7 @@ def run(opts): except ConnectionError as e: print(e) + print(e.response.dict()) if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/shopping.py b/samples/shopping.py index 4fff2e9..0131041 100644 --- a/samples/shopping.py +++ b/samples/shopping.py @@ -56,8 +56,8 @@ def run(opts): print(item.Title) except ConnectionError as e: - print(e) - + print(e) + print(e.response.dict()) def popularSearches(opts): @@ -102,6 +102,7 @@ def popularSearches(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def categoryInfo(opts): @@ -115,6 +116,7 @@ def categoryInfo(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def with_affiliate_info(opts): try: @@ -132,6 +134,7 @@ def with_affiliate_info(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def using_attributes(opts): @@ -147,6 +150,7 @@ def using_attributes(opts): except ConnectionError as e: print(e) + print(e.response.dict()) if __name__ == "__main__": (opts, args) = init_options() diff --git a/samples/trading.py b/samples/trading.py index 0450c0e..1ca7b81 100644 --- a/samples/trading.py +++ b/samples/trading.py @@ -55,6 +55,7 @@ def run(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def feedback(opts): try: @@ -71,7 +72,7 @@ def feedback(opts): except ConnectionError as e: print(e) - + print(e.response.dict()) def getTokenStatus(opts): @@ -84,6 +85,7 @@ def getTokenStatus(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def verifyAddItem(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc @@ -135,6 +137,8 @@ def verifyAddItem(opts): except ConnectionError as e: print(e) + print(e.response.dict()) + def verifyAddItemErrorCodes(opts): """http://www.utilities-online.info/xmltojson/#.UXli2it4avc @@ -193,6 +197,7 @@ def verifyAddItemErrorCodes(opts): print("Invalid data in request") print(e) + print(e.response.dict()) def uploadPicture(opts): @@ -211,6 +216,7 @@ def uploadPicture(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def memberMessages(opts): @@ -247,6 +253,7 @@ def memberMessages(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def getUser(opts): try: @@ -259,6 +266,7 @@ def getUser(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def getOrders(opts): @@ -271,6 +279,7 @@ def getOrders(opts): except ConnectionError as e: print(e) + print(e.response.dict()) def categories(opts): @@ -289,6 +298,7 @@ def categories(opts): except ConnectionError as e: print(e) + print(e.response.dict()) ''' api = trading(domain='api.sandbox.ebay.com') From 8e720caf20261772408ac935d7e9b39f2b51084f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 14:29:31 -0700 Subject: [PATCH 186/218] Update README.rst --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a63d137..065a2d9 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,9 @@ SDK Configuration * `YAML Configuration`_ * `Understanding eBay Credentials`_ +Sample Code + +Sample code can be found in the `samples directory`_. Support ------- @@ -83,4 +86,4 @@ License .. _eBay Developer Forums: https://www.x.com/developers/ebay/forums .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues .. _v1 to v2 guide: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 - +.. _samples directory: https://github.com/timotheus/ebaysdk-python/tree/master/samples From ad15ad937446702a4d6308a1a2dbfe311b083228 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 14:31:33 -0700 Subject: [PATCH 187/218] Update README.rst --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 065a2d9..f8ca436 100644 --- a/README.rst +++ b/README.rst @@ -50,8 +50,6 @@ SDK Configuration * `YAML Configuration`_ * `Understanding eBay Credentials`_ -Sample Code - Sample code can be found in the `samples directory`_. Support From 9a68716f0fb0a787ab04c760c6d41330c90d4ebd Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 14:32:58 -0700 Subject: [PATCH 188/218] Delete index.rst --- docs/index.rst | 94 -------------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 docs/index.rst diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 3c4f831..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,94 +0,0 @@ -Welcome to the python ebaysdk -============================= - -This SDK is a programmatic interface into the eBay APIs. It simplifies development and cuts development time by standardizing calls, response processing, error handling, and debugging across the Finding, Shopping, Merchandising & Trading APIs. - -Quick Start:: - import datetime - from lxml.etree import _Element - from ebaysdk.finding import Connection - - try: - api = Connection(appid='YOUR_APPID_HERE') - response = api.execute('findItemsAdvanced', {'keywords': 'legos'}) - - assert(response.reply.ack == 'Success') - assert(type(response.reply.timestamp) == datetime.datetime) - assert(type(response.reply.searchResult.item) == list) - - item = response.reply.searchResult.item[0] - assert(type(item.listingInfo.endTime) == datetime.datetime) - assert(type(response.dict()) == dict) - assert(type(response.dom() == _Element) - - except ConnectionError as e: - raise e - - -Migrating from v1 to v2 ------------------------ - -After lots of planning and beating my head against the wall, version 2 of this SDK has come to contain a few changes that break backward compatibility. These changes were necessary to optimize this project and help keep it moving forward. Below I’ll highlight the changes and point out code changes that you may need to make. - -Changes - -* Import: Modified package structure -* execute(): Modified return value -* Switched to lxml.etree -* Added a new response class (response.reply, response.dom(), response.dict(), response.json()) -* Added ebaysdk exception classes -* Modified utils.py (dict2xml, xml2dict) - -Please read the full doc on `Migrating from ebaysdk v1 to v2`_ - -Getting Started ---------------- - -SDK Classes - -* `Trading API Class`_ - secure, authenticated access to private eBay data. -* `Finding API Class`_ - access eBay's next generation search capabilities. -* `Shopping API Class`_ - performance-optimized, lightweight APIs for accessing public eBay data. -* `Merchandising API Class`_ - find items and products on eBay that provide good value or are otherwise popular with eBay buyers. -* `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. -* `Parallel Class`_ - SDK support for concurrent API calls. - -SDK Configuration - -* `YAML Configuration`_ -* `Understanding eBay Credentials`_ - - -Support -------- - -For developer support regarding the SDK code base please use this project's `Github issue tracking`_. - -For developer support regarding the eBay APIs please use the `eBay Developer Forums`_. - -Install -------- - -Installation instructions for *nix and windows can be found in the `INSTALL file`_. - -License -------- - -`COMMON DEVELOPMENT AND DISTRIBUTION LICENSE`_ Version 1.0 (CDDL-1.0) - - -.. _INSTALL file: https://github.com/timotheus/ebaysdk-python/blob/master/INSTALL -.. _COMMON DEVELOPMENT AND DISTRIBUTION LICENSE: http://opensource.org/licenses/CDDL-1.0 -.. _Understanding eBay Credentials: https://github.com/timotheus/ebaysdk-python/wiki/eBay-Credentials -.. _eBay Developer Site: http://developer.ebay.com/ -.. _YAML Configuration: https://github.com/timotheus/ebaysdk-python/wiki/YAML-Configuration -.. _Trading API Class: https://github.com/timotheus/ebaysdk-python/wiki/Trading-API-Class -.. _Finding API Class: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class -.. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class -.. _Merchandising API Class: https://github.com/timotheus/ebaysdk-python/wiki/Merchandising-API-Class -.. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class -.. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class -.. _eBay Developer Forums: https://www.x.com/developers/ebay/forums -.. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues -.. _Migrating from ebaysdk v1 to v2: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 - From 693178c91986f2940177ec953e4e8af3f96c789a Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 14:33:43 -0700 Subject: [PATCH 189/218] Update INSTALL --- INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 8896770..722dee1 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Running the tests: ~/> export EBAY_YAML='myebay.yaml'; python setup.py test -Installing ebaysdk on Mac: +Installing ebaysdk on Mac, Linux, Unix: 1) Install the SDK with easy_install From 40029c6d066105ae41206868a273e7232b7a322f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 16:31:30 -0700 Subject: [PATCH 190/218] fix request dictionary attr handling --- ebaysdk/utils.py | 4 ++ samples/request_dictionary.py | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 samples/request_dictionary.py diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index 51d23db..01368cc 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -143,6 +143,8 @@ def dict2xml(root): if not value: value = dict2xml(root[key]) + elif isinstance(value, dict): + value = dict2xml(value) attrs_sp = '' if len(attrs) > 0: @@ -159,6 +161,8 @@ def dict2xml(root): if not value: value = dict2xml(item) + elif isinstance(value, dict): + value = dict2xml(value) attrs_sp = '' if len(attrs) > 0: diff --git a/samples/request_dictionary.py b/samples/request_dictionary.py new file mode 100644 index 0000000..83bb55e --- /dev/null +++ b/samples/request_dictionary.py @@ -0,0 +1,83 @@ +import os +import sys + +sys.path.insert(0, '%s/../' % os.path.dirname(__file__)) + +from ebaysdk.utils import dict2xml + +dict1 = {'a': 'b'} + +assert(dict2xml(dict1)=='b') + +''' dict2 XML Output +222 +''' + +dict2 = { + 'tag': { + '#text': 222, + '@attrs': {'site': 'US', 'attr2': 'attr2value'} + } +} + +assert(dict2xml(dict2)=='222') + +''' dict3 XML Output + + Condition + Used + + + LocatedIn + GB + + + More + more + +''' + +dict3 = { + 'itemFilter': [ + {'name': 'Condition', 'value': 'Used'}, + {'name': 'LocatedIn', 'value': 'GB'}, + {'name': 'More', 'value': 'more'}, + ] +} + +assert(dict2xml(dict3)=='ConditionUsedLocatedInGBMoremore') + +''' dict4 XML Output + + tag2 value + +''' + +dict4 = { + 'tag1': { + '#text': {'tag2': 'tag2 value'}, + '@attrs': {'site': 'US', 'attr2': 'attr2value'} + } +} + +assert(dict2xml(dict4)=='tag2 value') + +''' dict5 XML Output + + tag2 value + +''' + +dict5 = { + 'tag1': { + '#text': {'tag2': { + '#text': 'tag2 value', + '@attrs': {'tag2attr': 'myvalue'} + }}, + '@attrs': {'site': 'US', 'tag1attr': 'myvalue'} + } +} + +assert(dict2xml(dict5)=='tag2 value') + + From 2f63007554fc541078a66750e164c9b3d6440d0f Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 11 Jun 2014 16:49:39 -0700 Subject: [PATCH 191/218] Update README.rst --- README.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index f8ca436..f58f6c1 100644 --- a/README.rst +++ b/README.rst @@ -36,7 +36,7 @@ For a complete guide on migrating from ebaysdk v1 to v2 and see an overview of t Getting Started --------------- -SDK Classes +1) SDK Classes * `Trading API Class`_ - secure, authenticated access to private eBay data. * `Finding API Class`_ - access eBay's next generation search capabilities. @@ -45,12 +45,14 @@ SDK Classes * `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. * `Parallel Class`_ - SDK support for concurrent API calls. -SDK Configuration +2) SDK Configuration * `YAML Configuration`_ * `Understanding eBay Credentials`_ -Sample code can be found in the `samples directory`_. +3) Sample code can be found in the `samples directory`_. + +4) Understanding the `Request Dictionary`_. Support ------- @@ -85,3 +87,4 @@ License .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues .. _v1 to v2 guide: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 .. _samples directory: https://github.com/timotheus/ebaysdk-python/tree/master/samples +.. _Request Dictionary: https://github.com/timotheus/ebaysdk-python/wiki/Request-Dictionary From dfee9ad9f307dc49a7b5c27dc5ee9bc2d1c8a246 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 13 Jun 2014 09:40:31 -0700 Subject: [PATCH 192/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f58f6c1..d768ca4 100644 --- a/README.rst +++ b/README.rst @@ -83,7 +83,7 @@ License .. _Merchandising API Class: https://github.com/timotheus/ebaysdk-python/wiki/Merchandising-API-Class .. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class -.. _eBay Developer Forums: https://www.x.com/developers/ebay/forums +.. _eBay Developer Forums: https://go.developer.ebay.com/developers/ebay/forums-support/support .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues .. _v1 to v2 guide: https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 .. _samples directory: https://github.com/timotheus/ebaysdk-python/tree/master/samples From 4ef4675b97eb253e5da29b8dafaf1882ac6ed051 Mon Sep 17 00:00:00 2001 From: Jovan Brakus Date: Sun, 15 Jun 2014 17:18:57 +0200 Subject: [PATCH 193/218] Fixed constructors of Error classes --- ebaysdk/exception.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ebaysdk/exception.py b/ebaysdk/exception.py index 37ab4f1..f1c2b01 100644 --- a/ebaysdk/exception.py +++ b/ebaysdk/exception.py @@ -17,7 +17,7 @@ def __str__(self): class ConnectionResponseError(Exception): def __init__(self, msg, response): - super(ConnectionError, self).__init__(u'%s' % msg) + super(ConnectionResponseError, self).__init__(u'%s' % msg) self.message = u'%s' % msg self.response = response @@ -26,7 +26,7 @@ def __str__(self): class RequestPaginationError(Exception): def __init__(self, msg, response): - super(ConnectionError, self).__init__(u'%s' % msg) + super(RequestPaginationError, self).__init__(u'%s' % msg) self.message = u'%s' % msg self.response = response @@ -35,7 +35,7 @@ def __str__(self): class PaginationLimit(Exception): def __init__(self, msg, response): - super(ConnectionError, self).__init__(u'%s' % msg) + super(PaginationLimit, self).__init__(u'%s' % msg) self.message = u'%s' % msg self.response = response From d68dc8353431c21c6c6f76cae2d0cfc133011808 Mon Sep 17 00:00:00 2001 From: Jovan Brakus Date: Sun, 15 Jun 2014 17:21:39 +0200 Subject: [PATCH 194/218] Fixed raising Errors with responses --- ebaysdk/finding/__init__.py | 4 ++-- ebaysdk/http/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index 5a6fbd5..f68db84 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -285,13 +285,13 @@ def _get_resp_body_errors(self): def next_page(self): if type(self._request_dict) is not dict: - raise RequestPaginationError("request data is not of type dict") + raise RequestPaginationError("request data is not of type dict", self.response) epp = self._request_dict.get('paginationInput', {}).get('enteriesPerPage', None) num = int(self.response.reply.paginationOutput.pageNumber) if num >= int(self.response.reply.paginationOutput.totalPages): - raise PaginationLimit("no more pages to process") + raise PaginationLimit("no more pages to process", self.response) return None self._request_dict['paginationInput'] = {} diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 48ee113..d43affa 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -76,7 +76,7 @@ def response_dom(self): return self._response_dom except ExpatError: - raise ConnectionResponseError('response is not well-formed') + raise ConnectionResponseError('response is not well-formed', self.response) def response_dict(self): "Return the HTTP response dictionary." @@ -86,7 +86,7 @@ def response_dict(self): return self.response.dict() except ExpatError: - raise ConnectionResponseError('response is not well-formed') + raise ConnectionResponseError('response is not well-formed', self.response) def execute(self, url, data=None, headers=dict(), method=None, parse_response=True): "Executes the HTTP request." From 82ebad302d4e82214f3c62b55cac4b34b1cf3782 Mon Sep 17 00:00:00 2001 From: Molanda Date: Tue, 17 Jun 2014 19:46:58 -0700 Subject: [PATCH 195/218] Execute on http should return self.response --- ebaysdk/http/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 296b102..474c366 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -108,7 +108,7 @@ def execute(self, url, data=None, headers=dict(), method=None): log.debug('total time=%s' % (time.time() - self._time)) - return self + return self.response def build_request(self, url, data, headers): From a31c092a8a90b1a96efbc1222939ab05d2ad240d Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 18 Jun 2014 02:55:49 +0000 Subject: [PATCH 196/218] Handle unicode and empty nodes --- ebaysdk/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/response.py b/ebaysdk/response.py index 6272cbf..bb74c66 100644 --- a/ebaysdk/response.py +++ b/ebaysdk/response.py @@ -62,7 +62,7 @@ def _load_dict(self, mydict, datetime_nodes): elif isinstance(a[1], list): objs = [] for i in a[1]: - if isinstance(i, str): + if i is None or isinstance(i, str) or isinstance(i, unicode): objs.append(i) else: objs.append(ResponseDataObject(i, datetime_nodes)) From f968fa434f3a04ea9e272ce2c7cda08d99ba0eaf Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 23 Jun 2014 14:12:41 -0700 Subject: [PATCH 197/218] add request dict example --- samples/request_dictionary.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/samples/request_dictionary.py b/samples/request_dictionary.py index 83bb55e..57fde67 100644 --- a/samples/request_dictionary.py +++ b/samples/request_dictionary.py @@ -80,4 +80,16 @@ assert(dict2xml(dict5)=='tag2 value') +''' dict6 outputSelector +SellerInfo +GalleryInfo +''' + +dict6 = { + 'outputSelector': [ + 'SellerInfo', + 'GalleryInfo' + ] +} +assert(dict2xml(dict6)=='SellerInfoGalleryInfo') From 1b4247d2d842b36c2859d5ed0af2c5b74f72cef8 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Thu, 26 Jun 2014 13:48:49 -0700 Subject: [PATCH 198/218] fix finditem soa error --- ebaysdk/soa/finditem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index fe9dbe9..c909dfc 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -92,6 +92,10 @@ def mappedResponse(self): i = 0 for values_dict in r.get('value', {}): + + if values_dict is None: + continue + for key, value in values_dict.items(): value_data = None if type(value) == list: From f185423456483145f67ca4b960d4091ac4cda942 Mon Sep 17 00:00:00 2001 From: Stewart Henderson Date: Sun, 29 Jun 2014 16:42:40 -0500 Subject: [PATCH 199/218] Update README.rst Corrected a syntax error in the readme. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d768ca4..c834d47 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Quick Example:: item = response.reply.searchResult.item[0] assert(type(item.listingInfo.endTime) == datetime.datetime) assert(type(response.dict()) == dict) - assert(type(response.dom() == _Element) + assert(type(response.dom() == _Element)) except ConnectionError as e: print(e) From 5c6bac70bd842a0338b38063b1786199669457fa Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 30 Jun 2014 09:39:02 -0700 Subject: [PATCH 200/218] add expection for config error --- ebaysdk/config.py | 4 ++-- ebaysdk/exception.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ebaysdk/config.py b/ebaysdk/config.py index 9722b2c..abf922c 100644 --- a/ebaysdk/config.py +++ b/ebaysdk/config.py @@ -10,7 +10,7 @@ import yaml from ebaysdk import log -from ebaysdk.exception import ConnectionError +from ebaysdk.exception import ConnectionConfigError class Config(object): """Config Class for all APIs connections @@ -69,7 +69,7 @@ def _populate_yaml_defaults(self): return self if self.config_file: - raise ConnectionError('config file %s not found' % self.config_file) + raise ConnectionConfigError('config file %s not found' % self.config_file) def file(self): return self.config_file_used diff --git a/ebaysdk/exception.py b/ebaysdk/exception.py index f1c2b01..eb3cc36 100644 --- a/ebaysdk/exception.py +++ b/ebaysdk/exception.py @@ -15,6 +15,14 @@ def __init__(self, msg, response): def __str__(self): return repr(self.message) +class ConnectionConfigError(Exception): + def __init__(self, msg): + super(ConnectionConfigError, self).__init__(u'%s' % msg) + self.message = u'%s' % msg + + def __str__(self): + return repr(self.message) + class ConnectionResponseError(Exception): def __init__(self, msg, response): super(ConnectionResponseError, self).__init__(u'%s' % msg) From 9a71b78ea911041d1c23a9633a2af22887140bda Mon Sep 17 00:00:00 2001 From: jweigel Date: Mon, 30 Jun 2014 14:54:50 -0700 Subject: [PATCH 201/218] Fix find item to allow alternate domain to be specified. --- ebaysdk/soa/finditem.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py index c909dfc..b99aead 100644 --- a/ebaysdk/soa/finditem.py +++ b/ebaysdk/soa/finditem.py @@ -23,7 +23,7 @@ class Connection(BaseConnection): SOAP support. FindItemServiceNextGen works fine with standard XML and lets avoid all of the ugliness associated with SOAP. - >>> from ebaysdk.shopping import Connection as Shopping + >>> from ebaysdk.shopping import Connection as Shopping >>> s = Shopping(config_file=os.environ.get('EBAY_YAML')) >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) >>> nodes = s.response_dom().getElementsByTagName('ItemID') @@ -36,15 +36,15 @@ class Connection(BaseConnection): True """ - def __init__(self, site_id='EBAY-US', debug=False, consumer_id=None, - **kwargs): + def __init__(self, site_id='EBAY-US', debug=False, consumer_id=None, + domain='apifindingcore.vip.ebay.com', **kwargs): super(Connection, self).__init__(consumer_id=consumer_id, - domain='apifindingcore.vip.ebay.com', + domain=domain, app_config=None, site_id=site_id, debug=debug, **kwargs) - + self.config.set('domain', 'apifindingcore.vip.ebay.com') self.config.set('service', 'FindItemServiceNextGen', force=True) self.config.set('https', False) @@ -79,8 +79,8 @@ def findItemsByIds(self, ebay_item_ids, 'name': rtype } }) - - args = {'id': ebay_item_ids, 'readSet': read_set_node} + + args = {'id': ebay_item_ids, 'readSet': read_set_node} self.execute('findItemsByIds', args) return self.mappedResponse() @@ -109,12 +109,12 @@ def mappedResponse(self): records.append(mydict) - return records + return records def find_items_by_ids(self, *args, **kwargs): return self.findItemsByIds(*args, **kwargs) - def build_request_data(self, verb, data, verb_attrs): + def build_request_data(self, verb, data, verb_attrs): xml = "" xml += "<" + verb + "Request" xml += ' xmlns="http://www.ebay.com/marketplace/search/v1/services"' From 81610efa6cf1d332f57f4ed5ade55b251790b2ab Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 7 Jul 2014 10:00:06 -0700 Subject: [PATCH 202/218] remove grequests install dep, modify install instructions --- INSTALL | 12 ++++++++++-- setup.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/INSTALL b/INSTALL index 722dee1..48975dd 100644 --- a/INSTALL +++ b/INSTALL @@ -3,11 +3,19 @@ Running the tests: Installing ebaysdk on Mac, Linux, Unix: -1) Install the SDK with easy_install +1) Install System Dependancies + + Red Hat: + sudo yum install python-lxml + + Ubuntu: + sudo apt-get install python-lxml + +2) Install the SDK with easy_install sudo easy_install ebaysdk - Or you can install the bleeding edge version, + Or install the latest version from github, sudo easy_install https://github.com/timotheus/ebaysdk-python/archive/master.zip diff --git a/setup.py b/setup.py index 6418009..35f0091 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ license="COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", packages=find_packages(), provides=[PKG], - install_requires=['PyYaml', 'lxml', 'requests', 'grequests'], + install_requires=['PyYaml', 'lxml', 'requests'], test_suite='tests', long_description=long_desc, classifiers=[ From cc77783977a2292de5d34dfefdcee9d19f488ab9 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Mon, 7 Jul 2014 10:04:28 -0700 Subject: [PATCH 203/218] bump version --- Changes | 4 ++++ ebaysdk/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 826df0f..bb28d07 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Changes for ebaysdk +2.1.0 Mon Jul 7 10:03:36 PDT 2014 +- modify install instructions +- remove grequests install dep from setup.py + 2.0.0 - WARNING!! This release breaks some backward compatibility Read https://github.com/timotheus/ebaysdk-python/wiki/Migrating-from-v1-to-v2 diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py index 3fdc46f..c8f76af 100644 --- a/ebaysdk/__init__.py +++ b/ebaysdk/__init__.py @@ -9,7 +9,7 @@ import platform import logging -__version__ = '2.0.0' +__version__ = '2.1.0' Version = __version__ # for backware compatibility try: From 19a9c8a189e9c7ec17a1aa20b2718a524ddd28cf Mon Sep 17 00:00:00 2001 From: Andrew Moss Date: Tue, 16 Sep 2014 21:23:10 +0100 Subject: [PATCH 204/218] Return the result of the Finding API next_page method. --- ebaysdk/finding/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py index f68db84..6f5b3f5 100644 --- a/ebaysdk/finding/__init__.py +++ b/ebaysdk/finding/__init__.py @@ -301,4 +301,4 @@ def next_page(self): self._request_dict['paginationInput']['pageNumber'] = int(num) + 1 - self.execute(self.verb, self._request_dict) + return self.execute(self.verb, self._request_dict) From e9600899309e4a14a89ff54f7eb77f5eb8d50469 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 30 Sep 2014 09:21:46 -0700 Subject: [PATCH 205/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c834d47..4ecf340 100644 --- a/README.rst +++ b/README.rst @@ -81,7 +81,7 @@ License .. _Finding API Class: https://github.com/timotheus/ebaysdk-python/wiki/Finding-API-Class .. _Shopping API Class: https://github.com/timotheus/ebaysdk-python/wiki/Shopping-API-Class .. _Merchandising API Class: https://github.com/timotheus/ebaysdk-python/wiki/Merchandising-API-Class -.. _HTML Class: https://github.com/timotheus/ebaysdk-python/wiki/HTML-Class +.. _HTTP Class: https://github.com/timotheus/ebaysdk-python/wiki/HTTP-Class .. _Parallel Class: https://github.com/timotheus/ebaysdk-python/wiki/Parallel-Class .. _eBay Developer Forums: https://go.developer.ebay.com/developers/ebay/forums-support/support .. _Github issue tracking: https://github.com/timotheus/ebaysdk-python/issues From bd5ec1ba185d4bb0554992b8859ff2849a8094f7 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 30 Sep 2014 09:22:12 -0700 Subject: [PATCH 206/218] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4ecf340..8bcae27 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ Getting Started * `Finding API Class`_ - access eBay's next generation search capabilities. * `Shopping API Class`_ - performance-optimized, lightweight APIs for accessing public eBay data. * `Merchandising API Class`_ - find items and products on eBay that provide good value or are otherwise popular with eBay buyers. -* `HTML Class`_ - generic back-end class the enbles and standardized way to make API calls. +* `HTTP Class`_ - generic back-end class the enbles and standardized way to make API calls. * `Parallel Class`_ - SDK support for concurrent API calls. 2) SDK Configuration From 9fa98078bfd7ddc2c3ec9fec54fca7ff8db1e3bb Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Tue, 30 Sep 2014 15:22:33 -0700 Subject: [PATCH 207/218] fix tests for http class --- ebaysdk/http/__init__.py | 6 +++--- tests/__init__.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py index 2ba70b8..093167d 100644 --- a/ebaysdk/http/__init__.py +++ b/ebaysdk/http/__init__.py @@ -30,12 +30,12 @@ class Connection(BaseConnection): Doctests: >>> h = Connection() - >>> retval = h.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> retval = h.execute('http://feeds.feedburner.com/slashdot/audio?format=xml') >>> print(h.response.reply.rss.channel.ttl) - 60 + 2 >>> title = h.response.dom().xpath('//title')[0] >>> print(title.text) - mytouch slide + Slashdot >>> print(h.error()) None >>> h = Connection(method='POST', debug=False) diff --git a/tests/__init__.py b/tests/__init__.py index f62df34..1fffd6d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -39,7 +39,9 @@ def getTestSuite(): suite.addTest(doctest.DocTestSuite(ebaysdk.merchandising)) suite.addTest(doctest.DocTestSuite(ebaysdk.finding)) - if not sys.version_info[0] >= 3: + if not sys.version_info[0] >= 3 \ + and sys.modules.has_key('grequests') is True: + suite.addTest(doctest.DocTestSuite(ebaysdk.parallel)) From e0ee1f1667c787b40bf13b7142e163b7b58aaeb2 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Wed, 29 Oct 2014 10:40:33 -0700 Subject: [PATCH 208/218] update test --- ebaysdk/parallel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py index 79e1ebc..c318b79 100644 --- a/ebaysdk/parallel.py +++ b/ebaysdk/parallel.py @@ -20,7 +20,7 @@ class Parallel(object): >>> import os >>> p = Parallel() >>> r1 = html(parallel=p) - >>> retval = r1.execute('http://shop.ebay.com/i.html?rt=nc&_nkw=mytouch+slide&_dmpt=PDA_Accessories&_rss=1') + >>> retval = r1.execute('http://feeds.feedburner.com/slashdot/audio?format=xml') >>> r2 = finding(parallel=p, config_file=os.environ.get('EBAY_YAML')) >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) @@ -29,7 +29,7 @@ class Parallel(object): >>> print(p.error()) None >>> print(r1.response.reply.rss.channel.ttl) - 60 + 2 >>> print(r2.response.dict()['ack']) Success >>> print(r3.response.reply.Ack) From 3156c077aee428ba223fd1690fc4ee1a8c468e55 Mon Sep 17 00:00:00 2001 From: Tim Keefer Date: Fri, 26 Dec 2014 12:56:09 -0800 Subject: [PATCH 209/218] add motors sample query --- samples/finding.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/samples/finding.py b/samples/finding.py index a44bd26..6572b0f 100644 --- a/samples/finding.py +++ b/samples/finding.py @@ -74,9 +74,29 @@ def run2(opts): print(e) print(e.response.dict()) +def run_motors(opts): + api = finding(siteid='EBAY-MOTOR', debug=opts.debug, appid=opts.appid, config_file=opts.yaml, + warnings=True) + api.execute('findItemsAdvanced', { + 'keywords': 'tesla', + }) + + if api.error(): + raise Exception(api.error()) + + if api.response_content(): + print "Call Success: %s in length" % len(api.response_content()) + + print "Response code: %s" % api.response_code() + print "Response DOM: %s" % api.response_dom() + + dictstr = "%s" % api.response_dict() + print "Response dictionary: %s..." % dictstr[:250] + if __name__ == "__main__": print("Finding samples for SDK version %s" % ebaysdk.get_version()) (opts, args) = init_options() run(opts) run2(opts) + run_motors(opts) From 22e0d8cbca6070b968905ff0f9db5e231410a9d1 Mon Sep 17 00:00:00 2001 From: Yarovoy Nikolay Date: Wed, 21 Jan 2015 23:56:46 +0100 Subject: [PATCH 210/218] Business Polices API was added --- .DS_Store | Bin 0 -> 6148 bytes ebaysdk/.DS_Store | Bin 0 -> 6148 bytes ebaysdk/polices/__init__.py | 224 ++++++++++++++++++++++++++++++++++++ samples/.DS_Store | Bin 0 -> 6148 bytes 4 files changed, 224 insertions(+) create mode 100644 .DS_Store create mode 100644 ebaysdk/.DS_Store create mode 100644 ebaysdk/polices/__init__.py create mode 100644 samples/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..df8237a12ada71bde97ac19e9857d9abcdc51b3f GIT binary patch literal 6148 zcmeHK%}yIJ5FUq6HgMUhMMC1@Yo#1WDk6^ClpZTZ3IZXh!n(UstZvs)ydgqSq&@dB z=O7zD}sD)_nc!~_r-mO3i0%ACWJNu_H+)^CUIPe(;kYSJ(V?7X=5;$@r z2AR~w*48zvELE4Etpv5zHG0D5s~@_3HSQL1G1BdLcw~AqNrkgJanYaIdrD<MGiZ|*dwbv|jm-l_BLMr%3^uYzFn)%KhBhlA;t^RM5&|M>aq_l4aiMz2ru z!opl%qV<3l`;S){#VkWXOrh2@_iU#56yER*cm_NJkIaBOmx8rN{vCdK&wyv(=`ld( z1II=fIV>%jqXU&G0TAgk(g@mAOHdAX7&$C0;s^?}sfad}xe|lfbo6r<7db2~+H_#9 z_+V~j<|-6sR>%3d2?rKg^x8Av8Mx2DlI@0c|G)ct{eM5n_dElhf&YpDR^9FHw(*tR z-Ma8{bk}lhC)h|RuCzE$LBm|d7)w|21~wz;=VTy84oi#ZLGd2~h6b-Z1OJqPTb~AT A?*IS* literal 0 HcmV?d00001 diff --git a/ebaysdk/.DS_Store b/ebaysdk/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c62aa420042249fa9c64134d699f218824d5cbd4 GIT binary patch literal 6148 zcmeHKUyBnl5TD%j>|PIHMTG-j@+$PUM}&(aP+2mk>pVWWlSH$vm2YqI5g zb`XV~LqP#AQZvpIy%xjGV^I;WNGt-Z! zPi#x2S>pXcTum`DY(5HaJ=mXC;vzd$MkOj&)$3rFzEQZpSoAvGw(K5tmuN8XlimM&k6g2)hwhleT`>0lMOp=2bd8{2` Q4+{Sgur#n?1~$sTPd!e@MF0Q* literal 0 HcmV?d00001 diff --git a/ebaysdk/polices/__init__.py b/ebaysdk/polices/__init__.py new file mode 100644 index 0000000..fdd3d85 --- /dev/null +++ b/ebaysdk/polices/__init__.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +''' +© 2012-2013 eBay Software Foundation +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +import os + +from ebaysdk import log +from ebaysdk.connection import BaseConnection +from ebaysdk.exception import RequestPaginationError, PaginationLimit +from ebaysdk.config import Config +from ebaysdk.utils import dict2xml + +class Connection(BaseConnection): + """Connection class for the Business Polices service + + API documentation: + http://developer.ebay.com/Devzone/business-policies + + Supported calls: + addSellerProfile + getSellerProfiles + (all others, see API docs) + + """ + + def __init__(self, **kwargs): + """Finding class constructor. + + Keyword arguments: + domain -- API endpoint (default: svcs.ebay.com) + config_file -- YAML defaults (default: ebay.yaml) + debug -- debugging enabled (default: False) + warnings -- warnings enabled (default: False) + uri -- API endpoint uri (default: /services/selling/v1/SellerProfilesManagementService) + appid -- eBay application id + siteid -- eBay country site id (default: EBAY-US) + version -- version number (default: 1.0.0) + https -- execute of https (default: False) + proxy_host -- proxy hostname + proxy_port -- proxy port number + timeout -- HTTP request timeout (default: 20) + parallel -- ebaysdk parallel object + response_encoding -- API encoding (default: XML) + request_encoding -- API encoding (default: XML) + """ + + super(Connection, self).__init__(method='POST', **kwargs) + + self.config=Config(domain=kwargs.get('domain', 'svcs.ebay.com'), + connection_kwargs=kwargs, + config_file=kwargs.get('config_file', 'ebay.yaml')) + + # override yaml defaults with args sent to the constructor + self.config.set('domain', kwargs.get('domain', 'svcs.ebay.com')) + self.config.set('uri', '/services/selling/v1/SellerProfilesManagementService') + self.config.set('https', True) + self.config.set('warnings', True) + self.config.set('errors', True) + self.config.set('siteid', 'EBAY-US') + self.config.set('response_encoding', 'XML') + self.config.set('request_encoding', 'XML') + self.config.set('proxy_host', None) + self.config.set('proxy_port', None) + self.config.set('token', None) + self.config.set('iaf_token', None) + self.config.set('appid', None) + self.config.set('version', '1.0.0') + self.config.set('service', 'SellerProfilesManagementService') + self.config.set('doc_url', 'http://developer.ebay.com/Devzone/business-policies/CallRef/index.html') + + self.datetime_nodes = ['deleteddate', 'timestamp', 'maxdeliverydate', + 'mindeliverydate'] + self.base_list_nodes = [ + 'setsellerprofileresponse.paymentprofile.categorygroups.categorygroup', + 'addsellerprofileresponse.paymentprofile.categorygroups.categorygroup', + 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.categorygroups.categorygroup', + 'addsellerprofileresponse.returnpolicyprofile.categorygroups.categorygroup', + 'setsellerprofileresponse.returnpolicyprofile.categorygroups.categorygroup', + 'getsellerprofilesresponse.returnpolicyprofilelist.returnpolicyprofile.categorygroups.categorygroup', + 'addsellerprofileresponse.shippingpolicyprofile.categorygroups.categorygroup', + 'setsellerprofileresponse.shippingpolicyprofile.categorygroups.categorygroup', + 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.categorygroups.categorygroup', + 'consolidateshippingprofilesresponse.consolidationjob', + 'getconsolidationjobstatusresponse.consolidationjob', + 'addsellerprofileresponse.paymentprofile.paymentinfo.depositdetails', + 'setsellerprofileresponse.paymentprofile.paymentinfo.depositdetails', + 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.paymentinfo.depositdetails', + 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.freightshipping', + 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.freightshipping', + 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.freightshipping', + 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.insurance', + 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.insurance', + 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.insurance', + 'addsellerprofileresponse.paymentprofile.paymentinfo', + 'setsellerprofileresponse.paymentprofile.paymentinfo', + 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.paymentinfo', + 'addsellerprofileresponse.returnpolicyprofile.returnpolicyinfo', + 'setsellerprofileresponse.returnpolicyprofile.returnpolicyinfo', + 'getsellerprofilesresponse.returnpolicyprofilelist.returnpolicyprofile.returnpolicyinfo', + 'addsellerprofileresponse.sellerprofile', + 'setsellerprofileresponse.sellerprofile', + 'getsellerprofilesresponse.paymentprofilelist.sellerprofile', + 'getsellerprofilesresponse.returnpolicyprofilelist.sellerprofile', + 'getsellerprofilesresponse.shippingpolicyprofilelist.sellerprofile', + 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', + 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', + 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', + 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo', + 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo', + 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo' + ] + + def build_request_headers(self, verb): + return { + "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), + "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), + "X-EBAY-SOA-SECURITY-TOKEN": self.config.get('token', ''), + "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), + "X-EBAY-SOA-OPERATION-NAME": verb, + "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.config.get('request_encoding', ''), + "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.config.get('response_encoding', ''), + "Content-Type": "text/xml" + } + + def build_request_data(self, verb, data, verb_attrs): + xml = "" + xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" + xml += dict2xml(data) + xml += "" + + return xml + + def warnings(self): + warning_string = "" + + if len(self._resp_body_warnings) > 0: + warning_string = "%s: %s" \ + % (self.verb, ", ".join(self._resp_body_warnings)) + + return warning_string + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + warnings = [] + resp_codes = [] + + if self.verb is None: + return errors + + dom = self.response.dom() + if dom is None: + return errors + + for e in dom.findall("error"): + eSeverity = None + eDomain = None + eMsg = None + eId = None + + try: + eSeverity = e.findall('severity')[0].text + except IndexError: + pass + + try: + eDomain = e.findall('domain')[0].text + except IndexError: + pass + + try: + eId = e.findall('errorId')[0].text + if int(eId) not in resp_codes: + resp_codes.append(int(eId)) + except IndexError: + pass + + try: + eMsg = e.findall('message')[0].text + except IndexError: + pass + + msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ + % (eDomain, eSeverity, eId, eMsg) + + if eSeverity == 'Warning': + warnings.append(msg) + else: + errors.append(msg) + + self._resp_body_warnings = warnings + self._resp_body_errors = errors + self._resp_codes = resp_codes + + if self.config.get('warnings') and len(warnings) > 0: + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + + try: + if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + + elif len(errors) > 0: + if self.config.get('errors'): + log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) + + return errors + except AttributeError as e: + return errors + + return [] diff --git a/samples/.DS_Store b/samples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Wed, 21 Jan 2015 23:58:30 +0100 Subject: [PATCH 211/218] Delete .DS_Store --- ebaysdk/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ebaysdk/.DS_Store diff --git a/ebaysdk/.DS_Store b/ebaysdk/.DS_Store deleted file mode 100644 index c62aa420042249fa9c64134d699f218824d5cbd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKUyBnl5TD%j>|PIHMTG-j@+$PUM}&(aP+2mk>pVWWlSH$vm2YqI5g zb`XV~LqP#AQZvpIy%xjGV^I;WNGt-Z! zPi#x2S>pXcTum`DY(5HaJ=mXC;vzd$MkOj&)$3rFzEQZpSoAvGw(K5tmuN8XlimM&k6g2)hwhleT`>0lMOp=2bd8{2` Q4+{Sgur#n?1~$sTPd!e@MF0Q* From 994804dfb40f5a5c124600b352018fcbb2d719fe Mon Sep 17 00:00:00 2001 From: nickspring Date: Wed, 21 Jan 2015 23:58:48 +0100 Subject: [PATCH 212/218] Delete .DS_Store --- samples/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 samples/.DS_Store diff --git a/samples/.DS_Store b/samples/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Wed, 21 Jan 2015 23:59:18 +0100 Subject: [PATCH 213/218] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index df8237a12ada71bde97ac19e9857d9abcdc51b3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}yIJ5FUq6HgMUhMMC1@Yo#1WDk6^ClpZTZ3IZXh!n(UstZvs)ydgqSq&@dB z=O7zD}sD)_nc!~_r-mO3i0%ACWJNu_H+)^CUIPe(;kYSJ(V?7X=5;$@r z2AR~w*48zvELE4Etpv5zHG0D5s~@_3HSQL1G1BdLcw~AqNrkgJanYaIdrD<MGiZ|*dwbv|jm-l_BLMr%3^uYzFn)%KhBhlA;t^RM5&|M>aq_l4aiMz2ru z!opl%qV<3l`;S){#VkWXOrh2@_iU#56yER*cm_NJkIaBOmx8rN{vCdK&wyv(=`ld( z1II=fIV>%jqXU&G0TAgk(g@mAOHdAX7&$C0;s^?}sfad}xe|lfbo6r<7db2~+H_#9 z_+V~j<|-6sR>%3d2?rKg^x8Av8Mx2DlI@0c|G)ct{eM5n_dElhf&YpDR^9FHw(*tR z-Ma8{bk}lhC)h|RuCzE$LBm|d7)w|21~wz;=VTy84oi#ZLGd2~h6b-Z1OJqPTb~AT A?*IS* From 49f4b26a6e23c76b5decdd7f643ec338c576e0f9 Mon Sep 17 00:00:00 2001 From: Yarovoy Nikolay Date: Thu, 22 Jan 2015 00:15:58 +0100 Subject: [PATCH 214/218] class and folder name for Business Policies API were changed --- ebaysdk/.DS_Store | Bin 6148 -> 6148 bytes ebaysdk/{polices => policies}/__init__.py | 2 +- samples/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) rename ebaysdk/{polices => policies}/__init__.py (99%) delete mode 100644 samples/.DS_Store diff --git a/ebaysdk/.DS_Store b/ebaysdk/.DS_Store index c62aa420042249fa9c64134d699f218824d5cbd4..9e5527d3add1cbbd26675f25891a0e8c14304e46 100644 GIT binary patch delta 31 mcmZoMXffCj$imCPP{5GSki(G4kj#*|Ifi8=^JaFAzx)7%9te;C delta 29 kcmZoMXffCj$imIeP{5GSki(G4ki0pLWhL`wR*wJt0CqG8CIA2c diff --git a/ebaysdk/polices/__init__.py b/ebaysdk/policies/__init__.py similarity index 99% rename from ebaysdk/polices/__init__.py rename to ebaysdk/policies/__init__.py index fdd3d85..04b68d5 100644 --- a/ebaysdk/polices/__init__.py +++ b/ebaysdk/policies/__init__.py @@ -15,7 +15,7 @@ from ebaysdk.utils import dict2xml class Connection(BaseConnection): - """Connection class for the Business Polices service + """Connection class for the Business Policies service API documentation: http://developer.ebay.com/Devzone/business-policies diff --git a/samples/.DS_Store b/samples/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Sat, 24 Jan 2015 16:18:03 +0100 Subject: [PATCH 215/218] debug --- ebaysdk/trading/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 176646a..2ee6e59 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -692,7 +692,14 @@ def build_request_data(self, verb, data, verb_attrs): if self.config.get('password', None): xml += "%s" % self.config.get('password', '') xml += "" - xml += dict2xml(data) + + try: + xml += dict2xml(data) + except: + print data + print dict2xml(data) + raise + xml += "" return xml From 2bd9bbc31bf7ce8d67c6d14e34dae819369b0ee1 Mon Sep 17 00:00:00 2001 From: Yarovoy Nikolay Date: Sat, 24 Jan 2015 16:30:27 +0100 Subject: [PATCH 216/218] debug --- ebaysdk/trading/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 2ee6e59..3b71254 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -681,17 +681,17 @@ def build_request_headers(self, verb): return headers def build_request_data(self, verb, data, verb_attrs): - xml = "" - xml += "<" + self.verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" + xml = u"" + xml += u"<" + self.verb + u"Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" if not self.config.get('iaf_token', None): - xml += "" + xml += u"" if self.config.get('token', None): - xml += "%s" % self.config.get('token') + xml += u"%s" % self.config.get('token') elif self.config.get('username', None): - xml += "%s" % self.config.get('username', '') + xml += u"%s" % self.config.get('username', '') if self.config.get('password', None): - xml += "%s" % self.config.get('password', '') - xml += "" + xml += u"%s" % self.config.get('password', '') + xml += u"" try: xml += dict2xml(data) @@ -700,7 +700,7 @@ def build_request_data(self, verb, data, verb_attrs): print dict2xml(data) raise - xml += "" + xml += u"" return xml def warnings(self): From b7c7681152758f6362215f55ba9c77a93d6dd03b Mon Sep 17 00:00:00 2001 From: Yarovoy Nikolay Date: Tue, 11 Aug 2015 00:28:36 +0200 Subject: [PATCH 217/218] bug with utf-8 was fixed --- ebaysdk/.DS_Store | Bin 6148 -> 6148 bytes ebaysdk/connection.py | 4 ++-- ebaysdk/trading/__init__.py | 8 ++------ ebaysdk/utils.py | 18 +++++++++--------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/ebaysdk/.DS_Store b/ebaysdk/.DS_Store index 9e5527d3add1cbbd26675f25891a0e8c14304e46..28189816b077c0e137ac46703aa67709bd79e5ac 100644 GIT binary patch delta 83 zcmZoMXfc@J&&a+pU^g=(`(_@N1V)JhhJ1z`hD?TJAWmf{PAN{#Ny^X9VceX~YR)LZ d&QQWo#E{640+h{TNQcXAEHq)<%+B$b9{{1K7S#X% delta 33 pcmZoMXfc@J&&awlU^g=(>t-I71jfystVWC*OEMWZvvd6A2LP(P3J3rI diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 5ceb6bc..f84884a 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -114,7 +114,7 @@ def execute(self, verb, data=None, list_nodes=[], verb_attrs=None): if hasattr(self, 'base_list_nodes'): self._list_nodes += self.base_list_nodes - + self.build_request(verb, data, verb_attrs) self.execute_request() @@ -143,7 +143,7 @@ def build_request(self, verb, data, verb_attrs): 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) request = Request(self.method, url, - data=self.build_request_data(verb, data, verb_attrs), + data=self.build_request_data(verb, data, verb_attrs).encode('utf-8', 'ignore'), headers=headers, ) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 3b71254..5f67665 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -693,12 +693,8 @@ def build_request_data(self, verb, data, verb_attrs): xml += u"%s" % self.config.get('password', '') xml += u"" - try: - xml += dict2xml(data) - except: - print data - print dict2xml(data) - raise + xml += dict2xml(data) + #print dict2xml(data) xml += u"" return xml diff --git a/ebaysdk/utils.py b/ebaysdk/utils.py index f78cfa3..3152e12 100644 --- a/ebaysdk/utils.py +++ b/ebaysdk/utils.py @@ -46,7 +46,7 @@ def attribute_check(root): def smart_encode(value): try: if sys.version_info[0] < 3: - return unicode(value).encode('utf-8', 'ignore') + return unicode(value) #.encode('utf-8', 'ignore') else: return value #return str(value) @@ -131,7 +131,7 @@ def dict2xml(root): mydevidmyappidmycertid ''' - xml = '' + xml = u'' if root is None: return xml @@ -146,12 +146,12 @@ def dict2xml(root): elif isinstance(value, dict): value = dict2xml(value) - attrs_sp = '' + attrs_sp = u'' if len(attrs) > 0: - attrs_sp = ' ' + attrs_sp = u' ' - xml = '%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ - {'tag': key, 'xml': xml, 'attrs': ' '.join(attrs), + xml = u'%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ + {'tag': key, 'xml': xml, 'attrs': u' '.join(attrs), 'value': smart_encode(value), 'attrs_sp': attrs_sp} elif isinstance(root[key], list): @@ -168,19 +168,19 @@ def dict2xml(root): if len(attrs) > 0: attrs_sp = ' ' - xml = '%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ + xml = u'%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s' % \ {'xml': xml, 'tag': key, 'attrs': ' '.join(attrs), 'value': smart_encode(value), 'attrs_sp': attrs_sp} else: value = root[key] - xml = '%(xml)s<%(tag)s>%(value)s' % \ + xml = u'%(xml)s<%(tag)s>%(value)s' % \ {'xml': xml, 'tag': key, 'value': smart_encode(value)} elif isinstance(root, str) or isinstance(root, int) \ or isinstance(root, unicode) or isinstance(root, long) \ or isinstance(root, float): - xml = '%s%s' % (xml, root) + xml = u'%s%s' % (xml, root) else: raise Exception('Unable to serialize node of type %s (%s)' % \ (type(root), root)) From 6aa09d86b8c11b4697f06ae394b1f273cfb854d8 Mon Sep 17 00:00:00 2001 From: Yarovoy Nikolay Date: Sat, 15 Aug 2015 19:41:10 +0200 Subject: [PATCH 218/218] fix site_id --- ebaysdk/connection.py | 3 +++ ebaysdk/policies/__init__.py | 4 +++- ebaysdk/trading/__init__.py | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index f84884a..d99cdb2 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -138,9 +138,12 @@ def build_request(self, verb, data, verb_attrs): self.config.get('uri') ) + headers = self.build_request_headers(verb) + #print headers headers.update({'User-Agent': UserAgent, 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) + request = Request(self.method, url, data=self.build_request_data(verb, data, verb_attrs).encode('utf-8', 'ignore'), diff --git a/ebaysdk/policies/__init__.py b/ebaysdk/policies/__init__.py index 04b68d5..41a7692 100644 --- a/ebaysdk/policies/__init__.py +++ b/ebaysdk/policies/__init__.py @@ -60,7 +60,9 @@ def __init__(self, **kwargs): self.config.set('https', True) self.config.set('warnings', True) self.config.set('errors', True) - self.config.set('siteid', 'EBAY-US') + #self.config.set('siteid', 'EBAY-US') + self.config.set('siteid', kwargs.get('siteid', 'EBAY-US')) + self.config.set('response_encoding', 'XML') self.config.set('request_encoding', 'XML') self.config.set('proxy_host', None) diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py index 5f67665..244f9ba 100644 --- a/ebaysdk/trading/__init__.py +++ b/ebaysdk/trading/__init__.py @@ -67,6 +67,7 @@ def __init__(self, **kwargs): response_encoding -- API encoding (default: XML) request_encoding -- API encoding (default: XML) """ + super(Connection, self).__init__(method='POST', **kwargs) self.config=Config(domain=kwargs.get('domain', 'api.ebay.com'), @@ -80,7 +81,7 @@ def __init__(self, **kwargs): self.config.set('warnings', True) self.config.set('errors', True) self.config.set('https', True) - self.config.set('siteid', 0) + self.config.set('siteid', kwargs.get('siteid', 0)) self.config.set('response_encoding', 'XML') self.config.set('request_encoding', 'XML') self.config.set('proxy_host', None) @@ -665,7 +666,7 @@ def __init__(self, **kwargs): 'getwantitnowsearchresultsresponse.wantitnowpostarraytype.wantitnowpost', ] - def build_request_headers(self, verb): + def build_request_headers(self, verb): headers = { "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('compatibility', ''), "X-EBAY-API-DEV-NAME": self.config.get('devid', ''),