Commits

Anonymous committed 941d63c

New http client, remove dependancy to httplib2

Comments (0)

Files changed (26)

 *.DS_Store
 *.beam
 *.log
-py_restclient.egg-info
+restkit.egg-info
 
 syntax: regexp
 .*\#.*\#$
+Simple Python REST Client
+==========================
+
+This package provide a symple python rest client.
+
+ <http://py-restclient.e-engura.org>

restclient/__init__.py

-# -*- coding: utf-8 -
-# Copyright (c) 2008, 2009  Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-
-try:
-    __version__ = __import__('pkg_resources').get_distribution('py-restclient').version
-except:
-    __version__ = '?'
-
-debuglevel = 0
-
-from restclient.errors import *
-from restclient.transport import CurlTransport, HTTPLib2Transport
-from restclient.rest import *
-
-

restclient/bin/__init__.py

Empty file removed.

restclient/bin/rest_cli.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-import os
-import sys
-from optparse import OptionParser, OptionGroup
-import urlparse
-import urllib
-
-# python 2.6 and above compatibility
-try:
-    from urlparse import parse_qs as _parse_qs
-except ImportError:
-    from cgi import parse_qs as _parse_qs
-
-import restclient
-from restclient.transport import useCurl, CurlTransport, HTTPLib2Transport
-
-class Url(object):
-    def __init__(self, string):
-        parts = urlparse.urlsplit(urllib.unquote(string))
-        if parts[0] != 'http' and parts[0] != 'https':
-            raise ValueError('Invalid url: %s.' % string)
-
-        if "@" in parts[1]:
-            host = parts[1].split('@').pop()
-        else:
-            host = parts[1]
-       
-        self.hostname = host
-        if parts[0] == 'http':
-            self.port = 80
-        else:
-            self.port = 443
-
-        if ":" in host:
-            try:
-                self.hostname, self.port = host.split(':')
-            except:
-                raise ValueError('Invalid url: %s.' % string)
-
-            self.port = int(self.port)
-
-        self.uri = "%s://%s" % (parts[0], host)
-
-        if parts[2]:
-            self.path = parts[2]
-        else:
-            self.path = ''
-
-        if parts[3]:
-            self.query = _parse_qs(parts[3])
-        else:
-            self.query = {}
-
-        self.username = parts.username
-        self.password = parts.password
-
-
-def make_query(string, method='GET', fname=None, 
-        list_headers=None, output=None, proxy=None):
-    try:
-        uri = Url(string)
-    except ValueError, e:
-        print >>sys.stderr, e
-        return 
-
-    transport = None 
-    proxy_infos = None
-    if proxy and proxy is not None:
-        try:
-            proxy_url = Url(proxy)
-        except:
-            print >>sys.stderr, "proxy url is invalid"
-            return
-        proxy_infos = { "proxy_host": proxy_url.hostname }
-        if proxy_url.port is not None:
-            proxy_infos["proxy_port"] = proxy_url.port
-        if proxy_url.username and proxy_url.username is not None:
-            proxy_infos["proxy_username"] = proxy_url.username
-            proxy_infos["proxy_password"] = proxy_url.password or ''
-
-    if useCurl():
-        transport = CurlTransport(proxy_infos=proxy_infos)
-    else:
-        transport = HTTPLib2Transport(proxy_infos=proxy_infos)
-    
-    if uri.username:
-        transport.add_credentials(uri.username, uri.password) 
-    
-    res = restclient.Resource(uri.uri, transport=transport)
-
-    list_headers = list_headers or []
-    headers = {}
-    if list_headers:
-        for header in list_headers:
-            if ":" in header:
-                k, v = header.split(':')
-                headers[k] = v
-
-    payload = None
-    if fname:
-        if fname == '-':
-            payload = sys.stdin.read()
-            headers['Content-Length'] = len(payload)
-        else:
-            fname = os.path.normpath(os.path.join(os.getcwd(),fname))
-            headers['Content-Length'] = os.path.getsize(fname)
-            payload = open(fname, 'r')
-
-    data = res.request(method, path=uri.path, payload=payload, headers=headers, **uri.query)
-
-    output = output or ''
-    if not output or output == '-':
-        return data
-    else:
-        try:
-            f = open(output, 'wb')
-            f.write(data)
-            f.close()
-        except:
-            print >>sys.stderr, "Can't save result in %s" % output
-            return
-
-
-def main():
-    parser = OptionParser(usage='%prog [options] url [METHOD] [filename]', version="%prog " + restclient.__version__)
-    parser.add_option('-H', '--header', action='append', dest='headers',
-            help='http string header in the form of Key:Value. '+
-            'For example: "Accept: application/json" ')
-    parser.add_option('-i', '--input', action='store', dest='input', metavar='FILE',
-                      help='the name of the file to read from')
-    parser.add_option('-o', '--output', action='store', dest='output',
-                      help='the name of the file to read from')
-
-    parser.add_option('--proxy', action='store', dest='proxy',
-            help='Full uri of proxy, ex:\n'+
-            'http://username:password@proxy:port/')
-
-    options, args = parser.parse_args()
-
-    if len(args) < 1:
-        return parser.error('incorrect number of arguments')
-    if options.input:
-        fname=options.input
-    else:
-        fname=None
-   
-    if len(args) == 3:
-        return make_query(args[0], method=args[1], fname=args[2], 
-                list_headers=options.headers, output=options.output,
-                proxy=options.proxy)
-    
-    elif len(args) == 2:
-        if args[1] == "-":
-            return make_query(args[0], method='POST', fname=args[1], 
-                    list_headers=options.headers, output=options.output,
-                    proxy=options.proxy)
-        return make_query(args[0], method=args[1], fname=fname, 
-                list_headers=options.headers, output=options.output,
-                proxy=options.proxy)
-    else:
-        if options.input:
-            method = 'POST'
-        else:
-            method='GET'
-        return make_query(args[0], method=method, fname=fname, 
-                list_headers=options.headers, output=options.output,
-                proxy=options.proxy)
-
-
-if __name__ == '__main__':
-    main()

restclient/errors.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-"""
-exception classes.
-"""
-
-class ResourceError(Exception):
-
-    def __init__(self, msg=None, http_code=None, response=None):
-        self.msg = msg or ''
-        self.status_code = http_code
-        self.response = response
-        Exception.__init__(self)
-        
-    def _get_message(self):
-        return self.msg
-    def _set_message(self, msg):
-        self.msg = msg or ''
-    message = property(_get_message, _set_message)    
-    
-    def __str__(self):
-        if self.msg:
-            return self.msg
-        try:
-            return self._fmt % self.__dict__
-        except (NameError, ValueError, KeyError), e:
-            return 'Unprintable exception %s: %s' \
-                % (self.__class__.__name__, str(e))
-        
-class ResourceNotFound(ResourceError):
-    """Exception raised when no resource was found at the given url. 
-    """
-
-class Unauthorized(ResourceError):
-    """Exception raised when an authorization is required to access to
-    the resource specified.
-    """
-
-class RequestFailed(ResourceError):
-    """Exception raised when an unexpected HTTP error is received in response
-    to a request.
-    
-
-    The request failed, meaning the remote HTTP server returned a code 
-    other than success, unauthorized, or NotFound.
-
-    The exception message attempts to extract the error
-
-    You can get the status code by e.http_code, or see anything about the 
-    response via e.response. For example, the entire result body (which is 
-    probably an HTML error page) is e.response.body.
-    """
-
-class RequestError(Exception):
-    """Exception raised when a request is malformed"""
-    
-class InvalidUrl(Exception):
-    """
-    Not a valid url for use with this software.
-    """
-    
-class TransportError(Exception):
-    """Error raised by a transport """

