diff --git a/.gitignore b/.gitignore index 883a83d..fa89355 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build dist prestapyt.egg-info +*.pyc +tags 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) 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 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 e4b10cb..a481b12 100644 --- a/prestapyt/prestapyt.py +++ b/prestapyt/prestapyt.py @@ -18,18 +18,23 @@ 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 @@ -38,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): @@ -56,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): """ @@ -81,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('/'): @@ -93,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 _check_status_code(self, status_code): + 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, 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 @@ -113,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): """ @@ -137,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']) - self._check_status_code(status_code) - self._check_version(header.get('psws-version')) + 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')) - 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 @@ -188,7 +219,12 @@ 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,)) @@ -198,12 +234,22 @@ def _validate(self, options): """ Check options against supported options (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') + 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: @@ -225,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) + + return self.add_with_url(self._api_url + resource, content, img_filename=img_filename) - def add_with_url(self, url, xml): + 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): """ @@ -259,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) @@ -289,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): """ @@ -315,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. @@ -326,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): @@ -338,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): """ @@ -415,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 @@ -423,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): """ @@ -446,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): """ diff --git a/setup.py b/setup.py index a9bb2d1..ac0763c 100644 --- a/setup.py +++ b/setup.py @@ -1,40 +1,46 @@ #!/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 -__author__ = 'Guewen Baconnier ' -__version__ = '0.4.0' - 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 = prestapyt.__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.md'), + 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' + ] )