Commits

Anonymous committed 4dc932d

- move tests, exceptions.
- add parse_url function for later

Comments (0)

Files changed (5)

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 __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

 HTTPTransportBase, TransportError
 from restclient.utils import to_bytestring
 
-__all__ = ['Resource', 'RestClient', 'ResourceNotFound', \
-        'Unauthorized', 'RequestFailed', 'ResourceError',
-        'RequestError', 'url_quote', 'url_encode']
+__all__ = ['Resource', 'RestClient', 'url_quote', 'url_encode']
 
 __docformat__ = 'restructuredtext en'
 
-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
-
-    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"""
+from restclient.errors import *
 
 
 class Resource(object):

restclient/transport.py

 import sys
 
 import restclient
+from restclient.errors import TransportError
 from restclient.utils import to_bytestring, iri2uri
 
 try:
 
 _default_http = None
 
-class TransportError(Exception):
-    """Error raised by a transport """
+
 
 USER_AGENT = "py-restclient/%s (%s)" % (restclient.__version__, sys.platform)
 DEFAULT_MAX_REDIRECT = 3
     def add_credentials(self, user, password):
         super(HTTPLib2Transport, self).add_credentials(user, password)
         self.http.add_credentials(user, password)
+
+        

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
 #
 
 Converts an IRI to a URI.
 """
-
+import re
 import urlparse
 
 
     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(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
 # 
         uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
         uri = "".join([encode(c) for c in uri])
     return uri
-        
-if __name__ == "__main__":
-    import unittest
-
-    class Test(unittest.TestCase):
-
-        def test_uris(self):
-            """Test that URIs are invariant under the transformation."""
-            invariant = [ 
-                u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
-                u"http://www.ietf.org/rfc/rfc2396.txt",
-                u"ldap://[2001:db8::7]/c=GB?objectClass?one",
-                u"mailto:John.Doe@example.com",
-                u"news:comp.infosystems.www.servers.unix",
-                u"tel:+1-816-555-1212",
-                u"telnet://192.0.2.16:80/",
-                u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
-            for uri in invariant:
-                self.assertEqual(uri, iri2uri(uri))
-            
-        def test_iri(self):
-            """ Test that the right type of escaping is done for each part of the URI."""
-            self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
-            self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
-            self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
-            self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
-            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
-            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
-            self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))
-
-    unittest.main()

tests/utils_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.
+#
+
+import unittest
+
+from restclient.utils import parse_url, iri2uri
+
+class UtilTestCase(unittest.TestCase):
+    
+    def test_parse_url(self):
+        """
+        Validate that the parse_url() function properly returns the hostname, 
+        port number, path (if any), and ssl boolean. Attempts several 
+        different URL permutations, (5 tests total).
+        """
+        urls = {
+            'http_noport_nopath': {
+                'url':   'http://bogus.not',
+                'host':  'bogus.not',
+                'port':  80,
+                'path':  '',
+                'ssl':   False,
+            },
+            'https_noport_nopath': {
+                'url':   'https://bogus.not',
+                'host':  'bogus.not',
+                'port':  443,
+                'path':  '',
+                'ssl':   True,
+            },
+            'http_noport_withpath': {
+                'url':   'http://bogus.not/v1/bar',
+                'host':  'bogus.not',
+                'port':  80,
+                'path':  'v1/bar',
+                'ssl':   False,
+            },
+            'http_withport_nopath': {
+                'url':   'http://bogus.not:8000',
+                'host':  'bogus.not',
+                'port':  8000,
+                'path':  '',
+                'ssl':   False,
+            },
+            'https_withport_withpath': {
+                'url':   'https://bogus.not:8443/v1/foo',
+                'host':  'bogus.not',
+                'port':  8443,
+                'path':  'v1/foo',
+                'ssl':   True,
+            },
+        }
+        for url in urls:
+            yield check_url, url, urls[url]
+
+    def check_url(test, urlspec):
+        (host, port, path, ssl) = parse_url(urlspec['url'])
+        self.assert_(host == urlspec['host'], "%s failed on host assertion" % test)
+        self.assert_(port == urlspec['port'], "%s failed on port assertion" % test)
+        self.assert_(path == urlspec['path'], "%s failed on path assertion" % test)
+        self.assert_(ssl == urlspec['ssl'], "%s failed on ssl assertion" % test)
+        
+    def test_uris(self):
+        """Test that URIs are invariant under the transformation."""
+        invariant = [ 
+            u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
+            u"http://www.ietf.org/rfc/rfc2396.txt",
+            u"ldap://[2001:db8::7]/c=GB?objectClass?one",
+            u"mailto:John.Doe@example.com",
+            u"news:comp.infosystems.www.servers.unix",
+            u"tel:+1-816-555-1212",
+            u"telnet://192.0.2.16:80/",
+            u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
+        for uri in invariant:
+            self.assertEqual(uri, iri2uri(uri))
+        
+    def test_iri(self):
+        """ Test that the right type of escaping is done for each part of the URI."""
+        self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
+        self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
+        self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
+        self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
+        self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
+        self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
+        self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))