restclient/rest.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-# _getCharacterEncoding from Feedparser under BSD License :
-#
-# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
-# 
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-# 
-# * Redistributions of source code must retain the above copyright notice,
-#   this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-#   this list of conditions and the following disclaimer in the documentation
-#   and/or other materials provided with the distribution.
-# 
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-
-"""
-restclient.rest
-~~~~~~~~~~~~~~~
-
-This module provide a common interface for all HTTP equest. 
-
-    >>> from restclient import Resource
-    >>> res = Resource('http://friendpaste.com')
-    >>> res.get('/5rOqE9XTz7lccLgZoQS4IP',headers={'Accept': 'application/json'})
-    u'{"snippet": "hi!", "title": "", "id": "5rOqE9XTz7lccLgZoQS4IP", "language": "text", "revision": "386233396230"}'
-    >>> res.status
-    200
-"""
-
-import cgi
-import mimetypes
-import os
-import StringIO
-import types
-import urllib
-
-try:
-    import chardet
-except ImportError:
-    chardet = False
-
-from restclient.errors import *
-from restclient.transport import getDefaultHTTPTransport, HTTPTransportBase
-from restclient.utils import to_bytestring
-
-__all__ = ['Resource', 'RestClient', 'url_quote', 'url_encode']
-
-__docformat__ = 'restructuredtext en'
-
-class Resource(object):
-    """A class that can be instantiated for access to a RESTful resource, 
-    including authentication. 
-
-    It can use pycurl, urllib2, httplib2 or any interface over
-    `restclient.http.HTTPClient`.
-
-    """
-    def __init__(self, uri, transport=None, headers=None):
-        """Constructor for a `Resource` object.
-
-        Resource represent an HTTP resource.
-
-        :param uri: str, full uri to the server.
-        :param transport: any http instance of object based on 
-                `restclient.http.HTTPClient`. By default it will use 
-                a client based on `pycurl <http://pycurl.sourceforge.net/>`_ if 
-                installed or urllib2. You could also use 
-                `restclient.http.HTTPLib2HTTPClient`,a client based on 
-                `Httplib2 <http://code.google.com/p/httplib2/>`_ or make your
-                own depending of the option you need to access to the serve
-                (authentification, proxy, ....).
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        """
-
-        self.client = RestClient(transport, headers=headers)
-        self.uri = uri
-        self.transport = self.client.transport 
-        self._headers = headers
-
-    def __repr__(self):
-        return '<%s %s>' % (self.__class__.__name__, self.uri)
-
-    def clone(self):
-        """if you want to add a path to resource uri, you can do:
-
-        .. code-block:: python
-
-            resr2 = res.clone()
-        
-        """
-        obj = self.__class__(self.uri, transport=self.transport)
-        return obj
-   
-    def __call__(self, path):
-        """if you want to add a path to resource uri, you can do:
-        
-        .. code-block:: python
-
-            Resource("/path").get()
-        """
-
-        return type(self)(self.client.make_uri(self.uri, path),
-                transport=self.transport)
-
-    
-    def get(self, path=None, headers=None, **params):
-        """ HTTP GET         
-        
-        :param path: string  additionnal path to the uri
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request.
-        """
-        return self.request("GET", path=path, headers=headers, **params)
-
-    def delete(self, path=None, headers=None, **params):
-        """ HTTP DELETE
-
-        see GET for params description.
-        """
-        return self.request("DELETE", path=path, headers=headers, **params)
-
-    def head(self, path=None, headers=None, **params):
-        """ HTTP HEAD
-
-        see GET for params description.
-        """
-        return self.request("HEAD", path=path, headers=headers, **params)
-
-    def post(self, path=None, payload=None, headers=None, **params):
-        """ HTTP POST
-
-        :param payload: string passed to the body of the request
-        :param path: string  additionnal path to the uri
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request
-        """
-
-        return self.request("POST", path=path, payload=payload, headers=headers, **params)
-
-    def put(self, path=None, payload=None, headers=None, **params):
-        """ HTTP PUT
-
-        see POST for params description.
-        """
-        return self.request("PUT", path=path, payload=payload, headers=headers, **params)
-
-    def request(self, method, path=None, payload=None, headers=None, **params):
-        """ HTTP request
-
-        This method may be the only one you want to override when
-        subclassing `restclient.rest.Resource`.
-        
-        :param payload: string or File object passed to the body of the request
-        :param path: string  additionnal path to the uri
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request
-        """
-        _headers = self._headers or {}
-        _headers.update(headers or {})
-        return self.client.request(method, self.uri, path=path,
-                body=payload, headers=_headers, **params)
-
-    def get_response(self):
-        return self.client.get_response()
-    response = property(get_response)
-
-    def get_status(self):
-        return self.client.status
-    status = property(get_status)
-
-    def update_uri(self, path):
-        """
-        to set a new uri absolute path
-        """
-        self.uri = self.client.make_uri(self.uri, 
-                path)
-
-
-class RestClient(object):
-    """Basic rest client
-
-        >>> res = RestClient()
-        >>> xml = res.get('http://pypaste.com/about')
-        >>> json = res.get('http://pypaste.com/3XDqQ8G83LlzVWgCeWdwru', headers={'accept': 'application/json'})
-        >>> json
-        u'{"snippet": "testing API.", "title": "", "id": "3XDqQ8G83LlzVWgCeWdwru", "language": "text", "revision": "363934613139"}'
-    """
-
-    charset = 'utf-8'
-    encode_keys = True
-    safe = "/:"
-
-    def __init__(self, transport=None, headers=None):
-        """Constructor for a `RestClient` object.
-
-        RestClient represent an HTTP client.
-
-        :param transport: any http instance of object based on 
-                `restclient.transport.HTTPTransportBase`. By default it will use 
-                a client based on `pycurl <http://pycurl.sourceforge.net/>`_ if 
-                installed or `restclient.transport.HTTPLib2Transport`,a client based on 
-                `Httplib2 <http://code.google.com/p/httplib2/>`_ or make your
-                own depending of the option you need to access to the serve
-                (authentification, proxy, ....).
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        """ 
-
-        if transport is None:
-            transport = getDefaultHTTPTransport()
-
-        self.transport = transport
-
-        self.status = None
-        self.response = None
-        self._headers = headers
-
-
-    def get(self, uri, path=None, headers=None, **params):
-        """ HTTP GET         
-        
-        :param uri: str, uri on which you make the request
-        :param path: string  additionnal path to the uri
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request.
-        """
-
-        return self.request('GET', uri, path=path, headers=headers, **params)
-
-    def head(self, uri, path=None, headers=None, **params):
-        """ HTTP HEAD
-
-        see GET for params description.
-        """
-        return self.request("HEAD", uri, path=path, headers=headers, **params)
-
-    def delete(self, uri, path=None, headers=None, **params):
-        """ HTTP DELETE
-
-        see GET for params description.
-        """
-        return self.request('DELETE', uri, path=path, headers=headers, **params)
-
-    def post(self, uri, path=None, body=None, headers=None, **params):
-        """ HTTP POST
-
-        :param uri: str, uri on which you make the request
-        :param body: string or File object passed to the body of the request
-        :param path: string  additionnal path to the uri
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request
-        """
-        return self.request("POST", uri, path=path, body=body, headers=headers, **params)
-
-    def put(self, uri, path=None, body=None, headers=None, **params):
-        """ HTTP PUT
-
-        see POST for params description.
-        """
-
-        return self.request('PUT', uri, path=path, body=body, headers=headers, **params)
-
-    def request(self, method, uri, path=None, body=None, headers=None, **params):
-        """ Perform HTTP call support GET, HEAD, POST, PUT and DELETE.
-        
-        Usage example, get friendpaste page :
-
-        .. code-block:: python
-
-            from restclient import RestClient
-            client = RestClient()
-            page = resource.request('GET', 'http://friendpaste.com')
-
-        Or get a paste in JSON :
-
-        .. code-block:: python
-
-            from restclient import RestClient
-            client = RestClient()
-            client.request('GET', 'http://friendpaste.com/5rOqE9XTz7lccLgZoQS4IP'),
-                headers={'Accept': 'application/json'})
-
-        :param method: str, the HTTP action to be performed: 
-            'GET', 'HEAD', 'POST', 'PUT', or 'DELETE'
-        :param path: str or list, path to add to the uri
-        :param data: tring or File object.
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request.
-        :param params: Optionnal parameterss added to the request.
-        
-        :return: str.
-        """
-
-        # init headers
-        _headers = self._headers or {}
-        _headers.update(headers or {})
-        
-        is_unicode = True
-        
-        if body and body is not None and 'Content-Length' not in headers:
-            if isinstance(body, file):
-                try:
-                    body.flush()
-                except IOError:
-                    pass
-                size = int(os.fstat(body.fileno())[6])
-            elif isinstance(body, types.StringTypes):
-                size = len(body)
-                body = to_bytestring(body)
-            elif isinstance(body, dict):
-                _headers.setdefault('Content-Type', "application/x-www-form-urlencoded; charset=utf-8")
-                body = form_encode(body)
-                size = len(body)
-            else:
-                raise RequestError('Unable to calculate '
-                    'the length of the data parameter. Specify a value for '
-                    'Content-Length')
-            _headers['Content-Length'] = size
-            
-            if 'Content-Type' not in headers:
-                type = None
-                if hasattr(body, 'name'):
-                    type = mimetypes.guess_type(body.name)[0]
-                _headers['Content-Type'] = type and type or 'application/octet-stream'
-                
-        try:
-            resp, data = self.transport.request(self.make_uri(uri, path, **params), 
-                method=method, body=body, headers=_headers)
-        except TransportError, e:
-            raise RequestError(str(e))
-
-        self.status  = status_code = resp.status
-        self.response = resp
-        
-        
-        if status_code >= 400:
-            if status_code == 404:
-                raise ResourceNotFound(data, http_code=404, response=resp)
-            elif status_code == 401 or status_code == 403:
-                raise Unauthorized(data, http_code=status_code,
-                        response=resp)
-            else:
-                raise RequestFailed(data, http_code=status_code,
-                    response=resp)
-
-        # determine character encoding
-        true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, \
-        acceptable_content_type = _getCharacterEncoding(resp, data)
-        
-
-        tried_encodings = []
-        # try: HTTP encoding, declared XML encoding, encoding sniffed from BOM
-        for proposed_encoding in (true_encoding, xml_encoding, sniffed_xml_encoding):
-            if not proposed_encoding: continue
-            if proposed_encoding in tried_encodings: continue
-            tried_encodings.append(proposed_encoding)
-            try:
-               return data.decode(proposed_encoding)
-               break
-            except:
-                pass
-                
-        # if still no luck and we haven't tried utf-8 yet, try that
-        if 'utf-8' not in tried_encodings:
-            try:
-                proposed_encoding = 'utf-8'
-                tried_encodings.append(proposed_encoding)
-                return data.decode(proposed_encoding)
-              
-            except:
-                pass
-                
-        # if still no luck and we haven't tried windows-1252 yet, try that
-        if 'windows-1252' not in tried_encodings:
-            try:
-                proposed_encoding = 'windows-1252'
-                tried_encodings.append(proposed_encoding)
-                return data.decode(proposed_encoding)
-            except:
-                pass
-                
-        # if no luck and we have auto-detection library, try that
-        if chardet:
-            try:
-                proposed_encoding = chardet.detect(data)['encoding']
-                if proposed_encoding and (proposed_encoding not in tried_encodings):
-                    tried_encodings.append(proposed_encoding)
-                    return data.decode(proposed_encoding)
-            except:
-                pass
-              
-        # give up, return data as is.   
-        return data 
-
-    def get_response(self):
-        return self.response
-
-    def make_uri(self, base, *path, **query):
-        """Assemble a uri based on a base, any number of path segments, and query
-        string parameters.
-
-        """
-        base_trailing_slash = False
-        if base and base.endswith("/"):
-            base_trailing_slash = True
-            base = base[:-1]
-        retval = [base]
-
-        # build the path
-        _path = []
-        trailing_slash = False       
-        for s in path:
-            if s is not None and isinstance(s, basestring):
-                if len(s) > 1 and s.endswith('/'):
-                    trailing_slash = True
-                else:
-                    trailing_slash = False
-                _path.append(url_quote(s.strip('/'), self.charset, self.safe))
-                       
-        path_str =""
-        if _path:
-            path_str = "/".join([''] + _path)
-            if trailing_slash:
-                path_str = path_str + "/" 
-        elif base_trailing_slash:
-            path_str = path_str + "/" 
-            
-        if path_str:
-            retval.append(path_str)
-
-        params = []
-        for k, v in query.items():
-            if type(v) in (list, tuple):
-                params.extend([(k, i) for i in v if i is not None])
-            elif v is not None:
-                params.append((k,v))
-        if params:
-            retval.extend(['?', url_encode(dict(params), self.charset, self.encode_keys)])
-
-        return ''.join(retval)
-
-
-# code borrowed to Wekzeug with minor changes
-
-def url_quote(s, charset='utf-8', safe='/:'):
-    """URL encode a single string with a given encoding."""
-    if isinstance(s, unicode):
-        s = s.encode(charset)
-    elif not isinstance(s, str):
-        s = str(s)
-    return urllib.quote(s, safe=safe)
-
-def url_encode(obj, charset="utf8", encode_keys=False):
-    if isinstance(obj, dict):
-        items = []
-        for k, v in obj.iteritems():
-            if not isinstance(v, (tuple, list)):
-                v = [v]
-            items.append((k, v))
-    else:
-        items = obj or ()
-
-    tmp = []
-    for key, values in items:
-        if encode_keys and isinstance(key, unicode):
-            key = key.encode(charset)
-        else:
-            key = str(key)
-
-        for value in values:
-            if value is None:
-                continue
-            elif isinstance(value, unicode):
-                value = value.encode(charset)
-            else:
-                value = str(value)
-        tmp.append('%s=%s' % (urllib.quote(key),
-            urllib.quote_plus(value)))
-
-    return '&'.join(tmp)
-    
-def form_encode(obj, charser="utf8"):
-    tmp = []
-    for key, value in obj.items():
-        tmp.append("%s=%s" % (url_quote(key), 
-                url_quote(value)))
-    return to_bytestring("&".join(tmp))
-
-
-
-def _getCharacterEncoding(http_headers, xml_data):
-    '''Get the character encoding of the XML document
-
-    http_headers is a dictionary
-    xml_data is a raw string (not Unicode)
-    
-    This is so much trickier than it sounds, it's not even funny.
-    According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
-    is application/xml, application/*+xml,
-    application/xml-external-parsed-entity, or application/xml-dtd,
-    the encoding given in the charset parameter of the HTTP Content-Type
-    takes precedence over the encoding given in the XML prefix within the
-    document, and defaults to 'utf-8' if neither are specified.  But, if
-    the HTTP Content-Type is text/xml, text/*+xml, or
-    text/xml-external-parsed-entity, the encoding given in the XML prefix
-    within the document is ALWAYS IGNORED and only the encoding given in
-    the charset parameter of the HTTP Content-Type header should be
-    respected, and it defaults to 'us-ascii' if not specified.
-
-    Furthermore, discussion on the atom-syntax mailing list with the
-    author of RFC 3023 leads me to the conclusion that any document
-    served with a Content-Type of text/* and no charset parameter
-    must be treated as us-ascii.  (We now do this.)  And also that it
-    must always be flagged as non-well-formed.  (We now do this too.)
-    
-    If Content-Type is unspecified (input was local file or non-HTTP source)
-    or unrecognized (server just got it totally wrong), then go by the
-    encoding given in the XML prefix of the document and default to
-    'iso-8859-1' as per the HTTP specification (RFC 2616).
-    
-    Then, assuming we didn't find a character encoding in the HTTP headers
-    (and the HTTP Content-type allowed us to look in the body), we need
-    to sniff the first few bytes of the XML data and try to determine
-    whether the encoding is ASCII-compatible.  Section F of the XML
-    specification shows the way here:
-    http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
-
-    If the sniffed encoding is not ASCII-compatible, we need to make it
-    ASCII compatible so that we can sniff further into the XML declaration
-    to find the encoding attribute, which will tell us the true encoding.
-
-    Of course, none of this guarantees that we will be able to parse the
-    feed in the declared character encoding (assuming it was declared
-    correctly, which many are not).  CJKCodecs and iconv_codec help a lot;
-    you should definitely install them if you can.
-    http://cjkpython.i18n.org/
-    '''
-
-    def _parseHTTPContentType(content_type):
-        '''takes HTTP Content-Type header and returns (content type, charset)
-
-        If no charset is specified, returns (content type, '')
-        If no content type is specified, returns ('', '')
-        Both return parameters are guaranteed to be lowercase strings
-        '''
-        content_type = content_type or ''
-        content_type, params = cgi.parse_header(content_type)
-        return content_type, params.get('charset', '').replace("'", '')
-
-    sniffed_xml_encoding = ''
-    xml_encoding = ''
-    true_encoding = ''
-    http_content_type, http_encoding = _parseHTTPContentType(http_headers.get('Content-Type'))
-    # Must sniff for non-ASCII-compatible character encodings before
-    # searching for XML declaration.  This heuristic is defined in
-    # section F of the XML specification:
-    # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
-    try:
-        if xml_data[:4] == '\x4c\x6f\xa7\x94':
-            # EBCDIC
-            xml_data = _ebcdic_to_ascii(xml_data)
-        elif xml_data[:4] == '\x00\x3c\x00\x3f':
-            # UTF-16BE
-            sniffed_xml_encoding = 'utf-16be'
-            xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
-        elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') and (xml_data[2:4] != '\x00\x00'):
-            # UTF-16BE with BOM
-            sniffed_xml_encoding = 'utf-16be'
-            xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
-        elif xml_data[:4] == '\x3c\x00\x3f\x00':
-            # UTF-16LE
-            sniffed_xml_encoding = 'utf-16le'
-            xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
-        elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and (xml_data[2:4] != '\x00\x00'):
-            # UTF-16LE with BOM
-            sniffed_xml_encoding = 'utf-16le'
-            xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
-        elif xml_data[:4] == '\x00\x00\x00\x3c':
-            # UTF-32BE
-            sniffed_xml_encoding = 'utf-32be'
-            xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
-        elif xml_data[:4] == '\x3c\x00\x00\x00':
-            # UTF-32LE
-            sniffed_xml_encoding = 'utf-32le'
-            xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
-        elif xml_data[:4] == '\x00\x00\xfe\xff':
-            # UTF-32BE with BOM
-            sniffed_xml_encoding = 'utf-32be'
-            xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
-        elif xml_data[:4] == '\xff\xfe\x00\x00':
-            # UTF-32LE with BOM
-            sniffed_xml_encoding = 'utf-32le'
-            xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
-        elif xml_data[:3] == '\xef\xbb\xbf':
-            # UTF-8 with BOM
-            sniffed_xml_encoding = 'utf-8'
-            xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
-        else:
-            # ASCII-compatible
-            pass
-        xml_encoding_match = re.compile('^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data)
-    except:
-        xml_encoding_match = None
-    if xml_encoding_match:
-        xml_encoding = xml_encoding_match.groups()[0].lower()
-        if sniffed_xml_encoding and (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16')):
-            xml_encoding = sniffed_xml_encoding
-    acceptable_content_type = 0
-    application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
-    text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
-    if (http_content_type in application_content_types) or \
-       (http_content_type.startswith('application/') and http_content_type.endswith('+xml')):
-        acceptable_content_type = 1
-        true_encoding = http_encoding or xml_encoding or 'utf-8'
-    elif (http_content_type in text_content_types) or \
-         (http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
-        acceptable_content_type = 1
-        true_encoding = http_encoding or 'us-ascii'
-    elif http_content_type.startswith('text/'):
-        true_encoding = http_encoding or 'us-ascii'
-    elif http_headers and (not http_headers.has_key('content-type')):
-        true_encoding = xml_encoding or 'iso-8859-1'
-    else:
-        true_encoding = xml_encoding or 'utf-8'
-    return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type

restclient/transport/__init__.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-from restclient.transport.base import HTTPResponse, HTTPTransportBase, USER_AGENT
-from restclient.transport._httplib2 import HTTPLib2Transport, httplib2
-from restclient.transport._curl import CurlTransport, pycurl
-
-__all__ = [
-    'HTTPResponse', 
-    'HTTPTransportBase', 
-    'USER_AGENT',
-    'HTTPLib2Transport',
-    'CurlTransport',
-    'createHTTPTransport',
-    'setDefaultHTTPTransport',
-    'useCurl']
-
-_default_http = None
-
-
-def createHTTPTransport():
-    """Create default HTTP client instance
-    prefers Curl to urllib"""
-    if pycurl is not None:
-        http = CurlTransport()
-    elif httplib2 is not None:
-        http = HTTPLib2Transport()
-    else:
-        raise RuntimeError("httplib2 or curl are missing")
-    return http
-
-def getDefaultHTTPTransport():
-    """ Return the default http transport instance instance
-    if no client has been set, it will create a default client.
-
-    :return: the default client
-    """
-    global _default_http
-    if _default_http is None:
-        setDefaultHTTPTransport(createHTTPTransport())
-    return _default_http
-
-def setDefaultHTTPTransport(httptransport):
-    """ set default http transport 
-    :param http: RestClient
-    """
-    global _default_http
-    _default_http = httptransport
-
-def useCurl():
-    global _default_http
-    if _default_http is None:
-        setDefaultHTTPTransport(createHTTPTransport())
-    return isinstance(_default_http, CurlTransport)

restclient/transport/_curl.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-"""
-curl transport
-"""
-
-import re
-import StringIO
-import sys
-
-
-import restclient
-from restclient.errors import TransportError
-from restclient.transport.base import *
-from restclient.utils import to_bytestring, iri2uri
-
-try:
-    import pycurl
-except ImportError:
-    pycurl = None
-    
-NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
-def _normalize_headers(headers):
-    return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip())  for (key, value) in headers.iteritems()])
-
-
-def _get_pycurl_errcode(symbol, default):
-    """
-    Returns the numerical error code for a symbol defined by pycurl.
-
-    Different pycurl implementations define different symbols for error
-    codes. Old versions never define some symbols (wether they can return the
-    corresponding error code or not). The following addresses the problem by
-    defining the symbols we care about.  Note: this allows to define symbols
-    for errors that older versions will never return, which is fine.
-    """
-    return pycurl.__dict__.get(symbol, default)
-
-if pycurl is not None:
-    CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
-    CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
-    CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
-    CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52)
-    CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18)
-    CURLE_SEND_ERROR = _get_pycurl_errcode('E_SEND_ERROR', 55)
-    CURLE_SSL_CACERT = _get_pycurl_errcode('E_SSL_CACERT', 60)
-    CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)    
-
-class CurlTransport(HTTPTransportBase):
-    """
-    An HTTP transportthat uses pycurl.
-
-    Pycurl is recommanded when you want fast access to http resources.
-    We have added some basic management of authentification and proxies,
-    but in case you want something specific you should use urllib2 or 
-    httplib2 http clients. Any patch is welcome though ;)
-
-
-    Here is an example to use authentification with curl httpclient :
-
-    .. code-block:: python
-
-        httpclient = CurlTransport()
-        httpclient.add_credentials("test", "test")        
-
-    .. seealso::
-
-        `Pycurl <http://pycurl.sourceforge.net>`_
-    """
-
-    def __init__(self, timeout=None, proxy_infos=None):
-        """ Curl transport constructor
-
-        :param timeout: int, timeout of request
-        :param proxy_infos: dict, infos to connect via proxy:
-
-        .. code-block:: python
-
-            {
-                'proxy_user': 'XXXXXXX',
-                'proxy_password': 'XXXXXXX',
-                'proxy_host': 'proxy',
-                'proxy_port': 8080,
-            }
-            
-        """
-        HTTPTransportBase.__init__(self, proxy_infos=proxy_infos)
-
-        # path to certificate file
-        self.cabundle = None
-
-        if pycurl is None:
-            raise RuntimeError('Cannot find pycurl library')
-
-        self.timeout = timeout
-
-    def _parseHeaders(self, header_file):
-        header_file.seek(0)
-
-        # Remove the status line from the beginning of the input
-        unused_http_status_line = header_file.readline()
-        lines = [line.strip() for line in header_file]
-
-        # and the blank line from the end
-        empty_line = lines.pop()
-        if empty_line:
-            raise TransportError("No blank line at end")
-
-        headers = {}
-        for line in lines:
-            if ":" in line:
-                try:
-                    name, value = line.split(':', 1)
-                except ValueError:
-                    raise TransportError(
-                        "Malformed HTTP header line in response: %r" % (line,))
-
-                value = value.strip()
-
-                # HTTP headers are case-insensitive
-                name = name.lower()
-                headers[name] = value
-
-        return headers
-
-
-    def request(self, url, method='GET', body=None, headers=None):
-        body = body or ""        
-        headers = headers or {}
-        headers.setdefault('User-Agent',
-                           "%s %s" % (USER_AGENT, pycurl.version,))
-
-        # by default turn off default pragma
-        headers.setdefault('Cache-control', 'max-age=0')
-        headers.setdefault('Pragma', 'no-cache')
-
-        if method == 'PUT':
-            headers.setdefault('Expect', '100-continue')
-
-        # encode url
-        origin_url = to_bytestring(url)
-        url = iri2uri(origin_url)
-
-        c = pycurl.Curl()
-        try:
-            # set curl options
-            if self.timeout is not None:
-                c.setopt(pycurl.TIMEOUT, self.timeout)
-            else: # no timeout by default
-                c.setopt(pycurl.TIMEOUT, 0)
-
-            data = StringIO.StringIO()
-            header = StringIO.StringIO()
-            c.setopt(pycurl.WRITEFUNCTION, data.write)
-            c.setopt(pycurl.HEADERFUNCTION, header.write)
-            c.setopt(pycurl.URL, url)
-            c.setopt(pycurl.FOLLOWLOCATION, 1)
-            c.setopt(pycurl.MAXREDIRS, 5)
-            c.setopt(pycurl.NOSIGNAL, 1)
-            if restclient.debuglevel > 0:
-                 c.setopt(pycurl.VERBOSE, 1)
-                 
-            # automatic decompression
-            c.setopt(pycurl.ENCODING, 'gzip,deflate')
-
-            if self.cabundle:
-                c.setopt(pycurl.CAINFO, celf.cabundle)
-
-            #set proxy
-            if self.proxy_infos and self.proxy_infos.get('proxy_host', ''):
-                c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
-                c.setopt(pycurl.PROXY, self.proxy_infos.get('proxy_host'))
-
-                proxy_port = self.proxy_infos.get('proxy_port', '')
-                if proxy_port:
-                    c.setopt(pycurl.PROXYPORT, str(proxy_port))
-
-                user = self.proxy_infos.get('proxy_user', '')
-                if user:
-                    userpass = "%s:%s" % (user, self.proxy_infos.get('proxy_password', ''))
-                    c.setopt(pycurl.PROXYUSERPWD, userpass)
-
-            # authentification
-            auth = self._get_credentials()
-            user = auth.get('user', None)
-            password = auth.get('password', None)
-            if user is not None:
-                c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_ANY)
-                userpass = user + ':'
-                if password is not None: # '' is a valid password
-                    userpass += password
-                c.setopt(pycurl.USERPWD, userpass)
-
-            # set method
-            if method == "GET":
-                c.setopt(pycurl.HTTPGET, 1)
-            elif method == "HEAD":
-                c.setopt(pycurl.HTTPGET, 1)
-                c.setopt(pycurl.NOBODY, 1)
-            elif method == "POST":
-                c.setopt(pycurl.POST, 1)
-            elif method == "PUT":
-                c.setopt(pycurl.UPLOAD, 1)
-            else:
-                c.setopt(pycurl.CUSTOMREQUEST, method)
-
-            if method in ('POST','PUT'):
-                if hasattr(body, 'read'):
-                    content_length = int(headers.pop('Content-Length',
-                        0))
-                    content = body
-                else:
-                    body = to_bytestring(body)
-                    content = StringIO.StringIO(body)
-                    if 'Content-Length' in headers:
-                        del headers['Content-Length']
-                    content_length = len(body)
-
-                if method == 'POST':
-                    c.setopt(pycurl.POSTFIELDSIZE, content_length)
-                else:
-                    c.setopt(pycurl.INFILESIZE, content_length)
-                c.setopt(pycurl.READFUNCTION, content.read)
-
-            if headers:
-                _normalize_headers(headers)
-                c.setopt(pycurl.HTTPHEADER,
-                        ["%s: %s" % pair for pair in sorted(headers.iteritems())])
-
-            try:
-                c.perform()
-            except pycurl.error, e:
-                if e[0] != CURLE_SEND_ERROR:
-                    if restclient.debuglevel > 0:
-                        print >>sys.stderr, str(e)
-                    raise TransportError(e)
-
-            response_headers = self._parseHeaders(header)
-            code = c.getinfo(pycurl.RESPONSE_CODE)
-            return self._make_response(final_url=url, origin_url=origin_url,
-                    status=code, headers=response_headers, body=data.getvalue())
-        finally:
-            c.close()
-
-    def _make_response(self, final_url=None, origin_url=None, status=None, 
-            headers=None, body=None):
-        infos = headers or {}    
-        final_url = infos.get('location', final_url)
-        infos.update({
-            'status': status,
-            'final_url': final_url,
-            'origin_url': origin_url
-        })
-        resp = HTTPResponse(infos)
-        return resp, body

