Commits

Robert Brewer committed f11e43c

Some work toward merging py2/3 back together again.

Comments (0)

Files changed (21)

py2/cherrypy/_cpreqbody.py

         # Copy the class 'attempt_charsets', prepending any Content-Type charset
         dec = self.content_type.params.get("charset", None)
         if dec:
-            #dec = dec.decode('ISO-8859-1')
             self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
                                              if c != dec]
         else:

py2/cherrypy/_cpserver.py

     ssl_private_key = None
     """The filename of the private key to use with SSL."""
     
-    ssl_module = 'pyopenssl'
-    """The name of a registered SSL adaptation module to use with the builtin
-    WSGI server. Builtin options are 'builtin' (to use the SSL library built
-    into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
-    project, which you must install separately). You may also register your
-    own classes in the wsgiserver.ssl_adapters dict."""
+    if sys.version >= (3, 0):
+        ssl_module = 'builtin'
+        """The name of a registered SSL adaptation module to use with the builtin
+        WSGI server. Builtin options are: 'builtin' (to use the SSL library built
+        into recent versions of Python). You may also register your
+        own classes in the wsgiserver.ssl_adapters dict."""
+    else:
+        ssl_module = 'pyopenssl'
+        """The name of a registered SSL adaptation module to use with the builtin
+        WSGI server. Builtin options are 'builtin' (to use the SSL library built
+        into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
+        project, which you must install separately). You may also register your
+        own classes in the wsgiserver.ssl_adapters dict."""
     
     statistics = False
     """Turns statistics-gathering on or off for aware HTTP servers."""

py2/cherrypy/_cpwsgi_server.py

                    )
         self.protocol = self.server_adapter.protocol_version
         self.nodelay = self.server_adapter.nodelay
