Commits

Anonymous committed 79fec97

moved http.py to transport.py, renamed *client* to *transport* since
this is excactly what is is.

  • Participants
  • Parent commits b7d3ff2

Comments (0)

Files changed (11)

File docs/httpclient.txt

-.. _httpclient:
-
-HTTP Clients
-============
-
-.. module:: restclient.http
-
-HTTP Clients are object based on :mod:`restclient.http.HTTPClient` that
-perform HTPP operations.
-
-You can choose the one you want as httpclient in Resource or RestClient:
-
-.. code-block:: python
-
-    from restclient.http import CurlHTTPClient
-    httpclient = CurlHTTPClient()
-    res = Resource(httpclient=httpclient)
-    
-Functions
----------
-
-.. autofunction:: restclient.http.createHTTPClient
-.. autofunction:: restclient.http.getDefaultHTTPClient
-.. autofunction:: restclient.http.setDefaultHTTPClient
-
-Clients:
---------
-.. autoclass:: restclient.http.HTTPClient
-
-    .. automethod:: restclient.http.HTTPClient.request
-
-
-.. autoclass:: restclient.http.Urllib2HTTPClient
-
-    .. automethod:: restclient.http.Urllib2HTTPClient.__init__
-
-.. autoclass:: restclient.http.CurlHTTPClient
-
-    .. automethod:: restclient.http.CurlHTTPClient.__init__
-    .. automethod:: restclient.http.CurlHTTPClient.add_credentials
-
-.. autoclass:: restclient.http.HTTPLib2HTTPClient
-

File docs/index.txt

    :maxdepth: 2
 
    resource
-   httpclient
+   transports
 
 * Follow development on `Bitbucket <http://www.bitbucket.org/benoitc/python-restclient/overview/>`_.
 * Report bugs, report features and browse the source through `Tracker <http://www.bitbucket.org/benoitc/python-restclient/issues/>`_.

File docs/installation.txt

 Requirements
 ============
 
-To use py-restclient you will need **Python 2.5** or later. 
+To use py-restclient you will need **Python 2.5** or later and
+`Httplib2`_ :: . 
 
 .. _cheeseshop: http://pypi.python.org/pypi/WTForms/
 .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
 .. _Mercurial: http://www.selenic.com/mercurial/
 .. _Bitbucket: http://www.bitbucket.org/benoitc/py-restclient
+.. _Httplib2: http://code.google.com/p/httplib2
 

File docs/transports.txt

+.. _transports:
+
+HTTP Clients
+============
+
+.. module:: restclient.transport
+
+HTTP Clients are object based on :mod:`restclient.transport.HTTPClient` that
+perform HTPP operations.
+
+You can choose the one you want as transports in Resource or RestClient:
+
+.. code-block:: python
+
+    from restclient.transport import CurlTransport
+    transport = CurlTransport()
+    res = Resource(transport=transport)
+    
+Functions
+---------
+
+.. autofunction:: restclient.transport.createHTTPTransport
+.. autofunction:: restclient.transport.getDefaultHTTPTRansport
+.. autofunction:: restclient.transport.setDefaultHTTPTransport
+
+Clients:
+--------
+.. autoclass:: restclient.transport.HTTPTransportBase
+
+    .. automethod:: restclient.transport.HTTPTransportBase.request
+
+
+.. autoclass:: restclient.transport.CurlTransport
+
+    .. automethod:: restclient.transport.CurlTransport.__init__
+    .. automethod:: restclient.transport.CurlTransport.add_credentials
+
+.. autoclass:: restclient.transport.HTTPLib2Transport
+