restclient/transport/_httplib2.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-import restclient
-from restclient.errors import InvalidUrl
-from restclient.transport.base import *
-
-try:
-    import httplib2
-except ImportError:
-    httplib2 = None
-    
-class HTTPLib2Transport(HTTPTransportBase):
-    """An http client that uses httplib2 for performing HTTP
-    requests. This implementation supports HTTP caching.
-
-    .. seealso::
-        
-        `Httplib2 <http://code.google.com/p/httplib2/>`_
-    """
-
-    def __init__(self, proxy_infos=None, http=None):
-        """
-        :param proxy_infos: dict, infos to connect via proxy:
-
-        .. code-block:: python
-    
-            {
-                'proxy_user': 'XXXXXXX',
-                'proxy_password': 'XXXXXXX',
-                'proxy_host': 'proxy',
-                'proxy_port': 8080,
-            }
-
-        :param http: An httplib2.HTTP instance.
-
-
-        """
-        if httplib2 is None:
-            raise RuntimeError('Cannot find httplib2 library. '
-                               'See http://bitworking.org/projects/httplib2/')
-
-        super(HTTPLib2Transport, self).__init__(proxy_infos=proxy_infos)
-        
-        # set debug level
-        httplib2.debuglevel = restclient.debuglevel
-        
-        _proxy_infos = None
-        if proxy_infos and proxy_infos is not None:
-            try:
-                import socks
-            except:
-                print >>sys.stderr, "socks module isn't installed, you can't use proxy"
-                socks = None
-
-            if socks is not None:
-                _proxy_infos = httplib2.ProxyInfo(
-                        socks.PROXY_TYPE_HTTP,
-                        proxy_infos.get('proxy_host'),
-                        proxy_infos.get('proxy_port'),
-                        proxy_infos.get('proxy_username'),
-                        proxy_infos.get('proxy_password')
-                )
-
-        if http is None:
-            http = httplib2.Http(proxy_info=_proxy_infos)
-        else:
-            if _proxy_infos is not None and \
-                    not http.proxy_info and \
-                    http.proxy_info is None:
-                proxy_info = _proxy_infos
-        self.http = http
-        
-        self.http.force_exception_to_status_code = False
-
-    def request(self, url, method='GET', body=None, headers=None):
-        headers = headers or {}
-        body = body or ''
-        
-        content = ''
-        if method in ('POST','PUT'):
-            if hasattr(body, 'read'):
-                content_length = int(headers.pop('Content-Length',
-                    0))
-                content = body.read()
-            else:
-                content = body
-                if 'Content-Length' in headers:
-                    del headers['Content-Length']
-                content_length = len(body)
-
-            headers.setdefault('Content-Length', str(content_length))
-
-        if not (url.startswith('http://') or url.startswith('https://')):
-            error = 'URL is not a HTTP URL: %r' % (url,)
-            if restclient.debuglevel > 0:
-                print >>sys.stderr, str(error)
-            raise InvalidUrl(error)
-
-        headers.setdefault('User-Agent', USER_AGENT)
-        
-        httplib2_response, content = self.http.request(url,
-                method=method, body=content, headers=headers)
-
-        try:
-            final_url = httplib2_response['content-location']
-        except KeyError:
-            final_url = url
-            
-        httplib2_response['final_url'] = final_url
-        httplib2_response['origin_url'] = url
-        resp = HTTPResponse(httplib2_response)
-        return resp, content
-
-    def add_credentials(self, user, password):
-        super(HTTPLib2Transport, self).add_credentials(user, password)
-        self.http.add_credentials(user, password)

