From 865465bf20c8fb542eaaf8ed7ba82d43f1d90287 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 16:42:17 +0200 Subject: [PATCH 01/16] Add missing query option "date" --- prestapyt/prestapyt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index e4b10cb..b379683 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -197,13 +197,13 @@ def _parse(self, content): def _validate(self, options): """ Check options against supported options - (reference : http://doc.prestashop.com/display/PS14/Cheat+Sheet_+Concepts+Outlined+in+this+Tutorial) + (reference : http://doc.prestashop.com/display/PS14/Cheat-sheet+-+Concepts+outlined+in+this+tutorial) @param options: dict of options to use for the request @return: True if valid, else raise an error PrestaShopWebServiceError """ if not isinstance(options, dict): raise PrestaShopWebServiceError('Parameters must be a instance of dict') - supported = ('filter', 'display', 'sort', 'limit', 'schema') + supported = ('filter', 'display', 'sort', 'limit', 'schema', 'date') # filter[firstname] (as e.g.) is allowed, so check only the part before a [ unsupported = set([param.split('[')[0] for param in options]).difference(supported) if unsupported: From d549c36ca08478ed4eae6f3de225ac4a12d289b9 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 16:44:51 +0200 Subject: [PATCH 02/16] Renamed method _validate to _validate_query_options which is more self-explanative --- prestapyt/prestapyt.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index b379683..18b2e1d 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -194,7 +194,7 @@ def _parse(self, content): return parsed_content - def _validate(self, options): + def _validate_query_options(self, options): """ Check options against supported options (reference : http://doc.prestashop.com/display/PS14/Cheat-sheet+-+Concepts+outlined+in+this+tutorial) @@ -211,6 +211,9 @@ def _validate(self, options): % (', '.join(tuple(unsupported)),)) return True + # _validate method is deprecated + _validate = _validate_query_options + def _options_to_querystring(self, options): """ Translate the dict of options to a url form @@ -278,7 +281,7 @@ def get(self, resource, resource_id=None, options=None): if resource_id is not None: full_url += "/%s" % (resource_id,) if options is not None: - self._validate(options) + self._validate_query_options(options) full_url += "?%s" % (self._options_to_querystring(options),) return self.get_with_url(full_url) @@ -304,7 +307,7 @@ def head(self, resource, resource_id=None, options=None): if resource_id is not None: full_url += "/%s" % (resource_id,) if options is not None: - self._validate(options) + self._validate_query_options(options) full_url += "?%s" % (self._options_to_querystring(options),) return self.head_with_url(full_url) From e5f11c793435b2842adda3dbcd620f409d6f4059 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 16:45:21 +0200 Subject: [PATCH 03/16] ignore *.pyc --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 883a83d..7dedd11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build dist prestapyt.egg-info +*.pyc From ccefd24e4add946528f7071c22be75ec92efdb56 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 16:48:09 +0200 Subject: [PATCH 04/16] fix setup.py indentation --- setup.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/setup.py b/setup.py index a9bb2d1..f591c4e 100644 --- a/setup.py +++ b/setup.py @@ -10,31 +10,31 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( - # Basic package information. - name = 'prestapyt', - version = __version__, + # Basic package information. + name = 'prestapyt', + version = __version__, - # Packaging options. - include_package_data = True, + # Packaging options. + include_package_data = True, - # Package dependencies. - install_requires = ['httplib2',], + # Package dependencies. + install_requires = ['httplib2',], - # Metadata for PyPI. - author = 'Guewen Baconnier', - author_email = 'guewen.baconnier@gmail.com', - license = 'GNU AGPL-3', - url = 'http://github.com/guewen/prestapyt', + # Metadata for PyPI. + author = 'Guewen Baconnier', + author_email = 'guewen.baconnier@gmail.com', + license = 'GNU AGPL-3', + url = 'http://github.com/guewen/prestapyt', packages=['prestapyt'], - keywords = 'prestashop api client rest', - description = 'A library to access Prestashop Web Service from Python.', - long_description = read('README'), - classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Affero General Public License v3', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Internet :: WWW/HTTP :: Site Management', - 'Topic :: Internet' - ] + keywords = 'prestashop api client rest', + description = 'A library to access Prestashop Web Service from Python.', + long_description = read('README'), + classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Affero General Public License v3', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Internet :: WWW/HTTP :: Site Management', + 'Topic :: Internet' + ] ) From 71d2e177a3c0fcb827673d6d3dace854cb0b4d70 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 16:50:04 +0200 Subject: [PATCH 05/16] Rised the version number to 0.5.0 to reflect the renaming of one method and the fix of query string options --- prestapyt/prestapyt.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index 18b2e1d..033cc9a 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -14,7 +14,7 @@ """ __author__ = "Guewen Baconnier " -__version__ = "0.4.0" +__version__ = "0.5.0" import urllib import warnings diff --git a/setup.py b/setup.py index f591c4e..4a44cc8 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup __author__ = 'Guewen Baconnier ' -__version__ = '0.4.0' +__version__ = '0.5.0' def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() From ea5ff6ae7a68b518ca457cb843bc6d23f3234df7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 23 Aug 2012 17:34:26 +0200 Subject: [PATCH 06/16] Replaced redondant README file with the markdown version only, updated version to 0.5.1 for packaging only --- README | 77 ------------------------------------------ README.md | 56 ++++++++++++++++++++++++------ prestapyt/prestapyt.py | 2 +- setup.py | 4 +-- 4 files changed, 48 insertions(+), 91 deletions(-) delete mode 100644 README diff --git a/README b/README deleted file mode 100644 index d75539d..0000000 --- a/README +++ /dev/null @@ -1,77 +0,0 @@ -prestapyt is a library for Python to interact with the PrestaShop's Web Service API. - -Learn more about the PrestaShop Web Service from the [Official Documentation](http://doc.prestashop.com/display/PS14/Using+the+REST+webservice). - -prestapyt is a direct port of the PrestaShop PHP API Client, PSWebServiceLibrary.php -Similar to PSWebServiceLibrary.php, prestapyt is a thin wrapper around the PrestaShop Web Service: it takes care of making the call to your PrestaShop instance's Web Service, supports the Web Service's HTTP-based CRUD operations (handling any errors) and then returns the XML ready for you to work with in Python (as well as prestasac if you work with scala) - -#Installation -TODO - -#Usage - - from prestapyt import PrestaShopWebServiceError, PrestaShopWebService - - prestashop = PrestaShopWebService('http://localhost:8080/api', 'BVWPFFYBT97WKM959D7AVVD0M4815Y1L') # messages will be as xml - # or - prestashop = PrestaShopWebServiceDict('http://localhost:8080/api', 'BVWPFFYBT97WKM959D7AVVD0M4815Y1L') # messages will be as dict - - # get all addresses - prestashop.get('addresses') # returns ElementTree (PrestaShopWebService) or dict (PrestaShopWebServiceDict) - - # get all addresses - prestashop.get('addresses') # returns ElementTree - - # get address 1 - prestashop.get('addresses', resource_id=1) - prestashop.get('addresses/1') - - # full url - prestashop.get('http://localhost:8080/api/addresses/1') - - #filters - prestashop.get('addresses', options={'limit': 10}) - - # head - print prestashop.head('addresses') - - # delete a resource - prestashop.delete('addresses', resource_ids=4) - - # delete many resources - prestashop.delete('addresses', resource_ids=[5,6]) - - # add - prestashop.add('addresses', xml) - - # edit - prestashop.edit('addresses', 5, xml) - - # get a blank xml - prestashop.get('addresses', options={'schema': 'blank'}) - -#API Documentation -Documentation for the PrestaShop Web Service can be found on the PrestaShop wiki: -[Using the REST webservice](http://doc.prestashop.com/display/PS14/Using+the+REST+webservice) - -#Credits: -Thanks to Prestashop SA for their PHP API Client PSWebServiceLibrary.php - -Thanks to Alex Dean for his port of PSWebServiceLibrary.php to the Scala language, prestasac (https://github.com/orderly/prestashop-scala-client) from which I also inspired my library. - -#Copyright and License - -prestapyt is copyright (c) 2012 Guewen Baconnier - -prestapyt is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -prestapyt is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public -License along with prestapyt. If not, see [GNU licenses](http://www.gnu.org/licenses/). diff --git a/README.md b/README.md index 1ee2f26..5c29871 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,38 @@ +Prestapyt +========= + prestapyt is a library for Python to interact with the PrestaShop's Web Service API. -Learn more about the PrestaShop Web Service from the [Official Documentation](http://doc.prestashop.com/display/PS14/Using+the+REST+webservice). +Learn more about the PrestaShop Web Service from the [Official Prestashop Documentation]. prestapyt is a direct port of the PrestaShop PHP API Client, PSWebServiceLibrary.php -Similar to PSWebServiceLibrary.php, prestapyt is a thin wrapper around the PrestaShop Web Service: it takes care of making the call to your PrestaShop instance's Web Service, supports the Web Service's HTTP-based CRUD operations (handling any errors) and then returns the XML ready for you to work with in Python (as well as prestasac if you work with scala) -#Installation +Similar to PSWebServiceLibrary.php, prestapyt is a thin wrapper around the PrestaShop Web Service: +it takes care of making the call to your PrestaShop instance's Web Service, +supports the Web Service's HTTP-based CRUD operations (handling any errors) +and then returns the XML ready for you to work with in Python +(as well as prestasac if you work with scala). + + +Installation +============ The easiest way to install prestapyt (needs setuptools): easy_install prestapyt -If you do not have setuptools, download prestapyt as a .tar.gz or .zip from here (https://github.com/guewen/prestapyt/downloads), untar it and run: +Or, better, using pip: + + pip install prestapyt + +If you do not have setuptools, download prestapyt as a .tar.gz or .zip from +[Prestapyt Source Archives], untar it and run: python setup.py install -#Usage + +Usage +===== from prestapyt import PrestaShopWebServiceError, PrestaShopWebService @@ -59,16 +76,25 @@ If you do not have setuptools, download prestapyt as a .tar.gz or .zip from here # get a blank xml prestashop.get('addresses', options={'schema': 'blank'}) -#API Documentation -Documentation for the PrestaShop Web Service can be found on the PrestaShop wiki: -[Using the REST webservice](http://doc.prestashop.com/display/PS14/Using+the+REST+webservice) -#Credits: +API Documentation +================= + +Documentation for the PrestaShop Web Service can be found on the +PrestaShop wiki: [Using the REST webservice] + + +Credits: +======== + Thanks to Prestashop SA for their PHP API Client PSWebServiceLibrary.php -Thanks to Alex Dean for his port of PSWebServiceLibrary.php to the Scala language, prestasac (https://github.com/orderly/prestashop-scala-client) from which I also inspired my library. +Thanks to Alex Dean for his port of PSWebServiceLibrary.php +to the Scala language, [prestasac] from which I also inspired my library. -#Copyright and License + +Copyright and License +===================== prestapyt is copyright (c) 2012 Guewen Baconnier @@ -84,3 +110,11 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with prestapyt. If not, see [GNU licenses](http://www.gnu.org/licenses/). + + + +[Official Prestashop Documentation]: http://doc.prestashop.com/display/PS14/Using+the+REST+webservice +[Using the REST webservice]: http://doc.prestashop.com/display/PS14/Using+the+REST+webservice +[Prestapyt Source Archives]: https://github.com/guewen/prestapyt/downloads +[prestasac]: https://github.com/orderly/prestashop-scala-client + diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index 033cc9a..c84e54e 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -14,7 +14,7 @@ """ __author__ = "Guewen Baconnier " -__version__ = "0.5.0" +__version__ = "0.5.1" import urllib import warnings diff --git a/setup.py b/setup.py index 4a44cc8..2b277b4 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup __author__ = 'Guewen Baconnier ' -__version__ = '0.5.0' +__version__ = '0.5.1' def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() @@ -28,7 +28,7 @@ def read(fname): packages=['prestapyt'], keywords = 'prestashop api client rest', description = 'A library to access Prestashop Web Service from Python.', - long_description = read('README'), + long_description = read('README.md'), classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', From 6cc20463133f6f77fa2f77c9b71c641735e3613e Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 4 Sep 2012 12:05:37 +0200 Subject: [PATCH 07/16] [FIX] README.md was not distributed as part the sdist --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bb3ec5f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md From 48e970016767a725bd2c553d5324b56c5771edb1 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 4 Sep 2012 12:06:35 +0200 Subject: [PATCH 08/16] bump version to 0.5.2 --- prestapyt/prestapyt.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index c84e54e..e00303b 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -14,7 +14,7 @@ """ __author__ = "Guewen Baconnier " -__version__ = "0.5.1" +__version__ = "0.5.2" import urllib import warnings diff --git a/setup.py b/setup.py index 2b277b4..6132cb8 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup __author__ = 'Guewen Baconnier ' -__version__ = '0.5.1' +__version__ = '0.5.2' def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() From be281e8fc5cbb678ae29ff1a2f82b05acd3263f1 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 18 Sep 2012 21:03:54 +0200 Subject: [PATCH 09/16] move the print of debugs before the check of status code, improvement proposed by mrainess --- prestapyt/prestapyt.py | 4 +-- prestapyt/tags | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 prestapyt/tags diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index c84e54e..b582b4f 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -168,13 +168,13 @@ def _execute(self, url, method, body=None, add_headers=None): header, content = client.request(url, method, body=body, headers=request_headers) status_code = int(header['status']) - self._check_status_code(status_code) - self._check_version(header.get('psws-version')) if self.debug: # TODO better debug logs print ("Response code: %s\nResponse headers:\n%s\nResponse body:\n%s" % (status_code, header, content)) + self._check_status_code(status_code) + self._check_version(header.get('psws-version')) return status_code, header, content def _parse(self, content): diff --git a/prestapyt/tags b/prestapyt/tags new file mode 100644 index 0000000..1841837 --- /dev/null +++ b/prestapyt/tags @@ -0,0 +1,61 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.9~svn20110310 // +ET2dict xml2dict.py /^def ET2dict(element_tree):$/;" f +MAX_COMPATIBLE_VERSION prestapyt.py /^ MAX_COMPATIBLE_VERSION = '1.5.0.5'$/;" v class:PrestaShopWebService +MIN_COMPATIBLE_VERSION prestapyt.py /^ MIN_COMPATIBLE_VERSION = '1.4.0.17'$/;" v class:PrestaShopWebService +PrestaShopAuthenticationError prestapyt.py /^class PrestaShopAuthenticationError(PrestaShopWebServiceError):$/;" c +PrestaShopWebService prestapyt.py /^class PrestaShopWebService(object):$/;" c +PrestaShopWebServiceDict prestapyt.py /^class PrestaShopWebServiceDict(PrestaShopWebService):$/;" c +PrestaShopWebServiceError prestapyt.py /^class PrestaShopWebServiceError(Exception):$/;" c +__author__ prestapyt.py /^__author__ = "Guewen Baconnier "$/;" v +__init__ prestapyt.py /^ def __init__(self, api_url, api_key, debug=False, headers=None, client_args=None):$/;" m class:PrestaShopWebService +__init__ prestapyt.py /^ def __init__(self, msg, error_code=None):$/;" m class:PrestaShopWebServiceError +__str__ prestapyt.py /^ def __str__(self):$/;" m class:PrestaShopWebServiceError file: +__version__ prestapyt.py /^__version__ = "0.4.0"$/;" v +_check_status_code prestapyt.py /^ def _check_status_code(self, status_code):$/;" m class:PrestaShopWebService +_check_version prestapyt.py /^ def _check_version(self, version):$/;" m class:PrestaShopWebService +_execute prestapyt.py /^ def _execute(self, url, method, body=None, add_headers=None):$/;" m class:PrestaShopWebService +_make_dict xml2dict.py /^def _make_dict(tag, value):$/;" f +_options_to_querystring prestapyt.py /^ def _options_to_querystring(self, options):$/;" m class:PrestaShopWebService +_parse prestapyt.py /^ def _parse(self, content):$/;" m class:PrestaShopWebService +_parse prestapyt.py /^ def _parse(self, content):$/;" m class:PrestaShopWebServiceDict +_parse_node xml2dict.py /^def _parse_node(node):$/;" f +_process dict2xml.py /^def _process(doc, tag, tag_value):$/;" f +_process_attr dict2xml.py /^def _process_attr(doc, attr_value):$/;" f +_process_complex dict2xml.py /^def _process_complex(doc, children):$/;" f +_process_simple dict2xml.py /^def _process_simple(doc, tag, tag_value):$/;" f +_validate prestapyt.py /^ def _validate(self, options):$/;" m class:PrestaShopWebService +add prestapyt.py /^ def add(self, resource, content):$/;" m class:PrestaShopWebService +add_with_url prestapyt.py /^ def add_with_url(self, url, content):$/;" m class:PrestaShopWebServiceDict +add_with_url prestapyt.py /^ def add_with_url(self, url, xml):$/;" m class:PrestaShopWebService +address_data prestapyt.py /^ address_data = prestashop.get('addresses', 1)$/;" v class:PrestaShopWebServiceDict +address_data prestapyt.py /^ address_data = prestashop.get('addresses', options={'schema': 'blank'})$/;" v class:PrestaShopWebServiceDict +delete prestapyt.py /^ def delete(self, resource, resource_ids):$/;" m class:PrestaShopWebService +delete_with_url prestapyt.py /^ def delete_with_url(self, url):$/;" m class:PrestaShopWebService +dict2xml dict2xml.py /^def dict2xml(data):$/;" f +dive prestapyt.py /^ def dive(response, level=1):$/;" f function:PrestaShopWebServiceDict.search +edit prestapyt.py /^ def edit(self, resource, resource_id, content):$/;" m class:PrestaShopWebService +edit_with_url prestapyt.py /^ def edit_with_url(self, url, content):$/;" m class:PrestaShopWebService +edit_with_url prestapyt.py /^ def edit_with_url(self, url, content):$/;" m class:PrestaShopWebServiceDict +encode unicode_encode.py /^def encode(text, encoding='utf-8'):$/;" f +get prestapyt.py /^ def get(self, resource, resource_id=None, options=None):$/;" m class:PrestaShopWebService +get_with_url prestapyt.py /^ def get_with_url(self, url):$/;" m class:PrestaShopWebService +get_with_url prestapyt.py /^ def get_with_url(self, url):$/;" m class:PrestaShopWebServiceDict +head prestapyt.py /^ def head(self, resource, resource_id=None, options=None):$/;" m class:PrestaShopWebService +head_with_url prestapyt.py /^ def head_with_url(self, url):$/;" m class:PrestaShopWebService +partial_add prestapyt.py /^ def partial_add(self, resource, fields):$/;" m class:PrestaShopWebServiceDict +partial_edit prestapyt.py /^ def partial_edit(self, resource, resource_id, fields):$/;" m class:PrestaShopWebServiceDict +prestashop dict2xml.py /^ prestashop = PrestaShopWebService('http:\/\/localhost:8080\/api',$/;" v +prestashop prestapyt.py /^ prestashop = PrestaShopWebServiceDict('http:\/\/localhost:8080\/api',$/;" v class:PrestaShopWebServiceDict +products_dict dict2xml.py /^ products_dict = xml2dict.ET2dict(products_xml)$/;" v +products_xml dict2xml.py /^ products_xml = prestashop.get('products', 1)$/;" v +search prestapyt.py /^ def search(self, resource, options=None):$/;" m class:PrestaShopWebService +search prestapyt.py /^ def search(self, resource, options=None):$/;" m class:PrestaShopWebServiceDict +unicode2encoding unicode_encode.py /^def unicode2encoding(text, encoding='utf-8'):$/;" f +x dict2xml.py /^ x = {'prestashop': {'address': {'address1': '1 Infinite Loop',$/;" v +x dict2xml.py /^ x = {'prestashop': {'addresses': {'address': [{'attrs': {'href': {'value': 'http:\/\/localhost:8080\/api\/addresses\/1',$/;" v +xml2dict xml2dict.py /^def xml2dict(xml):$/;" f From 0ffe774341129c23e9c1dc4566366561bdf8956d Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 18 Sep 2012 21:09:01 +0200 Subject: [PATCH 10/16] removed tags file from repository --- .gitignore | 1 + prestapyt/tags | 61 -------------------------------------------------- 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 prestapyt/tags diff --git a/.gitignore b/.gitignore index 7dedd11..fa89355 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build dist prestapyt.egg-info *.pyc +tags diff --git a/prestapyt/tags b/prestapyt/tags deleted file mode 100644 index 1841837..0000000 --- a/prestapyt/tags +++ /dev/null @@ -1,61 +0,0 @@ -!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ -!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ -!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ -!_TAG_PROGRAM_NAME Exuberant Ctags // -!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ -!_TAG_PROGRAM_VERSION 5.9~svn20110310 // -ET2dict xml2dict.py /^def ET2dict(element_tree):$/;" f -MAX_COMPATIBLE_VERSION prestapyt.py /^ MAX_COMPATIBLE_VERSION = '1.5.0.5'$/;" v class:PrestaShopWebService -MIN_COMPATIBLE_VERSION prestapyt.py /^ MIN_COMPATIBLE_VERSION = '1.4.0.17'$/;" v class:PrestaShopWebService -PrestaShopAuthenticationError prestapyt.py /^class PrestaShopAuthenticationError(PrestaShopWebServiceError):$/;" c -PrestaShopWebService prestapyt.py /^class PrestaShopWebService(object):$/;" c -PrestaShopWebServiceDict prestapyt.py /^class PrestaShopWebServiceDict(PrestaShopWebService):$/;" c -PrestaShopWebServiceError prestapyt.py /^class PrestaShopWebServiceError(Exception):$/;" c -__author__ prestapyt.py /^__author__ = "Guewen Baconnier "$/;" v -__init__ prestapyt.py /^ def __init__(self, api_url, api_key, debug=False, headers=None, client_args=None):$/;" m class:PrestaShopWebService -__init__ prestapyt.py /^ def __init__(self, msg, error_code=None):$/;" m class:PrestaShopWebServiceError -__str__ prestapyt.py /^ def __str__(self):$/;" m class:PrestaShopWebServiceError file: -__version__ prestapyt.py /^__version__ = "0.4.0"$/;" v -_check_status_code prestapyt.py /^ def _check_status_code(self, status_code):$/;" m class:PrestaShopWebService -_check_version prestapyt.py /^ def _check_version(self, version):$/;" m class:PrestaShopWebService -_execute prestapyt.py /^ def _execute(self, url, method, body=None, add_headers=None):$/;" m class:PrestaShopWebService -_make_dict xml2dict.py /^def _make_dict(tag, value):$/;" f -_options_to_querystring prestapyt.py /^ def _options_to_querystring(self, options):$/;" m class:PrestaShopWebService -_parse prestapyt.py /^ def _parse(self, content):$/;" m class:PrestaShopWebService -_parse prestapyt.py /^ def _parse(self, content):$/;" m class:PrestaShopWebServiceDict -_parse_node xml2dict.py /^def _parse_node(node):$/;" f -_process dict2xml.py /^def _process(doc, tag, tag_value):$/;" f -_process_attr dict2xml.py /^def _process_attr(doc, attr_value):$/;" f -_process_complex dict2xml.py /^def _process_complex(doc, children):$/;" f -_process_simple dict2xml.py /^def _process_simple(doc, tag, tag_value):$/;" f -_validate prestapyt.py /^ def _validate(self, options):$/;" m class:PrestaShopWebService -add prestapyt.py /^ def add(self, resource, content):$/;" m class:PrestaShopWebService -add_with_url prestapyt.py /^ def add_with_url(self, url, content):$/;" m class:PrestaShopWebServiceDict -add_with_url prestapyt.py /^ def add_with_url(self, url, xml):$/;" m class:PrestaShopWebService -address_data prestapyt.py /^ address_data = prestashop.get('addresses', 1)$/;" v class:PrestaShopWebServiceDict -address_data prestapyt.py /^ address_data = prestashop.get('addresses', options={'schema': 'blank'})$/;" v class:PrestaShopWebServiceDict -delete prestapyt.py /^ def delete(self, resource, resource_ids):$/;" m class:PrestaShopWebService -delete_with_url prestapyt.py /^ def delete_with_url(self, url):$/;" m class:PrestaShopWebService -dict2xml dict2xml.py /^def dict2xml(data):$/;" f -dive prestapyt.py /^ def dive(response, level=1):$/;" f function:PrestaShopWebServiceDict.search -edit prestapyt.py /^ def edit(self, resource, resource_id, content):$/;" m class:PrestaShopWebService -edit_with_url prestapyt.py /^ def edit_with_url(self, url, content):$/;" m class:PrestaShopWebService -edit_with_url prestapyt.py /^ def edit_with_url(self, url, content):$/;" m class:PrestaShopWebServiceDict -encode unicode_encode.py /^def encode(text, encoding='utf-8'):$/;" f -get prestapyt.py /^ def get(self, resource, resource_id=None, options=None):$/;" m class:PrestaShopWebService -get_with_url prestapyt.py /^ def get_with_url(self, url):$/;" m class:PrestaShopWebService -get_with_url prestapyt.py /^ def get_with_url(self, url):$/;" m class:PrestaShopWebServiceDict -head prestapyt.py /^ def head(self, resource, resource_id=None, options=None):$/;" m class:PrestaShopWebService -head_with_url prestapyt.py /^ def head_with_url(self, url):$/;" m class:PrestaShopWebService -partial_add prestapyt.py /^ def partial_add(self, resource, fields):$/;" m class:PrestaShopWebServiceDict -partial_edit prestapyt.py /^ def partial_edit(self, resource, resource_id, fields):$/;" m class:PrestaShopWebServiceDict -prestashop dict2xml.py /^ prestashop = PrestaShopWebService('http:\/\/localhost:8080\/api',$/;" v -prestashop prestapyt.py /^ prestashop = PrestaShopWebServiceDict('http:\/\/localhost:8080\/api',$/;" v class:PrestaShopWebServiceDict -products_dict dict2xml.py /^ products_dict = xml2dict.ET2dict(products_xml)$/;" v -products_xml dict2xml.py /^ products_xml = prestashop.get('products', 1)$/;" v -search prestapyt.py /^ def search(self, resource, options=None):$/;" m class:PrestaShopWebService -search prestapyt.py /^ def search(self, resource, options=None):$/;" m class:PrestaShopWebServiceDict -unicode2encoding unicode_encode.py /^def unicode2encoding(text, encoding='utf-8'):$/;" f -x dict2xml.py /^ x = {'prestashop': {'address': {'address1': '1 Infinite Loop',$/;" v -x dict2xml.py /^ x = {'prestashop': {'addresses': {'address': [{'attrs': {'href': {'value': 'http:\/\/localhost:8080\/api\/addresses\/1',$/;" v -xml2dict xml2dict.py /^def xml2dict(xml):$/;" f From af7ae77fd3806062a8498190750c18c44721771d Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 18 Sep 2012 21:41:46 +0200 Subject: [PATCH 11/16] updated setup.py to use the package version --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 6132cb8..5cb5758 100644 --- a/setup.py +++ b/setup.py @@ -3,16 +3,13 @@ import os from setuptools import setup -__author__ = 'Guewen Baconnier ' -__version__ = '0.5.2' - def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( # Basic package information. name = 'prestapyt', - version = __version__, + version = prestapyt.__version__, # Packaging options. include_package_data = True, From 485629522602fe56839c64e515dc0632254ed9b9 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 18 Sep 2012 21:45:44 +0200 Subject: [PATCH 12/16] add license information --- prestapyt/prestapyt.py | 4 ++++ setup.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index ce2ea6d..489299a 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -5,6 +5,10 @@ Prestapyt is a library for Python to interact with the PrestaShop's Web Service API. Prestapyt is a direct port of the PrestaShop PHP API Client, PSWebServiceLibrary.php + :copyright: (c) 2011-2012 Guewen Baconnier + :copyright: (c) 2011 Camptocamp SA + :license: AGPLv3, see LICENSE for more details + Credits: Thanks to Prestashop SA for their PHP API Client PSWebServiceLibrary.php Thanks to Alex Dean for his port of PSWebServiceLibrary.php to the Scala language (https://github.com/orderly/prestashop-scala-client) diff --git a/setup.py b/setup.py index 5cb5758..ac0763c 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,14 @@ #!/usr/bin/env python +""" + Prestapyt + + :copyright: (c) 2011-2012 Guewen Baconnier + :copyright: (c) 2011 Camptocamp SA + :license: AGPLv3, see LICENSE for more details + +""" + import os from setuptools import setup From 34ff05cc8cdbaa848c7ad4a690b4a13086c75402 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 18 Sep 2012 21:51:08 +0200 Subject: [PATCH 13/16] add credits --- CREDITS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CREDITS diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..adb6786 --- /dev/null +++ b/CREDITS @@ -0,0 +1,5 @@ +CREDITS +------- +Camptocamp SA +Akretion +Mark Rainess (mrainess) From 4553c6feaab25c78215072ef2b80248bc4c642ff Mon Sep 17 00:00:00 2001 From: wantellets Date: Fri, 30 Aug 2013 16:35:34 +0200 Subject: [PATCH 14/16] Update prestapyt.py Add id_shop to supported parameters for OpenERP prestashop connector --- prestapyt/prestapyt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index 489299a..6ccd1b7 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -207,7 +207,7 @@ def _validate_query_options(self, options): """ if not isinstance(options, dict): raise PrestaShopWebServiceError('Parameters must be a instance of dict') - supported = ('filter', 'display', 'sort', 'limit', 'schema', 'date') + supported = ('filter', 'display', 'sort', 'limit', 'schema', 'date', 'id_shop') # filter[firstname] (as e.g.) is allowed, so check only the part before a [ unsupported = set([param.split('[')[0] for param in options]).difference(supported) if unsupported: From 99beb15652c50d28b06358865c3c3fa92bfea678 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 30 Aug 2013 20:44:33 +0200 Subject: [PATCH 15/16] bump to version 0.5.3 --- prestapyt/prestapyt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index 6ccd1b7..3ea8d32 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -18,7 +18,7 @@ """ __author__ = "Guewen Baconnier " -__version__ = "0.5.2" +__version__ = "0.5.3" import urllib import warnings From 097128abb7a7b020acee6387425936b2ee0bb976 Mon Sep 17 00:00:00 2001 From: wantellets Date: Mon, 2 Sep 2013 11:45:01 +0200 Subject: [PATCH 16/16] Update prestapyt.py Add id_shop to the supported parameters, this is used in the multishop configurations. --- prestapyt/prestapyt.py | 304 +++++++++++++++++++++++++++++++---------- 1 file changed, 229 insertions(+), 75 deletions(-) diff --git a/prestapyt/prestapyt.py b/prestapyt/prestapyt.py index 3ea8d32..a481b12 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -5,10 +5,6 @@ Prestapyt is a library for Python to interact with the PrestaShop's Web Service API. Prestapyt is a direct port of the PrestaShop PHP API Client, PSWebServiceLibrary.php - :copyright: (c) 2011-2012 Guewen Baconnier - :copyright: (c) 2011 Camptocamp SA - :license: AGPLv3, see LICENSE for more details - Credits: Thanks to Prestashop SA for their PHP API Client PSWebServiceLibrary.php Thanks to Alex Dean for his port of PSWebServiceLibrary.php to the Scala language (https://github.com/orderly/prestashop-scala-client) @@ -18,22 +14,27 @@ """ __author__ = "Guewen Baconnier " -__version__ = "0.5.3" +__version__ = "0.4.0" import urllib import warnings -import httplib2 +import requests import xml2dict import dict2xml import unicode_encode +import base64 +from cStringIO import StringIO from xml.parsers.expat import ExpatError +from xml.dom.minidom import parseString from distutils.version import LooseVersion try: from xml.etree import cElementTree as ElementTree except ImportError, e: from xml.etree import ElementTree +requests.defaults.defaults['base_headers']['User-Agent'] = 'Prestapyt: Python Prestashop Library' + class PrestaShopWebServiceError(Exception): """Generic PrestaShop WebServices error class @@ -42,12 +43,14 @@ class PrestaShopWebServiceError(Exception): from prestapyt import PrestaShopWebServiceError """ - def __init__(self, msg, error_code=None): - self.error_code = error_code + def __init__(self, msg, error_code=None, ps_error_msg='', ps_error_code=None): self.msg = msg + self.error_code = error_code + self.ps_error_msg = ps_error_msg + self.ps_error_code = ps_error_code def __str__(self): - return repr(self.msg) + return repr(self.ps_error_msg) class PrestaShopAuthenticationError(PrestaShopWebServiceError): @@ -60,7 +63,7 @@ class PrestaShopWebService(object): """ MIN_COMPATIBLE_VERSION = '1.4.0.17' - MAX_COMPATIBLE_VERSION = '1.5.0.5' + MAX_COMPATIBLE_VERSION = '1.5.4.0' def __init__(self, api_url, api_key, debug=False, headers=None, client_args=None): """ @@ -85,7 +88,6 @@ def __init__(self, api_url, api_key, debug=False, headers=None, client_args=None # required to hit prestashop self._api_url = api_url - self._api_key = api_key # add a trailing slash to the url if there is not one if not self._api_url.endswith('/'): @@ -97,14 +99,29 @@ def __init__(self, api_url, api_key, debug=False, headers=None, client_args=None # optional arguments self.debug = debug - self.client_args = client_args + client_args.update({'auth' : (api_key, '')}) # use header you coders you want, otherwise, use a default - self.headers = headers - if self.headers is None: - self.headers = {'User-agent': 'Prestapyt: Python Prestashop Library'} + self.headers = {} if headers is None else headers + + # init http client in the init for re-use the same connection for all call + self.client = requests.session(**client_args) + + def _parse_error(self, xml_content): + """ + Take the XML content as string and extracts the PrestaShop error + @param xml_content: xml content returned by the PS server as string + @return (prestashop_error_code, prestashop_error_message) + """ + error_answer = self._parse(xml_content) + ps_error_code = '' + ps_error_msg = '' + if isinstance(error_answer, dict): + error_content = error_answer.get('prestashop', {}).get('errors', {}).get('error', {}) + return (error_content.get('code'), error_content.get('message')) + - def _check_status_code(self, status_code): + def _check_status_code(self, status_code, content): """ Take the status code and throw an exception if the server didn't return 200 or 201 code @param status_code: status code returned by the server @@ -117,20 +134,20 @@ def _check_status_code(self, status_code): 405: 'Method Not Allowed', 500: 'Internal Server Error',} - error_label = ('This call to PrestaShop Web Services failed and ' - 'returned an HTTP status of %d. That means: %s.') if status_code in (200, 201): return True elif status_code == 401: - raise PrestaShopAuthenticationError(error_label - % (status_code, message_by_code[status_code]), status_code) + # the content is empty for auth errors + raise PrestaShopAuthenticationError(message_by_code[status_code], + status_code) elif status_code in message_by_code: - raise PrestaShopWebServiceError(error_label - % (status_code, message_by_code[status_code]), status_code) + ps_error_code, ps_error_msg = self._parse_error(content) + raise PrestaShopWebServiceError(message_by_code[status_code], + status_code, ps_error_msg, ps_error_code) else: - raise PrestaShopWebServiceError(("This call to PrestaShop Web Services returned " - "an unexpected HTTP status of: %d") - % (status_code,), status_code) + ps_error_code, ps_error_msg = self._parse_error(content) + raise PrestaShopWebServiceError('Unknown error', status_code, + ps_error_msg, ps_error_code) def _check_version(self, version): """ @@ -141,49 +158,59 @@ def _check_version(self, version): """ if version: if not (LooseVersion(self.MIN_COMPATIBLE_VERSION) < - LooseVersion(version) < + LooseVersion(version) <= LooseVersion(self.MAX_COMPATIBLE_VERSION)): warnings.warn(("This library may not be compatible with this version of PrestaShop (%s). " "Please upgrade/downgrade this library") % (version,)) return True - def _execute(self, url, method, body=None, add_headers=None): + def _execute(self, url, method, data=None, files=None, add_headers=None): """ Execute a request on the PrestaShop Webservice @param url: full url to call @param method: GET, POST, PUT, DELETE, HEAD - @param body: for PUT (edit) and POST (add) only, the xml sent to PrestaShop + @param data: for PUT (edit) and POST (add) only, the xml sent to PrestaShop + @param files: should contain {'image': (img_filename, img_file)} @param add_headers: additional headers merged on the instance's headers @return: tuple with (status code, header, content) of the response """ if add_headers is None: add_headers = {} - client = httplib2.Http(**self.client_args) - # Prestashop use the key as username without password - client.add_credentials(self._api_key, False) - client.follow_all_redirects = True - - if self.debug: - print "Execute url: %s / method: %s" % (url, method) + # Don't print when method = POST, because it contains an encoded URL + # The print for POST is in the method add_with_url() + if self.debug and data and method <> 'POST': + try: + xml = parseString(data) + pretty_body = xml.toprettyxml(indent=" ") + except: + pretty_body = data + print "Execute url: %s / method: %s\nbody: %s" % (url, method, pretty_body) request_headers = self.headers.copy() request_headers.update(add_headers) - header, content = client.request(url, method, body=body, headers=request_headers) - status_code = int(header['status']) + if not files: + r = self.client.request(method, url, data=data, headers=request_headers) + else: + r = self.client.request(method, url, files=files) if self.debug: # TODO better debug logs - print ("Response code: %s\nResponse headers:\n%s\nResponse body:\n%s" - % (status_code, header, content)) + print ("Response code: %s\nResponse headers:\n%s\n" + % (r.status_code, r.headers)) + if r.headers.get('content-type') and r.headers.get('content-type').startswith('image'): + print "Response body: Image in binary format\n" + else: + print "Response body:\n%s\n" % r.content + + self._check_status_code(r.status_code, r.content) + self._check_version(r.headers.get('psws-version')) - self._check_status_code(status_code) - self._check_version(header.get('psws-version')) - return status_code, header, content + return r def _parse(self, content): """ - Parse the response of the webservice, assumed to be a XML in utf-8 + Parse the response of the webservice @param content: response from the webservice @return: an ElementTree of the content @@ -192,22 +219,37 @@ def _parse(self, content): raise PrestaShopWebServiceError('HTTP response is empty') try: - parsed_content = ElementTree.fromstring(content) + # We have to encode it in utf-8, because content has the XML header + # cf http://lxml.de/FAQ.html#why-can-t-lxml-parse-my-xml-from-unicode-strings + # WARNING : old versions of 'requests', for instance version 0.8.2 + # packaged in Ubuntu 12.04, return a unicode... but more recent of + # requests, for instance 0.13.5 return a str in utf-8 ! + parsed_content = ElementTree.fromstring(unicode_encode.unicode2encoding(content)) except ExpatError, err: raise PrestaShopWebServiceError('HTTP XML response is not parsable : %s' % (err,)) return parsed_content - def _validate_query_options(self, options): + def _validate(self, options): """ Check options against supported options - (reference : http://doc.prestashop.com/display/PS14/Cheat-sheet+-+Concepts+outlined+in+this+tutorial) + (reference : http://doc.prestashop.com/display/PS14/Cheat+Sheet_+Concepts+Outlined+in+this+Tutorial) + + This syntax also works for options dict : + (reference : http://www.prestashop.com/forums/topic/101502-webservice-api-filter-for-date-ranges/#post_id_708102) + {'filter[date_upd]': '>[2012-07-30]', + 'date': '1'} + will returns : + '/?filter[date_upd]=>[2012-07-30]&date=1' + you may also define {'filter[date_upd]': '>[2012-07-30 16:00:00]', 'date': '1'} + Note : you must consider that '>[2012-07-30]' is interpreted like 'equal or greater than' by web service + @param options: dict of options to use for the request @return: True if valid, else raise an error PrestaShopWebServiceError """ if not isinstance(options, dict): raise PrestaShopWebServiceError('Parameters must be a instance of dict') - supported = ('filter', 'display', 'sort', 'limit', 'schema', 'date', 'id_shop') + supported = ('filter', 'display', 'sort', 'limit', 'schema', 'date', 'date_filter', 'id_shop') # filter[firstname] (as e.g.) is allowed, so check only the part before a [ unsupported = set([param.split('[')[0] for param in options]).difference(supported) if unsupported: @@ -215,9 +257,6 @@ def _validate_query_options(self, options): % (', '.join(tuple(unsupported)),)) return True - # _validate method is deprecated - _validate = _validate_query_options - def _options_to_querystring(self, options): """ Translate the dict of options to a url form @@ -232,30 +271,66 @@ def _options_to_querystring(self, options): """ if self.debug: options.update({'debug': True}) + if options.get('date_filter'): + options['date'] = 1 + for field, operator, date in options.pop('date_filter'): + options['filter[%s]'%field] = '%s[%s]'%(operator, date.strftime('%Y-%m-%d %H:%M:%S')) return urllib.urlencode(options) - def add(self, resource, content): + def add(self, resource, content, img_filename=None): """ Add (POST) a resource. The content can be a dict of values to create. @param resource: type of resource to create @param content: Full XML as string or dict of new resource values. - If a dict is given, it will be converted to XML with the necessary root tag ie: + If a dict is given, it will be converted to XML with the necessary + root tag ie: [[dict converted to xml]] - @return: an ElementTree of the response from the web service + If we add an image, it should contain the binary of the image as string. + @param img_filename: Filename of the image with its extension as string, + for example 'myproduct.jpg' + @return: an ElementTree of the response from the web service if it's an XML + or True if the response from the web service is a binary """ - return self.add_with_url(self._api_url + resource, content) + if img_filename: + # Check that we have a valid filename with an extension + if isinstance(img_filename, (str, unicode)) and 1<=len(img_filename)<= 255 and "/" not in img_filename and "\000" not in img_filename and '.' in img_filename: + if self.debug: + print "Filename '%s' considered valid" % img_filename + else: + raise PrestaShopWebServiceError('Invalid image filename: %s' + % img_filename) - def add_with_url(self, url, xml): + return self.add_with_url(self._api_url + resource, content, img_filename=img_filename) + + def add_with_url(self, url, content, img_filename=None): """ Add (POST) a resource @param url: A full URL which for the resource type to create - @param xml: Full XML as string of new resource. + @param content: a string containing the full XML of new resource or an image encoded in base64. + @param img_filename: a string containing the filename of the image. @return: an ElementTree of the response from the web service """ - headers = {'Content-Type': 'application/x-www-form-urlencoded'} - return self._parse(self._execute(url, 'POST', body=urllib.urlencode({'xml': xml}), add_headers=headers)[2]) + if not img_filename: + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + if self.debug and content: + try: + xml = parseString(content) + pretty_body = xml.toprettyxml(indent=" ") + except: + pretty_body = content + print "Execute url: %s / method: POST\nbody: %s" % (url, pretty_body) + + r = self._execute(url, 'POST', data=urllib.urlencode({'xml': content.encode('utf-8')}), add_headers=headers) + else: + img_binary = base64.decodestring(content) + img_file = StringIO(img_binary) + r = self._execute(url, 'POST', files={'image': (img_filename, img_file)}) + if r.headers.get('content-type') and r.headers.get('content-type').startswith('image'): + return True + else: + return self._parse(r.content) def search(self, resource, options=None): """ @@ -266,7 +341,8 @@ def search(self, resource, options=None): it is more clear than "get without id" to search resources @param resource: string of the resource to search like 'addresses', 'products' - @param options: Optional dict of parameters to filter the search (one or more of 'filter', 'display', 'sort', 'limit', 'schema') + @param options: Optional dict of parameters to filter the search (one or more of + 'filter', 'display', 'sort', 'limit', 'schema') @return: ElementTree of the xml message """ return self.get(resource, options=options) @@ -285,7 +361,7 @@ def get(self, resource, resource_id=None, options=None): if resource_id is not None: full_url += "/%s" % (resource_id,) if options is not None: - self._validate_query_options(options) + self._validate(options) full_url += "?%s" % (self._options_to_querystring(options),) return self.get_with_url(full_url) @@ -296,7 +372,11 @@ def get_with_url(self, url): @param url: An URL which explicitly sets the resource type and ID to retrieve @return: an ElementTree of the resource """ - return self._parse(self._execute(url, 'GET')[2]) + r = self._execute(url, 'GET') + if r.headers.get('content-type') and r.headers.get('content-type').startswith('image'): + return r.content + else: + return self._parse(r.content) def head(self, resource, resource_id=None, options=None): """ @@ -311,7 +391,7 @@ def head(self, resource, resource_id=None, options=None): if resource_id is not None: full_url += "/%s" % (resource_id,) if options is not None: - self._validate_query_options(options) + self._validate(options) full_url += "?%s" % (self._options_to_querystring(options),) return self.head_with_url(full_url) @@ -322,9 +402,9 @@ def head_with_url(self, url): @param url: An URL which explicitly sets the resource type and ID to retrieve @return: the header of the response as a dict """ - return self._execute(url, 'HEAD')[1] + return self._execute(url, 'HEAD').headers - def edit(self, resource, resource_id, content): + def edit(self, resource, content): """ Edit (PUT) a resource. @@ -333,7 +413,7 @@ def edit(self, resource, resource_id, content): @param content: modified XML as string of the resource. @return: an ElementTree of the Webservice's response """ - full_url = "%s%s/%s" % (self._api_url, resource, resource_id) + full_url = "%s%s" % (self._api_url, resource) return self.edit_with_url(full_url, content) def edit_with_url(self, url, content): @@ -345,7 +425,8 @@ def edit_with_url(self, url, content): @return: an ElementTree of the Webservice's response """ headers = {'Content-Type': 'application/x-www-form-urlencoded'} - return self._parse(self._execute(url, 'PUT', body=unicode_encode.encode(content), add_headers=headers)[2]) + r = self._execute(unicode_encode.encode(url), 'PUT', data=unicode_encode.encode(content), add_headers=headers) + return self._parse(r.content) def delete(self, resource, resource_ids): """ @@ -422,6 +503,29 @@ def dive(response, level=1): ids = [int(elems['attrs']['id'])] return ids + def get(self, resource, resource_id=None, options=None): + """ + Retrieve (GET) a resource + + @param resource: type of resource to retrieve + @param resource_id: optional resource id to retrieve + @param options: Optional dict of parameters (one or more of + 'filter', 'display', 'sort', 'limit', 'schema') + @return: a dict of the response + """ + response = super(PrestaShopWebServiceDict, self).get(resource, resource_id=resource_id, options=options) + if resource == 'images/products' and resource_id: + images = [] + for image in response['image']['declination']: + image_id = image['attrs']['id'] + image_url = '%s%s/%s/%s'%(self._api_url, resource, resource_id, image_id) + images.append({ + 'id': image_id, + 'image': self._execute(image_url, 'get').content + }) + return images + return response + def get_with_url(self, url): """ Retrieve (GET) a resource from a full URL @@ -430,19 +534,69 @@ def get_with_url(self, url): @return: a dict of the response. Remove root keys ['prestashop'] from the message """ response = super(PrestaShopWebServiceDict, self).get_with_url(url) - return response['prestashop'] + if isinstance(response, dict): + return response['prestashop'] + else: + return response + + def partial_add(self, resource, fields): + """ + Add (POST) a resource without necessary all the content. + Retrieve the full empty envelope + and merge the given fields in this envelope. + + @param resource: type of resource to create + @param fields: dict of fields of the resource to create + @return: response of the server + """ + blank_envelope = self.get(resource, options={'schema': 'blank'}) + complete_content = dict(blank_envelope, **fields) + return self.add(resource, complete_content) - def add_with_url(self, url, content): + def partial_edit(self, resource, resource_id, fields): + """ + Edit (PUT) partially a resource. + Standard REST PUT means a full replacement of the resource. + Allows to edit only only some fields of the resource with + a perf penalty. It will read on prestashop, + then modify the keys in content, + and write on prestashop. + + @param resource: type of resource to edit + @param resource_id: id of the resource to edit + @param fields: dict containing the field name as key + and the values of the files to modify + @return: an ElementTree of the Webservice's response + """ + complete_content = self.get(resource, resource_id) + for key in complete_content: + if fields.get(key): + complete_content[key].update(fields[key]) + return self.edit(resource, complete_content) + + def add_with_url(self, url, content, img_filename=None): """ Add (POST) a resource @param url: A full URL which for the resource type to create - @param content: dict of new resource values. it will be converted to XML with the necessary root tag ie: - [[dict converted to xml]] - @return: a dict of the response from the web service - """ - xml_content = dict2xml.dict2xml({'prestashop': content}) - return super(PrestaShopWebServiceDict, self).add_with_url(url, xml_content) + @param content: a string containing the full XML of new resource + or an image encoded in base64. + @param img_filename: a string containing the filename of the image. + @return: a dict of the response from the web service or True if the + response is a binary. + """ + if isinstance(content, dict): + xml_content = dict2xml.dict2xml({'prestashop': content}) + else: + xml_content = content + res = super(PrestaShopWebServiceDict, self).add_with_url(url, xml_content, img_filename=img_filename) + if isinstance(res, dict) and res.get('prestashop'): + res_l2 = res['prestashop'].keys() + if 'content' in res['prestashop'].keys(): + res_l2.remove('content') + return res['prestashop'][res_l2[0]]['id'] + else: + return True def edit_with_url(self, url, content): """ @@ -453,7 +607,7 @@ def edit_with_url(self, url, content): @return: an ElementTree of the Webservice's response """ xml_content = dict2xml.dict2xml({'prestashop': content}) - return super(PrestaShopWebServiceDict, self).edit_with_url(url, xml_content) + return super(PrestaShopWebServiceDict, self).edit_with_url(url, xml_content) def _parse(self, content): """