File restclient/http.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008 (c) 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 StringIO
-import httplib
-import re
-import sys
-
-import restclient
-
-try:
-    import httplib2
-except ImportError:
-    httplib2 = None
-
-# try to import pycurl, which will let use of CurlHttpClient
-try:
-    import pycurl
-except ImportError:
-    pycurl=None
-
-_default_http = None
-
-USER_AGENT = "py-restclient/%s (%s)" % (restclient.__version__, sys.platform)
-DEFAULT_MAX_REDIRECT = 3
-
-def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
-    """
-    Returns a bytestring version of 's', encoded as specified in 'encoding'.
-
-    If strings_only is True, don't convert (some) non-string-like objects.
-    """
-    if strings_only and isinstance(s, (types.NoneType, int)):
-        return s
-   
-    if not isinstance(s, basestring):
-        try:
-            return str(s)
-        except UnicodeEncodeError:
-            if isinstance(s, Exception):
-                # An Exception subclass containing non-ASCII data that doesn't
-                # know how to print itself properly. We shouldn't raise a
-                # further exception.
-                return ' '.join([smart_str(arg, encoding, strings_only,
-                        errors) for arg in s])
-            return unicode(s).encode(encoding, errors)
-    elif isinstance(s, unicode):
-        return s.encode(encoding, errors)
-    elif s and encoding != 'utf-8':
-        return s.decode('utf-8', errors).encode(encoding, errors)
-    else:
-        return s
-
-
-def createHTTPClient():
-    """Create default HTTP client instance
-    prefers Curl to urllib"""
-
-    if pycurl is None:
-        http = HTTPLib2HTTPClient()
-    else:
-        http = CurlHTTPClient()
-
-    return http
-
-def getDefaultHTTPClient():
-    """ Return the default http client 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:
-        setDefaultHTTPClient(createHTTPClient())
-
-    return _default_http
-
-def setDefaultHTTPClient(httpclient):
-    """ set default httpClient 
-    :param http: RestClient
-    """
-    global _default_http
-
-    _default_http = httpclient
-
-def useCurl():
-    global _default_http
-    return isinstance(_default_http, CurlHTTPClient)
-
-class HTTPError(Exception):
-    """ raised when there is an HTTP error """
-
-class HTTPResponse(dict):
-    headers = None
-    status = 200
-    final_url = None
-    
-    def __init__(self, final_url=None, status=None, headers=None,
-            body=None):
-        self.final_url = final_url
-        self.status_code = status
-        self.headers = headers
-        self.body = body
-
-    def __repr__(self):
-        return "<%s status %s for %s>" % (self.__class__.__name__,
-                                          self.status,
-                                          self.final_url)
-
-class HTTPClient(object):
-    """ Interface for HTTP clients """
-
-    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
-
-    
-
-class CurlHTTPClient(HTTPClient):
-    """
-    An HTTPClient that 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 = CurlHTTPClient()
-        httpclient.add_credentials("test", "test")        
-
-    .. seealso::
-        
-        `Pycurl <http://pycurl.sourceforge.net>`_
-    """
-
-    def __init__(self, timeout=None):
-        HTTPClient.__init__(self)
-        self._credentials = {}
-
-        # path to certificate file
-        self.cabundle = None
-
-        if pycurl is None:
-            raise RuntimeError('Cannot find pycurl library')
-
-        self.timeout = timeout
-            
-
-    def _parseHeaders(self, status_and_headers):
-        status_and_headers.seek(0)
-
-        # Ignore status line
-        status_and_headers.readline()
-        msg = httplib.HTTPMessage(status_and_headers)
-        return dict(msg.items())
-
-    def request(self, url, method='GET', body=None, headers=None):
-        put = method in ('PUT')
-        body = body or ""        
-        headers = headers or {}
-        headers.setdefault('User-Agent',
-                           "%s %s" % (USER_AGENT, pycurl.version,))
-
-        # turn off default pragma provided by Curl
-        headers.update({
-            'Cache-control': 'max-age=0',
-            'Pragma': 'no-cache'
-        })
-
-        if put:
-            headers.setdefault('Expect', '100-continue')
-        
-        if method in ['POST', 'PUT']:
-            body = body or ''
-            headers.setdefault('Content-Length', str(len(body))) 
-
-
-        c = pycurl.Curl()
-        try:
-            # set curl options
-            if self.timeout is not None:
-                c.setopt(pycurl.TIMEOUT, self.timeout)
-            else:
-                c.setopt(pycurl.TIMEOUT, 20)
-
-            data = StringIO.StringIO()
-            header = StringIO.StringIO()
-            c.setopt(pycurl.WRITEFUNCTION, data.write)
-            c.setopt(pycurl.HEADERFUNCTION, header.write)
-            c.setopt(pycurl.URL , smart_str(url))
-            c.setopt(pycurl.FOLLOWLOCATION, 1)
-            c.setopt(pycurl.MAXREDIRS, 5)
-
-            if self.cabundle:
-                c.setopt(pycurl.CAINFO, celf.cabundle)
-
-            auth = self._get_credentials()
-            user = auth.get('user', None)
-            password = auth.get('password', None)
-            if user is not None:
-                # accept any auth methode
-                c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_ANY)
-                c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
-                userpass = user + ':'
-                if password is not None: # '' is a valid password
-                    userpass += password
-                c.setopt(pycurl.USERPWD, userpass)
-            #.setopt(pycurl.VERBOSE, 1)
-
-            if headers:
-                c.setopt(pycurl.HTTPHEADER,
-                        ["%s: %s" % pair for pair in sorted(headers.iteritems())])
-
-
-            # 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 put:
-                    c.setopt(pycurl.INFILESIZE, len(body))
-                if method in ('POST'):
-                    c.setopt(pycurl.POSTFIELDSIZE, len(body))
-                s = StringIO.StringIO(body)
-                c.setopt(pycurl.READFUNCTION, s.read)
-            
-            try:
-                c.perform()
-            except pycurl.error, e:
-                errno, message = e
-                return self._make_response(final_url=url, status=errno,
-                        body=message)
-
-            response_headers = self._parseHeaders(header)
-            code = c.getinfo(pycurl.RESPONSE_CODE)
-            
-            return self._make_response(final_url=url, status=code,
-                    headers=response_headers, body=data.getvalue())
-        finally:
-            c.close()
-
-    def add_credentials(self, user, password):
-        self._credentials = {
-                "user": user,
-                "password": password
-        }
-
-    def _get_credentials(self):
-        return self._credentials
-
-
-    def _make_response(self, final_url=None, status=None, headers=None,
-            body=None):
-        resp = HTTPResponse()
-        resp.headers = headers
-        resp.status = status
-        resp.final_url = final_url
-        resp.body = body
-        return resp, body 
-    
-class HTTPLib2HTTPClient(HTTPClient):
-    """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, http=None):
-        """@param http: An httplib2.HTTP instance.
-        """
-        if httplib2 is None:
-            raise RuntimeError('Cannot find httplib2 library. '
-                               'See http://bitworking.org/projects/httplib2/')
-
-        super(HTTPLib2HTTPClient, self).__init__()
-        
-        if http is None:
-            http = httplib2.Http()
-
-        self.http = http
-        self.http.force_exception_to_status_code = False
-
-    def request(self, url, method='GET', body=None, headers=None):
-        headers = headers or {}
-       
-        if method in ['POST', 'PUT']:
-            body = body or ''
-            headers.setdefault('Content-Length', str(len(body))) 
-
-        if not (url.startswith('http://') or url.startswith('https://')):
-            raise ValueError('URL is not a HTTP URL: %r' % (url,))
-
-        headers.setdefault('User-Agent', USER_AGENT)
-        
-        httplib2_response, content = self.http.request(url,
-                method=method, body=body, headers=headers)
-
-
-        try:
-            final_url = httplib2_response['content-location']
-        except KeyError:
-            final_url = url
-
-        resp = HTTPResponse()
-        resp.headers = dict(httplib2_response.items())
-        resp.status = int(httplib2_response.status)
-        resp.final_url = final_url
-        resp.body = content
-
-        return resp, content