restclient/transport/base.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-import sys
-
-import restclient
-
-USER_AGENT = "py-restclient/%s (%s)" % (restclient.__version__, sys.platform)
-
-class HTTPResponse(dict):
-    """An object more like email.Message than httplib.HTTPResponse.
-    
-        >>> from restclient import Resource
-        >>> res = Resource('http://e-engura.org')
-        >>> from restclient import Resource
-        >>> res = Resource('http://e-engura.org')
-        >>> page = res.get()
-        >>> res.status
-        200
-        >>> res.response['content-type']
-        'text/html'
-        >>> logo = res.get('/images/logo.gif')
-        >>> res.response['content-type']
-        'image/gif'
-    """
-
-    final_url = None
-    
-    "Status code returned by server. "
-    status = 200
-
-    """Reason phrase returned by server."""
-    reason = "Ok"
-
-    def __init__(self, info):
-        for key, value in info.iteritems(): 
-            self[key] = value 
-        self.status = int(self.get('status', self.status))
-        self.final_url = self.get('final_url', self.final_url)
-
-    def __getattr__(self, name):
-        if name == 'dict':
-            return self 
-        else:  
-            raise AttributeError, name
-
-    def __repr__(self):
-        return "<%s status %s for %s>" % (self.__class__.__name__,
-                                          self.status,
-                                          self.final_url)
-
-
-
-class HTTPTransportBase(object):
-    """ Interface for HTTP clients """
-
-    def __init__(self, proxy_infos=None):
-        """ constructor for HTTP transport interface
-
-        :param proxy_infos: dict, infos to connect via proxy:
-
-        .. code-block:: python
-
-            {
-                'proxy_user': 'XXXXXXX',
-                'proxy_password': 'XXXXXXX',
-                'proxy_host': 'proxy',
-                'proxy_port': 8080,
-            }
-        """
-        self._credentials = {}
-        self.proxy_infos = proxy_infos or {}
-
-    def request(self, url, method='GET', body=None, headers=None):
-        """Perform HTTP call and manage , support GET, HEAD, POST, PUT and
-        DELETE
-
-        :param url: url on which to perform the actuib
-        :param body: str
-        :param headers: dict, optionnal headers that will
-            be added to HTTP request
-
-        :return: object representing HTTP Response
-        """
-        raise NotImplementedError
-
-    def add_credentials(self, user, password):
-        self._credentials = {
-                "user": user,
-                "password": password
-        }
-
-    def _get_credentials(self):
-        return self._credentials

