Commits

Robert Brewer committed 51acde9

Yet more py2/py3 sync

Comments (0)

Files changed (12)

py2/cherrypy/_cpcompat.py

         """Return the given native string as a unicode string with the given encoding."""
         # In Python 3, the native string type is unicode
         return n
+    def tonative(n, encoding='ISO-8859-1'):
+        """Return the given string as a native string in the given encoding."""
+        # In Python 3, the native string type is unicode
+        if isinstance(n, bytes):
+            return n.decode(encoding)
+        return n
     # type("")
     from io import StringIO
     # bytes:
         # in the given encoding, which for ISO-8859-1 is almost always what
         # was intended.
         return n.decode(encoding)
+    def tonative(n, encoding='ISO-8859-1'):
+        """Return the given string as a native string in the given encoding."""
+        # In Python 2, the native string type is bytes.
+        if isinstance(n, unicode):
+            return n.encode(encoding)
+        return n
     try:
         # type("")
         from cStringIO import StringIO

py2/cherrypy/_cpdispatch.py

                       'cherrypy.request.params copied in)')
 
 
-punctuation_to_underscores = string.maketrans(
-    string.punctuation, '_' * len(string.punctuation))
+if sys.version_info < (3, 0):
+    punctuation_to_underscores = string.maketrans(
+        string.punctuation, '_' * len(string.punctuation))
+    def validate_translator(t):
+        if not isinstance(t, str) or len(t) != 256:
+            raise ValueError("The translate argument must be a str of len 256.")
+else:
+    punctuation_to_underscores = str.maketrans(
+        string.punctuation, '_' * len(string.punctuation))
+    def validate_translator(t):
+        if not isinstance(t, dict):
+            raise ValueError("The translate argument must be a dict.")
 
 class Dispatcher(object):
     """CherryPy Dispatcher which walks a tree of objects to find a handler.
     
     def __init__(self, dispatch_method_name=None,
                  translate=punctuation_to_underscores):
-        if not isinstance(translate, str) or len(translate) != 256:
-            raise ValueError("The translate argument must be a str of len 256.")
+        validate_translator(translate)
         self.translate = translate
         if dispatch_method_name:
             self.dispatch_method_name = dispatch_method_name

py2/cherrypy/_cperror.py

 from cgi import escape as _escape
 from sys import exc_info as _exc_info
 from traceback import format_exception as _format_exception
-from cherrypy._cpcompat import basestring, iteritems, urljoin as _urljoin
+from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
 from cherrypy.lib import httputil as _httputil
 
 
     """The list of URL's to emit."""
 
     encoding = 'utf-8'
-    """The encoding when passed urls are unicode objects"""
+    """The encoding when passed urls are not native strings"""
     
     def __init__(self, urls, status=None, encoding=None):
         import cherrypy
         
         abs_urls = []
         for url in urls:
-            if isinstance(url, unicode):
-                url = url.encode(encoding or self.encoding)
+            url = tonative(url, encoding or self.encoding)
                 
             # Note that urljoin will "do the right thing" whether url is:
             #  1. a complete URL with host (e.g. "http://www.example.com/test")
                    307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
                    }[status]
             msgs = [msg % (u, u) for u in self.urls]
-            response.body = "<br />\n".join(msgs)
+            response.body = ntob("<br />\n".join(msgs), 'utf-8')
             # Previous code may have set C-L, so we have to reset it
             # (allow finalize to set it).
             response.headers.pop('Content-Length', None)
         self.status = status
         try:
             self.code, self.reason, defaultmsg = _httputil.valid_status(status)
-        except ValueError, x:
-            raise self.__class__(500, x.args[0])
+        except ValueError:
+            raise self.__class__(500, _exc_info()[1].args[0])
         
         if self.code < 400 or self.code > 599:
             raise ValueError("status must be between 400 and 599.")
         response.headers['Content-Type'] = "text/html;charset=utf-8"
         response.headers.pop('Content-Length', None)
         
-        content = self.get_error_page(self.status, traceback=tb,
-                                      message=self._message)
+        content = ntob(self.get_error_page(self.status, traceback=tb,
+                                           message=self._message), 'utf-8')
         response.body = content
         
         _be_ie_unfriendly(self.code)
     
     try:
         code, reason, message = _httputil.valid_status(status)