File restclient/rest.py

 """
 from urllib import quote, urlencode
 
-from restclient.http import getDefaultHTTPClient, HTTPClient 
+from restclient.transport import getDefaultHTTPTransport, HTTPTransportBase 
 
 
 __all__ = ['Resource', 'RestClient', 'ResourceNotFound', \
     `restclient.http.HTTPClient`.
 
     """
-    def __init__(self, uri, httpclient=None):
+    def __init__(self, uri, transport=None):
         """Constructor for a `Resource` object.
 
         Resource represent an HTTP resource.
 
         :param uri: str, full uri to the server.
-        :param httpclient: any http instance of object based on 
+        :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 
                 (authentification, proxy, ....).
         """
 
-        self.client = RestClient(httpclient)
+        self.client = RestClient(transport)
         self.uri = uri
-        self.httpclient = httpclient
+        self.transport = transport
 
     def __repr__(self):
         return '<%s %s>' % (self.__class__.__name__, self.uri)
             resr2 = res.clone()
         
         """
-        obj = self.__class__(self.uri, http=self.httpclient)
+        obj = self.__class__(self.uri, http=self.transport)
         return obj
    
     def __call__(self, path):
             Resource("/path").get()
         """
 
-        return type(self)(make_uri(self.uri, path), http=self.httpclient)
+        return type(self)(make_uri(self.uri, path), http=self.transport)
 
     
     def get(self, path=None, headers=None, **params):
         '{"snippet": "testing API.", "title": "", "id": "3XDqQ8G83LlzVWgCeWdwru", "language": "text", "revision": "363934613139"}'
     """
 
-    def __init__(self, httpclient=None):
+    def __init__(self, transport=None):
         """Constructor for a `RestClient` object.
 
         RestClient represent an HTTP client.
 