restclient/utils.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-# iri2uri code taken from httplib2 under the following license:
-# MIT/X Consortium License
-#
-# 2006 Joe Gregorio <joe@bitworking.org>
-# 
-# Permission is hereby granted, free of charge, to any person obtaining a
-# copy of this software and associated documentation files (the "Software"),
-# to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# and/or sell copies of the Software, and to permit persons to whom the
-# Software is furnished to do so, subject to the following conditions:
-# 
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-# 
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
-# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# DEALINGS IN THE SOFTWARE.
-#
-"""
-iri2uri
-
-Converts an IRI to a URI.
-"""
-import re
-import urlparse
-
-
-def to_bytestring(s):
-    if not isinstance(s, basestring):
-        raise TypeError("value should be a str or unicode")
-
-    if isinstance(s, unicode):
-        return s.encode('utf-8')
-    return s
-    
-    
-def parse_url(url):
-    """
-    Given a URL, returns a 4-tuple containing the hostname, port,
-    a path relative to root (if any), and a boolean representing 
-    whether the connection should use SSL or not.
-    """
-    (scheme, netloc, path, params, query, frag) = urlparse.urlparse(url)
-
-    # We only support web services
-    if not scheme in ('http', 'https'):
-        raise InvalidUrl('Scheme must be one of http or https')
-
-    is_ssl = scheme == 'https' and True or False
-
-    # Verify hostnames are valid and parse a port spec (if any)
-    match = re.match('([a-zA-Z0-9\-\.]+):?([0-9]{2,5})?', netloc)
-
-    if match:
-        (host, port) = match.groups()
-        if not port:
-            port = is_ssl and '443' or '80'
-    else:
-        raise InvalidUrl('Invalid host and/or port: %s' % netloc)
-
-    return (host, int(port), path.strip('/'), is_ssl)
-    
-
-
-# Convert an IRI to a URI following the rules in RFC 3987
-# 
-# The characters we need to enocde and escape are defined in the spec:
-#
-# iprivate =  %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
-# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
-#         / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
-#         / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
-#         / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
-#         / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
-#         / %xD0000-DFFFD / %xE1000-EFFFD
-
-escape_range = [
-   (0xA0, 0xD7FF ),
-   (0xE000, 0xF8FF ),
-   (0xF900, 0xFDCF ),
-   (0xFDF0, 0xFFEF),
-   (0x10000, 0x1FFFD ),
-   (0x20000, 0x2FFFD ),
-   (0x30000, 0x3FFFD),
-   (0x40000, 0x4FFFD ),
-   (0x50000, 0x5FFFD ),
-   (0x60000, 0x6FFFD),
-   (0x70000, 0x7FFFD ),
-   (0x80000, 0x8FFFD ),
-   (0x90000, 0x9FFFD),
-   (0xA0000, 0xAFFFD ),
-   (0xB0000, 0xBFFFD ),
-   (0xC0000, 0xCFFFD),
-   (0xD0000, 0xDFFFD ),
-   (0xE1000, 0xEFFFD),
-   (0xF0000, 0xFFFFD ),
-   (0x100000, 0x10FFFD)
-]
- 
-def encode(c):
-    retval = c
-    i = ord(c)
-    for low, high in escape_range:
-        if i < low:
-            break
-        if i >= low and i <= high:
-            retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')])
-            break
-    return retval
-
-
-def iri2uri(uri):
-    """Convert an IRI to a URI. Note that IRIs must be 
-    passed in a unicode strings. That is, do not utf-8 encode
-    the IRI before passing it into the function.""" 
-    if isinstance(uri ,unicode):
-        (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri)
-        authority = authority.encode('idna')
-        # For each character in 'ucschar' or 'iprivate'
-        #  1. encode as utf-8
-        #  2. then %-encode each octet of that utf-8 
-        uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
-        uri = "".join([encode(c) for c in uri])
-    return uri

