diff --git a/lib/cherrypy/_cpchecker.py b/lib/cherrypy/_cpchecker.py
index 39b7c972..f26f319c 100644
--- a/lib/cherrypy/_cpchecker.py
+++ b/lib/cherrypy/_cpchecker.py
@@ -1,9 +1,7 @@
"""Checker for CherryPy sites and mounted apps."""
import os
import warnings
-
-import six
-from six.moves import builtins
+import builtins
import cherrypy
@@ -70,14 +68,14 @@ class Checker(object):
def check_site_config_entries_in_app_config(self):
"""Check for mounted Applications that have site-scoped config."""
- for sn, app in six.iteritems(cherrypy.tree.apps):
+ for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
msg = []
- for section, entries in six.iteritems(app.config):
+ for section, entries in app.config.items():
if section.startswith('/'):
- for key, value in six.iteritems(entries):
+ for key, value in entries.items():
for n in ('engine.', 'server.', 'tree.', 'checker.'):
if key.startswith(n):
msg.append('[%s] %s = %s' %
diff --git a/lib/cherrypy/_cpcompat.py b/lib/cherrypy/_cpcompat.py
index f454505c..a43f6d36 100644
--- a/lib/cherrypy/_cpcompat.py
+++ b/lib/cherrypy/_cpcompat.py
@@ -18,74 +18,33 @@ Instead, use unicode literals (from __future__) and bytes literals
and their .encode/.decode methods as needed.
"""
-import re
-import sys
-import threading
-
-import six
-from six.moves import urllib
+import http.client
-if six.PY3:
- def ntob(n, encoding='ISO-8859-1'):
- """Return the given native string as a byte string in the given
- encoding.
- """
- assert_native(n)
- # In Python 3, the native string type is unicode
- return n.encode(encoding)
+def ntob(n, encoding='ISO-8859-1'):
+ """Return the given native string as a byte string in the given
+ encoding.
+ """
+ assert_native(n)
+ # In Python 3, the native string type is unicode
+ return n.encode(encoding)
- def ntou(n, encoding='ISO-8859-1'):
- """Return the given native string as a unicode string with the given
- encoding.
- """
- assert_native(n)
- # 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
-else:
- # Python 2
- def ntob(n, encoding='ISO-8859-1'):
- """Return the given native string as a byte string in the given
- encoding.
- """
- assert_native(n)
- # In Python 2, the native string type is bytes. Assume it's already
- # in the given encoding, which for ISO-8859-1 is almost always what
- # was intended.
- return n
+def ntou(n, encoding='ISO-8859-1'):
+ """Return the given native string as a unicode string with the given
+ encoding.
+ """
+ assert_native(n)
+ # In Python 3, the native string type is unicode
+ return n
- def ntou(n, encoding='ISO-8859-1'):
- """Return the given native string as a unicode string with the given
- encoding.
- """
- assert_native(n)
- # In Python 2, the native string type is bytes.
- # First, check for the special encoding 'escape'. The test suite uses
- # this to signal that it wants to pass a string with embedded \uXXXX
- # escapes, but without having to prefix it with u'' for Python 2,
- # but no prefix for Python 3.
- if encoding == 'escape':
- return six.text_type( # unicode for Python 2
- re.sub(r'\\u([0-9a-zA-Z]{4})',
- lambda m: six.unichr(int(m.group(1), 16)),
- n.decode('ISO-8859-1')))
- # Assume it's already in the given encoding, which for ISO-8859-1
- # is almost always what was intended.
+
+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)
-
- 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, six.text_type): # unicode for Python 2
- return n.encode(encoding)
- return n
+ return n
def assert_native(n):
@@ -94,69 +53,7 @@ def assert_native(n):
# Some platforms don't expose HTTPSConnection, so handle it separately
-HTTPSConnection = getattr(six.moves.http_client, 'HTTPSConnection', None)
+HTTPSConnection = getattr(http.client, 'HTTPSConnection', None)
-def _unquote_plus_compat(string, encoding='utf-8', errors='replace'):
- return urllib.parse.unquote_plus(string).decode(encoding, errors)
-
-
-def _unquote_compat(string, encoding='utf-8', errors='replace'):
- return urllib.parse.unquote(string).decode(encoding, errors)
-
-
-def _quote_compat(string, encoding='utf-8', errors='replace'):
- return urllib.parse.quote(string.encode(encoding, errors))
-
-
-unquote_plus = urllib.parse.unquote_plus if six.PY3 else _unquote_plus_compat
-unquote = urllib.parse.unquote if six.PY3 else _unquote_compat
-quote = urllib.parse.quote if six.PY3 else _quote_compat
-
-try:
- # Prefer simplejson
- import simplejson as json
-except ImportError:
- import json
-
-
-json_decode = json.JSONDecoder().decode
-_json_encode = json.JSONEncoder().iterencode
-
-
-if six.PY3:
- # Encode to bytes on Python 3
- def json_encode(value):
- for chunk in _json_encode(value):
- yield chunk.encode('utf-8')
-else:
- json_encode = _json_encode
-
-
-text_or_bytes = six.text_type, bytes
-
-
-if sys.version_info >= (3, 3):
- Timer = threading.Timer
- Event = threading.Event
-else:
- # Python 3.2 and earlier
- Timer = threading._Timer
- Event = threading._Event
-
-# html module come in 3.2 version
-try:
- from html import escape
-except ImportError:
- from cgi import escape
-
-
-# html module needed the argument quote=False because in cgi the default
-# is False. With quote=True the results differ.
-
-def escape_html(s, escape_quote=False):
- """Replace special characters "&", "<" and ">" to HTML-safe sequences.
-
- When escape_quote=True, escape (') and (") chars.
- """
- return escape(s, quote=escape_quote)
+text_or_bytes = str, bytes
diff --git a/lib/cherrypy/_cperror.py b/lib/cherrypy/_cperror.py
index e2a8fad8..4e727682 100644
--- a/lib/cherrypy/_cperror.py
+++ b/lib/cherrypy/_cperror.py
@@ -34,6 +34,7 @@ user:
POST should not raise this error
305 Use Proxy Confirm with the user
307 Temporary Redirect Confirm with the user
+308 Permanent Redirect No confirmation
===== ================================= ===========
However, browsers have historically implemented these restrictions poorly;
@@ -119,17 +120,15 @@ and not simply return an error message as a result.
import io
import contextlib
+import urllib.parse
from sys import exc_info as _exc_info
from traceback import format_exception as _format_exception
from xml.sax import saxutils
-
-import six
-from six.moves import urllib
+import html
from more_itertools import always_iterable
import cherrypy
-from cherrypy._cpcompat import escape_html
from cherrypy._cpcompat import ntob
from cherrypy._cpcompat import tonative
from cherrypy._helper import classproperty
@@ -256,7 +255,7 @@ class HTTPRedirect(CherryPyException):
response = cherrypy.serving.response
response.status = status = self.status
- if status in (300, 301, 302, 303, 307):
+ if status in (300, 301, 302, 303, 307, 308):
response.headers['Content-Type'] = 'text/html;charset=utf-8'
# "The ... URI SHOULD be given by the Location field
# in the response."
@@ -271,10 +270,11 @@ class HTTPRedirect(CherryPyException):
302: 'This resource resides temporarily at ',
303: 'This resource can be found at ',
307: 'This resource has moved temporarily to ',
+ 308: 'This resource has been moved to ',
}[status]
msg += '%s.'
msgs = [
- msg % (saxutils.quoteattr(u), escape_html(u))
+ msg % (saxutils.quoteattr(u), html.escape(u, quote=False))
for u in self.urls
]
response.body = ntob('
\n'.join(msgs), 'utf-8')
@@ -496,11 +496,11 @@ def get_error_page(status, **kwargs):
if kwargs.get('version') is None:
kwargs['version'] = cherrypy.__version__
- for k, v in six.iteritems(kwargs):
+ for k, v in kwargs.items():
if v is None:
kwargs[k] = ''
else:
- kwargs[k] = escape_html(kwargs[k])
+ kwargs[k] = html.escape(kwargs[k], quote=False)
# Use a custom template or callable for the error page?
pages = cherrypy.serving.request.error_page
@@ -520,13 +520,13 @@ def get_error_page(status, **kwargs):
if cherrypy.lib.is_iterator(result):
from cherrypy.lib.encoding import UTF8StreamEncoder
return UTF8StreamEncoder(result)
- elif isinstance(result, six.text_type):
+ elif isinstance(result, str):
return result.encode('utf-8')
else:
if not isinstance(result, bytes):
raise ValueError(
'error page function did not '
- 'return a bytestring, six.text_type or an '
+ 'return a bytestring, str or an '
'iterator - returned object of type %s.'
% (type(result).__name__))
return result
diff --git a/lib/cherrypy/_cplogging.py b/lib/cherrypy/_cplogging.py
index 53b9addb..151d3b40 100644
--- a/lib/cherrypy/_cplogging.py
+++ b/lib/cherrypy/_cplogging.py
@@ -113,8 +113,6 @@ import logging
import os
import sys
-import six
-
import cherrypy
from cherrypy import _cperror
@@ -155,11 +153,7 @@ class LogManager(object):
access_log = None
"""The actual :class:`logging.Logger` instance for access messages."""
- access_log_format = (
- '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
- if six.PY3 else
- '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
- )
+ access_log_format = '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
logger_root = None
"""The "top-level" logger name.
@@ -254,8 +248,7 @@ class LogManager(object):
status = '-'
else:
status = response.output_status.split(b' ', 1)[0]
- if six.PY3:
- status = status.decode('ISO-8859-1')
+ status = status.decode('ISO-8859-1')
atoms = {'h': remote.name or remote.ip,
'l': '-',
@@ -270,45 +263,27 @@ class LogManager(object):
'i': request.unique_id,
'z': LazyRfc3339UtcTime(),
}
- if six.PY3:
- for k, v in atoms.items():
- if not isinstance(v, str):
- v = str(v)
- v = v.replace('"', '\\"').encode('utf8')
- # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
- # and backslash for us. All we have to do is strip the quotes.
- v = repr(v)[2:-1]
+ for k, v in atoms.items():
+ if not isinstance(v, str):
+ v = str(v)
+ v = v.replace('"', '\\"').encode('utf8')
+ # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
+ # and backslash for us. All we have to do is strip the quotes.
+ v = repr(v)[2:-1]
- # in python 3.0 the repr of bytes (as returned by encode)
- # uses double \'s. But then the logger escapes them yet, again
- # resulting in quadruple slashes. Remove the extra one here.
- v = v.replace('\\\\', '\\')
+ # in python 3.0 the repr of bytes (as returned by encode)
+ # uses double \'s. But then the logger escapes them yet, again
+ # resulting in quadruple slashes. Remove the extra one here.
+ v = v.replace('\\\\', '\\')
- # Escape double-quote.
- atoms[k] = v
+ # Escape double-quote.
+ atoms[k] = v
- try:
- self.access_log.log(
- logging.INFO, self.access_log_format.format(**atoms))
- except Exception:
- self(traceback=True)
- else:
- for k, v in atoms.items():
- if isinstance(v, six.text_type):
- v = v.encode('utf8')
- elif not isinstance(v, str):
- v = str(v)
- # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
- # and backslash for us. All we have to do is strip the quotes.
- v = repr(v)[1:-1]
- # Escape double-quote.
- atoms[k] = v.replace('"', '\\"')
-
- try:
- self.access_log.log(
- logging.INFO, self.access_log_format % atoms)
- except Exception:
- self(traceback=True)
+ try:
+ self.access_log.log(
+ logging.INFO, self.access_log_format.format(**atoms))
+ except Exception:
+ self(traceback=True)
def time(self):
"""Return now() in Apache Common Log Format (no timezone)."""
diff --git a/lib/cherrypy/_cpmodpy.py b/lib/cherrypy/_cpmodpy.py
index ac91e625..0e608c48 100644
--- a/lib/cherrypy/_cpmodpy.py
+++ b/lib/cherrypy/_cpmodpy.py
@@ -61,8 +61,6 @@ import os
import re
import sys
-import six
-
from more_itertools import always_iterable
import cherrypy
@@ -197,7 +195,7 @@ def handler(req):
path = req.uri
qs = req.args or ''
reqproto = req.protocol
- headers = list(six.iteritems(req.headers_in))
+ headers = list(req.headers_in.copy().items())
rfile = _ReadOnlyRequest(req)
prev = None
diff --git a/lib/cherrypy/_cpreqbody.py b/lib/cherrypy/_cpreqbody.py
index 893fe5f5..4d3cefe7 100644
--- a/lib/cherrypy/_cpreqbody.py
+++ b/lib/cherrypy/_cpreqbody.py
@@ -115,30 +115,29 @@ except ImportError:
import re
import sys
import tempfile
-try:
- from urllib import unquote_plus
-except ImportError:
- 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)
+from urllib.parse import unquote
-import six
import cheroot.server
import cherrypy
-from cherrypy._cpcompat import ntou, unquote
+from cherrypy._cpcompat import ntou
from cherrypy.lib import httputil
+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)
+
+
# ------------------------------- Processors -------------------------------- #
def process_urlencoded(entity):
@@ -986,12 +985,6 @@ class RequestBody(Entity):
# 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, six.text_type):
- key = key.encode('ISO-8859-1')
-
if key in request_params:
if not isinstance(request_params[key], list):
request_params[key] = [request_params[key]]
diff --git a/lib/cherrypy/_cprequest.py b/lib/cherrypy/_cprequest.py
index 3cc0c811..9b86bd67 100644
--- a/lib/cherrypy/_cprequest.py
+++ b/lib/cherrypy/_cprequest.py
@@ -1,11 +1,11 @@
import sys
import time
+import collections
+import operator
+from http.cookies import SimpleCookie, CookieError
import uuid
-import six
-from six.moves.http_cookies import SimpleCookie, CookieError
-
from more_itertools import consume
import cherrypy
@@ -92,28 +92,36 @@ class HookMap(dict):
def run(self, point):
"""Execute all registered Hooks (callbacks) for the given point."""
- exc = None
- hooks = self[point]
- hooks.sort()
+ self.run_hooks(iter(sorted(self[point])))
+
+ @classmethod
+ def run_hooks(cls, hooks):
+ """Execute the indicated hooks, trapping errors.
+
+ Hooks with ``.failsafe == True`` are guaranteed to run
+ even if others at the same hookpoint fail. In this case,
+ log the failure and proceed on to the next hook. The only
+ way to stop all processing from one of these hooks is
+ to raise a BaseException like SystemExit or
+ KeyboardInterrupt and stop the whole server.
+ """
+ assert isinstance(hooks, collections.abc.Iterator)
+ quiet_errors = (
+ cherrypy.HTTPError,
+ cherrypy.HTTPRedirect,
+ cherrypy.InternalRedirect,
+ )
+ safe = filter(operator.attrgetter('failsafe'), hooks)
for hook in hooks:
- # Some hooks are guaranteed to run even if others at
- # the same hookpoint fail. We will still log the failure,
- # but proceed on to the next hook. The only way
- # to stop all processing from one of these hooks is
- # to raise SystemExit and stop the whole server.
- if exc is None or hook.failsafe:
- try:
- hook()
- except (KeyboardInterrupt, SystemExit):
- raise
- except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
- cherrypy.InternalRedirect):
- exc = sys.exc_info()[1]
- except Exception:
- exc = sys.exc_info()[1]
- cherrypy.log(traceback=True, severity=40)
- if exc:
- raise exc
+ try:
+ hook()
+ except quiet_errors:
+ cls.run_hooks(safe)
+ raise
+ except Exception:
+ cherrypy.log(traceback=True, severity=40)
+ cls.run_hooks(safe)
+ raise
def __copy__(self):
newmap = self.__class__()
@@ -141,7 +149,7 @@ def hooks_namespace(k, v):
# hookpoint per path (e.g. "hooks.before_handler.1").
# Little-known fact you only get from reading source ;)
hookpoint = k.split('.', 1)[0]
- if isinstance(v, six.string_types):
+ if isinstance(v, str):
v = cherrypy.lib.reprconf.attributes(v)
if not isinstance(v, Hook):
v = Hook(v)
@@ -704,12 +712,6 @@ class Request(object):
'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 six.PY2:
- for key, value in p.items():
- if isinstance(key, six.text_type):
- del p[key]
- p[key.encode(self.query_string_encoding)] = value
self.params.update(p)
def process_headers(self):
@@ -786,11 +788,11 @@ class ResponseBody(object):
def __set__(self, obj, value):
# Convert the given value to an iterable object.
- if isinstance(value, six.text_type):
+ if isinstance(value, str):
raise ValueError(self.unicode_err)
elif isinstance(value, list):
# every item in a list must be bytes...
- if any(isinstance(item, six.text_type) for item in value):
+ if any(isinstance(item, str) for item in value):
raise ValueError(self.unicode_err)
obj._body = encoding.prepare_iter(value)
@@ -903,9 +905,9 @@ class Response(object):
if cookie:
for line in cookie.split('\r\n'):
name, value = line.split(': ', 1)
- if isinstance(name, six.text_type):
+ if isinstance(name, str):
name = name.encode('ISO-8859-1')
- if isinstance(value, six.text_type):
+ if isinstance(value, str):
value = headers.encode(value)
h.append((name, value))
diff --git a/lib/cherrypy/_cpserver.py b/lib/cherrypy/_cpserver.py
index 0f60e2c8..5f8d98fa 100644
--- a/lib/cherrypy/_cpserver.py
+++ b/lib/cherrypy/_cpserver.py
@@ -1,7 +1,5 @@
"""Manage HTTP servers with CherryPy."""
-import six
-
import cherrypy
from cherrypy.lib.reprconf import attributes
from cherrypy._cpcompat import text_or_bytes
@@ -116,21 +114,12 @@ class Server(ServerAdapter):
ssl_ciphers = None
"""The ciphers list of SSL."""
- if six.PY3:
- 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
- cheroot.server.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 cheroot.server.ssl_adapters
- dict."""
+ 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
+ cheroot.server.ssl_adapters dict."""
statistics = False
"""Turns statistics-gathering on or off for aware HTTP servers."""
diff --git a/lib/cherrypy/_cptools.py b/lib/cherrypy/_cptools.py
index 57460285..716f99a4 100644
--- a/lib/cherrypy/_cptools.py
+++ b/lib/cherrypy/_cptools.py
@@ -22,8 +22,6 @@ Tools may be implemented as any object with a namespace. The builtins
are generally either modules or instances of the tools.Tool class.
"""
-import six
-
import cherrypy
from cherrypy._helper import expose
@@ -37,14 +35,9 @@ def _getargs(func):
"""Return the names of all static arguments to the given function."""
# Use this instead of importing inspect for less mem overhead.
import types
- if six.PY3:
- if isinstance(func, types.MethodType):
- func = func.__func__
- co = func.__code__
- else:
- if isinstance(func, types.MethodType):
- func = func.im_func
- co = func.func_code
+ if isinstance(func, types.MethodType):
+ func = func.__func__
+ co = func.__code__
return co.co_varnames[:co.co_argcount]
diff --git a/lib/cherrypy/_cptree.py b/lib/cherrypy/_cptree.py
index ceb54379..917c5b1a 100644
--- a/lib/cherrypy/_cptree.py
+++ b/lib/cherrypy/_cptree.py
@@ -2,10 +2,7 @@
import os
-import six
-
import cherrypy
-from cherrypy._cpcompat import ntou
from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
from cherrypy.lib import httputil, reprconf
@@ -289,8 +286,6 @@ class Tree(object):
# to '' (some WSGI servers always set SCRIPT_NAME to '').
# Try to look up the app using the full path.
env1x = environ
- if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
- env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
env1x.get('PATH_INFO', ''))
sn = self.script_name(path or '/')
@@ -302,12 +297,6 @@ class Tree(object):
# Correct the SCRIPT_NAME and PATH_INFO environ entries.
environ = environ.copy()
- if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
- # Python 2/WSGI u.0: all strings MUST be of type unicode
- enc = environ[ntou('wsgi.url_encoding')]
- environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
- environ[ntou('PATH_INFO')] = path[len(sn.rstrip('/')):].decode(enc)
- else:
- environ['SCRIPT_NAME'] = sn
- environ['PATH_INFO'] = path[len(sn.rstrip('/')):]
+ environ['SCRIPT_NAME'] = sn
+ environ['PATH_INFO'] = path[len(sn.rstrip('/')):]
return app(environ, start_response)
diff --git a/lib/cherrypy/_cpwsgi.py b/lib/cherrypy/_cpwsgi.py
index 0b4942ff..b4f55fd6 100644
--- a/lib/cherrypy/_cpwsgi.py
+++ b/lib/cherrypy/_cpwsgi.py
@@ -10,8 +10,6 @@ still be translatable to bytes via the Latin-1 encoding!"
import sys as _sys
import io
-import six
-
import cherrypy as _cherrypy
from cherrypy._cpcompat import ntou
from cherrypy import _cperror
@@ -25,10 +23,10 @@ def downgrade_wsgi_ux_to_1x(environ):
env1x = {}
url_encoding = environ[ntou('wsgi.url_encoding')]
- for k, v in list(environ.items()):
+ for k, v in environ.copy().items():
if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
v = v.encode(url_encoding)
- elif isinstance(v, six.text_type):
+ elif isinstance(v, str):
v = v.encode('ISO-8859-1')
env1x[k.encode('ISO-8859-1')] = v
@@ -177,10 +175,6 @@ class _TrappedResponse(object):
def __next__(self):
return self.trap(next, self.iter_response)
- # todo: https://pythonhosted.org/six/#six.Iterator
- if six.PY2:
- next = __next__
-
def close(self):
if hasattr(self.response, 'close'):
self.response.close()
@@ -198,7 +192,7 @@ class _TrappedResponse(object):
if not _cherrypy.request.show_tracebacks:
tb = ''
s, h, b = _cperror.bare_error(tb)
- if six.PY3:
+ if True:
# What fun.
s = s.decode('ISO-8859-1')
h = [
@@ -238,9 +232,6 @@ class AppResponse(object):
def __init__(self, environ, start_response, cpapp):
self.cpapp = cpapp
try:
- if six.PY2:
- if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
- environ = downgrade_wsgi_ux_to_1x(environ)
self.environ = environ
self.run()
@@ -262,7 +253,7 @@ class AppResponse(object):
raise TypeError(tmpl % v)
outheaders.append((k, v))
- if six.PY3:
+ if True:
# According to PEP 3333, when using Python 3, the response
# status and headers must be bytes masquerading as unicode;
# that is, they must be of type "str" but are restricted to
@@ -285,10 +276,6 @@ class AppResponse(object):
def __next__(self):
return next(self.iter_response)
- # todo: https://pythonhosted.org/six/#six.Iterator
- if six.PY2:
- next = __next__
-
def close(self):
"""Close and de-reference the current request and response. (Core)"""
streaming = _cherrypy.serving.response.stream
@@ -356,9 +343,6 @@ class AppResponse(object):
}
def recode_path_qs(self, path, qs):
- if not six.PY3:
- return
-
# This isn't perfect; if the given PATH_INFO is in the
# wrong encoding, it may fail to match the appropriate config
# section URI. But meh.
diff --git a/lib/cherrypy/_helper.py b/lib/cherrypy/_helper.py
index 314550cb..d57cd1f9 100644
--- a/lib/cherrypy/_helper.py
+++ b/lib/cherrypy/_helper.py
@@ -1,7 +1,6 @@
"""Helper functions for CP apps."""
-import six
-from six.moves import urllib
+import urllib.parse
from cherrypy._cpcompat import text_or_bytes
@@ -26,9 +25,6 @@ def expose(func=None, alias=None):
import sys
import types
decoratable_types = types.FunctionType, types.MethodType, type,
- if six.PY2:
- # Old-style classes are type types.ClassType.
- decoratable_types += types.ClassType,
if isinstance(func, decoratable_types):
if alias is None:
# @expose
@@ -87,53 +83,61 @@ def popargs(*args, **kwargs):
This decorator may be used in one of two ways:
As a class decorator:
- @cherrypy.popargs('year', 'month', 'day')
- class Blog:
- def index(self, year=None, month=None, day=None):
- #Process the parameters here; any url like
- #/, /2009, /2009/12, or /2009/12/31
- #will fill in the appropriate parameters.
- def create(self):
- #This link will still be available at /create. Defined functions
- #take precedence over arguments.
+ .. code-block:: python
+
+ @cherrypy.popargs('year', 'month', 'day')
+ class Blog:
+ def index(self, year=None, month=None, day=None):
+ #Process the parameters here; any url like
+ #/, /2009, /2009/12, or /2009/12/31
+ #will fill in the appropriate parameters.
+
+ def create(self):
+ #This link will still be available at /create.
+ #Defined functions take precedence over arguments.
Or as a member of a class:
- class Blog:
- _cp_dispatch = cherrypy.popargs('year', 'month', 'day')
- #...
+
+ .. code-block:: python
+
+ class Blog:
+ _cp_dispatch = cherrypy.popargs('year', 'month', 'day')
+ #...
The handler argument may be used to mix arguments with built in functions.
For instance, the following setup allows different activities at the
day, month, and year level:
- class DayHandler:
- def index(self, year, month, day):
- #Do something with this day; probably list entries
+ .. code-block:: python
- def delete(self, year, month, day):
- #Delete all entries for this day
+ class DayHandler:
+ def index(self, year, month, day):
+ #Do something with this day; probably list entries
- @cherrypy.popargs('day', handler=DayHandler())
- class MonthHandler:
- def index(self, year, month):
- #Do something with this month; probably list entries
+ def delete(self, year, month, day):
+ #Delete all entries for this day
- def delete(self, year, month):
- #Delete all entries for this month
+ @cherrypy.popargs('day', handler=DayHandler())
+ class MonthHandler:
+ def index(self, year, month):
+ #Do something with this month; probably list entries
- @cherrypy.popargs('month', handler=MonthHandler())
- class YearHandler:
- def index(self, year):
- #Do something with this year
+ def delete(self, year, month):
+ #Delete all entries for this month
- #...
+ @cherrypy.popargs('month', handler=MonthHandler())
+ class YearHandler:
+ def index(self, year):
+ #Do something with this year
- @cherrypy.popargs('year', handler=YearHandler())
- class Root:
- def index(self):
#...
+ @cherrypy.popargs('year', handler=YearHandler())
+ class Root:
+ def index(self):
+ #...
+
"""
# Since keyword arg comes after *args, we have to process it ourselves
# for lower versions of python.
diff --git a/lib/cherrypy/_json.py b/lib/cherrypy/_json.py
new file mode 100644
index 00000000..0c2a0f0e
--- /dev/null
+++ b/lib/cherrypy/_json.py
@@ -0,0 +1,25 @@
+"""
+JSON support.
+
+Expose preferred json module as json and provide encode/decode
+convenience functions.
+"""
+
+try:
+ # Prefer simplejson
+ import simplejson as json
+except ImportError:
+ import json
+
+
+__all__ = ['json', 'encode', 'decode']
+
+
+decode = json.JSONDecoder().decode
+_encode = json.JSONEncoder().iterencode
+
+
+def encode(value):
+ """Encode to bytes."""
+ for chunk in _encode(value):
+ yield chunk.encode('utf-8')
diff --git a/lib/cherrypy/lib/__init__.py b/lib/cherrypy/lib/__init__.py
index f815f76a..0edaaf20 100644
--- a/lib/cherrypy/lib/__init__.py
+++ b/lib/cherrypy/lib/__init__.py
@@ -70,6 +70,11 @@ class file_generator(object):
raise StopIteration()
next = __next__
+ def __del__(self):
+ """Close input on descturct."""
+ if hasattr(self.input, 'close'):
+ self.input.close()
+
def file_generator_limited(fileobj, count, chunk_size=65536):
"""Yield the given file object in chunks.
diff --git a/lib/cherrypy/lib/auth_digest.py b/lib/cherrypy/lib/auth_digest.py
index 9b4f55c8..fbb5df64 100644
--- a/lib/cherrypy/lib/auth_digest.py
+++ b/lib/cherrypy/lib/auth_digest.py
@@ -23,8 +23,7 @@ of plaintext passwords as the credentials store::
import time
import functools
from hashlib import md5
-
-from six.moves.urllib.request import parse_http_list, parse_keqv_list
+from urllib.request import parse_http_list, parse_keqv_list
import cherrypy
from cherrypy._cpcompat import ntob, tonative
diff --git a/lib/cherrypy/lib/caching.py b/lib/cherrypy/lib/caching.py
index 1673b3c8..08d2d8e4 100644
--- a/lib/cherrypy/lib/caching.py
+++ b/lib/cherrypy/lib/caching.py
@@ -37,11 +37,8 @@ import sys
import threading
import time
-import six
-
import cherrypy
from cherrypy.lib import cptools, httputil
-from cherrypy._cpcompat import Event
class Cache(object):
@@ -82,7 +79,7 @@ class AntiStampedeCache(dict):
If timeout is None, no waiting is performed nor sentinels used.
"""
value = self.get(key)
- if isinstance(value, Event):
+ if isinstance(value, threading.Event):
if timeout is None:
# Ignore the other thread and recalc it ourselves.
if debug:
@@ -122,7 +119,7 @@ class AntiStampedeCache(dict):
"""Set the cached value for the given key."""
existing = self.get(key)
dict.__setitem__(self, key, value)
- if isinstance(existing, Event):
+ if isinstance(existing, threading.Event):
# Set Event.result so other threads waiting on it have
# immediate access without needing to poll the cache again.
existing.result = value
@@ -199,8 +196,7 @@ class MemoryCache(Cache):
now = time.time()
# Must make a copy of expirations so it doesn't change size
# during iteration
- items = list(six.iteritems(self.expirations))
- for expiration_time, objects in items:
+ for expiration_time, objects in self.expirations.copy().items():
if expiration_time <= now:
for obj_size, uri, sel_header_values in objects:
try:
diff --git a/lib/cherrypy/lib/covercp.py b/lib/cherrypy/lib/covercp.py
index 0bafca13..3e219713 100644
--- a/lib/cherrypy/lib/covercp.py
+++ b/lib/cherrypy/lib/covercp.py
@@ -25,8 +25,7 @@ import sys
import cgi
import os
import os.path
-
-from six.moves import urllib
+import urllib.parse
import cherrypy
diff --git a/lib/cherrypy/lib/cpstats.py b/lib/cherrypy/lib/cpstats.py
index ae9f7475..111af063 100644
--- a/lib/cherrypy/lib/cpstats.py
+++ b/lib/cherrypy/lib/cpstats.py
@@ -193,10 +193,8 @@ import sys
import threading
import time
-import six
-
import cherrypy
-from cherrypy._cpcompat import json
+from cherrypy._json import json
# ------------------------------- Statistics -------------------------------- #
@@ -207,7 +205,7 @@ if not hasattr(logging, 'statistics'):
def extrapolate_statistics(scope):
"""Return an extrapolated copy of the given scope."""
c = {}
- for k, v in list(scope.items()):
+ for k, v in scope.copy().items():
if isinstance(v, dict):
v = extrapolate_statistics(v)
elif isinstance(v, (list, tuple)):
@@ -366,8 +364,8 @@ class StatsTool(cherrypy.Tool):
w['Bytes Written'] = cl
appstats['Total Bytes Written'] += cl
- w['Response Status'] = getattr(
- resp, 'output_status', None) or resp.status
+ w['Response Status'] = \
+ getattr(resp, 'output_status', resp.status).decode()
w['End Time'] = time.time()
p = w['End Time'] - w['Start Time']
@@ -613,7 +611,7 @@ table.stats2 th {
"""Return ([headers], [rows]) for the given collection."""
# E.g., the 'Requests' dict.
headers = []
- vals = six.itervalues(v)
+ vals = v.values()
for record in vals:
for k3 in record:
format = formatting.get(k3, missing)
@@ -679,7 +677,7 @@ table.stats2 th {
def data(self):
s = extrapolate_statistics(logging.statistics)
cherrypy.response.headers['Content-Type'] = 'application/json'
- return json.dumps(s, sort_keys=True, indent=4)
+ return json.dumps(s, sort_keys=True, indent=4).encode('utf-8')
@cherrypy.expose
def pause(self, namespace):
diff --git a/lib/cherrypy/lib/cptools.py b/lib/cherrypy/lib/cptools.py
index 1c079634..613a8995 100644
--- a/lib/cherrypy/lib/cptools.py
+++ b/lib/cherrypy/lib/cptools.py
@@ -3,9 +3,7 @@
import logging
import re
from hashlib import md5
-
-import six
-from six.moves import urllib
+import urllib.parse
import cherrypy
from cherrypy._cpcompat import text_or_bytes
@@ -307,7 +305,7 @@ class SessionAuth(object):
def login_screen(self, from_page='..', username='', error_msg='',
**kwargs):
- return (six.text_type("""