-        :param httpclient: any http instance of object based on 
-                `restclient.http.HTTPClient`. By default it will use 
+        :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 urllib2. You could also use 
-                `restclient.http.HTTPLib2HTTPClient`,a client based on 
+                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, ....).
         """ 
 
-        if httpclient is None:
-            httpclient = getDefaultHTTPClient()
+        if transport is None:
+            transport = getDefaultHTTPTransport()
 
-        self.httpclient = httpclient
+        self.transport = transport
 
         self.status_code = None
         self.response = None
         
         headers = headers or {}
 
-        resp, data = self.httpclient.request(make_uri(uri, path, **params), 
+        resp, data = self.transport.request(make_uri(uri, path, **params), 
                 method=method, body=body, headers=headers)
 
         status_code = int(resp.status)

File restclient/transport.py

+# -*- coding: utf-8 -
+#
+# Copyright (c) 2008 (c) 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 StringIO
+import httplib
+import re
+import sys
+
+import restclient
+
+try:
+    import httplib2
+except ImportError:
+    httplib2 = None
+
+# try to import pycurl, which will let use of CurlHttpClient
+try:
+    import pycurl
+except ImportError:
+    pycurl=None
+
+_default_http = None
+
+USER_AGENT = "py-restclient/%s (%s)" % (restclient.__version__, sys.platform)
+DEFAULT_MAX_REDIRECT = 3
+
+def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
+    """
+    Returns a bytestring version of 's', encoded as specified in 'encoding'.
+
+    If strings_only is True, don't convert (some) non-string-like objects.
+    """
+    if strings_only and isinstance(s, (types.NoneType, int)):
+        return s
+   
+    if not isinstance(s, basestring):
+        try:
+            return str(s)
+        except UnicodeEncodeError:
+            if isinstance(s, Exception):
+                # An Exception subclass containing non-ASCII data that doesn't
+                # know how to print itself properly. We shouldn't raise a
+                # further exception.
+                return ' '.join([smart_str(arg, encoding, strings_only,
+                        errors) for arg in s])
+            return unicode(s).encode(encoding, errors)
+    elif isinstance(s, unicode):
+        return s.encode(encoding, errors)
+    elif s and encoding != 'utf-8':
+        return s.decode('utf-8', errors).encode(encoding, errors)
+    else:
+        return s
+
+
+def createHTTPClient():
+    """Create default HTTP client instance
+    prefers Curl to urllib"""
+
+    if pycurl is None:
+        http = HTTPLib2Transport()
+    else:
+        http = CurlTransport()
+
+    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
+    return isinstance(_default_http, CurlHTTPTransport)
+
+class HTTPError(Exception):
+    """ raised when there is an HTTP error """
+
+class HTTPResponse(dict):
+    headers = None
+    status = 200
+    final_url = None
+    
+    def __init__(self, final_url=None, status=None, headers=None,
+            body=None):
+        self.final_url = final_url
+        self.status_code = status
+        self.headers = headers
+        self.body = body
+
+    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 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
+
+    
+
+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):
+        HTTPTransportBase.__init__(self)
+        self._credentials = {}
+
+        # path to certificate file
+        self.cabundle = None
+
+        if pycurl is None:
+            raise RuntimeError('Cannot find pycurl library')
+
+        self.timeout = timeout
+            
+
+    def _parseHeaders(self, status_and_headers):
+        status_and_headers.seek(0)
+
+        # Ignore status line
+        status_and_headers.readline()
+        msg = httplib.HTTPMessage(status_and_headers)
+        return dict(msg.items())
+
+    def request(self, url, method='GET', body=None, headers=None):
+        put = method in ('PUT')
+        body = body or ""        
+        headers = headers or {}
+        headers.setdefault('User-Agent',
+                           "%s %s" % (USER_AGENT, pycurl.version,))
+
+        # turn off default pragma provided by Curl
+        headers.update({
+            'Cache-control': 'max-age=0',
+            'Pragma': 'no-cache'
+        })
+
+        if put:
+            headers.setdefault('Expect', '100-continue')
+        
+        if method in ['POST', 'PUT']:
+            body = body or ''
+            headers.setdefault('Content-Length', str(len(body))) 
+
+
+        c = pycurl.Curl()
+        try:
+            # set curl options
+            if self.timeout is not None:
+                c.setopt(pycurl.TIMEOUT, self.timeout)
+            else:
+                c.setopt(pycurl.TIMEOUT, 20)
+
+            data = StringIO.StringIO()
+            header = StringIO.StringIO()
+            c.setopt(pycurl.WRITEFUNCTION, data.write)
+            c.setopt(pycurl.HEADERFUNCTION, header.write)
+            c.setopt(pycurl.URL , smart_str(url))
+            c.setopt(pycurl.FOLLOWLOCATION, 1)
+            c.setopt(pycurl.MAXREDIRS, 5)
+
+            if self.cabundle:
+                c.setopt(pycurl.CAINFO, celf.cabundle)
+
+            auth = self._get_credentials()
+            user = auth.get('user', None)
+            password = auth.get('password', None)
+            if user is not None:
+                # accept any auth methode
+                c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_ANY)
+                c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
+                userpass = user + ':'
+                if password is not None: # '' is a valid password
+                    userpass += password
+                c.setopt(pycurl.USERPWD, userpass)
+            #.setopt(pycurl.VERBOSE, 1)
+
+            if headers:
+                c.setopt(pycurl.HTTPHEADER,
+                        ["%s: %s" % pair for pair in sorted(headers.iteritems())])
+
+
+            # 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 put:
+                    c.setopt(pycurl.INFILESIZE, len(body))
+                if method in ('POST'):
+                    c.setopt(pycurl.POSTFIELDSIZE, len(body))
+                s = StringIO.StringIO(body)
+                c.setopt(pycurl.READFUNCTION, s.read)
+            
+            try:
+                c.perform()
+            except pycurl.error, e:
+                errno, message = e
+                return self._make_response(final_url=url, status=errno,
+                        body=message)
+
+            response_headers = self._parseHeaders(header)
+            code = c.getinfo(pycurl.RESPONSE_CODE)
+            
+            return self._make_response(final_url=url, status=code,
+                    headers=response_headers, body=data.getvalue())
+        finally:
+            c.close()
+
+    def add_credentials(self, user, password):
+        self._credentials = {
+                "user": user,
+                "password": password
+        }
+
+    def _get_credentials(self):
+        return self._credentials
+
+
+    def _make_response(self, final_url=None, status=None, headers=None,
+            body=None):
+        resp = HTTPResponse()
+        resp.headers = headers
+        resp.status = status
+        resp.final_url = final_url
+        resp.body = body
+        return resp, body 
+    
+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, http=None):
+        """@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__()
+        
+        if http is None:
+            http = httplib2.Http()
+
+        self.http = http
+        self.http.force_exception_to_status_code = False
+
+    def request(self, url, method='GET', body=None, headers=None):
+        headers = headers or {}
+       
+        if method in ['POST', 'PUT']:
+            body = body or ''
+            headers.setdefault('Content-Length', str(len(body))) 
+
+        if not (url.startswith('http://') or url.startswith('https://')):
+            raise ValueError('URL is not a HTTP URL: %r' % (url,))
+
+        headers.setdefault('User-Agent', USER_AGENT)
+        
+        httplib2_response, content = self.http.request(url,
+                method=method, body=body, headers=headers)
+
+
+        try:
+            final_url = httplib2_response['content-location']
+        except KeyError:
+            final_url = url
+
+        resp = HTTPResponse()
+        resp.headers = dict(httplib2_response.items())
+        resp.status = int(httplib2_response.status)
+        resp.final_url = final_url
+        resp.body = content
+
+        return resp, content