restkit/__init__.py

+# -*- coding: utf-8 -
+# Copyright (c) 2008, 2009  Benoit Chesneau <benoitc@e-engura.com> 
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+try:
+    __version__ = __import__('pkg_resources').get_distribution('py-restkit').version
+except:
+    __version__ = '?'
+
+USER_AGENT = "restkit/%s" % __version__
+
+debuglevel = 0
+
+from restkit.errors import *
+from restkit.httpc import HttpClient
+from restkit.rest import *
+
+
+

restkit/bin/__init__.py

Empty file added.

restkit/bin/rest_cli.py

+# -*- coding: utf-8 -
+#
+# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+import os
+import sys
+from optparse import OptionParser, OptionGroup
+import urlparse
+import urllib
+
+# python 2.6 and above compatibility
+try:
+    from urlparse import parse_qs as _parse_qs
+except ImportError:
+    from cgi import parse_qs as _parse_qs
+
+import restkit
+from restkit import httpc
+
+class Url(object):
+    def __init__(self, string):
+        parts = urlparse.urlsplit(urllib.unquote(string))
+        if parts[0] != 'http' and parts[0] != 'https':
+            raise ValueError('Invalid url: %s.' % string)
+
+        if "@" in parts[1]:
+            host = parts[1].split('@').pop()
+        else:
+            host = parts[1]
+       
+        self.hostname = host
+        if parts[0] == 'http':
+            self.port = 80
+        else:
+            self.port = 443
+
+        if ":" in host:
+            try:
+                self.hostname, self.port = host.split(':')
+            except:
+                raise ValueError('Invalid url: %s.' % string)
+
+            self.port = int(self.port)
+
+        self.uri = "%s://%s" % (parts[0], host)
+
+        if parts[2]:
+            self.path = parts[2]
+        else:
+            self.path = ''
+
+        if parts[3]:
+            self.query = _parse_qs(parts[3])
+        else:
+            self.query = {}
+
+        self.username = parts.username
+        self.password = parts.password
+
+
+def make_query(string, method='GET', fname=None, 
+        list_headers=None, output=None, proxy=None):
+    try:
+        uri = Url(string)
+    except ValueError, e:
+        print >>sys.stderr, e
+        return 
+
+    transport = None 
+    proxy_infos = None
+    if proxy and proxy is not None:
+        try:
+            proxy_url = Url(proxy)
+        except:
+            print >>sys.stderr, "proxy url is invalid"
+            return
+        proxy_infos = { "proxy_host": proxy_url.hostname }
+        if proxy_url.port is not None:
+            proxy_infos["proxy_port"] = proxy_url.port
+        if proxy_url.username and proxy_url.username is not None:
+            proxy_infos["proxy_username"] = proxy_url.username
+            proxy_infos["proxy_password"] = proxy_url.password or ''
+
+    if useCurl():
+        transport = CurlTransport(proxy_infos=proxy_infos)
+    else:
+        transport = HTTPLib2Transport(proxy_infos=proxy_infos)
+    
+    if uri.username:
+        transport.add_credentials(uri.username, uri.password) 
+    
+    res = restclient.Resource(uri.uri, transport=transport)
+
+    list_headers = list_headers or []
+    headers = {}
+    if list_headers:
+        for header in list_headers:
+            if ":" in header:
+                k, v = header.split(':')
+                headers[k] = v
+
+    payload = None
+    if fname:
+        if fname == '-':
+            payload = sys.stdin.read()
+            headers['Content-Length'] = len(payload)
+        else:
+            fname = os.path.normpath(os.path.join(os.getcwd(),fname))
+            headers['Content-Length'] = os.path.getsize(fname)
+            payload = open(fname, 'r')
+
+    data = res.request(method, path=uri.path, payload=payload, headers=headers, **uri.query)
+
+    output = output or ''
+    if not output or output == '-':
+        return data
+    else:
+        try:
+            f = open(output, 'wb')
+            f.write(data)
+            f.close()
+        except:
+            print >>sys.stderr, "Can't save result in %s" % output
+            return
+
+
+def main():
+    parser = OptionParser(usage='%prog [options] url [METHOD] [filename]', version="%prog " + restclient.__version__)
+    parser.add_option('-H', '--header', action='append', dest='headers',
+            help='http string header in the form of Key:Value. '+
+            'For example: "Accept: application/json" ')
+    parser.add_option('-i', '--input', action='store', dest='input', metavar='FILE',
+                      help='the name of the file to read from')
+    parser.add_option('-o', '--output', action='store', dest='output',
+                      help='the name of the file to read from')
+
+    parser.add_option('--proxy', action='store', dest='proxy',
+            help='Full uri of proxy, ex:\n'+
+            'http://username:password@proxy:port/')
+
+    options, args = parser.parse_args()
+
+    if len(args) < 1:
+        return parser.error('incorrect number of arguments')
+    if options.input:
+        fname=options.input
+    else:
+        fname=None
+   
+    if len(args) == 3:
+        return make_query(args[0], method=args[1], fname=args[2], 
+                list_headers=options.headers, output=options.output,
+                proxy=options.proxy)
+    
+    elif len(args) == 2:
+        if args[1] == "-":
+            return make_query(args[0], method='POST', fname=args[1], 
+                    list_headers=options.headers, output=options.output,
+                    proxy=options.proxy)
+        return make_query(args[0], method=args[1], fname=fname, 
+                list_headers=options.headers, output=options.output,
+                proxy=options.proxy)
+    else:
+        if options.input:
+            method = 'POST'
+        else:
+            method='GET'
+        return make_query(args[0], method=method, fname=fname, 
+                list_headers=options.headers, output=options.output,
+                proxy=options.proxy)
+
+
+if __name__ == '__main__':
+    main()