-        
-        ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
+
+        if sys.version >= (3, 0):
+            ssl_module = self.server_adapter.ssl_module or 'builtin'
+        else:
+            ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
         if self.server_adapter.ssl_context:
             adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
             self.ssl_adapter = adapter_class(

py2/cherrypy/lib/cptools.py

         self.accessed_headers.add(key)
         return _httputil.HeaderMap.get(self, key, default=default)
     
-    def has_key(self, key):
-        self.accessed_headers.add(key)
-        return _httputil.HeaderMap.has_key(self, key)
+    if hasattr({}, 'has_key'):
+        # Python 2
+        def has_key(self, key):
+            self.accessed_headers.add(key)
+            return _httputil.HeaderMap.has_key(self, key)
 
 
 def autovary(ignore=None, debug=False):

py2/cherrypy/lib/httputil.py

 
 from binascii import b2a_base64
 from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou, reversed, sorted
-from cherrypy._cpcompat import basestring, iteritems, unicodestr, unquote_qs
+from cherrypy._cpcompat import basestring, nativestr, bytestr, iteritems, unicodestr, unquote_qs
 response_codes = BaseHTTPRequestHandler.responses.copy()
 
 # From http://www.cherrypy.org/ticket/361
     # Special-case the final url of "", and return "/" instead.
     return url or "/"
 
+def urljoin_bytes(*atoms):
+    """Return the given path *atoms, joined into a single URL.
+    
+    This will correctly join a SCRIPT_NAME and PATH_INFO into the
+    original URL, even if either atom is blank.
+    """
+    url = ntob("/").join([x for x in atoms if x])
+    while ntob("//") in url:
+        url = url.replace(ntob("//"), ntob("/"))
+    # Special-case the final url of "", and return "/" instead.
+    return url or ntob("/")
+
 def protocol_from_http(protocol_str):
     """Return a protocol tuple from the given 'HTTP/x.y' string."""
     return int(protocol_str[5]), int(protocol_str[7])
     def __cmp__(self, other):
         return cmp(self.value, other.value)
     
+    def __lt__(self, other):
+        return self.value < other.value
+    
     def __str__(self):
         p = [";%s=%s" % (k, v) for k, v in iteritems(self.params)]
         return "%s%s" % (self.value, "".join(p))
+    
+    def __bytes__(self):
+        return ntob(self.__str__())
 
     def __unicode__(self):
         return ntou(self.__str__())
         if diff == 0:
             diff = cmp(str(self), str(other))
         return diff
+    
+    def __lt__(self, other):
+        if self.qvalue == other.qvalue:
+            return str(self) < str(other)
+        else:
+            return self.qvalue < other.qvalue
 
 
 def header_elements(fieldname, fieldvalue):
     return list(reversed(sorted(result)))
 
 def decode_TEXT(value):
-    r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
-    from email.Header import decode_header
+    r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
+    try:
+        # Python 3
+        from email.header import decode_header
+    except ImportError:
+        from email.Header import decode_header
     atoms = decode_header(value)
     decodedvalue = ""
     for atom, charset in atoms:
     return code, reason, message
 
 
+# NOTE: the parse_qs functions that follow are modified version of those
+# in the python3.0 source - we need to pass through an encoding to the unquote
+# method, but the default parse_qs function doesn't allow us to.  These do.
+
 def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
     """Parse a query given as a string argument.
     
     def get(self, key, default=None):
         return dict.get(self, str(key).title(), default)
     
-    def has_key(self, key):
-        return dict.has_key(self, str(key).title())
+    if hasattr({}, 'has_key'):
+        def has_key(self, key):
+            return dict.has_key(self, str(key).title())
     
     def update(self, E):
         for k in E.keys():
 # A CRLF is allowed in the definition of TEXT only as part of a header
 # field continuation. It is expected that the folding LWS will be
 # replaced with a single SP before interpretation of the TEXT value."
-header_translate_table = ''.join([chr(i) for i in xrange(256)])
-header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+if nativestr == bytestr:
+    header_translate_table = ''.join([chr(i) for i in xrange(256)])
+    header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+else:
+    header_translate_table = None
+    header_translate_deletechars = bytes(range(32)) + bytes([127])
 
 
 class HeaderMap(CaseInsensitiveDict):

py2/cherrypy/lib/sessions.py

         if not self.loaded: self.load()
         return key in self._data
     
-    def has_key(self, key):
-        """D.has_key(k) -> True if D has a key k, else False."""
-        if not self.loaded: self.load()
-        return key in self._data
+    if hasattr({}, 'has_key'):
+        def has_key(self, key):
+            """D.has_key(k) -> True if D has a key k, else False."""
+            if not self.loaded: self.load()
+            return key in self._data
     
     def get(self, key, default=None):
         """D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""

py2/cherrypy/lib/static.py

             else:
                 # Return a multipart/byteranges response.
                 response.status = "206 Partial Content"
-                from mimetools import choose_boundary
+                try:
+                    # Python 3
+                    from email.generator import _make_boundary as choose_boundary
+                except ImportError:
+                    # Python 2
+                    from mimetools import choose_boundary
                 boundary = choose_boundary()
                 ct = "multipart/byteranges; boundary=%s" % boundary
                 response.headers['Content-Type'] = ct

py2/cherrypy/lib/xmlrpc.py

 import sys
 
 import cherrypy
-
+from cherrypy._cpcompat import ntob
 
 def process_body():
     """Return (params, method) from request body."""
     try:
-        import xmlrpclib
-        return xmlrpclib.loads(cherrypy.request.body.read())
+        # Python 3
+        from xmlrpc.client import loads
+    except ImportError:
+        # Python 2
+        from xmlrpclib import loads
+    
+    try:
+        return loads(cherrypy.request.body.read())
     except Exception:
         return ('ERROR PARAMS', ), 'ERRORMETHOD'
 
     # as a "Protocol Error", we'll just return 200 every time.
     response = cherrypy.response
     response.status = '200 OK'
-    response.body = body
+    response.body = ntob(body, 'utf-8')
     response.headers['Content-Type'] = 'text/xml'
     response.headers['Content-Length'] = len(body)
 
 
 def respond(body, encoding='utf-8', allow_none=0):
-    from xmlrpclib import Fault, dumps
+    try:
+        # Python 2
+        from xmlrpclib import Fault, dumps
+    except ImportError:
+        # Python 3
+        from xmlrpc.client import Fault, dumps
     if not isinstance(body, Fault):
         body = (body,)
     _set_response(dumps(body, methodresponse=1,
 
 def on_error(*args, **kwargs):
     body = str(sys.exc_info()[1])
-    from xmlrpclib import Fault, dumps
+    try:
+        # Python 2
+        from xmlrpclib import Fault, dumps
+    except ImportError:
+        # Python 3
+        from xmlrpc.client import Fault, dumps
     _set_response(dumps(Fault(1, body)))
 

py2/cherrypy/test/logtest.py

 import time
 
 import cherrypy
+from cherrypy._cpcompat import ntob
 
 
 try:
     
     logfile = None
     lastmarker = None
-    markerPrefix = "test suite marker: "
+    markerPrefix = ntob("test suite marker: ")
     
     def _handleLogError(self, msg, data, marker, pattern):
         print("")
             raise self.failureException(msg)
         
         p = "    Show: [L]og [M]arker [P]attern; [I]gnore, [R]aise, or sys.e[X]it >> "
-        print p,
+        sys.stdout.write(p + ' ')
         # ARGH
         sys.stdout.flush()
         while True:
                 for x, line in enumerate(data):
                     if (x + 1) % self.console_height == 0:
                         # The \r and comma should make the next line overwrite
-                        print "<-- More -->\r",
+                        sys.stdout.write("<-- More -->\r ")
                         m = getchar().lower()
                         # Erase our "More" prompt
-                        print "            \r",
+                        sys.stdout.write("            \r ")
                         if m == "q":
                             break
                     print(line.rstrip())
                 raise self.failureException(msg)
             elif i == "X":
                 self.exit()
-            print p,
+            sys.stdout.write(p + ' ')
     
     def exit(self):
         sys.exit()
             key = str(time.time())
         self.lastmarker = key
         
-        open(self.logfile, 'ab+').write("%s%s\n" % (self.markerPrefix, key))
+        open(self.logfile, 'ab+').write(ntob("%s%s\n" % (self.markerPrefix, key),"utf-8"))
     
     def _read_marked_region(self, marker=None):
         """Return lines from self.logfile in the marked region.

py2/cherrypy/test/test_caching.py

         for t in ts:
             t.join()
         self.assertEqualDates(start, datetime.datetime.now(),
-                              # Allow a second for our thread/TCP overhead etc.
-                              seconds=SECONDS + 1.1)
+                              # Allow a second (two, for slow hosts)
+                              # for our thread/TCP overhead etc.
+                              seconds=SECONDS + 2)
     
     def test_cache_control(self):
         self.getPage("/control")

py3/cherrypy/_cpserver.py

     ssl_private_key = None
     """The filename of the private key to use with SSL."""
     
-    ssl_module = 'builtin'
-    """The name of a registered SSL adaptation module to use with the builtin
-    WSGI server. Builtin options are: 'builtin' (to use the SSL library built
-    into recent versions of Python). You may also register your
-    own classes in the wsgiserver.ssl_adapters dict."""
+    if sys.version() >= (3, 0):
+        ssl_module = 'builtin'
+        """The name of a registered SSL adaptation module to use with the builtin
+        WSGI server. Builtin options are: 'builtin' (to use the SSL library built
+        into recent versions of Python). You may also register your
+        own classes in the wsgiserver.ssl_adapters dict."""
+    else:
+        ssl_module = 'pyopenssl'
+        """The name of a registered SSL adaptation module to use with the builtin
+        WSGI server. Builtin options are 'builtin' (to use the SSL library built
+        into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
+        project, which you must install separately). You may also register your
+        own classes in the wsgiserver.ssl_adapters dict."""
     
     statistics = False
     """Turns statistics-gathering on or off for aware HTTP servers."""

py3/cherrypy/_cpwsgi_server.py

                    )
         self.protocol = self.server_adapter.protocol_version
         self.nodelay = self.server_adapter.nodelay
-        
-        ssl_module = self.server_adapter.ssl_module or 'builtin'
+
+        if sys.version >= (3, 0):
+            ssl_module = self.server_adapter.ssl_module or 'builtin'
+        else:
+            ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
         if self.server_adapter.ssl_context:
             adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
             self.ssl_adapter = adapter_class(

py3/cherrypy/lib/cptools.py

     def get(self, key, default=None):
         self.accessed_headers.add(key)
         return _httputil.HeaderMap.get(self, key, default=default)
+    
+    if hasattr({}, 'has_key'):
+        # Python 2
+        def has_key(self, key):
+            self.accessed_headers.add(key)
+            return _httputil.HeaderMap.has_key(self, key)
 
 
 def autovary(ignore=None, debug=False):

py3/cherrypy/lib/httputil.py

     This will correctly join a SCRIPT_NAME and PATH_INFO into the
     original URL, even if either atom is blank.
     """
-    url = b"/".join([x for x in atoms if x])
-    while b"//" in url:
-        url = url.replace(b"//", b"/")
-    # Special-case the final url of b"", and return b"/" instead.
-    return url or b"/"
+    url = ntob("/").join([x for x in atoms if x])
+    while ntob("//") in url:
+        url = url.replace(ntob("//"), ntob("/"))
+    # Special-case the final url of "", and return "/" instead.
+    return url or ntob("/")
 
 def protocol_from_http(protocol_str):
     """Return a protocol tuple from the given 'HTTP/x.y' string."""
             params = {}
         self.params = params
     
+    def __cmp__(self, other):
+        return cmp(self.value, other.value)
+    
     def __lt__(self, other):
         return self.value < other.value
     
     
     def __bytes__(self):
         return ntob(self.__str__())
+
+    def __unicode__(self):
+        return ntou(self.__str__())
     
     def parse(elementstr):
         """Transform 'token;key=val' to ('token', {'key': 'val'})."""
         return float(val)
     qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
     
+    def __cmp__(self, other):
+        diff = cmp(self.qvalue, other.qvalue)
+        if diff == 0:
+            diff = cmp(str(self), str(other))
+        return diff
+    
     def __lt__(self, other):
         if self.qvalue == other.qvalue:
             return str(self) < str(other)
     return list(reversed(sorted(result)))
 
 def decode_TEXT(value):
-    r"""Decode :rfc:`2047` TEXT (e.g. b"=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
-    from email.header import decode_header
+    r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
+    try:
+        # Python 3
+        from email.header import decode_header
+    except ImportError:
+        from email.Header import decode_header
     atoms = decode_header(value)
     decodedvalue = ""
     for atom, charset in atoms:
     def get(self, key, default=None):
         return dict.get(self, str(key).title(), default)
     
+    if hasattr({}, 'has_key'):
+        def has_key(self, key):
+            return dict.has_key(self, str(key).title())
+    
     def update(self, E):
         for k in E.keys():
             self[str(k).title()] = E[k]
 # A CRLF is allowed in the definition of TEXT only as part of a header
 # field continuation. It is expected that the folding LWS will be
 # replaced with a single SP before interpretation of the TEXT value."
-header_translate_table = None
-header_translate_deletechars = bytes(range(32)) + bytes([127])
+if nativestr == bytestr:
+    header_translate_table = ''.join([chr(i) for i in xrange(256)])
+    header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+else:
+    header_translate_table = None
+    header_translate_deletechars = bytes(range(32)) + bytes([127])
 
 
 class HeaderMap(CaseInsensitiveDict):

py3/cherrypy/lib/profiler.py

         """:rtype stats(index): output of print_stats() for the given profile.
         """
         sio = BytesIO()
-        s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
-        s.strip_dirs()
-        s.sort_stats(sortby)
-        s.print_stats()
+        if sys.version_info >= (2, 5):
+            s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
+            s.strip_dirs()
+            s.sort_stats(sortby)
+            s.print_stats()
+        else:
+            # pstats.Stats before Python 2.5 didn't take a 'stream' arg,
+            # but just printed to stdout. So re-route stdout.
+            s = pstats.Stats(os.path.join(self.path, filename))
+            s.strip_dirs()
+            s.sort_stats(sortby)
+            oldout = sys.stdout
+            try:
+                sys.stdout = sio
+                s.print_stats()
+            finally:
+                sys.stdout = oldout
         response = sio.getvalue()
         sio.close()
         return response

py3/cherrypy/lib/sessions.py

         if not self.loaded: self.load()
         return key in self._data
     
+    if hasattr({}, 'has_key'):
+        def has_key(self, key):
+            """D.has_key(k) -> True if D has a key k, else False."""
+            if not self.loaded: self.load()
+            return key in self._data
+    
     def get(self, key, default=None):
         """D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
         if not self.loaded: self.load()

py3/cherrypy/lib/static.py

             else:
                 # Return a multipart/byteranges response.
                 response.status = "206 Partial Content"
-                from email.generator import _make_boundary as choose_boundary
+                try:
+                    # Python 3
+                    from email.generator import _make_boundary as choose_boundary
+                except ImportError:
+                    # Python 2
+                    from mimetools import choose_boundary
                 boundary = choose_boundary()
                 ct = "multipart/byteranges; boundary=%s" % boundary
                 response.headers['Content-Type'] = ct

py3/cherrypy/lib/xmlrpc.py

 import sys
 
 import cherrypy
-
+from cherrypy._cpcompat import ntob
 
 def process_body():
     """Return (params, method) from request body."""
     try:
-        from xmlrpc import client
-        return client.loads(cherrypy.request.body.read())
+        # Python 3
+        from xmlrpc.client import loads
+    except ImportError:
+        # Python 2
+        from xmlrpclib import loads
+    
+    try:
+        return loads(cherrypy.request.body.read())
     except Exception:
         return ('ERROR PARAMS', ), 'ERRORMETHOD'
 
     # as a "Protocol Error", we'll just return 200 every time.
     response = cherrypy.response
     response.status = '200 OK'
-    response.body = bytes(body, 'utf-8')
+    response.body = ntob(body, 'utf-8')
     response.headers['Content-Type'] = 'text/xml'
     response.headers['Content-Length'] = len(body)
 
 
 def respond(body, encoding='utf-8', allow_none=0):
-    from xmlrpc.client import Fault, dumps
+    try:
+        # Python 2
+        from xmlrpclib import Fault, dumps
+    except ImportError:
+        # Python 3
+        from xmlrpc.client import Fault, dumps
     if not isinstance(body, Fault):
         body = (body,)
     _set_response(dumps(body, methodresponse=1,
 
 def on_error(*args, **kwargs):
     body = str(sys.exc_info()[1])
-    from xmlrpc.client import Fault, dumps
+    try:
+        # Python 2
+        from xmlrpclib import Fault, dumps
+    except ImportError:
+        # Python 3
+        from xmlrpc.client import Fault, dumps
     _set_response(dumps(Fault(1, body)))
 

py3/cherrypy/process/wspbus.py

         for t in threading.enumerate():
             if t != threading.currentThread() and t.isAlive():
                 # Note that any dummy (external) threads are always daemonic.
-                if not t.daemon:
+                if hasattr(threading.Thread, "daemon"):
+                    # Python 2.6+
+                    d = t.daemon
+                else:
+                    d = t.isDaemon()
+                if not d:
                     self.log("Waiting for thread %s." % t.getName())
                     t.join()
         

py3/cherrypy/test/helper.py

 
         cherrypy.server.httpserver = self.httpserver_class
 
+        # This is perhaps the wrong place for this call but this is the only
+        # place that i've found so far that I KNOW is early enough to set this.
+        cherrypy.config.update({'log.screen': False})
         engine = cherrypy.engine
         if hasattr(engine, "signal_handler"):
             engine.signal_handler.subscribe()

py3/cherrypy/test/logtest.py

 import time
 
 import cherrypy
+from cherrypy._cpcompat import ntob
 
 
 try:
     
     logfile = None
     lastmarker = None
-    markerPrefix = b"test suite marker: "
+    markerPrefix = ntob("test suite marker: ")
     
     def _handleLogError(self, msg, data, marker, pattern):
         print("")
             raise self.failureException(msg)
         
         p = "    Show: [L]og [M]arker [P]attern; [I]gnore, [R]aise, or sys.e[X]it >> "
-        print(p, end=' ')
+        sys.stdout.write(p + ' ')
         # ARGH
         sys.stdout.flush()
         while True:
                 for x, line in enumerate(data):
                     if (x + 1) % self.console_height == 0:
                         # The \r and comma should make the next line overwrite
-                        print("<-- More -->\r", end=' ')
+                        sys.stdout.write("<-- More -->\r ")
                         m = getchar().lower()
                         # Erase our "More" prompt
-                        print("            \r", end=' ')
+                        sys.stdout.write("            \r ")
                         if m == "q":
                             break
                     print(line.rstrip())
                 raise self.failureException(msg)
             elif i == "X":
                 self.exit()
-            print(p, end=' ')
+            sys.stdout.write(p + ' ')
     
     def exit(self):
         sys.exit()
             key = str(time.time())
         self.lastmarker = key
         
-        open(self.logfile, 'ab+').write(bytes("%s%s\n" % (self.markerPrefix, key),"utf-8"))
+        open(self.logfile, 'ab+').write(ntob("%s%s\n" % (self.markerPrefix, key),"utf-8"))
     
     def _read_marked_region(self, marker=None):
         """Return lines from self.logfile in the marked region.