File tests/clients_test.py

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008 (c) 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 BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-import cgi
-import os
-import socket
-import threading
-import unittest
-import urlparse
-
-
-from restclient.http import CurlHTTPClient
-from restclient.rest import Resource, RestClient, RequestFailed, \
-ResourceNotFound, Unauthorized
-from _server_test import HOST, PORT
-
-class HTTPClientTestCase(unittest.TestCase):
-    httpclient = CurlHTTPClient()
-
-    def setUp(self):
-        self.url = 'http://%s:%s' % (HOST, PORT)
-        self.res = Resource(self.url, self.httpclient)
-
-    def tearDown(self):
-        self.res = None
-
-    def testGet(self):
-        result = self.res.get()
-        self.assert_(result == "welcome")
-
-    def testGetWithContentType(self):
-        result = self.res.get('/json', headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200)
-        def bad_get():
-            result = self.res.get('/json', headers={'Content-Type': 'text/plain'})
-        self.assertRaises(RequestFailed, bad_get) 
-
-    def testNotFound(self):
-        def bad_get():
-            result = self.res.get("/unknown")
-
-        self.assertRaises(ResourceNotFound, bad_get)
-
-    def testGetWithQuery(self):
-        result = self.res.get('/query', test="testing")
-        self.assert_(result.http_code == 200)
-
-
-    def testSimplePost(self):
-        result = self.res.post(payload="test")
-        self.assert_(result=="test")
-
-    def testPostWithContentType(self):
-        result = self.res.post('/json', payload="test",
-                headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-        def bad_post():
-            result = self.res.post('/json', payload="test",
-                    headers={'Content-Type': 'text/plain'})
-        self.assertRaises(RequestFailed, bad_post)
-
-    def testEmptyPost(self):
-        result = self.res.post('/empty', payload="",
-                headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-        result = self.res.post('/empty',headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-
-    def testPostWithQuery(self):
-        result = self.res.post('/query', test="testing")
-        self.assert_(result.http_code == 200)
-
-    def testSimplePut(self):
-        result = self.res.put(payload="test")
-        self.assert_(result=="test")
-
-    def testPutWithContentType(self):
-        result = self.res.put('/json', payload="test",
-                headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-        def bad_put():
-            result = self.res.put('/json', payload="test",
-                    headers={'Content-Type': 'text/plain'})
-        self.assertRaises(RequestFailed, bad_put)
-
-    def testEmptyPut(self):
-        result = self.res.put('/empty', payload="",
-                headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-        result = self.res.put('/empty',headers={'Content-Type': 'application/json'})
-        self.assert_(result.http_code == 200 )
-
-    def testPuWithQuery(self):
-        result = self.res.put('/query', test="testing")
-        self.assert_(result.http_code == 200)
-
-    def testHead(self):
-        result = self.res.head('/ok')
-        self.assert_(result.http_code == 200)
-
-    def testDelete(self):
-        result = self.res.delete('/delete')
-        self.assert_(result.http_code == 200)
-
-    def testAuth(self):
-        httpclient = self.httpclient 
-        httpclient.add_credentials("test", "test")
-        
-        res = Resource(self.url, httpclient)
-        result = res.get('/auth')
-        self.assert_(result.http_code == 200)
-
-        httpclient.add_credentials("test", "test2")
-        
-        def niettest():
-            res = Resource(self.url, httpclient)
-            result = res.get('/auth')
-        self.assertRaises(Unauthorized, niettest)
-
-
- 
-if __name__ == '__main__':
-    from _server_test import run_server_test
-    run_server_test()
-    unittest.main()

File tests/resource_test.py

 import urlparse
 import urllib2
 
-from restclient.http import HTTPLib2HTTPClient
+from restclient.transport import HTTPLib2Transport
 from restclient.rest import Resource, RestClient, RequestFailed, \
 ResourceNotFound, Unauthorized
 
 class ResourceTestCase(unittest.TestCase):
 
     def setUp(self):
-        httpclient = HTTPLib2HTTPClient()
+        transport = HTTPLib2Transport()
         self.url = 'http://%s:%s' % (HOST, PORT)
-        self.res = Resource(self.url, httpclient)
+        self.res = Resource(self.url, transport)
 
     def tearDown(self):
         self.res = None

File tests/run_alltests.py

 from _server_test import run_server_test
 
 import resource_test
-import clients_test
+import transports_test
 
 
    
 def RunAllTests():
     run_server_test()
     test_runner = module_test_runner.ModuleTestRunner()
-    test_runner.modules = [resource_test, clients_test]
+    test_runner.modules = [resource_test, transports_test]
     test_runner.RunAllTests()
 
 if __name__ == '__main__':

File tests/transports_test.py

+# -*- coding: utf-8 -
+#
+# Copyright (c) 2008 (c) 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 BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import cgi
+import os
+import socket
+import threading
+import unittest
+import urlparse
+
+
+from restclient.transport import CurlTransport
+from restclient.rest import Resource, RestClient, RequestFailed, \
+ResourceNotFound, Unauthorized
+from _server_test import HOST, PORT
+
+class HTTPClientTestCase(unittest.TestCase):
+    httptransport = CurlTransport()
+
+    def setUp(self):
+        self.url = 'http://%s:%s' % (HOST, PORT)
+        self.res = Resource(self.url, self.httptransport)
+
+    def tearDown(self):
+        self.res = None
+
+    def testGet(self):
+        result = self.res.get()
+        self.assert_(result == "welcome")
+
+    def testGetWithContentType(self):
+        result = self.res.get('/json', headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200)
+        def bad_get():
+            result = self.res.get('/json', headers={'Content-Type': 'text/plain'})
+        self.assertRaises(RequestFailed, bad_get) 
+
+    def testNotFound(self):
+        def bad_get():
+            result = self.res.get("/unknown")
+
+        self.assertRaises(ResourceNotFound, bad_get)
+
+    def testGetWithQuery(self):
+        result = self.res.get('/query', test="testing")
+        self.assert_(result.http_code == 200)
+
+
+    def testSimplePost(self):
+        result = self.res.post(payload="test")
+        self.assert_(result=="test")
+
+    def testPostWithContentType(self):
+        result = self.res.post('/json', payload="test",
+                headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+        def bad_post():
+            result = self.res.post('/json', payload="test",
+                    headers={'Content-Type': 'text/plain'})
+        self.assertRaises(RequestFailed, bad_post)
+
+    def testEmptyPost(self):
+        result = self.res.post('/empty', payload="",
+                headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+        result = self.res.post('/empty',headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+
+    def testPostWithQuery(self):
+        result = self.res.post('/query', test="testing")
+        self.assert_(result.http_code == 200)
+
+    def testSimplePut(self):
+        result = self.res.put(payload="test")
+        self.assert_(result=="test")
+
+    def testPutWithContentType(self):
+        result = self.res.put('/json', payload="test",
+                headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+        def bad_put():
+            result = self.res.put('/json', payload="test",
+                    headers={'Content-Type': 'text/plain'})
+        self.assertRaises(RequestFailed, bad_put)
+
+    def testEmptyPut(self):
+        result = self.res.put('/empty', payload="",
+                headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+        result = self.res.put('/empty',headers={'Content-Type': 'application/json'})
+        self.assert_(result.http_code == 200 )
+
+    def testPuWithQuery(self):
+        result = self.res.put('/query', test="testing")
+        self.assert_(result.http_code == 200)
+
+    def testHead(self):
+        result = self.res.head('/ok')
+        self.assert_(result.http_code == 200)
+
+    def testDelete(self):
+        result = self.res.delete('/delete')
+        self.assert_(result.http_code == 200)
+
+    def testAuth(self):
+        httptransport = self.httptransport 
+        httptransport.add_credentials("test", "test")
+        
+        res = Resource(self.url, httptransport)
+        result = res.get('/auth')
+        self.assert_(result.http_code == 200)
+
+        httptransport.add_credentials("test", "test2")
+        
+        def niettest():
+            res = Resource(self.url, httptransport)
+            result = res.get('/auth')
+        self.assertRaises(Unauthorized, niettest)
+
+
+ 
+if __name__ == '__main__':
+    from _server_test import run_server_test
+    run_server_test()
+    unittest.main()