restkit/errors.py

+# -*- coding: utf-8 -
+#
+# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+"""
+exception classes.
+"""
+
+class ResourceError(Exception):
+
+    def __init__(self, msg=None, http_code=None, response=None):
+        self.msg = msg or ''
+        self.status_code = http_code
+        self.response = response
+        Exception.__init__(self)
+        
+    def _get_message(self):
+        return self.msg
+    def _set_message(self, msg):
+        self.msg = msg or ''
+    message = property(_get_message, _set_message)    
+    
+    def __str__(self):
+        if self.msg:
+            return self.msg
+        try:
+            return self._fmt % self.__dict__
+        except (NameError, ValueError, KeyError), e:
+            return 'Unprintable exception %s: %s' \
+                % (self.__class__.__name__, str(e))
+        
+class ResourceNotFound(ResourceError):
+    """Exception raised when no resource was found at the given url. 
+    """
+
+class Unauthorized(ResourceError):
+    """Exception raised when an authorization is required to access to
+    the resource specified.
+    """
+
+class RequestFailed(ResourceError):
+    """Exception raised when an unexpected HTTP error is received in response
+    to a request.
+    
+
+    The request failed, meaning the remote HTTP server returned a code 
+    other than success, unauthorized, or NotFound.
+
+    The exception message attempts to extract the error
+
+    You can get the status code by e.http_code, or see anything about the 
+    response via e.response. For example, the entire result body (which is 
+    probably an HTML error page) is e.response.body.
+    """
+    
+class RedirectLimit(Exception):
+    """Exception raised when the redirection limit is reached."""
+
+class RequestError(Exception):
+    """Exception raised when a request is malformed"""
+    
+class InvalidUrl(Exception):
+    """
+    Not a valid url for use with this software.
+    """
+    
+class TransportError(Exception):
+    """Error raised by a transport """
+    
+class ResponseError(Exception):
+    """ Error raised while getting response or decompressing response stream"""
+    
+# -*- coding: utf-8 -
+#
+# Copyright (c) 2008, 2009 Benoit Chesneau <benoitc@e-engura.com> 
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+import base64
+import copy
+import httplib
+import re
+import StringIO
+import types
+import urllib
+import urlparse
+
+import restkit
+from restkit import errors
+from restkit.utils import to_bytestring
+
+url_parser = urlparse.urlparse
+
+NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
+def _normalize_headers(headers):
+    return dict([ (key.lower(), NORMALIZE_SPACE.sub(str(value), ' ').strip())  for (key, value) in headers.iteritems()])
+
+def _relative_uri(uri):
+    if not uri.path:
+        path = "/"
+    else:
+        path = uri.path
+    if uri.query:
+        return path + "?" + uri.query
+    return path
+
+class Auth(object):
+    """ Interface for Auth classes """
+    def __init__(self, credentials, headers=None, **kwargs):
+        self.credentials = credentials
+        self.headers = headers or {}
+        
+    def depth(self, uri):
+        return uri.path.count("/")
+        
+    def inscope(self, hostname, uri):
+        """ if you want to set multiple authorization on an
+        http client depending on hostname or uri"""
+        return True
+        
+    def request(self, url, method, body, headers):
+        pass
+        
+    def response(self, response, content):
+        """ allow us to store new auth info from the response."""
+        return False
+        
+    def add_credentials(self, *args, **kwargs):
+        raise NotImplementedError
+
+class BasicAuth(Auth):
+    """ basic authentification """
+    def request(self, url, method, body, headers):
+        headers['authorization'] = 'Basic ' + base64.b64encode("%s:%s" % self.credentials).strip()
+        
+    def add_credentials(self, username, password=None):
+        password = password or ""
+        self.credentials = (username, password)
+
+class HttpClient(object):
+    MAX_REDIRECTIONS = 5
+    
+    def __init__(self, follow_redirect=True, force_follow_redirect=False):
+        self.authorizations = []
+        self.use_proxy = False
+        self.follow_redirect = follow_redirect
+        self.force_follow_redirect = force_follow_redirect
+        
+    def add_authorization(self, obj_auth):
+        self.authorizations.append(obj_auth)
+        
+    def _get_connection(self, uri, headers=None):
+        connection = None
+        if uri.scheme == 'https':
+            if not uri.port:
+                connection = httplib.HTTPSConnection(uri.hostname)
+            else:
+                connection = httplib.HTTPSConnection(uri.hostname, uri.port)
+        else:
+            if not uri.port:
+                connection = httplib.HTTPConnection(uri.hostname)
+            else:
+                connection = httplib.HTTPConnection(uri.hostname, uri.port)
+        return connection
+        
+        
+    def _make_request(self, uri, method, body, headers):
+        connection = self._get_connection(uri, headers)
+        connection.debuglevel = restkit.debuglevel
+        
+        if connection.host != uri.hostname:
+            connection.putrequest(method, uri.geturl())
+        else:
+            connection.putrequest(method, _relative_uri(uri))
+        
+        # bug in Python 2.4 and 2.5
+        # httplib.HTTPConnection.putrequest adding 
+        # HTTP request header 'Host: domain.tld:443' instead of
+        # 'Host: domain.tld'
+        if (uri.scheme == 'https' and (uri.port or 443) == 443 and
+                hasattr(connection, '_buffer') and
+                isinstance(connection._buffer, list)):
+            header_line = 'Host: %s:443' % uri.hostname
+            replacement_header_line = 'Host: %s' % uri.hostname
+            try:
+                connection._buffer[connection._buffer.index(header_line)] = (
+                    replacement_header_line)
+            except ValueError:  # header_line missing from connection._buffer
+                pass
+        
+        # Send the HTTP headers.
+        for header_name, value in headers.iteritems():
+          connection.putheader(header_name, value)
+        connection.endheaders()
+        
+        if body:
+            if isinstance(body, types.StringTypes) or hasattr(body, 'read'):