diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..638e9ad
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+timebay.yaml
+build/
+dist/
+tkebay.yaml
+ebaysdk.egg-info/
+*.swp
+*.pyc
+.svn
diff --git a/Changes b/Changes
index 16caf09..bb28d07 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,117 @@
Changes for ebaysdk
-- add proxy support
+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
+- 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
+
+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
+- 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
+- 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()
+ 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
+- added Merchandising API class
+- 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
+- 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
+- add retry to standard & parallel calls
+- 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
+- remove silent depedency on simplejson, at least for Python >= 2.6
+- fix bug in html class that allows for POST
+- refactor and style cleanup
+- added parallel support using the parallel class
+- created new prepare, process, and _process_http_request methods
+
+0.1.7
+- update tests
+- modify response_obj() to return response_dict()
+
+0.0.6
+- support older version of Element tree
+- 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
+- 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
+ 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..48975dd
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,61 @@
+Running the tests:
+~/> export EBAY_YAML='myebay.yaml'; python setup.py test
+
+Installing ebaysdk on Mac, Linux, Unix:
+
+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 install the latest version from github,
+
+ 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/
+
+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!
+
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/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..cd79363
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +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
new file mode 100644
index 0000000..8bcae27
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,90 @@
+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': '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:
+ 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
+---------------
+
+1) 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.
+* `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
+
+* `YAML Configuration`_
+* `Understanding eBay Credentials`_
+
+3) Sample code can be found in the `samples directory`_.
+
+4) Understanding the `Request Dictionary`_.
+
+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
+.. _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
+.. _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
diff --git a/ebay.yaml b/ebay.yaml
new file mode 100644
index 0000000..e79658c
--- /dev/null
+++ b/ebay.yaml
@@ -0,0 +1,34 @@
+# eBay SDK Defaults
+
+name: ebay_api_config
+
+# 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
+
+# Trading API - https://www.x.com/developers/ebay/products/trading-api
+api.ebay.com:
+ version: 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: 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
+
+ # 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/.DS_Store b/ebaysdk/.DS_Store
new file mode 100644
index 0000000..2818981
Binary files /dev/null and b/ebaysdk/.DS_Store differ
diff --git a/ebaysdk/__init__.py b/ebaysdk/__init__.py
new file mode 100644
index 0000000..c8f76af
--- /dev/null
+++ b/ebaysdk/__init__.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+import platform
+import logging
+
+__version__ = '2.1.0'
+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__,
+ platform.python_version(),
+ platform.system(),
+ platform.release()
+)
+
+log = logging.getLogger('ebaysdk')
+
+if not log.handlers:
+ log.addHandler(NullHandler())
+
+def get_version():
+ return __version__
+
+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"
+
+ log.setLevel(level)
+ fh = logging.StreamHandler()
+ fh.setLevel(level)
+ formatter = logging.Formatter(format_string)
+ fh.setFormatter(formatter)
+ log.addHandler(fh)
+
+def 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):
+ 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):
+ 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):
+ 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):
+ 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):
+ 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/config.py b/ebaysdk/config.py
new file mode 100644
index 0000000..abf922c
--- /dev/null
+++ b/ebaysdk/config.py
@@ -0,0 +1,98 @@
+# -*- 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
+from ebaysdk.exception import ConnectionConfigError
+
+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, {}).items():
+ self.set(k, val)
+
+ return self
+
+ # check other directories
+ dirs = ['.', os.path.expanduser('~'), '/etc']
+ for mydir in dirs:
+ myfile = "%s/%s" % (mydir, self.config_file)
+
+ if os.path.exists(myfile):
+ self.config_file_used=myfile
+
+ fhandle = open(myfile, "r")
+ dataobj = yaml.load(fhandle.read())
+
+ for k, val in dataobj.get(self.domain, {}).items():
+ self.set(k, val)
+
+ return self
+
+ if self.config_file:
+ raise ConnectionConfigError('config file %s not found' % self.config_file)
+
+ 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
diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py
new file mode 100644
index 0000000..d99cdb2
--- /dev/null
+++ b/ebaysdk/connection.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+from ebaysdk import log
+
+import re
+import time
+import uuid
+import webbrowser
+
+from requests import Request, Session
+from requests.adapters import HTTPAdapter
+
+from xml.dom.minidom import parseString
+from xml.parsers.expat import ExpatError
+
+from ebaysdk import set_stream_logger, UserAgent
+from ebaysdk.utils import getNodeText as getNodeTextUtils
+from ebaysdk.utils import getValue
+from ebaysdk.response import Response
+from ebaysdk.exception import ConnectionError, ConnectionResponseError
+
+HTTP_SSL = {
+ False: 'http',
+ True: 'https',
+}
+
+class BaseConnection(object):
+ """Base Connection Class."""
+
+ def __init__(self, debug=False, method='GET',
+ proxy_host=None, timeout=20, proxy_port=80,
+ parallel=None, **kwargs):
+
+ if debug:
+ set_stream_logger()
+
+ 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.datetime_nodes = []
+ self._list_nodes = []
+
+ 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.parallel = parallel
+
+ self.base_list_nodes = []
+ self.datetime_nodes = []
+
+ 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._list_nodes = []
+ self._request_id = None
+ self._request_dict = {}
+ 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 _add_prefix(self, nodes, verb):
+ if verb:
+ for i, v in enumerate(nodes):
+ if not nodes[i].startswith(verb.lower()):
+ nodes[i] = "%sresponse.%s" % (verb.lower(), nodes[i].lower())
+
+ def execute(self, verb, data=None, list_nodes=[], verb_attrs=None):
+ "Executes the HTTP request."
+ log.debug('execute: verb=%s data=%s' % (verb, data))
+
+ self._reset()
+
+ self._list_nodes += list_nodes
+ self._add_prefix(self._list_nodes, verb)
+
+ if hasattr(self, 'base_list_nodes'):
+ self._list_nodes += self.base_list_nodes
+
+ self.build_request(verb, data, verb_attrs)
+ self.execute_request()
+
+ if hasattr(self.response, 'content'):
+ self.process_response()
+ self.error_check()
+
+ log.debug('total time=%s' % (time.time() - self._time))
+
+ return self.response
+
+ def build_request(self, verb, data, verb_attrs):
+
+ self.verb = verb
+ self._request_dict = data
+ 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)
+ #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'),
+ 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)
+
+ if self.parallel:
+ self.parallel._add_request(self)
+ return None
+
+ 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, 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,
+ parse_response=parse_response)
+
+ # set for backward compatibility
+ self._response_content = self.response.content
+
+ if self.response.status_code != 200:
+ self._response_error = self.response.reason
+
+ def error_check(self):
+ estr = self.error()
+
+ if estr and self.config.get('errors', True):
+ log.error(estr)
+ raise ConnectionError(estr, self.response)
+
+ 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:
+ 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')
+ )
+
+ 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
+ content = None
+
+ try:
+ if self.response.content:
+ regex = re.compile(b'xmlns="[^"]+"')
+ content = regex.sub(b'', self.response.content)
+ else:
+ content = "<%sResponse>%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), self.response)
+ except IndexError:
+ self._response_dom = dom
+
+ return self._response_dom
+
+ 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 self.response.json()
+
+ 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 = u"%s: %s" % (self.verb, u", ".join(error_array))
+
+ 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
new file mode 100644
index 0000000..eb3cc36
--- /dev/null
+++ b/ebaysdk/exception.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+class ConnectionError(Exception):
+ 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 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)
+ self.message = u'%s' % msg
+ self.response = response
+
+ def __str__(self):
+ return repr(self.message)
+
+class RequestPaginationError(Exception):
+ def __init__(self, msg, response):
+ super(RequestPaginationError, self).__init__(u'%s' % msg)
+ self.message = u'%s' % msg
+ self.response = response
+
+ def __str__(self):
+ return repr(self.message)
+
+class PaginationLimit(Exception):
+ def __init__(self, msg, response):
+ super(PaginationLimit, self).__init__(u'%s' % msg)
+ self.message = u'%s' % msg
+ self.response = response
+
+ def __str__(self):
+ return repr(self.message)
diff --git a/ebaysdk/finding/__init__.py b/ebaysdk/finding/__init__.py
new file mode 100644
index 0000000..6f5b3f5
--- /dev/null
+++ b/ebaysdk/finding/__init__.py
@@ -0,0 +1,304 @@
+# -*- 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 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'), debug=False)
+ >>> retval = f.execute('findItemsAdvanced', {'keywords': u'niño'})
+ >>> error = f.error()
+ >>> print(error)
+ None
+ >>> if not f.error():
+ ... print(f.response.reply.itemSearchURL != '')
+ ... items = f.response.reply.searchResult.item
+ ... print(len(items) > 2)
+ ... print(f.response.reply.ack)
+ True
+ True
+ 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)
+ 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/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('service', 'FindingService')
+ self.config.set('doc_url', 'http://developer.ebay.com/DevZone/finding/CallRef/index.html')
+
+ self.datetime_nodes = ['starttimefrom', 'timestamp', 'starttime',
+ 'endtime']
+ self.base_list_nodes = [
+ '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', ''),
+ "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, verb_attrs):
+ xml = ""
+ xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">"
+ xml += dict2xml(data)
+ xml += "" + verb + "Request>"
+
+ 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 []
+
+ def next_page(self):
+ if type(self._request_dict) is not 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", self.response)
+ return None
+
+ self._request_dict['paginationInput'] = {}
+
+ if epp:
+ self._request_dict['paginationInput']['enteriesPerPage'] = epp
+
+ self._request_dict['paginationInput']['pageNumber'] = int(num) + 1
+
+ return self.execute(self.verb, self._request_dict)
diff --git a/ebaysdk/http/__init__.py b/ebaysdk/http/__init__.py
new file mode 100644
index 0000000..093167d
--- /dev/null
+++ b/ebaysdk/http/__init__.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+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
+from ebaysdk.response import Response
+
+class Connection(BaseConnection):
+ """HTML class for traditional calls.
+
+ Doctests:
+ >>> h = Connection()
+ >>> retval = h.execute('http://feeds.feedburner.com/slashdot/audio?format=xml')
+ >>> print(h.response.reply.rss.channel.ttl)
+ 2
+ >>> title = h.response.dom().xpath('//title')[0]
+ >>> print(title.text)
+ Slashdot
+ >>> 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
+ >>> h.response.reply
+ {}
+ """
+
+ 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', self.response)
+
+ def response_dict(self):
+ "Return the HTTP response dictionary."
+
+ try:
+
+ return self.response.dict()
+
+ except ExpatError:
+ 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."
+ 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()
+
+ if self.parallel:
+ self.parallel._add_request(self)
+ return None
+
+ self.process_response(parse_response=parse_response)
+ self.error_check()
+
+ log.debug('total time=%s' % (time.time() - self._time))
+
+ return self.response
+
+ 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)})
+
+ kw = dict()
+ if self.method == 'POST':
+ kw['data'] = data
+ else:
+ kw['params'] = data
+
+ request = Request(self.method,
+ url,
+ headers=headers,
+ **kw
+ )
+
+ self.request = request.prepare()
+
+ def warnings(self):
+ return ''
+
+
diff --git a/ebaysdk/merchandising/__init__.py b/ebaysdk/merchandising/__init__.py
new file mode 100644
index 0000000..a858967
--- /dev/null
+++ b/ebaysdk/merchandising/__init__.py
@@ -0,0 +1,91 @@
+# -*- 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 dict2xml
+
+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.reply.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))
+ version -- 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)
+ 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 = [
+ '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', ''),
+ "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, verb_attrs):
+ xml = ""
+ xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">"
+ xml += dict2xml(data)
+ xml += "" + verb + "Request>"
+
+ return xml
diff --git a/ebaysdk/parallel.py b/ebaysdk/parallel.py
new file mode 100644
index 0000000..c318b79
--- /dev/null
+++ b/ebaysdk/parallel.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+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
+
+class Parallel(object):
+ """
+ >>> from ebaysdk.finding import Connection as finding
+ >>> from ebaysdk.shopping import Connection as shopping
+ >>> from ebaysdk.http import Connection as html
+ >>> import os
+ >>> p = Parallel()
+ >>> r1 = html(parallel=p)
+ >>> 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'))
+ >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897})
+ >>> p.wait()
+ >>> print(p.error())
+ None
+ >>> print(r1.response.reply.rss.channel.ttl)
+ 2
+ >>> print(r2.response.dict()['ack'])
+ Success
+ >>> print(r3.response.reply.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
+
+
+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/policies/__init__.py b/ebaysdk/policies/__init__.py
new file mode 100644
index 0000000..41a7692
--- /dev/null
+++ b/ebaysdk/policies/__init__.py
@@ -0,0 +1,226 @@
+# -*- 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 Policies 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('siteid', kwargs.get('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 += "" + verb + "Request>"
+
+ 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/ebaysdk/response.py b/ebaysdk/response.py
new file mode 100644
index 0000000..bb74c66
--- /dev/null
+++ b/ebaysdk/response.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+import sys
+import lxml
+import copy
+import datetime
+
+from collections import defaultdict
+import json
+
+from ebaysdk.utils import get_dom_tree, python_2_unicode_compatible
+from ebaysdk import log
+
+@python_2_unicode_compatible
+class ResponseDataObject(object):
+
+ def __init__(self, mydict, datetime_nodes=[]):
+ self._load_dict(mydict, list(datetime_nodes))
+
+ def __repr__(self):
+ return str(self)
+
+ def __str__(self):
+ return "%s" % 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 _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:
+ 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], datetime_nodes)
+ setattr(self, a[0], o)
+
+ elif isinstance(a[1], list):
+ objs = []
+ for i in a[1]:
+ if i is None or isinstance(i, str) or isinstance(i, unicode):
+ objs.append(i)
+ else:
+ objs.append(ResponseDataObject(i, datetime_nodes))
+
+ setattr(self, a[0], objs)
+ else:
+ self._setattr(a[0], a[1], datetime_nodes)
+
+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 = b'Success1.12.02014-02-07T23:31:13.941Z- Item Two
1190179http://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
+ >>> type(r.reply.searchResult.item)==list
+ True
+ >>> len(r.reply.paginationOutput.pageNumber) == 1
+ True
+ >>> xml = b'Success1.12.02014-02-07T23:31:13.941Z- Item TwoUSMX
- Item One
1290179http://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
+ >>> 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'
+ True
+ >>> item = r.reply.searchResult.item[0]
+ >>> item.name == 'Item Two'
+ True
+ >>> len(item.shipping.c) == 2
+ True
+ '''
+
+ def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[], parse_response=True):
+ 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.debug('response parse failed: %s' % e)
+ self.reply = ResponseDataObject({}, [])
+ else:
+ self.reply = ResponseDataObject({}, [])
+
+ 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)
+
+ @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 {}
+
+ # 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.items():
+ dd[k].append(v)
+
+ 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)
+ for k in d[t.tag].keys():
+ path = "%s.%s" % (parent_path, k)
+ if path.lower() in self._list_nodes:
+ 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.items())
+ if t.text:
+ text = t.text.strip()
+ if children or t.attrib:
+ if text:
+ d[t.tag]['value'] = text
+ else:
+ d[t.tag] = text
+ return d
+
+ 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 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())
+
+
+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/shopping/__init__.py b/ebaysdk/shopping/__init__.py
new file mode 100644
index 0000000..11a4cc8
--- /dev/null
+++ b/ebaysdk/shopping/__init__.py
@@ -0,0 +1,254 @@
+# -*- 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, dict2xml
+
+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)
+ 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.")
+
+ 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',
+ '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',
+ 'findproductsresponse.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', ''),
+ "X-EBAY-API-APP-ID": self.config.get('appid', ''),
+ "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"
+ }
+
+ 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, verb_attrs):
+
+ xml = ""
+ xml += "<" + verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
+ xml += dict2xml(data)
+ xml += "" + verb + "Request>"
+
+ 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('Errors'):
+ eSeverity = None
+ eClass = None
+ eShortMsg = None
+ eLongMsg = None
+ eCode = None
+
+ 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].text)
+ if eCode.is_integer():
+ eCode = int(eCode)
+
+ if eCode not in resp_codes:
+ resp_codes.append(eCode)
+ except IndexError:
+ pass
+
+ 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.reply.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..ef01121
--- /dev/null
+++ b/ebaysdk/soa/__init__.py
@@ -0,0 +1,209 @@
+# -*- 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, dict2xml
+
+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):
+ return self.response.dict()
+
+ '''
+ if self._response_dict:
+ return self._response_dict
+
+ if self._response_content:
+
+ mydict = self.response.dict()
+
+ 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, verb_attrs):
+ xml = ''
+ xml += '' % self.config.get('soap_env_str')
+ xml += ''
+ xml += '' % verb
+ xml += dict2xml(self.soapify(data))
+ xml += '' % verb
+ xml += ''
+ xml += ''
+ return xml
+
+ def soapify(self, xml):
+ xml_type = type(xml)
+ if xml_type == dict:
+ soap = {}
+ for k, v in list(xml.items()):
+ if k == '@attrs' or k == '#text':
+ soap[k] = v
+
+ # skip nodes that have ns defined
+ elif ':' in k:
+ soap[k] = self.soapify(v)
+ else:
+ soap['ser:%s' % (k)] = self.soapify(v)
+
+ elif xml_type == list:
+ soap = []
+ for x in xml:
+ soap.append(self.soapify(x))
+ else:
+ soap = xml
+ return soap
+
+ 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()
+
+ 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.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:
+ pass
+
+ return []
diff --git a/ebaysdk/soa/finditem.py b/ebaysdk/soa/finditem.py
new file mode 100644
index 0000000..b99aead
--- /dev/null
+++ b/ebaysdk/soa/finditem.py
@@ -0,0 +1,126 @@
+# -*- 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 dict2xml, 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 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')
+ >>> 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,
+ domain='apifindingcore.vip.ebay.com', **kwargs):
+
+ super(Connection, self).__init__(consumer_id=consumer_id,
+ 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)
+ self.config.set('uri', "/services/search/FindItemServiceNextGen/v1", force=True)
+ self.config.set('consumer_id', consumer_id)
+
+ 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', ''),
+ "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.get('value', {}):
+
+ if values_dict is None:
+ continue
+
+ for key, value in values_dict.items():
+ value_data = None
+ if type(value) == list:
+ value_data = [x for x in value]
+ else:
+ value_data = 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, verb_attrs):
+ xml = ""
+ xml += "<" + verb + "Request"
+ xml += ' xmlns="http://www.ebay.com/marketplace/search/v1/services"'
+ xml += '>'
+ xml += dict2xml(data)
+ xml += "" + verb + "Request>"
+
+ return xml
+
diff --git a/ebaysdk/trading/__init__.py b/ebaysdk/trading/__init__.py
new file mode 100644
index 0000000..244f9ba
--- /dev/null
+++ b/ebaysdk/trading/__init__.py
@@ -0,0 +1,795 @@
+# -*- 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, dict2xml
+
+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:
+ >>> import datetime
+ >>> t = Connection(config_file=os.environ.get('EBAY_YAML'))
+ >>> response = t.execute('GetCharities', {'CharityID': 3897})
+ >>> charity_name = ''
+ >>> 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)
+ True
+ >>> print(t.error())
+ None
+ >>> t2 = Connection(errors=False, debug=False, config_file=os.environ.get('EBAY_YAML'))
+ >>> response = 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)
+ """
+
+ 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', kwargs.get('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('compatibility', '837')
+ self.config.set('doc_url', 'http://developer.ebay.com/devzone/xml/docs/reference/ebay/index.html')
+
+ 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',
+ '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('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', ''),
+ "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, verb_attrs):
+ xml = u""
+ xml += u"<" + self.verb + u"Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
+ if not self.config.get('iaf_token', None):
+ xml += u""
+ if self.config.get('token', None):
+ xml += u"%s" % self.config.get('token')
+ elif self.config.get('username', None):
+ xml += u"%s" % self.config.get('username', '')
+ if self.config.get('password', None):
+ xml += u"%s" % self.config.get('password', '')
+ xml += u""
+
+ xml += dict2xml(data)
+ #print dict2xml(data)
+
+ xml += u"" + self.verb + u"Request>"
+ 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('Errors'):
+ eSeverity = None
+ eClass = None
+ eShortMsg = None
+ eLongMsg = None
+ eCode = None
+
+ 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))
+ except IndexError:
+ pass
+
+ 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.reply.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
new file mode 100644
index 0000000..3152e12
--- /dev/null
+++ b/ebaysdk/utils.py
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+import sys
+
+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', 'ignore')
+ return klass
+
+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 sorted(root.pop('@attrs').items()):
+ attrs.append('%s="%s"' % (ak, av))
+
+ return attrs, value
+
+def smart_encode(value):
+ try:
+ if sys.version_info[0] < 3:
+ return unicode(value) #.encode('utf-8', 'ignore')
+ else:
+ return value
+ #return str(value)
+
+ except UnicodeDecodeError:
+ return value
+
+def to_xml(root):
+ return dict2xml(root)
+
+def dict2xml(root):
+ '''
+ Doctests:
+ >>> dict1 = {'Items': {'ItemId': ['1234', '2222']}}
+ >>> dict2xml(dict1)
+ '12342222'
+ >>> dict2 = {
+ ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }},
+ ... 'paginationInput': {
+ ... 'pageNumber': '1',
+ ... 'pageSize': '25'
+ ... },
+ ... 'sortOrder': 'StartTimeNewest'
+ ... }
+ >>> dict2xml(dict2)
+ '125222StartTimeNewest'
+ >>> dict3 = {
+ ... 'parent': {'child': {'#text': 222, '@attrs': {'site': 'US', 'id': 1234}}}
+ ... }
+ >>> dict2xml(dict3)
+ '222'
+ >>> dict5 = {
+ ... 'parent': {'child': {'@attrs': {'site': 'US', 'id': 1234}, }}
+ ... }
+ >>> dict2xml(dict5)
+ ''
+ >>> 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)
+ 'ConditionUsedLocatedInGB125222StartTimeNewest'
+ >>> dict2xml({})
+ ''
+ >>> dict2xml('b')
+ '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 = u''
+ if root is None:
+ return xml
+
+ if isinstance(root, dict):
+ for key in sorted(root.keys()):
+
+ if isinstance(root[key], dict):
+ attrs, value = attribute_check(root[key])
+
+ if not value:
+ value = dict2xml(root[key])
+ elif isinstance(value, dict):
+ value = dict2xml(value)
+
+ attrs_sp = u''
+ if len(attrs) > 0:
+ attrs_sp = u' '
+
+ xml = u'%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s%(tag)s>' % \
+ {'tag': key, 'xml': xml, 'attrs': u' '.join(attrs),
+ '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)
+ elif isinstance(value, dict):
+ value = dict2xml(value)
+
+ attrs_sp = ''
+ if len(attrs) > 0:
+ attrs_sp = ' '
+
+ xml = u'%(xml)s<%(tag)s%(attrs_sp)s%(attrs)s>%(value)s%(tag)s>' % \
+ {'xml': xml, 'tag': key, 'attrs': ' '.join(attrs),
+ 'value': smart_encode(value), 'attrs_sp': attrs_sp}
+
+ else:
+ value = root[key]
+ xml = u'%(xml)s<%(tag)s>%(value)s%(tag)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 = u'%s%s' % (xml, root)
+ else:
+ raise Exception('Unable to serialize node of type %s (%s)' % \
+ (type(root), root))
+
+ 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)
+
+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
+ failure_count, test_count = doctest.testmod()
+ sys.exit(failure_count)
+
diff --git a/samples/common.py b/samples/common.py
new file mode 100644
index 0000000..83f00e4
--- /dev/null
+++ b/samples/common.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+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 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/finding.py b/samples/finding.py
new file mode 100644
index 0000000..6572b0f
--- /dev/null
+++ b/samples/finding.py
@@ -0,0 +1,102 @@
+# -*- 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.finding import Connection as finding
+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.")
+
+ (opts, args) = parser.parse_args()
+ return opts, args
+
+
+def run(opts):
+
+ try:
+ api = finding(debug=opts.debug, appid=opts.appid,
+ config_file=opts.yaml, warnings=True)
+
+ api_request = {
+ #'keywords': u'niño',
+ 'keywords': u'GRAMMY Foundation®',
+ 'itemFilter': [
+ {'name': 'Condition',
+ 'value': 'Used'},
+ {'name': 'LocatedIn',
+ 'value': 'GB'},
+ ],
+ 'affiliate': {'trackingId': 1},
+ 'sortOrder': 'CountryDescending',
+ }
+
+ response = api.execute('findItemsAdvanced', api_request)
+
+ dump(api)
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def run2(opts):
+ try:
+ api = finding(debug=opts.debug, appid=opts.appid, config_file=opts.yaml)
+
+ response = api.execute('findItemsByProduct',
+ '530390311')
+
+ dump(api)
+
+ except ConnectionError as e:
+ 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)
diff --git a/samples/finditem.py b/samples/finditem.py
new file mode 100644
index 0000000..9af3400
--- /dev/null
+++ b/samples/finditem.py
@@ -0,0 +1,80 @@
+# -*- 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)
+
+ 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)
+
+ 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:
+ print("ID(%s) TITLE(%s)" % (r['ITEM_ID'], r['TITLE'][:35]))
+
+ dump(api)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+
+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
new file mode 100644
index 0000000..c5cefa4
--- /dev/null
+++ b/samples/merchandising.py
@@ -0,0 +1,52 @@
+# -*- 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.merchandising import Connection as merchandising
+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.")
+
+ (opts, args) = parser.parse_args()
+ return opts, args
+
+def run(opts):
+ try:
+ api = merchandising(debug=opts.debug, appid=opts.appid,
+ config_file=opts.yaml, warnings=True)
+
+ response = api.execute('getMostWatchedItems', {'maxResults': 4})
+
+ dump(api)
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+
+if __name__ == "__main__":
+ (opts, args) = init_options()
+ run(opts)
diff --git a/samples/parallel.py b/samples/parallel.py
new file mode 100644
index 0000000..788b06b
--- /dev/null
+++ b/samples/parallel.py
@@ -0,0 +1,74 @@
+# -*- 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
+from ebaysdk.finding import Connection as finding
+from ebaysdk.http import Connection as html
+from ebaysdk.parallel import Parallel
+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.")
+
+ (opts, args) = parser.parse_args()
+ return opts, args
+
+
+def run(opts):
+
+ try:
+ 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)
+
+ 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)
+
+ p.wait()
+
+ if p.error():
+ print(p.error())
+
+ for api in apis:
+ dump(api)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+if __name__ == "__main__":
+ (opts, args) = init_options()
+ run(opts)
diff --git a/samples/request_dictionary.py b/samples/request_dictionary.py
new file mode 100644
index 0000000..57fde67
--- /dev/null
+++ b/samples/request_dictionary.py
@@ -0,0 +1,95 @@
+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')
+
+''' dict6 outputSelector
+SellerInfo
+GalleryInfo
+'''
+
+dict6 = {
+ 'outputSelector': [
+ 'SellerInfo',
+ 'GalleryInfo'
+ ]
+}
+
+assert(dict2xml(dict6)=='SellerInfoGalleryInfo')
diff --git a/samples/shopping.py b/samples/shopping.py
new file mode 100644
index 0000000..0131041
--- /dev/null
+++ b/samples/shopping.py
@@ -0,0 +1,161 @@
+# -*- 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
+
+try:
+ input = raw_input
+except NameError:
+ pass
+
+sys.path.insert(0, '%s/../' % os.path.dirname(__file__))
+
+from common import dump
+
+import ebaysdk
+from ebaysdk.exception import ConnectionError
+from ebaysdk.shopping import Connection as 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
+
+def run(opts):
+ api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml,
+ warnings=True)
+
+ print("Shopping samples for SDK version %s" % ebaysdk.get_version())
+
+ try:
+ response = api.execute('FindPopularItems', {'QueryKeywords': 'Python'})
+
+ dump(api)
+
+ print("Matching Titles:")
+ for item in response.reply.ItemArray.Item:
+ print(item.Title)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def popularSearches(opts):
+
+ api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml,
+ warnings=True)
+
+ choice = True
+
+ while choice:
+
+ choice = input('Search: ')
+
+ if choice == 'quit':
+ break
+
+ mySearch = {
+ "MaxKeywords": 10,
+ "QueryKeywords": choice,
+ }
+
+ try:
+ response = api.execute('FindPopularSearches', mySearch)
+
+ dump(api, full=False)
+
+ print("Related: %s" % response.reply.PopularSearchResult.RelatedSearches)
+
+ for term in response.reply.PopularSearchResult.AlternativeSearches.split(';')[:3]:
+ api.execute('FindPopularItems', {'QueryKeywords': term, 'MaxEntries': 3})
+
+ print("Term: %s" % term)
+
+ try:
+ for item in response.reply.ItemArray.Item:
+ print(item.Title)
+ except AttributeError:
+ pass
+
+ dump(api)
+
+ print("\n")
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def categoryInfo(opts):
+
+ try:
+ api = Shopping(debug=opts.debug, appid=opts.appid, config_file=opts.yaml,
+ warnings=True)
+
+ response = api.execute('GetCategoryInfo', {"CategoryID": 3410})
+
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def with_affiliate_info(opts):
+ try:
+ api = Shopping(debug=opts.debug, appid=opts.appid,
+ config_file=opts.yaml, warnings=True,
+ trackingid=1234, trackingpartnercode=9)
+
+ mySearch = {
+ "MaxKeywords": 10,
+ "QueryKeywords": 'shirt',
+ }
+
+ response = api.execute('FindPopularSearches', mySearch)
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def using_attributes(opts):
+
+ try:
+ api = Shopping(debug=opts.debug, appid=opts.appid,
+ config_file=opts.yaml, warnings=True)
+
+ response = api.execute('FindProducts', {
+ "ProductID": {'@attrs': {'type': 'ISBN'},
+ '#text': '0596154488'}})
+
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+if __name__ == "__main__":
+ (opts, args) = init_options()
+ run(opts)
+ popularSearches(opts)
+ categoryInfo(opts)
+ with_affiliate_info(opts)
+ using_attributes(opts)
diff --git a/samples/t_http.py b/samples/t_http.py
new file mode 100644
index 0000000..698299d
--- /dev/null
+++ b/samples/t_http.py
@@ -0,0 +1,46 @@
+# -*- 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/samples/trading.py b/samples/trading.py
new file mode 100644
index 0000000..1ca7b81
--- /dev/null
+++ b/samples/trading.py
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 -*-
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+import os
+import sys
+import datetime
+from optparse import OptionParser
+
+sys.path.insert(0, '%s/../' % os.path.dirname(__file__))
+
+from common import dump
+
+import ebaysdk
+from ebaysdk.utils import getNodeText
+from ebaysdk.exception import ConnectionError
+from ebaysdk.trading import Connection as 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
+
+def run(opts):
+
+ try:
+ api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid,
+ certid=opts.certid, devid=opts.devid)
+
+ api.execute('GetCharities', {'CharityID': 3897})
+ dump(api)
+ print(api.response.reply.Charity.Name)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def feedback(opts):
+ try:
+ 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'})
+ dump(api)
+
+ if int(api.response.reply.FeedbackScore) > 50:
+ print("Doing good!")
+ else:
+ print("Sell more, buy more..")
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def getTokenStatus(opts):
+
+ try:
+ api = Trading(debug=opts.debug, config_file=opts.yaml, appid=opts.appid,
+ certid=opts.certid, devid=opts.devid, warnings=False)
+
+ api.execute('GetTokenStatus')
+ dump(api)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def verifyAddItem(opts):
+ """http://www.utilities-online.info/xmltojson/#.UXli2it4avc
+ """
+
+ 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)
+ dump(api)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+
+def verifyAddItemErrorCodes(opts):
+ """http://www.utilities-online.info/xmltojson/#.UXli2it4avc
+ """
+
+ 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)
+
+ except ConnectionError as e:
+ # traverse the DOM to look for error codes
+ 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():
+ print("Invalid data in request")
+
+ print(e)
+ print(e.response.dict())
+
+def uploadPicture(opts):
+
+ try:
+ 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)
+ dump(api)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def memberMessages(opts):
+
+ 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)
+
+ dump(api)
+
+ if api.response.reply.has_key('MemberMessage'):
+ messages = api.response.reply.MemberMessage.MemberMessageExchange
+
+ if type(messages) != list:
+ messages = [ messages ]
+
+ for m in messages:
+ print("%s: %s" % (m.CreationDate, m.Question.Subject[:50]))
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def getUser(opts):
+ 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)
+
+ api.execute('GetUser', {'UserID': 'biddergoat'})
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def getOrders(opts):
+
+ 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})
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+def categories(opts):
+
+ 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,
+ }
+
+ api.execute('GetCategories', callData)
+ dump(api, full=False)
+
+ except ConnectionError as e:
+ print(e)
+ print(e.response.dict())
+
+'''
+api = trading(domain='api.sandbox.ebay.com')
+api.execute('GetCategories', {
+ 'DetailLevel': 'ReturnAll',
+ 'CategorySiteID': 101,
+ 'LevelLimit': 4,
+})
+'''
+
+if __name__ == "__main__":
+ (opts, args) = init_options()
+
+ print("Trading API Samples for version %s" % ebaysdk.get_version())
+
+ run(opts)
+ feedback(opts)
+ verifyAddItem(opts)
+ getTokenStatus(opts)
+ verifyAddItemErrorCodes(opts)
+ uploadPicture(opts)
+ memberMessages(opts)
+ categories(opts)
+ getUser(opts)
+ getOrders(opts)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..35f0091
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,51 @@
+#!/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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from setuptools import setup, find_packages
+import re
+import os
+
+PKG = 'ebaysdk'
+
+# Get the version
+VERSIONFILE = os.path.join(PKG, "__init__.py")
+version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
+ open(VERSIONFILE, "rt").read(), re.M).group(1)
+
+
+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(
+ name=PKG,
+ version=version,
+ 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', 'lxml', 'requests'],
+ test_suite='tests',
+ long_description=long_desc,
+ classifiers=[
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Intended Audience :: Developers',
+ ]
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..1fffd6d
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+'''
+© 2012-2013 eBay Software Foundation
+Authored by: Tim Keefer
+Licensed under CDDL 1.0
+'''
+
+import sys
+import unittest
+import doctest
+import ebaysdk.utils
+import ebaysdk.config
+import ebaysdk.response
+import ebaysdk.connection
+import ebaysdk.http
+import ebaysdk.shopping
+import ebaysdk.trading
+import ebaysdk.merchandising
+import ebaysdk.soa.finditem
+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.connection))
+ 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 \
+ and sys.modules.has_key('grequests') is True:
+
+ suite.addTest(doctest.DocTestSuite(ebaysdk.parallel))
+
+
+ # inside only
+ #suite.addTest(doctest.DocTestSuite(ebaysdk.soa.finditem))
+
+ return suite
+
+runner = unittest.TextTestRunner()
+runner.run(getTestSuite())
+