-    except ValueError, x:
-        raise cherrypy.HTTPError(500, x.args[0])
+    except ValueError:
+        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
     
     # We can't use setdefault here, because some
     # callers send None for kwarg values.
             if hasattr(error_page, '__call__'):
                 return error_page(**kwargs)
             else:
-                return open(error_page, 'rb').read() % kwargs
+                data = open(error_page, 'rb').read()
+                return tonative(data) % kwargs
         except:
             e = _format_exception(*_exc_info())[-1]
             m = kwargs['message']
         if l and l < s:
             # IN ADDITION: the response must be written to IE
             # in one chunk or it will still get replaced! Bah.
-            content = content + (" " * (s - l))
+            content = content + (ntob(" ") * (s - l))
         response.body = content
         response.headers['Content-Length'] = str(len(content))
 
     # it cannot be allowed to fail. Therefore, don't add to it!
     # In particular, don't call any other CP functions.
     
-    body = "Unrecoverable error in the server."
+    body = ntob("Unrecoverable error in the server.")
     if extrabody is not None:
-        if not isinstance(extrabody, str): 
+        if not isinstance(extrabody, bytestr):
             extrabody = extrabody.encode('utf-8')
-        body += "\n" + extrabody
+        body += ntob("\n") + extrabody
     
-    return ("500 Internal Server Error",
-            [('Content-Type', 'text/plain'),
-             ('Content-Length', str(len(body)))],
+    return (ntob("500 Internal Server Error"),
+            [(ntob('Content-Type'), ntob('text/plain')),
+             (ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
             [body])
 
 

py2/cherrypy/_cpmodpy.py

 
 import os
 import re
+try:
+    import subprocess
+    def popen(fullcmd):
+        p = subprocess.Popen(fullcmd, shell=True,
+                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                             close_fds=True)
+        return p.stdout
+except ImportError:
+    def popen(fullcmd):
+        pipein, pipeout = os.popen4(fullcmd)
+        return pipeout
 
 
 def read_process(cmd, args=""):
     fullcmd = "%s %s" % (cmd, args)
-    pipein, pipeout = os.popen4(fullcmd)
+    pipeout = popen(fullcmd)
     try:
         firstline = pipeout.readline()
         if (re.search(ntob("(not recognized|No such file|not found)"), firstline,

py2/cherrypy/_cpreqbody.py

 Note that you can only replace the ``processors`` dict wholesale this way, not update the existing one.
 """
 
+try:
+    from io import DEFAULT_BUFFER_SIZE
+except ImportError:
+    DEFAULT_BUFFER_SIZE = 8192
 import re
 import sys
 import tempfile
-from urllib import unquote_plus
+try:
+    from urllib import unquote_plus
+except ImportError:
+    def unquote_plus(bs):
+        """Bytes version of urllib.parse.unquote_plus."""
+        bs = bs.replace(ntob('+'), ntob(' '))
+        atoms = bs.split(ntob('%'))
+        for i in range(1, len(atoms)):
+            item = atoms[i]
+            try:
+                pct = int(item[:2], 16)
+                atoms[i] = bytes([pct]) + item[2:]
+            except ValueError:
+                pass
+        return ntob('').join(atoms)
 
 import cherrypy
 from cherrypy._cpcompat import basestring, ntob, ntou
     def __iter__(self):
         return self
     
-    def next(self):
+    def __next__(self):
         line = self.readline()
         if not line:
             raise StopIteration
         return line
+
+    def next(self):
+        return self.__next__()
     
     def read_into_file(self, fp_out=None):
         """Read the request body into fp_out (or make_file() if None). Return fp_out."""
 
 Entity.part_class = Part
 
-
-class Infinity(object):
-    def __cmp__(self, other):
-        return 1
-    def __sub__(self, other):
-        return self
-inf = Infinity()
+try:
+    inf = float('inf')
+except ValueError:
+    # Python 2.4 and lower
+    class Infinity(object):
+        def __cmp__(self, other):
+            return 1
+        def __sub__(self, other):
+            return self
+    inf = Infinity()
 
 
 comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
 
 class SizedReader:
     
-    def __init__(self, fp, length, maxbytes, bufsize=8192, has_trailers=False):
+    def __init__(self, fp, length, maxbytes, bufsize=DEFAULT_BUFFER_SIZE, has_trailers=False):
         # Wrap our fp in a buffer so peek() works
         self.fp = fp
         self.length = length
         request_params = self.request_params
         for key, value in self.params.items():
             # Python 2 only: keyword arguments must be byte strings (type 'str').
-            if isinstance(key, unicode):
-                key = key.encode('ISO-8859-1')
+            if sys.version_info < (3, 0):
+                if isinstance(key, unicode):
+                    key = key.encode('ISO-8859-1')
             
             if key in request_params:
                 if not isinstance(request_params[key], list):

py2/cherrypy/_cprequest.py

         
         self.kwargs = kwargs
     
+    def __lt__(self, other):
+        # Python 3
+        return self.priority < other.priority
+
     def __cmp__(self, other):
+        # Python 2
         return cmp(self.priority, other.priority)
     
     def __call__(self):
             self.stage = 'close'
     
     def run(self, method, path, query_string, req_protocol, headers, rfile):
-        """Process the Request. (Core)
+        r"""Process the Request. (Core)
         
         method, path, query_string, and req_protocol should be pulled directly
         from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
         
         path
             This should be %XX-unquoted, but query_string should not be.
-            They both MUST be byte strings, not unicode strings.
+            
+            When using Python 2, they both MUST be byte strings,
+            not unicode strings.
+            
+            When using Python 3, they both MUST be unicode strings,
+            not byte strings, and preferably not bytes \x00-\xFF
+            disguised as unicode.
         
         headers
             A list of (name, value) tuples.
                 self.query_string_encoding)
         
         # Python 2 only: keyword arguments must be byte strings (type 'str').
-        for key, value in p.items():
-            if isinstance(key, unicode):
-                del p[key]
-                p[key.encode(self.query_string_encoding)] = value
+        if sys.version_info < (3, 0):
+            for key, value in p.items():
+                if isinstance(key, unicode):
+                    del p[key]
+                    p[key.encode(self.query_string_encoding)] = value
         self.params.update(p)
     
     def process_headers(self):

py3/cherrypy/_cpcompat.py

         """Return the given native string as a unicode string with the given encoding."""
         # In Python 3, the native string type is unicode
         return n
+    def tonative(n, encoding='ISO-8859-1'):
+        """Return the given string as a native string in the given encoding."""
+        # In Python 3, the native string type is unicode
+        if isinstance(n, bytes):
+            return n.decode(encoding)
+        return n
     # type("")
     from io import StringIO
     # bytes:
         # in the given encoding, which for ISO-8859-1 is almost always what
         # was intended.
         return n.decode(encoding)
+    def tonative(n, encoding='ISO-8859-1'):
+        """Return the given string as a native string in the given encoding."""
+        # In Python 2, the native string type is bytes.
+        if isinstance(n, unicode):
+            return n.encode(encoding)
+        return n
     try:
         # type("")
         from cStringIO import StringIO

py3/cherrypy/_cpdispatch.py

                       'cherrypy.request.params copied in)')
 
 
-punctuation_to_underscores = str.maketrans(
-    string.punctuation, '_' * len(string.punctuation))
+if sys.version_info < (3, 0):
+    punctuation_to_underscores = string.maketrans(
+        string.punctuation, '_' * len(string.punctuation))
+    def validate_translator(t):
+        if not isinstance(t, str) or len(t) != 256:
+            raise ValueError("The translate argument must be a str of len 256.")
+else:
+    punctuation_to_underscores = str.maketrans(
+        string.punctuation, '_' * len(string.punctuation))
+    def validate_translator(t):
+        if not isinstance(t, dict):
+            raise ValueError("The translate argument must be a dict.")
 
 class Dispatcher(object):
     """CherryPy Dispatcher which walks a tree of objects to find a handler.
     
     def __init__(self, dispatch_method_name=None,
                  translate=punctuation_to_underscores):
-        if not isinstance(translate, dict):
-            raise ValueError("The translate argument must be a dict.")
+        validate_translator(translate)
         self.translate = translate
         if dispatch_method_name:
             self.dispatch_method_name = dispatch_method_name

py3/cherrypy/_cperror.py

 from cgi import escape as _escape
 from sys import exc_info as _exc_info
 from traceback import format_exception as _format_exception
-from cherrypy._cpcompat import basestring, iteritems, urljoin as _urljoin
+from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
 from cherrypy.lib import httputil as _httputil
 
 
     """The list of URL's to emit."""
 
     encoding = 'utf-8'
-    """The encoding when passed urls are bytes objects"""
+    """The encoding when passed urls are not native strings"""
     
     def __init__(self, urls, status=None, encoding=None):
         import cherrypy
         
         abs_urls = []
         for url in urls:
-            if isinstance(url, bytes):
-                url = url.decode(encoding or self.encoding)
+            url = tonative(url, encoding or self.encoding)
                 
             # Note that urljoin will "do the right thing" whether url is:
             #  1. a complete URL with host (e.g. "http://www.example.com/test")
                    307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
                    }[status]
             msgs = [msg % (u, u) for u in self.urls]
-            response.body = ("<br />\n".join(msgs)).encode('utf-8')
+            response.body = ntob("<br />\n".join(msgs), 'utf-8')
             # Previous code may have set C-L, so we have to reset it
             # (allow finalize to set it).
             response.headers.pop('Content-Length', None)
         self.status = status
         try:
             self.code, self.reason, defaultmsg = _httputil.valid_status(status)
-        except ValueError as x:
-            raise self.__class__(500, x.args[0])
+        except ValueError:
+            raise self.__class__(500, _exc_info()[1].args[0])
         
         if self.code < 400 or self.code > 599:
             raise ValueError("status must be between 400 and 599.")
         response.headers['Content-Type'] = "text/html;charset=utf-8"
         response.headers.pop('Content-Length', None)
         
-        content = self.get_error_page(self.status, traceback=tb,
-                                      message=self._message).encode('utf-8')
+        content = ntob(self.get_error_page(self.status, traceback=tb,
+                                           message=self._message), 'utf-8')
         response.body = content
         
         _be_ie_unfriendly(self.code)
     
     try:
         code, reason, message = _httputil.valid_status(status)
-    except ValueError as x:
-        raise cherrypy.HTTPError(500, x.args[0])
+    except ValueError:
+        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
     
     # We can't use setdefault here, because some
     # callers send None for kwarg values.
             if hasattr(error_page, '__call__'):
                 return error_page(**kwargs)
             else:
-                return open(error_page, 'rb').read().decode("utf-8") % kwargs
+                data = open(error_page, 'rb').read()
+                return tonative(data) % kwargs
         except:
             e = _format_exception(*_exc_info())[-1]
             m = kwargs['message']
         if l and l < s:
             # IN ADDITION: the response must be written to IE
             # in one chunk or it will still get replaced! Bah.
-            content = content + (b" " * (s - l))
+            content = content + (ntob(" ") * (s - l))
         response.body = content
         response.headers['Content-Length'] = str(len(content))
 
     # it cannot be allowed to fail. Therefore, don't add to it!
     # In particular, don't call any other CP functions.
     
-    body = b"Unrecoverable error in the server."
+    body = ntob("Unrecoverable error in the server.")
     if extrabody is not None:
-        if not isinstance(extrabody, bytes):
+        if not isinstance(extrabody, bytestr):
             extrabody = extrabody.encode('utf-8')
-        body += b"\n" + extrabody
+        body += ntob("\n") + extrabody
     
-    return (b"500 Internal Server Error",
-            [(b'Content-Type', b'text/plain'),
-             (b'Content-Length', str(len(body)).encode('ISO-8859-1'))],
+    return (ntob("500 Internal Server Error"),
+            [(ntob('Content-Type'), ntob('text/plain')),
+             (ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
             [body])
 
 

py3/cherrypy/_cpmodpy.py

 
 import os
 import re
-import subprocess
+try:
+    import subprocess
+    def popen(fullcmd):
+        p = subprocess.Popen(fullcmd, shell=True,
+                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                             close_fds=True)
+        return p.stdout
+except ImportError:
+    def popen(fullcmd):
+        pipein, pipeout = os.popen4(fullcmd)
+        return pipeout
 
 
 def read_process(cmd, args=""):
     fullcmd = "%s %s" % (cmd, args)
-    p = subprocess.Popen(fullcmd, shell=True,
-                         stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                         close_fds=True)
-    pipeout = p.stdout
+    pipeout = popen(fullcmd)
     try:
         firstline = pipeout.readline()
         if (re.search(ntob("(not recognized|No such file|not found)"), firstline,

py3/cherrypy/_cpreqbody.py

 Note that you can only replace the ``processors`` dict wholesale this way, not update the existing one.
 """
 
-import io
+try:
+    from io import DEFAULT_BUFFER_SIZE
+except ImportError:
+    DEFAULT_BUFFER_SIZE = 8192
 import re
 import sys
 import tempfile
-
-def unquote_plus(bs):
-    """Bytes version of urllib.parse.unquote_plus."""
-    bs = bs.replace(b'+', b' ')
-    atoms = bs.split(b'%')
-    for i in range(1, len(atoms)):
-        item = atoms[i]
-        try:
-            pct = int(item[:2], 16)
-            atoms[i] = bytes([pct]) + item[2:]
-        except ValueError:
-            pass
-    return b''.join(atoms)
+try:
+    from urllib import unquote_plus
+except ImportError:
+    def unquote_plus(bs):
+        """Bytes version of urllib.parse.unquote_plus."""
+        bs = bs.replace(ntob('+'), ntob(' '))
+        atoms = bs.split(ntob('%'))
+        for i in range(1, len(atoms)):
+            item = atoms[i]
+            try:
+                pct = int(item[:2], 16)
+                atoms[i] = bytes([pct]) + item[2:]
+            except ValueError:
+                pass
+        return ntob('').join(atoms)
 
 import cherrypy
 from cherrypy._cpcompat import basestring, ntob, ntou
         if not line:
             raise StopIteration
         return line
+
+    def next(self):
+        return self.__next__()
     
     def read_into_file(self, fp_out=None):
         """Read the request body into fp_out (or make_file() if None). Return fp_out."""
 
 Entity.part_class = Part
 
-
-inf = float('inf')
+try:
+    inf = float('inf')
+except ValueError:
+    # Python 2.4 and lower
+    class Infinity(object):
+        def __cmp__(self, other):
+            return 1
+        def __sub__(self, other):
+            return self
+    inf = Infinity()
 
 
 comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
 
 class SizedReader:
     
-    def __init__(self, fp, length, maxbytes, bufsize=io.DEFAULT_BUFFER_SIZE, has_trailers=False):
+    def __init__(self, fp, length, maxbytes, bufsize=DEFAULT_BUFFER_SIZE, has_trailers=False):
         # Wrap our fp in a buffer so peek() works
         self.fp = fp
         self.length = length
         # add them in here.
         request_params = self.request_params
         for key, value in self.params.items():
+            # Python 2 only: keyword arguments must be byte strings (type 'str').
+            if sys.version_info < (3, 0):
+                if isinstance(key, unicode):
+                    key = key.encode('ISO-8859-1')
+            
             if key in request_params:
                 if not isinstance(request_params[key], list):
                     request_params[key] = [request_params[key]]

py3/cherrypy/_cprequest.py

         self.kwargs = kwargs
     
     def __lt__(self, other):
+        # Python 3
         return self.priority < other.priority
+
+    def __cmp__(self, other):
+        # Python 2
+        return cmp(self.priority, other.priority)
     
     def __call__(self):
         """Run self.callback(**self.kwargs)."""
             self.stage = 'close'
     
     def run(self, method, path, query_string, req_protocol, headers, rfile):
-        """Process the Request. (Core)
+        r"""Process the Request. (Core)
         
         method, path, query_string, and req_protocol should be pulled directly
         from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
         
         path
             This should be %XX-unquoted, but query_string should not be.
-            They both MUST be unicode strings, not byte strings,
-            and preferably not bytes \x00-\xFF disguised as unicode.
+            
+            When using Python 2, they both MUST be byte strings,
+            not unicode strings.
+            
+            When using Python 3, they both MUST be unicode strings,
+            not byte strings, and preferably not bytes \x00-\xFF
+            disguised as unicode.
         
         headers
             A list of (name, value) tuples.
                 404, "The given query string could not be processed. Query "
                 "strings for this resource must be encoded with %r." %
                 self.query_string_encoding)
+        
+        # Python 2 only: keyword arguments must be byte strings (type 'str').
+        if sys.version_info < (3, 0):
+            for key, value in p.items():
+                if isinstance(key, unicode):
+                    del p[key]
+                    p[key.encode(self.query_string_encoding)] = value
         self.params.update(p)
     
     def process_headers(self):