diff options
72 files changed, 1360 insertions, 812 deletions
diff --git a/cherrypy/__init__.py b/cherrypy/__init__.py index a7d352b3..dc986569 100644 --- a/cherrypy/__init__.py +++ b/cherrypy/__init__.py @@ -315,7 +315,8 @@ class _GlobalLogManager(_cplogging.LogManager): """ def __call__(self, *args, **kwargs): - """Log the given message to the app.log or global log as appropriate.""" + """Log the given message to the app.log or global log as appropriate. + """ # Do NOT use try/except here. See # https://bitbucket.org/cherrypy/cherrypy/issue/945 if hasattr(request, 'app') and hasattr(request.app, 'log'): @@ -325,7 +326,8 @@ class _GlobalLogManager(_cplogging.LogManager): return log.error(*args, **kwargs) def access(self): - """Log an access message to the app.log or global log as appropriate.""" + """Log an access message to the app.log or global log as appropriate. + """ try: return request.app.log.access() except AttributeError: diff --git a/cherrypy/_cpchecker.py b/cherrypy/_cpchecker.py index 3c26d24a..4ef82597 100644 --- a/cherrypy/_cpchecker.py +++ b/cherrypy/_cpchecker.py @@ -48,7 +48,8 @@ class Checker(object): global_config_contained_paths = False def check_app_config_entries_dont_start_with_script_name(self): - """Check for Application config with sections that repeat script_name.""" + """Check for Application config with sections that repeat script_name. + """ for sn, app in cherrypy.tree.apps.items(): if not isinstance(app, cherrypy.Application): continue @@ -62,7 +63,8 @@ class Checker(object): if key_atoms[:len(sn_atoms)] == sn_atoms: warnings.warn( "The application mounted at %r has config " - "entries that start with its script name: %r" % (sn, key)) + "entries that start with its script name: %r" % (sn, + key)) def check_site_config_entries_in_app_config(self): """Check for mounted Applications that have site-scoped config.""" @@ -80,10 +82,11 @@ class Checker(object): (section, key, value)) if msg: msg.insert(0, - "The application mounted at %r contains the following " - "config entries, which are only allowed in site-wide " - "config. Move them to a [global] section and pass them " - "to cherrypy.config.update() instead of tree.mount()." % sn) + "The application mounted at %r contains the " + "following config entries, which are only allowed " + "in site-wide config. Move them to a [global] " + "section and pass them to cherrypy.config.update() " + "instead of tree.mount()." % sn) warnings.warn(os.linesep.join(msg)) def check_skipped_app_config(self): @@ -103,7 +106,9 @@ class Checker(object): return def check_app_config_brackets(self): - """Check for Application config with extraneous brackets in section names.""" + """Check for Application config with extraneous brackets in section + names. + """ for sn, app in cherrypy.tree.apps.items(): if not isinstance(app, cherrypy.Application): continue @@ -145,16 +150,20 @@ class Checker(object): "though a root is provided.") testdir = os.path.join(root, dir[1:]) if os.path.exists(testdir): - msg += ("\nIf you meant to serve the " - "filesystem folder at %r, remove " - "the leading slash from dir." % testdir) + msg += ( + "\nIf you meant to serve the " + "filesystem folder at %r, remove the " + "leading slash from dir." % (testdir,)) else: if not root: - msg = "dir is a relative path and no root provided." + msg = ( + "dir is a relative path and " + "no root provided.") else: fulldir = os.path.join(root, dir) if not os.path.isabs(fulldir): - msg = "%r is not an absolute path." % fulldir + msg = ("%r is not an absolute path." % ( + fulldir,)) if fulldir and not os.path.exists(fulldir): if msg: @@ -232,20 +241,24 @@ class Checker(object): if atoms[0] not in ns: # Spit out a special warning if a known # namespace is preceded by "cherrypy." - if (atoms[0] == "cherrypy" and atoms[1] in ns): - msg = ("The config entry %r is invalid; " - "try %r instead.\nsection: [%s]" - % (k, ".".join(atoms[1:]), section)) + if atoms[0] == "cherrypy" and atoms[1] in ns: + msg = ( + "The config entry %r is invalid; " + "try %r instead.\nsection: [%s]" + % (k, ".".join(atoms[1:]), section)) else: - msg = ("The config entry %r is invalid, because " - "the %r config namespace is unknown.\n" - "section: [%s]" % (k, atoms[0], section)) + msg = ( + "The config entry %r is invalid, " + "because the %r config namespace " + "is unknown.\n" + "section: [%s]" % (k, atoms[0], section)) warnings.warn(msg) elif atoms[0] == "tools": if atoms[1] not in dir(cherrypy.tools): - msg = ("The config entry %r may be invalid, " - "because the %r tool was not found.\n" - "section: [%s]" % (k, atoms[1], section)) + msg = ( + "The config entry %r may be invalid, " + "because the %r tool was not found.\n" + "section: [%s]" % (k, atoms[1], section)) warnings.warn(msg) def check_config_namespaces(self): @@ -313,6 +326,7 @@ class Checker(object): for k, v in cherrypy.config.items(): if k == 'server.socket_host' and v == 'localhost': warnings.warn("The use of 'localhost' as a socket host can " - "cause problems on newer systems, since 'localhost' can " - "map to either an IPv4 or an IPv6 address. You should " - "use '127.0.0.1' or '[::1]' instead.") + "cause problems on newer systems, since " + "'localhost' can map to either an IPv4 or an " + "IPv6 address. You should use '127.0.0.1' " + "or '[::1]' instead.") diff --git a/cherrypy/_cpcompat.py b/cherrypy/_cpcompat.py index e4ecdccc..7a124579 100644 --- a/cherrypy/_cpcompat.py +++ b/cherrypy/_cpcompat.py @@ -28,13 +28,17 @@ if sys.version_info >= (3, 0): basestring = (bytes, str) def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """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.""" + """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 @@ -58,7 +62,9 @@ else: basestring = basestring def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """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 @@ -66,20 +72,22 @@ else: return n def ntou(n, encoding='ISO-8859-1'): - """Return the given native string as a unicode string with the given encoding.""" + """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. + # 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 unicode( re.sub(r'\\u([0-9a-zA-Z]{4})', lambda m: 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. + # Assume it's already 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'): @@ -218,12 +226,14 @@ try: # Python 2. We try Python 2 first clients on Python 2 # don't try to import the 'http' module from cherrypy.lib from Cookie import SimpleCookie, CookieError - from httplib import BadStatusLine, HTTPConnection, IncompleteRead, NotConnected + from httplib import BadStatusLine, HTTPConnection, IncompleteRead + from httplib import NotConnected from BaseHTTPServer import BaseHTTPRequestHandler except ImportError: # Python 3 from http.cookies import SimpleCookie, CookieError - from http.client import BadStatusLine, HTTPConnection, IncompleteRead, NotConnected + from http.client import BadStatusLine, HTTPConnection, IncompleteRead + from http.client import NotConnected from http.server import BaseHTTPRequestHandler # Some platforms don't expose HTTPSConnection, so handle it separately @@ -274,7 +284,10 @@ try: from urllib.parse import unquote as parse_unquote def unquote_qs(atom, encoding, errors='strict'): - return parse_unquote(atom.replace('+', ' '), encoding=encoding, errors=errors) + return parse_unquote( + atom.replace('+', ' '), + encoding=encoding, + errors=errors) except ImportError: # Python 2 from urllib import unquote as parse_unquote diff --git a/cherrypy/_cpcompat_subprocess.py b/cherrypy/_cpcompat_subprocess.py index 71b27a3f..ce363725 100644 --- a/cherrypy/_cpcompat_subprocess.py +++ b/cherrypy/_cpcompat_subprocess.py @@ -420,7 +420,8 @@ class CalledProcessError(Exception): self.output = output
def __str__(self):
- return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+ return "Command '%s' returned non-zero exit status %d" % (
+ self.cmd, self.returncode)
if mswindows:
@@ -649,7 +650,8 @@ class Popen(object): if close_fds and (stdin is not None or stdout is not None or
stderr is not None):
raise ValueError("close_fds is not supported on Windows "
- "platforms if you redirect stdin/stdout/stderr")
+ "platforms if you redirect "
+ "stdin/stdout/stderr")
else:
# POSIX
if startupinfo is not None:
@@ -832,10 +834,14 @@ class Popen(object): def _make_inheritable(self, handle):
"""Return a duplicate of handle, which is inheritable"""
- return _subprocess.DuplicateHandle(_subprocess.GetCurrentProcess(),
- handle, _subprocess.GetCurrentProcess(
- ), 0, 1,
- _subprocess.DUPLICATE_SAME_ACCESS)
+ return _subprocess.DuplicateHandle(
+ _subprocess.GetCurrentProcess(),
+ handle,
+ _subprocess.GetCurrentProcess(),
+ 0,
+ 1,
+ _subprocess.DUPLICATE_SAME_ACCESS
+ )
def _find_w9xpopen(self):
"""Find and return absolut path to w9xpopen.exe"""
@@ -933,10 +939,12 @@ class Popen(object): self.pid = pid
ht.Close()
- def _internal_poll(self, _deadstate=None,
- _WaitForSingleObject=_subprocess.WaitForSingleObject,
- _WAIT_OBJECT_0=_subprocess.WAIT_OBJECT_0,
- _GetExitCodeProcess=_subprocess.GetExitCodeProcess):
+ def _internal_poll(
+ self, _deadstate=None,
+ _WaitForSingleObject=_subprocess.WaitForSingleObject,
+ _WAIT_OBJECT_0=_subprocess.WAIT_OBJECT_0,
+ _GetExitCodeProcess=_subprocess.GetExitCodeProcess
+ ):
"""Check if child process has terminated. Returns returncode
attribute.
@@ -1093,8 +1101,9 @@ class Popen(object): def pipe_cloexec(self):
"""Create a pipe with FDs set CLOEXEC."""
# Pipes' FDs are set CLOEXEC by default because we don't want them
- # to be inherited by other subprocesses: the CLOEXEC flag is removed
- # from the child's FDs by _dup2(), between fork() and exec().
+ # to be inherited by other subprocesses: the CLOEXEC flag is
+ # removed from the child's FDs by _dup2(), between fork() and
+ # exec().
# This is not atomic: we would need the pipe2() syscall for that.
r, w = os.pipe()
self._set_cloexec_flag(r)
@@ -1219,8 +1228,8 @@ class Popen(object): exc_value.child_traceback = ''.join(exc_lines)
os.write(errpipe_write, pickle.dumps(exc_value))
- # This exitcode won't be reported to applications, so it
- # really doesn't matter what we return.
+ # This exitcode won't be reported to applications,
+ # so it really doesn't matter what we return.
os._exit(255)
# Parent
diff --git a/cherrypy/_cpconfig.py b/cherrypy/_cpconfig.py index 8b9b4adc..c11bc1d1 100644 --- a/cherrypy/_cpconfig.py +++ b/cherrypy/_cpconfig.py @@ -256,13 +256,17 @@ def _engine_namespace_handler(k, v): engine = cherrypy.engine deprecated = { - 'autoreload_on': 'autoreload.on', 'autoreload_frequency': 'autoreload.frequency', - 'autoreload_match': 'autoreload.match', 'reload_files': 'autoreload.files', - 'deadlock_poll_freq': 'timeout_monitor.frequency'} + 'autoreload_on': 'autoreload.on', + 'autoreload_frequency': 'autoreload.frequency', + 'autoreload_match': 'autoreload.match', + 'reload_files': 'autoreload.files', + 'deadlock_poll_freq': 'timeout_monitor.frequency' + } if k in deprecated: - engine.log('WARNING: Use of engine.%s is deprecated and will be removed in ' - 'a future version. Use engine.%s instead.' % (k, deprecated[k])) + engine.log( + 'WARNING: Use of engine.%s is deprecated and will be removed in a ' + 'future version. Use engine.%s instead.' % (k, deprecated[k])) if k == 'autoreload_on': if v: @@ -288,7 +292,10 @@ def _engine_namespace_handler(k, v): if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'): plugin.subscribe() return - elif (not v) and hasattr(getattr(plugin, 'unsubscribe', None), '__call__'): + elif ( + (not v) and + hasattr(getattr(plugin, 'unsubscribe', None), '__call__') + ): plugin.unsubscribe() return setattr(plugin, attrname, v) diff --git a/cherrypy/_cpdispatch.py b/cherrypy/_cpdispatch.py index b0535a0b..7289e494 100644 --- a/cherrypy/_cpdispatch.py +++ b/cherrypy/_cpdispatch.py @@ -37,8 +37,11 @@ class PageHandler(object): cherrypy.serving.request.args = args return cherrypy.serving.request.args - args = property(get_args, set_args, - doc="The ordered args should be accessible from post dispatch hooks") + args = property( + get_args, + set_args, + doc="The ordered args should be accessible from post dispatch hooks" + ) def get_kwargs(self): return cherrypy.serving.request.kwargs @@ -47,8 +50,11 @@ class PageHandler(object): cherrypy.serving.request.kwargs = kwargs return cherrypy.serving.request.kwargs - kwargs = property(get_kwargs, set_kwargs, - doc="The named kwargs should be accessible from post dispatch hooks") + kwargs = property( + get_kwargs, + set_kwargs, + doc="The named kwargs should be accessible from post dispatch hooks" + ) def __call__(self): try: @@ -75,7 +81,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs): 2. Too little parameters are passed to the function. There are 3 sources of parameters to a cherrypy handler. - 1. query string parameters are passed as keyword parameters to the handler. + 1. query string parameters are passed as keyword parameters to the + handler. 2. body parameters are also passed as keyword parameters. 3. when partial matching occurs, the final path atoms are passed as positional args. @@ -381,7 +388,8 @@ class Dispatcher(object): object_trail.append([name, node, nodeconf, segleft]) def set_conf(): - """Collapse all object_trail config into cherrypy.request.config.""" + """Collapse all object_trail config into cherrypy.request.config. + """ base = cherrypy.config.copy() # Note that we merge the config from each node # even if that node was None. @@ -413,7 +421,8 @@ class Dispatcher(object): request.is_index = path.endswith("/") return defhandler, fullpath[fullpath_len - segleft:-1] - # Uncomment the next line to restrict positional params to "default". + # Uncomment the next line to restrict positional params to + # "default". # if i < num_candidates - 2: continue # Try the current leaf. @@ -604,7 +613,8 @@ def XMLRPCDispatcher(next_dispatcher=Dispatcher()): return xmlrpc_dispatch -def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domains): +def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, + **domains): """ Select a different handler based on the Host header. diff --git a/cherrypy/_cperror.py b/cherrypy/_cperror.py index 4540dce5..f09fa270 100644 --- a/cherrypy/_cperror.py +++ b/cherrypy/_cperror.py @@ -2,8 +2,9 @@ CherryPy provides (and uses) exceptions for declaring that the HTTP response should be a status other than the default "200 OK". You can ``raise`` them like -normal Python exceptions. You can also call them and they will raise themselves; -this means you can set an :class:`HTTPError<cherrypy._cperror.HTTPError>` +normal Python exceptions. You can also call them and they will raise +themselves; this means you can set an +:class:`HTTPError<cherrypy._cperror.HTTPError>` or :class:`HTTPRedirect<cherrypy._cperror.HTTPRedirect>` as the :attr:`request.handler<cherrypy._cprequest.Request.handler>`. @@ -21,7 +22,8 @@ POST, however, is neither safe nor idempotent--if you charge a credit card, you don't want to be charged twice by a redirect! For this reason, *none* of the 3xx responses permit a user-agent (browser) to -resubmit a POST on redirection without first confirming the action with the user: +resubmit a POST on redirection without first confirming the action with the +user: ===== ================================= =========== 300 Multiple Choices Confirm with the user @@ -53,14 +55,16 @@ Anticipated HTTP responses -------------------------- The 'error_page' config namespace can be used to provide custom HTML output for -expected responses (like 404 Not Found). Supply a filename from which the output -will be read. The contents will be interpolated with the values %(status)s, -%(message)s, %(traceback)s, and %(version)s using plain old Python +expected responses (like 404 Not Found). Supply a filename from which the +output will be read. The contents will be interpolated with the values +%(status)s, %(message)s, %(traceback)s, and %(version)s using plain old Python `string formatting <http://docs.python.org/2/library/stdtypes.html#string-formatting-operations>`_. :: - _cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")} + _cp_config = { + 'error_page.404': os.path.join(localDir, "static/index.html") + } Beginning in version 3.1, you may also provide a function or other callable as @@ -72,7 +76,8 @@ version arguments that are interpolated into templates:: cherrypy.config.update({'error_page.402': error_page_402}) Also in 3.1, in addition to the numbered error codes, you may also supply -"error_page.default" to handle all codes which do not have their own error_page entry. +"error_page.default" to handle all codes which do not have their own error_page +entry. @@ -81,8 +86,9 @@ Unanticipated errors CherryPy also has a generic error handling mechanism: whenever an unanticipated error occurs in your code, it will call -:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to set -the response status, headers, and body. By default, this is the same output as +:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to +set the response status, headers, and body. By default, this is the same +output as :class:`HTTPError(500) <cherrypy._cperror.HTTPError>`. If you want to provide some other behavior, you generally replace "request.error_response". @@ -93,21 +99,27 @@ send an e-mail containing the error:: def handle_error(): cherrypy.response.status = 500 - cherrypy.response.body = ["<html><body>Sorry, an error occured</body></html>"] - sendMail('error@domain.com', 'Error in your web app', _cperror.format_exc()) + cherrypy.response.body = [ + "<html><body>Sorry, an error occured</body></html>" + ] + sendMail('error@domain.com', + 'Error in your web app', + _cperror.format_exc()) class Root: _cp_config = {'request.error_response': handle_error} -Note that you have to explicitly set :attr:`response.body <cherrypy._cprequest.Response.body>` +Note that you have to explicitly set +:attr:`response.body <cherrypy._cprequest.Response.body>` and not simply return an error message as a result. """ 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, bytestr, iteritems, ntob, tonative, urljoin as _urljoin +from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob +from cherrypy._cpcompat import tonative, urljoin as _urljoin from cherrypy.lib import httputil as _httputil @@ -129,7 +141,8 @@ class InternalRedirect(CherryPyException): This exception will redirect processing to another path within the site (without informing the client). Provide the new path as an argument when - raising the exception. Provide any params in the querystring for the new URL. + raising the exception. Provide any params in the querystring for the new + URL. """ def __init__(self, path, query_string=""): @@ -226,7 +239,8 @@ class HTTPRedirect(CherryPyException): CherryPyException.__init__(self, abs_urls, status) def set_response(self): - """Modify cherrypy.response status, headers, and body to represent self. + """Modify cherrypy.response status, headers, and body to represent + self. CherryPy uses this internally, but you can also use it to create an HTTPRedirect object and set its output without *raising* the exception. @@ -244,12 +258,14 @@ class HTTPRedirect(CherryPyException): # "Unless the request method was HEAD, the entity of the response # SHOULD contain a short hypertext note with a hyperlink to the # new URI(s)." - msg = {300: "This resource can be found at <a href='%s'>%s</a>.", - 301: "This resource has permanently moved to <a href='%s'>%s</a>.", - 302: "This resource resides temporarily at <a href='%s'>%s</a>.", - 303: "This resource can be found at <a href='%s'>%s</a>.", - 307: "This resource has moved temporarily to <a href='%s'>%s</a>.", - }[status] + msg = { + 300: "This resource can be found at ", + 301: "This resource has permanently moved to ", + 302: "This resource resides temporarily at ", + 303: "This resource can be found at ", + 307: "This resource has moved temporarily to ", + }[status] + msg += "<a href='%s'>%s</a>." msgs = [msg % (u, u) for u in self.urls] response.body = ntob("<br />\n".join(msgs), 'utf-8') # Previous code may have set C-L, so we have to reset it @@ -318,22 +334,24 @@ class HTTPError(CherryPyException): """Exception used to return an HTTP error code (4xx-5xx) to the client. - This exception can be used to automatically send a response using a http status - code, with an appropriate error page. It takes an optional + This exception can be used to automatically send a response using a + http status code, with an appropriate error page. It takes an optional ``status`` argument (which must be between 400 and 599); it defaults to 500 ("Internal Server Error"). It also takes an optional ``message`` argument, which will be returned in the response body. See - `RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_ + `RFC2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_ for a complete list of available error codes and when to use them. Examples:: raise cherrypy.HTTPError(403) - raise cherrypy.HTTPError("403 Forbidden", "You are not allowed to access this resource.") + raise cherrypy.HTTPError( + "403 Forbidden", "You are not allowed to access this resource.") """ status = None - """The HTTP status code. May be of type int or str (with a Reason-Phrase).""" + """The HTTP status code. May be of type int or str (with a Reason-Phrase). + """ code = None """The integer HTTP status code.""" @@ -357,7 +375,8 @@ class HTTPError(CherryPyException): CherryPyException.__init__(self, status, message) def set_response(self): - """Modify cherrypy.response status, headers, and body to represent self. + """Modify cherrypy.response status, headers, and body to represent + self. CherryPy uses this internally, but you can also use it to create an HTTPError object and set its output without *raising* the exception. @@ -408,7 +427,8 @@ class NotFound(HTTPError): HTTPError.__init__(self, 404, "The path '%s' was not found." % path) -_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC +"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> @@ -431,7 +451,9 @@ _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitiona <p>%(message)s</p> <pre id="traceback">%(traceback)s</pre> <div id="powered_by"> - <span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span> + <span> + Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a> + </span> </div> </body> </html> diff --git a/cherrypy/_cplogging.py b/cherrypy/_cplogging.py index 091ce67d..d1b99f70 100644 --- a/cherrypy/_cplogging.py +++ b/cherrypy/_cplogging.py @@ -34,10 +34,11 @@ and another set of rules specific to each application. The global log manager is found at :func:`cherrypy.log`, and the log manager for each application is found at :attr:`app.log<cherrypy._cptree.Application.log>`. If you're inside a request, the latter is reachable from -``cherrypy.request.app.log``; if you're outside a request, you'll have to obtain -a reference to the ``app``: either the return value of +``cherrypy.request.app.log``; if you're outside a request, you'll have to +obtain a reference to the ``app``: either the return value of :func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used -:func:`quickstart()<cherrypy.quickstart>` instead, via ``cherrypy.tree.apps['/']``. +:func:`quickstart()<cherrypy.quickstart>` instead, via +``cherrypy.tree.apps['/']``. By default, the global logs are named "cherrypy.error" and "cherrypy.access", and the application logs are named "cherrypy.error.2378745" and @@ -191,7 +192,8 @@ class LogManager(object): h.stream = open(h.baseFilename, h.mode) h.release() - def error(self, msg='', context='', severity=logging.INFO, traceback=False): + def error(self, msg='', context='', severity=logging.INFO, + traceback=False): """Write the given ``msg`` to the error log. This is not just for errors! Applications may call this at any time @@ -211,7 +213,8 @@ class LogManager(object): def access(self): """Write to the access log (in Apache/NCSA Combined Log format). - See `the apache documentation <http://httpd.apache.org/docs/current/logs.html#combined>`_ + See the + `apache documentation <http://httpd.apache.org/docs/current/logs.html#combined>`_ for format details. CherryPy calls this automatically for you. Note there are no arguments; diff --git a/cherrypy/_cpmodpy.py b/cherrypy/_cpmodpy.py index cbb17652..02154d69 100644 --- a/cherrypy/_cpmodpy.py +++ b/cherrypy/_cpmodpy.py @@ -106,7 +106,7 @@ def setup(req): elif logging.WARNING >= level: newlevel = apache.APLOG_WARNING # On Windows, req.server is required or the msg will vanish. See - # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html. + # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html # Also, "When server is not specified...LogLevel does not apply..." apache.log_error(msg, newlevel, req.server) engine.subscribe('log', _log) @@ -215,8 +215,9 @@ def handler(req): if not recursive: if ir.path in redirections: - raise RuntimeError("InternalRedirector visited the " - "same URL twice: %r" % ir.path) + raise RuntimeError( + "InternalRedirector visited the same URL " + "twice: %r" % ir.path) else: # Add the *previous* path_info + qs to # redirections. @@ -289,8 +290,12 @@ def read_process(cmd, args=""): pipeout = popen(fullcmd) try: firstline = pipeout.readline() - if (re.search(ntob("(not recognized|No such file|not found)"), firstline, - re.IGNORECASE)): + cmd_not_found = re.search( + ntob("(not recognized|No such file|not found)"), + firstline, + re.IGNORECASE + ) + if cmd_not_found: raise IOError('%s must be on your system path.' % cmd) output = firstline + pipeout.read() finally: diff --git a/cherrypy/_cpnative_server.py b/cherrypy/_cpnative_server.py index 6e811c47..e303573d 100644 --- a/cherrypy/_cpnative_server.py +++ b/cherrypy/_cpnative_server.py @@ -59,8 +59,9 @@ class NativeGateway(wsgiserver.Gateway): if not self.recursive: if ir.path in redirections: - raise RuntimeError("InternalRedirector visited the " - "same URL twice: %r" % ir.path) + raise RuntimeError( + "InternalRedirector visited the same " + "URL twice: %r" % ir.path) else: # Add the *previous* path_info + qs to # redirections. @@ -127,8 +128,10 @@ class CPHTTPServer(wsgiserver.HTTPServer): maxthreads=server_adapter.thread_pool_max, server_name=server_name) - self.max_request_header_size = self.server_adapter.max_request_header_size or 0 - self.max_request_body_size = self.server_adapter.max_request_body_size or 0 + self.max_request_header_size = ( + self.server_adapter.max_request_header_size or 0) + self.max_request_body_size = ( + self.server_adapter.max_request_body_size or 0) self.request_queue_size = self.server_adapter.socket_queue_size self.timeout = self.server_adapter.socket_timeout self.shutdown_timeout = self.server_adapter.shutdown_timeout diff --git a/cherrypy/_cpreqbody.py b/cherrypy/_cpreqbody.py index 10011ef2..d2dbbc92 100644 --- a/cherrypy/_cpreqbody.py +++ b/cherrypy/_cpreqbody.py @@ -3,8 +3,10 @@ .. versionadded:: 3.2 Application authors have complete control over the parsing of HTTP request -entities. In short, :attr:`cherrypy.request.body<cherrypy._cprequest.Request.body>` -is now always set to an instance of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>`, +entities. In short, +:attr:`cherrypy.request.body<cherrypy._cprequest.Request.body>` +is now always set to an instance of +:class:`RequestBody<cherrypy._cpreqbody.RequestBody>`, and *that* class is a subclass of :class:`Entity<cherrypy._cpreqbody.Entity>`. When an HTTP request includes an entity body, it is often desirable to @@ -21,9 +23,9 @@ key to look up a value in the :attr:`request.body.processors<cherrypy._cpreqbody.Entity.processors>` dict. If the full media type is not found, then the major type is tried; for example, if no processor -is found for the 'image/jpeg' type, then we look for a processor for the 'image' -types altogether. If neither the full type nor the major type has a matching -processor, then a default processor is used +is found for the 'image/jpeg' type, then we look for a processor for the +'image' types altogether. If neither the full type nor the major type has a +matching processor, then a default processor is used (:func:`default_proc<cherrypy._cpreqbody.Entity.default_proc>`). For most types, this means no processing is done, and the body is left unread as a raw byte stream. Processors are configurable in an 'on_start_resource' hook. @@ -74,31 +76,36 @@ Here's the built-in JSON tool for an example:: 415, 'Expected an application/json content type') request.body.processors['application/json'] = json_processor -We begin by defining a new ``json_processor`` function to stick in the ``processors`` -dictionary. All processor functions take a single argument, the ``Entity`` instance -they are to process. It will be called whenever a request is received (for those -URI's where the tool is turned on) which has a ``Content-Type`` of -"application/json". - -First, it checks for a valid ``Content-Length`` (raising 411 if not valid), then -reads the remaining bytes on the socket. The ``fp`` object knows its own length, so -it won't hang waiting for data that never arrives. It will return when all data -has been read. Then, we decode those bytes using Python's built-in ``json`` module, -and stick the decoded result onto ``request.json`` . If it cannot be decoded, we -raise 400. - -If the "force" argument is True (the default), the ``Tool`` clears the ``processors`` -dict so that request entities of other ``Content-Types`` aren't parsed at all. Since -there's no entry for those invalid MIME types, the ``default_proc`` method of ``cherrypy.request.body`` -is called. But this does nothing by default (usually to provide the page handler an opportunity to handle it.) -But in our case, we want to raise 415, so we replace ``request.body.default_proc`` +We begin by defining a new ``json_processor`` function to stick in the +``processors`` dictionary. All processor functions take a single argument, +the ``Entity`` instance they are to process. It will be called whenever a +request is received (for those URI's where the tool is turned on) which +has a ``Content-Type`` of "application/json". + +First, it checks for a valid ``Content-Length`` (raising 411 if not valid), +then reads the remaining bytes on the socket. The ``fp`` object knows its +own length, so it won't hang waiting for data that never arrives. It will +return when all data has been read. Then, we decode those bytes using +Python's built-in ``json`` module, and stick the decoded result onto +``request.json`` . If it cannot be decoded, we raise 400. + +If the "force" argument is True (the default), the ``Tool`` clears the +``processors`` dict so that request entities of other ``Content-Types`` +aren't parsed at all. Since there's no entry for those invalid MIME +types, the ``default_proc`` method of ``cherrypy.request.body`` is +called. But this does nothing by default (usually to provide the page +handler an opportunity to handle it.) +But in our case, we want to raise 415, so we replace +``request.body.default_proc`` with the error (``HTTPError`` instances, when called, raise themselves). -If we were defining a custom processor, we can do so without making a ``Tool``. Just add the config entry:: +If we were defining a custom processor, we can do so without making a ``Tool``. +Just add the config entry:: request.body.processors = {'application/json': json_processor} -Note that you can only replace the ``processors`` dict wholesale this way, not update the existing one. +Note that you can only replace the ``processors`` dict wholesale this way, +not update the existing one. """ try: @@ -129,7 +136,7 @@ from cherrypy._cpcompat import basestring, ntob, ntou from cherrypy.lib import httputil -# -------------------------------- Processors -------------------------------- # +# ------------------------------- Processors -------------------------------- # def process_urlencoded(entity): """Read application/x-www-form-urlencoded data into entity.params.""" @@ -211,7 +218,8 @@ def process_multipart(entity): def process_multipart_form_data(entity): - """Read all multipart/form-data parts into entity.parts or entity.params.""" + """Read all multipart/form-data parts into entity.parts or entity.params. + """ process_multipart(entity) kept_parts = [] @@ -265,7 +273,7 @@ def _old_process_multipart(entity): params[key] = value -# --------------------------------- Entities --------------------------------- # +# -------------------------------- Entities --------------------------------- # class Entity(object): """An HTTP request body, or MIME multipart body. @@ -278,15 +286,18 @@ class Entity(object): Between the ``before_request_body`` and ``before_handler`` tools, CherryPy tries to process the request body (if any) by calling :func:`request.body.process<cherrypy._cpreqbody.RequestBody.process>`. - This uses the ``content_type`` of the Entity to look up a suitable processor - in :attr:`Entity.processors<cherrypy._cpreqbody.Entity.processors>`, a dict. + This uses the ``content_type`` of the Entity to look up a suitable + processor in + :attr:`Entity.processors<cherrypy._cpreqbody.Entity.processors>`, + a dict. If a matching processor cannot be found for the complete Content-Type, it tries again using the major type. For example, if a request with an entity of type "image/jpeg" arrives, but no processor can be found for that complete type, then one is sought for the major type "image". If a processor is still not found, then the - :func:`default_proc<cherrypy._cpreqbody.Entity.default_proc>` method of the - Entity is called (which does nothing by default; you can override this too). + :func:`default_proc<cherrypy._cpreqbody.Entity.default_proc>` method + of the Entity is called (which does nothing by default; you can + override this too). CherryPy includes processors for the "application/x-www-form-urlencoded" type, the "multipart/form-data" type, and the "multipart" major type. @@ -381,7 +392,8 @@ class Entity(object): """A dict of Content-Type names to processor methods.""" parts = None - """A list of Part instances if ``Content-Type`` is of major type "multipart".""" + """A list of Part instances if ``Content-Type`` is of major type + "multipart".""" part_class = None """The class used for multipart parts. @@ -427,7 +439,10 @@ class Entity(object): self.length = None clen = headers.get('Content-Length', None) # If Transfer-Encoding is 'chunked', ignore any Content-Length. - if clen is not None and 'chunked' not in headers.get('Transfer-Encoding', ''): + if ( + clen is not None and + 'chunked' not in headers.get('Transfer-Encoding', '') + ): try: self.length = int(clen) except ValueError: @@ -445,12 +460,18 @@ class Entity(object): self.name = self.name[1:-1] if 'filename' in disp.params: self.filename = disp.params['filename'] - if self.filename.startswith('"') and self.filename.endswith('"'): + if ( + self.filename.startswith('"') and + self.filename.endswith('"') + ): self.filename = self.filename[1:-1] # The 'type' attribute is deprecated in 3.2; remove it in 3.3. - type = property(lambda self: self.content_type, - doc="""A deprecated alias for :attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`.""") + type = property( + lambda self: self.content_type, + doc="A deprecated alias for " + ":attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`." + ) def read(self, size=None, fp_out=None): return self.fp.read(size, fp_out) @@ -474,7 +495,10 @@ class Entity(object): 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.""" + """Read the request body into fp_out (or make_file() if None). + + Return fp_out. + """ if fp_out is None: fp_out = self.make_file() self.read(fp_out=fp_out) @@ -516,7 +540,9 @@ class Entity(object): proc(self) def default_proc(self): - """Called if a more-specific processor is not found for the ``Content-Type``.""" + """Called if a more-specific processor is not found for the + ``Content-Type``. + """ # Leave the fp alone for someone else to read. This works fine # for request.body, but the Part subclasses need to override this # so they can move on to the next part. @@ -556,10 +582,11 @@ class Part(Entity): # This is the default in stdlib cgi. We may want to increase it. maxrambytes = 1000 - """The threshold of bytes after which point the ``Part`` will store its data - in a file (generated by :func:`make_file<cherrypy._cprequest.Entity.make_file>`) - instead of a string. Defaults to 1000, just like the :mod:`cgi` module in - Python's standard library. + """The threshold of bytes after which point the ``Part`` will store + its data in a file (generated by + :func:`make_file<cherrypy._cprequest.Entity.make_file>`) + instead of a string. Defaults to 1000, just like the :mod:`cgi` + module in Python's standard library. """ def __init__(self, fp, headers, boundary): @@ -609,9 +636,9 @@ class Part(Entity): If the 'fp_out' argument is None (the default), all bytes read are returned in a single byte string. - If the 'fp_out' argument is not None, it must be a file-like object that - supports the 'write' method; all bytes read will be written to the fp, - and that fp is returned. + If the 'fp_out' argument is not None, it must be a file-like + object that supports the 'write' method; all bytes read will be + written to the fp, and that fp is returned. """ endmarker = self.boundary + ntob("--") delim = ntob("") @@ -666,14 +693,18 @@ class Part(Entity): return result else: raise cherrypy.HTTPError( - 400, "The request entity could not be decoded. The following " - "charsets were attempted: %s" % repr(self.attempt_charsets)) + 400, + "The request entity could not be decoded. The following " + "charsets were attempted: %s" % repr(self.attempt_charsets) + ) else: fp_out.seek(0) return fp_out def default_proc(self): - """Called if a more-specific processor is not found for the ``Content-Type``.""" + """Called if a more-specific processor is not found for the + ``Content-Type``. + """ if self.filename: # Always read into a file if a .filename was given. self.file = self.read_into_file() @@ -685,7 +716,10 @@ class Part(Entity): self.file = result def read_into_file(self, fp_out=None): - """Read the request body into fp_out (or make_file() if None). Return fp_out.""" + """Read the request body into fp_out (or make_file() if None). + + Return fp_out. + """ if fp_out is None: fp_out = self.make_file() self.read_lines_to_boundary(fp_out=fp_out) @@ -707,16 +741,21 @@ except ValueError: inf = Infinity() -comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding', - 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', 'Connection', - 'Content-Encoding', 'Content-Language', 'Expect', 'If-Match', - 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'Te', 'Trailer', - 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', 'Www-Authenticate'] +comma_separated_headers = [ + 'Accept', 'Accept-Charset', 'Accept-Encoding', + 'Accept-Language', 'Accept-Ranges', 'Allow', + 'Cache-Control', 'Connection', 'Content-Encoding', + 'Content-Language', 'Expect', 'If-Match', + 'If-None-Match', 'Pragma', 'Proxy-Authenticate', + 'Te', 'Trailer', 'Transfer-Encoding', 'Upgrade', + 'Vary', 'Via', 'Warning', 'Www-Authenticate' +] class SizedReader: - def __init__(self, fp, length, maxbytes, bufsize=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 @@ -740,9 +779,9 @@ class SizedReader: If the 'fp_out' argument is None (the default), all bytes read are returned in a single byte string. - If the 'fp_out' argument is not None, it must be a file-like object that - supports the 'write' method; all bytes read will be written to the fp, - and None is returned. + If the 'fp_out' argument is not None, it must be a file-like + object that supports the 'write' method; all bytes read will be + written to the fp, and None is returned. """ if self.length is None: @@ -913,7 +952,9 @@ class RequestBody(Entity): """ maxbytes = None - """Raise ``MaxSizeExceeded`` if more bytes than this are read from the socket.""" + """Raise ``MaxSizeExceeded`` if more bytes than this are read from + the socket. + """ def __init__(self, fp, headers, params=None, request_params=None): Entity.__init__(self, fp, headers, params) diff --git a/cherrypy/_cprequest.py b/cherrypy/_cprequest.py index 6c76205b..290bd2eb 100644 --- a/cherrypy/_cprequest.py +++ b/cherrypy/_cprequest.py @@ -124,7 +124,11 @@ class HookMap(dict): def __repr__(self): cls = self.__class__ - return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, copykeys(self)) + return "%s.%s(points=%r)" % ( + cls.__module__, + cls.__name__, + copykeys(self) + ) # Config namespace handlers @@ -429,9 +433,9 @@ class Request(object): If a callable is provided, it will be called by default with keyword arguments 'status', 'message', 'traceback', and 'version', as for a - string-formatting template. The callable must return a string or iterable of - strings which will be set to response.body. It may also override headers or - perform any other processing. + string-formatting template. The callable must return a string or + iterable of strings which will be set to response.body. It may also + override headers or perform any other processing. If no entry is given for an error code, and no 'default' entry exists, a default template will be used. @@ -714,9 +718,10 @@ class Request(object): name = name.title() value = value.strip() - # Warning: if there is more than one header entry for cookies (AFAIK, - # only Konqueror does that), only the last one will remain in headers - # (but they will be correctly stored in request.cookie). + # Warning: if there is more than one header entry for cookies + # (AFAIK, only Konqueror does that), only the last one will + # remain in headers (but they will be correctly stored in + # request.cookie). if "=?" in value: dict.__setitem__(headers, name, httputil.decode_TEXT(value)) else: diff --git a/cherrypy/_cpserver.py b/cherrypy/_cpserver.py index b415d4c5..1ee3ee2b 100644 --- a/cherrypy/_cpserver.py +++ b/cherrypy/_cpserver.py @@ -37,8 +37,10 @@ class Server(ServerAdapter): "Use '0.0.0.0' instead to listen on all active " "interfaces (INADDR_ANY).") self._socket_host = value - socket_host = property(_get_socket_host, _set_socket_host, - doc="""The hostname or IP address on which to listen for connections. + socket_host = property( + _get_socket_host, + _set_socket_host, + doc="""The hostname or IP address on which to listen for connections. Host values may be any IPv4 or IPv6 address, or any valid hostname. The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if @@ -72,11 +74,13 @@ class Server(ServerAdapter): """The number of worker threads to start up in the pool.""" thread_pool_max = -1 - """The maximum size of the worker-thread pool. Use -1 to indicate no limit.""" + """The maximum size of the worker-thread pool. Use -1 to indicate no limit. + """ max_request_header_size = 500 * 1024 - """The maximum number of bytes allowable in the request headers. If exceeded, - the HTTP server should return "413 Request Entity Too Large".""" + """The maximum number of bytes allowable in the request headers. + If exceeded, the HTTP server should return "413 Request Entity Too Large". + """ max_request_body_size = 100 * 1024 * 1024 """The maximum number of bytes allowable in the request body. If exceeded, @@ -103,17 +107,19 @@ class Server(ServerAdapter): if py3k: 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.""" + """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.""" + """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.""" @@ -178,11 +184,15 @@ class Server(ServerAdapter): raise ValueError("bind_addr must be a (host, port) tuple " "(for TCP sockets) or a string (for Unix " "domain sockets), not %r" % value) - bind_addr = property(_get_bind_addr, _set_bind_addr, - doc='A (host, port) tuple for TCP sockets or a str for Unix domain sockets.') + bind_addr = property( + _get_bind_addr, + _set_bind_addr, + doc='A (host, port) tuple for TCP sockets or ' + 'a str for Unix domain sockets.') def base(self): - """Return the base (scheme://host[:port] or sock file) for this server.""" + """Return the base (scheme://host[:port] or sock file) for this server. + """ if self.socket_file: return self.socket_file diff --git a/cherrypy/_cptools.py b/cherrypy/_cptools.py index 6f501a5d..ce574492 100644 --- a/cherrypy/_cptools.py +++ b/cherrypy/_cptools.py @@ -43,8 +43,10 @@ def _getargs(func): return co.co_varnames[:co.co_argcount] -_attr_error = ("CherryPy Tools cannot be turned on directly. Instead, turn them " - "on via config, or use them as decorators on your page handlers.") +_attr_error = ( + "CherryPy Tools cannot be turned on directly. Instead, turn them " + "on via config, or use them as decorators on your page handlers." +) class Tool(object): @@ -215,7 +217,8 @@ class HandlerWrapperTool(Tool): cherrypy.tools.jinja = HandlerWrapperTool(interpolator) """ - def __init__(self, newhandler, point='before_handler', name=None, priority=50): + def __init__(self, newhandler, point='before_handler', name=None, + priority=50): self.newhandler = newhandler self._point = point self._name = name @@ -267,7 +270,8 @@ class SessionTool(Tool): When 'early', the session will be locked before reading the request body. This is off by default for safety reasons; for example, a large upload would block the session, denying an AJAX - progress meter (`issue <https://bitbucket.org/cherrypy/cherrypy/issue/630>`_). + progress meter + (`issue <https://bitbucket.org/cherrypy/cherrypy/issue/630>`_). When 'explicit' (or any other value), you need to call cherrypy.session.acquire_lock() yourself before using @@ -498,12 +502,16 @@ _d.sessions = SessionTool() _d.xmlrpc = ErrorTool(_xmlrpc.on_error) _d.caching = CachingTool('before_handler', _caching.get, 'caching') _d.expires = Tool('before_finalize', _caching.expires) -_d.tidy = DeprecatedTool('before_finalize', - "The tidy tool has been removed from the standard distribution of CherryPy. " - "The most recent version can be found at http://tools.cherrypy.org/browser.") -_d.nsgmls = DeprecatedTool('before_finalize', - "The nsgmls tool has been removed from the standard distribution of CherryPy. " - "The most recent version can be found at http://tools.cherrypy.org/browser.") +_d.tidy = DeprecatedTool( + 'before_finalize', + "The tidy tool has been removed from the standard distribution of " + "CherryPy. The most recent version can be found at " + "http://tools.cherrypy.org/browser.") +_d.nsgmls = DeprecatedTool( + 'before_finalize', + "The nsgmls tool has been removed from the standard distribution of " + "CherryPy. The most recent version can be found at " + "http://tools.cherrypy.org/browser.") _d.ignore_headers = Tool('before_request_body', cptools.ignore_headers) _d.referer = Tool('before_request_body', cptools.referer) _d.basic_auth = Tool('on_start_resource', auth.basic_auth) diff --git a/cherrypy/_cptree.py b/cherrypy/_cptree.py index f53712ad..32e79b02 100644 --- a/cherrypy/_cptree.py +++ b/cherrypy/_cptree.py @@ -63,10 +63,10 @@ class Application(object): return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__, self.root, self.script_name) - script_name_doc = """The URI "mount point" for this app. A mount point is that portion of - the URI which is constant for all URIs that are serviced by this - application; it does not include scheme, host, or proxy ("virtual host") - portions of the URI. + script_name_doc = """The URI "mount point" for this app. A mount point + is that portion of the URI which is constant for all URIs that are + serviced by this application; it does not include scheme, host, or proxy + ("virtual host") portions of the URI. For example, if script_name is "/my/cool/app", then the URL "http://www.example.com/my/cool/app/page1" might be handled by a @@ -80,11 +80,12 @@ class Application(object): """ def _get_script_name(self): - if self._script_name is None: - # None signals that the script name should be pulled from WSGI - # environ. - return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/") - return self._script_name + if self._script_name is not None: + return self._script_name + + # A `_script_name` with a value of None signals that the script name + # should be pulled from WSGI environ. + return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/") def _set_script_name(self, value): if value: @@ -206,8 +207,9 @@ class Tree(object): if isinstance(root, Application): app = root if script_name != "" and script_name != app.script_name: - raise ValueError("Cannot specify a different script name and " - "pass an Application instance to cherrypy.mount") + raise ValueError( + "Cannot specify a different script name and pass an " + "Application instance to cherrypy.mount") script_name = app.script_name else: app = Application(root, script_name) diff --git a/cherrypy/_cpwsgi.py b/cherrypy/_cpwsgi.py index 1e15411a..4d1fd704 100644 --- a/cherrypy/_cpwsgi.py +++ b/cherrypy/_cpwsgi.py @@ -16,7 +16,8 @@ from cherrypy.lib import httputil def downgrade_wsgi_ux_to_1x(environ): - """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ.""" + """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ. + """ env1x = {} url_encoding = environ[ntou('wsgi.url_encoding')] @@ -137,7 +138,12 @@ class ExceptionTrapper(object): self.throws = throws def __call__(self, environ, start_response): - return _TrappedResponse(self.nextapp, environ, start_response, self.throws) + return _TrappedResponse( + self.nextapp, + environ, + start_response, + self.throws + ) class _TrappedResponse(object): @@ -237,17 +243,19 @@ class AppResponse(object): for k, v in r.header_list: if not isinstance(k, bytestr): raise TypeError( - "response.header_list key %r is not a byte string." % k) + "response.header_list key %r is not a byte string." % + k) if not isinstance(v, bytestr): raise TypeError( - "response.header_list value %r is not a byte string." % v) + "response.header_list value %r is not a byte string." % + v) outheaders.append((k, v)) if py3k: - # 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 code points in the - # "latin-1" set. + # 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 + # code points in the "latin-1" set. outstatus = outstatus.decode('ISO-8859-1') outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1')) for k, v in outheaders] @@ -301,16 +309,17 @@ class AppResponse(object): qs = self.environ.get('QUERY_STRING', '') if py3k: - # 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. + # 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. old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1') new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''), "request.uri_encoding", 'utf-8') if new_enc.lower() != old_enc.lower(): - # Even though the path and qs are unicode, the WSGI server is - # required by PEP 3333 to coerce them to ISO-8859-1 masquerading - # as unicode. So we have to encode back to bytes and then decode - # again using the "correct" encoding. + # Even though the path and qs are unicode, the WSGI server + # is required by PEP 3333 to coerce them to ISO-8859-1 + # masquerading as unicode. So we have to encode back to + # bytes and then decode again using the "correct" encoding. try: u_path = path.encode(old_enc).decode(new_enc) u_qs = qs.encode(old_enc).decode(new_enc) @@ -370,7 +379,8 @@ class CPWSGIApp(object): named WSGI callable (from the pipeline) as keyword arguments.""" response_class = AppResponse - """The class to instantiate and return as the next app in the WSGI chain.""" + """The class to instantiate and return as the next app in the WSGI chain. + """ def __init__(self, cpapp, pipeline=None): self.cpapp = cpapp diff --git a/cherrypy/_cpwsgi_server.py b/cherrypy/_cpwsgi_server.py index 52c2e421..b648bab1 100644 --- a/cherrypy/_cpwsgi_server.py +++ b/cherrypy/_cpwsgi_server.py @@ -19,8 +19,12 @@ class CPWSGIServer(wsgiserver.CherryPyWSGIServer): def __init__(self, server_adapter=cherrypy.server): self.server_adapter = server_adapter - self.max_request_header_size = self.server_adapter.max_request_header_size or 0 - self.max_request_body_size = self.server_adapter.max_request_body_size or 0 + self.max_request_header_size = ( + self.server_adapter.max_request_header_size or 0 + ) + self.max_request_body_size = ( + self.server_adapter.max_request_body_size or 0 + ) server_name = (self.server_adapter.socket_host or self.server_adapter.socket_file or diff --git a/cherrypy/lib/auth.py b/cherrypy/lib/auth.py index 79ca83ee..71591aaa 100644 --- a/cherrypy/lib/auth.py +++ b/cherrypy/lib/auth.py @@ -3,7 +3,8 @@ from cherrypy.lib import httpauth def check_auth(users, encrypt=None, realm=None): - """If an authorization header contains credentials, return True, else False.""" + """If an authorization header contains credentials, return True or False. + """ request = cherrypy.serving.request if 'authorization' in request.headers: # make sure the provided credentials are correctly set @@ -53,7 +54,8 @@ def basic_auth(realm, users, encrypt=None, debug=False): A string containing the authentication realm. users - A dict of the form: {username: password} or a callable returning a dict. + A dict of the form: {username: password} or a callable returning + a dict. encrypt callable used to encrypt the password returned from the user-agent. @@ -79,7 +81,8 @@ def digest_auth(realm, users, debug=False): realm A string containing the authentication realm. users - A dict of the form: {username: password} or a callable returning a dict. + A dict of the form: {username: password} or a callable returning + a dict. """ if check_auth(users, realm=realm): if debug: diff --git a/cherrypy/lib/auth_basic.py b/cherrypy/lib/auth_basic.py index 9b684b6a..5ba16f7f 100644 --- a/cherrypy/lib/auth_basic.py +++ b/cherrypy/lib/auth_basic.py @@ -3,7 +3,8 @@ # vim:ts=4:sw=4:expandtab:fileencoding=utf-8 __doc__ = """This module provides a CherryPy 3.x tool which implements -the server-side of HTTP Basic Access Authentication, as described in :rfc:`2617`. +the server-side of HTTP Basic Access Authentication, as described in +:rfc:`2617`. Example usage, using the built-in checkpassword_dict function which uses a dict as the credentials store:: diff --git a/cherrypy/lib/auth_digest.py b/cherrypy/lib/auth_digest.py index 0f3a64f2..e06535dc 100644 --- a/cherrypy/lib/auth_digest.py +++ b/cherrypy/lib/auth_digest.py @@ -103,8 +103,9 @@ def get_ha1_file_htdigest(filename): def synthesize_nonce(s, key, timestamp=None): - """Synthesize a nonce value which resists spoofing and can be checked for staleness. - Returns a string suitable as the value for 'nonce' in the www-authenticate header. + """Synthesize a nonce value which resists spoofing and can be checked + for staleness. Returns a string suitable as the value for 'nonce' in + the www-authenticate header. s A string related to the resource, such as the hostname of the server. @@ -166,13 +167,16 @@ class HttpDigestAuthorization (object): # perform some correctness checks if self.algorithm not in valid_algorithms: raise ValueError( - self.errmsg("Unsupported value for algorithm: '%s'" % self.algorithm)) - - has_reqd = self.username and \ - self.realm and \ - self.nonce and \ - self.uri and \ + self.errmsg("Unsupported value for algorithm: '%s'" % + self.algorithm)) + + has_reqd = ( + self.username and + self.realm and + self.nonce and + self.uri and self.response + ) if not has_reqd: raise ValueError( self.errmsg("Not all required parameters are present.")) @@ -183,28 +187,31 @@ class HttpDigestAuthorization (object): self.errmsg("Unsupported value for qop: '%s'" % self.qop)) if not (self.cnonce and self.nc): raise ValueError( - self.errmsg("If qop is sent then cnonce and nc MUST be present")) + self.errmsg("If qop is sent then " + "cnonce and nc MUST be present")) else: if self.cnonce or self.nc: raise ValueError( - self.errmsg("If qop is not sent, neither cnonce nor nc can be present")) + self.errmsg("If qop is not sent, " + "neither cnonce nor nc can be present")) def __str__(self): return 'authorization : %s' % self.auth_header def validate_nonce(self, s, key): """Validate the nonce. - Returns True if nonce was generated by synthesize_nonce() and the timestamp - is not spoofed, else returns False. + Returns True if nonce was generated by synthesize_nonce() and the + timestamp is not spoofed, else returns False. s - A string related to the resource, such as the hostname of the server. + A string related to the resource, such as the hostname of + the server. key A secret string known only to the server. - Both s and key must be the same values which were used to synthesize the nonce - we are trying to validate. + Both s and key must be the same values which were used to synthesize + the nonce we are trying to validate. """ try: timestamp, hashpart = self.nonce.split(':', 1) @@ -220,8 +227,9 @@ class HttpDigestAuthorization (object): def is_nonce_stale(self, max_age_seconds=600): """Returns True if a validated nonce is stale. The nonce contains a - timestamp in plaintext and also a secure hash of the timestamp. You should - first validate the nonce to ensure the plaintext timestamp is not spoofed. + timestamp in plaintext and also a secure hash of the timestamp. + You should first validate the nonce to ensure the plaintext + timestamp is not spoofed. """ try: timestamp, hashpart = self.nonce.split(':', 1) @@ -236,7 +244,8 @@ class HttpDigestAuthorization (object): def HA2(self, entity_body=''): """Returns the H(A2) string. See :rfc:`2617` section 3.2.2.3.""" # RFC 2617 3.2.2.3 - # If the "qop" directive's value is "auth" or is unspecified, then A2 is: + # If the "qop" directive's value is "auth" or is unspecified, + # then A2 is: # A2 = method ":" digest-uri-value # # If the "qop" value is "auth-int", then A2 is: @@ -261,9 +270,9 @@ class HttpDigestAuthorization (object): If 'qop' is set to 'auth-int', then A2 includes a hash of the "entity body". The entity body is the part of the message which follows the HTTP headers. See :rfc:`2617` section - 4.3. This refers to the entity the user agent sent in the request which - has the Authorization header. Typically GET requests don't have an entity, - and POST requests do. + 4.3. This refers to the entity the user agent sent in the + request which has the Authorization header. Typically GET + requests don't have an entity, and POST requests do. """ ha2 = self.HA2(entity_body) @@ -276,8 +285,9 @@ class HttpDigestAuthorization (object): # RFC 2617 3.2.2.2 # - # If the "algorithm" directive's value is "MD5" or is unspecified, then A1 is: - # A1 = unq(username-value) ":" unq(realm-value) ":" passwd + # If the "algorithm" directive's value is "MD5" or is unspecified, + # then A1 is: + # A1 = unq(username-value) ":" unq(realm-value) ":" passwd # # If the "algorithm" directive's value is "MD5-sess", then A1 is # calculated only once - on the first request by the client following @@ -291,7 +301,8 @@ class HttpDigestAuthorization (object): return digest -def www_authenticate(realm, key, algorithm='MD5', nonce=None, qop=qop_auth, stale=False): +def www_authenticate(realm, key, algorithm='MD5', nonce=None, qop=qop_auth, + stale=False): """Constructs a WWW-Authenticate header for Digest authentication.""" if qop not in valid_qops: raise ValueError("Unsupported value for qop: '%s'" % qop) @@ -311,11 +322,11 @@ def digest_auth(realm, get_ha1, key, debug=False): """A CherryPy tool which hooks at before_handler to perform HTTP Digest Access Authentication, as specified in :rfc:`2617`. - If the request has an 'authorization' header with a 'Digest' scheme, this - tool authenticates the credentials supplied in that header. If - the request has no 'authorization' header, or if it does but the scheme is - not "Digest", or if authentication fails, the tool sends a 401 response with - a 'WWW-Authenticate' Digest header. + If the request has an 'authorization' header with a 'Digest' scheme, + this tool authenticates the credentials supplied in that header. + If the request has no 'authorization' header, or if it does but the + scheme is not "Digest", or if authentication fails, the tool sends + a 401 response with a 'WWW-Authenticate' Digest header. realm A string containing the authentication realm. @@ -330,7 +341,8 @@ def digest_auth(realm, get_ha1, key, debug=False): None. key - A secret string known only to the server, used in the synthesis of nonces. + A secret string known only to the server, used in the synthesis + of nonces. """ request = cherrypy.serving.request @@ -351,9 +363,9 @@ def digest_auth(realm, get_ha1, key, debug=False): if auth.validate_nonce(realm, key): ha1 = get_ha1(realm, auth.username) if ha1 is not None: - # note that for request.body to be available we need to hook in at - # before_handler, not on_start_resource like 3.1.x digest_auth - # does. + # note that for request.body to be available we need to + # hook in at before_handler, not on_start_resource like + # 3.1.x digest_auth does. digest = auth.request_digest(ha1, entity_body=request.body) if digest == auth.response: # authenticated if debug: diff --git a/cherrypy/lib/caching.py b/cherrypy/lib/caching.py index cc13255a..fab6b569 100644 --- a/cherrypy/lib/caching.py +++ b/cherrypy/lib/caching.py @@ -1,7 +1,7 @@ """ -CherryPy implements a simple caching system as a pluggable Tool. This tool tries -to be an (in-process) HTTP/1.1-compliant cache. It's not quite there yet, but -it's probably good enough for most sites. +CherryPy implements a simple caching system as a pluggable Tool. This tool +tries to be an (in-process) HTTP/1.1-compliant cache. It's not quite there +yet, but it's probably good enough for most sites. In general, GET responses are cached (along with selecting headers) and, if another request arrives for the same resource, the caching Tool will return 304 @@ -9,8 +9,8 @@ Not Modified if possible, or serve the cached response otherwise. It also sets request.cached to True if serving a cached representation, and sets request.cacheable to False (so it doesn't get cached again). -If POST, PUT, or DELETE requests are made for a cached resource, they invalidate -(delete) any cached response. +If POST, PUT, or DELETE requests are made for a cached resource, they +invalidate (delete) any cached response. Usage ===== @@ -63,7 +63,7 @@ class Cache(object): raise NotImplemented -# ------------------------------- Memory Cache ------------------------------- # +# ------------------------------ Memory Cache ------------------------------- # class AntiStampedeCache(dict): """A storage system for cached items which reduces stampede collisions.""" @@ -153,7 +153,8 @@ class MemoryCache(Cache): """The maximum size of the entire cache in bytes; defaults to 10 MB.""" delay = 600 - """Seconds until the cached content expires; defaults to 600 (10 minutes).""" + """Seconds until the cached content expires; defaults to 600 (10 minutes). + """ antistampede_timeout = 5 """Seconds to wait for other threads to release a cache lock.""" diff --git a/cherrypy/lib/covercp.py b/cherrypy/lib/covercp.py index 83fa69b0..a74ec342 100644 --- a/cherrypy/lib/covercp.py +++ b/cherrypy/lib/covercp.py @@ -42,7 +42,8 @@ except ImportError: import warnings warnings.warn( - "No code coverage will be performed; coverage.py could not be imported.") + "No code coverage will be performed; " + "coverage.py could not be imported.") def start(): pass @@ -121,10 +122,13 @@ TEMPLATE_FORM = """ <div id="options"> <form action='menu' method=GET> <input type='hidden' name='base' value='%(base)s' /> - Show percentages <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br /> - Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br /> + Show percentages + <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br /> + Hide files over + <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br /> Exclude files matching<br /> - <input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' /> + <input type='text' id='exclude' name='exclude' + value='%(exclude)s' size='20' /> <br /> <input type='submit' value='Change view' id="submit"/> @@ -176,7 +180,9 @@ TEMPLATE_LOC_EXCLUDED = """<tr class="excluded"> <td>%s</td> </tr>\n""" -TEMPLATE_ITEM = "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\n" +TEMPLATE_ITEM = ( + "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\n" +) def _percent(statements, missing): @@ -199,10 +205,16 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="", if newpath.lower().startswith(base): relpath = newpath[len(base):] yield "| " * relpath.count(os.sep) - yield "<a class='directory' href='menu?base=%s&exclude=%s'>%s</a>\n" % \ + yield ( + "<a class='directory' " + "href='menu?base=%s&exclude=%s'>%s</a>\n" % (newpath, quote_plus(exclude), name) + ) - for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude, coverage=coverage): + for chunk in _show_branch( + root[name], base, newpath, pct, showpct, + exclude, coverage=coverage + ): yield chunk # Now list the files @@ -310,7 +322,8 @@ class CoverStats(object): yield "<p>No modules covered.</p>" else: for chunk in _show_branch(tree, base, "/", pct, - showpct == 'checked', exclude, coverage=self.coverage): + showpct == 'checked', exclude, + coverage=self.coverage): yield chunk yield "</div>" diff --git a/cherrypy/lib/cpstats.py b/cherrypy/lib/cpstats.py index 3539507a..a8661a14 100644 --- a/cherrypy/lib/cpstats.py +++ b/cherrypy/lib/cpstats.py @@ -23,7 +23,8 @@ re-use the `logging` module by adding a `statistics` object to it. That `logging.statistics` object is a nested dict. It is not a custom class, because that would: - 1. require libraries and applications to import a third-party module in order to participate + 1. require libraries and applications to import a third-party module in + order to participate 2. inhibit innovation in extrapolation approaches and in reporting tools, and 3. be slow. @@ -89,17 +90,17 @@ scalar values you already have on hand. When it comes time to report on the gathered data, however, we usually have much more freedom in what we can calculate. Therefore, whenever reporting -tools (like the provided :class:`StatsPage` CherryPy class) fetch the contents of -`logging.statistics` for reporting, they first call `extrapolate_statistics` -(passing the whole `statistics` dict as the only argument). This makes a -deep copy of the statistics dict so that the reporting tool can both iterate -over it and even change it without harming the original. But it also expands -any functions in the dict by calling them. For example, you might have a -'Current Time' entry in the namespace with the value "lambda scope: time.time()". -The "scope" parameter is the current namespace dict (or record, if we're -currently expanding one of those instead), allowing you access to existing -static entries. If you're truly evil, you can even modify more than one entry -at a time. +tools (like the provided :class:`StatsPage` CherryPy class) fetch the contents +of `logging.statistics` for reporting, they first call +`extrapolate_statistics` (passing the whole `statistics` dict as the only +argument). This makes a deep copy of the statistics dict so that the +reporting tool can both iterate over it and even change it without harming +the original. But it also expands any functions in the dict by calling them. +For example, you might have a 'Current Time' entry in the namespace with the +value "lambda scope: time.time()". The "scope" parameter is the current +namespace dict (or record, if we're currently expanding one of those +instead), allowing you access to existing static entries. If you're truly +evil, you can even modify more than one entry at a time. However, don't try to calculate an entry and then use its value in further extrapolations; the order in which the functions are called is not guaranteed. @@ -111,19 +112,20 @@ After the whole thing has been extrapolated, it's time for: Reporting --------- -The :class:`StatsPage` class grabs the `logging.statistics` dict, extrapolates it all, -and then transforms it to HTML for easy viewing. Each namespace gets its own -header and attribute table, plus an extra table for each collection. This is -NOT part of the statistics specification; other tools can format how they like. +The :class:`StatsPage` class grabs the `logging.statistics` dict, extrapolates +it all, and then transforms it to HTML for easy viewing. Each namespace gets +its own header and attribute table, plus an extra table for each collection. +This is NOT part of the statistics specification; other tools can format how +they like. You can control which columns are output and how they are formatted by updating StatsPage.formatting, which is a dict that mirrors the keys and nesting of `logging.statistics`. The difference is that, instead of data values, it has formatting values. Use None for a given key to indicate to the StatsPage that a -given column should not be output. Use a string with formatting (such as '%.3f') -to interpolate the value(s), or use a callable (such as lambda v: v.isoformat()) -for more advanced formatting. Any entry which is not mentioned in the formatting -dict is output unchanged. +given column should not be output. Use a string with formatting +(such as '%.3f') to interpolate the value(s), or use a callable (such as +lambda v: v.isoformat()) for more advanced formatting. Any entry which is not +mentioned in the formatting dict is output unchanged. Monitoring ---------- @@ -185,7 +187,7 @@ To format statistics reports:: """ -# -------------------------------- Statistics -------------------------------- # +# ------------------------------- Statistics -------------------------------- # import logging if not hasattr(logging, 'statistics'): @@ -206,7 +208,7 @@ def extrapolate_statistics(scope): return c -# --------------------- CherryPy Applications Statistics --------------------- # +# -------------------- CherryPy Applications Statistics --------------------- # import threading import time @@ -216,12 +218,20 @@ import cherrypy appstats = logging.statistics.setdefault('CherryPy Applications', {}) appstats.update({ 'Enabled': True, - 'Bytes Read/Request': lambda s: (s['Total Requests'] and - (s['Total Bytes Read'] / float(s['Total Requests'])) or 0.0), + 'Bytes Read/Request': lambda s: ( + s['Total Requests'] and + (s['Total Bytes Read'] / float(s['Total Requests'])) or + 0.0 + ), 'Bytes Read/Second': lambda s: s['Total Bytes Read'] / s['Uptime'](s), - 'Bytes Written/Request': lambda s: (s['Total Requests'] and - (s['Total Bytes Written'] / float(s['Total Requests'])) or 0.0), - 'Bytes Written/Second': lambda s: s['Total Bytes Written'] / s['Uptime'](s), + 'Bytes Written/Request': lambda s: ( + s['Total Requests'] and + (s['Total Bytes Written'] / float(s['Total Requests'])) or + 0.0 + ), + 'Bytes Written/Second': lambda s: ( + s['Total Bytes Written'] / s['Uptime'](s) + ), 'Current Time': lambda s: time.time(), 'Current Requests': 0, 'Requests/Second': lambda s: float(s['Total Requests']) / s['Uptime'](s), @@ -512,8 +522,11 @@ table.stats2 th { if colnum == 0: yield """ <tr>""" - yield """ - <th>%(key)s</th><td id='%(title)s-%(key)s'>%(value)s</td>""" % vars() + yield ( + """ + <th>%(key)s</th><td id='%(title)s-%(key)s'>%(value)s</td>""" % + vars() + ) if colnum == 2: yield """ </tr>""" diff --git a/cherrypy/lib/cptools.py b/cherrypy/lib/cptools.py index 066fb4af..84b8fad0 100644 --- a/cherrypy/lib/cptools.py +++ b/cherrypy/lib/cptools.py @@ -302,13 +302,17 @@ class SessionAuth(object): def on_check(self, username): pass - def login_screen(self, from_page='..', username='', error_msg='', **kwargs): + def login_screen(self, from_page='..', username='', error_msg='', + **kwargs): return (unicodestr("""<html><body> Message: %(error_msg)s <form method="post" action="do_login"> - Login: <input type="text" name="username" value="%(username)s" size="10" /><br /> - Password: <input type="password" name="password" size="10" /><br /> - <input type="hidden" name="from_page" value="%(from_page)s" /><br /> + Login: <input type="text" name="username" value="%(username)s" size="10" /> + <br /> + Password: <input type="password" name="password" size="10" /> + <br /> + <input type="hidden" name="from_page" value="%(from_page)s" /> + <br /> <input type="submit" /> </form> </body></html>""") % vars()).encode("utf-8") @@ -341,7 +345,8 @@ Message: %(error_msg)s raise cherrypy.HTTPRedirect(from_page) def do_check(self): - """Assert username. May raise redirect, or return True if request handled.""" + """Assert username. Raise redirect, or return True if request handled. + """ sess = cherrypy.session request = cherrypy.serving.request response = cherrypy.serving.response @@ -607,7 +612,8 @@ class MonitoredHeaderMap(_httputil.HeaderMap): def autovary(ignore=None, debug=False): - """Auto-populate the Vary response header based on request.header access.""" + """Auto-populate the Vary response header based on request.header access. + """ request = cherrypy.serving.request req_h = request.headers diff --git a/cherrypy/lib/encoding.py b/cherrypy/lib/encoding.py index d0b66b49..59ae3b15 100644 --- a/cherrypy/lib/encoding.py +++ b/cherrypy/lib/encoding.py @@ -14,13 +14,13 @@ def decode(encoding=None, default_encoding='utf-8'): encoding If not None, restricts the set of charsets attempted while decoding - a request entity to the given set (even if a different charset is given in - the Content-Type request header). + a request entity to the given set (even if a different charset is + given in the Content-Type request header). default_encoding Only in effect if the 'encoding' argument is not given. - If given, the set of charsets attempted while decoding a request entity is - *extended* with the given value(s). + If given, the set of charsets attempted while decoding a request + entity is *extended* with the given value(s). """ body = cherrypy.request.body @@ -219,8 +219,8 @@ class ResponseEncoder: do_find = True else: if self.debug: - cherrypy.log('Not finding because Content-Type %s does ' - 'not start with "text/"' % ct, + cherrypy.log('Not finding because Content-Type %s ' + 'does not start with "text/"' % ct, 'TOOLS.ENCODE') do_find = False else: @@ -284,7 +284,8 @@ def decompress(body): return data -def gzip(compress_level=5, mime_types=['text/html', 'text/plain'], debug=False): +def gzip(compress_level=5, mime_types=['text/html', 'text/plain'], + debug=False): """Try to gzip the response body if Content-Type in mime_types. cherrypy.response.headers['Content-Type'] must be set to one of the diff --git a/cherrypy/lib/gctools.py b/cherrypy/lib/gctools.py index 46365576..4b616c59 100644 --- a/cherrypy/lib/gctools.py +++ b/cherrypy/lib/gctools.py @@ -134,13 +134,14 @@ class GCRoot(object): """A CherryPy page handler for testing reference leaks.""" - classes = [(_cprequest.Request, 2, 2, - "Should be 1 in this request thread and 1 in the main thread."), - (_cprequest.Response, 2, 2, - "Should be 1 in this request thread and 1 in the main thread."), - (_cpwsgi.AppResponse, 1, 1, - "Should be 1 in this request thread only."), - ] + classes = [ + (_cprequest.Request, 2, 2, + "Should be 1 in this request thread and 1 in the main thread."), + (_cprequest.Response, 2, 2, + "Should be 1 in this request thread and 1 in the main thread."), + (_cpwsgi.AppResponse, 1, 1, + "Should be 1 in this request thread only."), + ] def index(self): return "Hello, world!" diff --git a/cherrypy/lib/httpauth.py b/cherrypy/lib/httpauth.py index 6b223955..66d254e6 100644 --- a/cherrypy/lib/httpauth.py +++ b/cherrypy/lib/httpauth.py @@ -1,5 +1,6 @@ """ -This module defines functions to implement HTTP Digest Authentication (:rfc:`2617`). +This module defines functions to implement HTTP Digest Authentication +(:rfc:`2617`). This has full compliance with 'Digest' and 'Basic' authentication methods. In 'Digest' it supports both MD5 and MD5-sess algorithms. @@ -11,9 +12,10 @@ Usage: Then use 'parseAuthorization' to retrieve the 'auth_map' used in 'checkResponse'. - To use 'checkResponse' you must have already verified the password associated - with the 'username' key in 'auth_map' dict. Then you use the 'checkResponse' - function to verify if the password matches the one sent by the client. + To use 'checkResponse' you must have already verified the password + associated with the 'username' key in 'auth_map' dict. Then you use the + 'checkResponse' function to verify if the password matches the one sent + by the client. SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms SUPPORTED_QOP - list of supported 'Digest' 'qop'. @@ -21,7 +23,8 @@ SUPPORTED_QOP - list of supported 'Digest' 'qop'. __version__ = 1, 0, 1 __author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>" __credits__ = """ - Peter van Kampen for its recipe which implement most of Digest authentication: + Peter van Kampen for its recipe which implement most of Digest + authentication: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/302378 """ @@ -29,17 +32,17 @@ __license__ = """ Copyright (c) 2005, Tiago Cogumbreiro <cogumbreiro@users.sf.net> All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Sylvain Hellegouarch nor the names of his contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. + * Neither the name of Sylvain Hellegouarch nor the names of his + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -260,7 +263,8 @@ def _A2(params, method, kwargs): raise NotImplementedError("The 'qop' method is unknown: %s" % qop) -def _computeDigestResponse(auth_map, password, method="GET", A1=None, **kwargs): +def _computeDigestResponse(auth_map, password, method="GET", A1=None, + **kwargs): """ Generates a response respecting the algorithm defined in RFC 2617 """ @@ -326,7 +330,8 @@ def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs): return response == auth_map["response"] -def _checkBasicResponse(auth_map, password, method='GET', encrypt=None, **kwargs): +def _checkBasicResponse(auth_map, password, method='GET', encrypt=None, + **kwargs): # Note that the Basic response doesn't provide the realm value so we cannot # test it try: @@ -347,15 +352,16 @@ def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs): If the response is of type 'Basic' then the function has the following signature:: - checkBasicResponse (auth_map, password) -> bool + checkBasicResponse(auth_map, password) -> bool If the response is of type 'Digest' then the function has the following signature:: - checkDigestResponse (auth_map, password, method = 'GET', A1 = None) -> bool + checkDigestResponse(auth_map, password, method='GET', A1=None) -> bool The 'A1' argument is only used in MD5_SESS algorithm based responses. Check md5SessionKey() for more info. """ checker = AUTH_RESPONSES[auth_map["auth_scheme"]] - return checker(auth_map, password, method=method, encrypt=encrypt, **kwargs) + return checker(auth_map, password, method=method, encrypt=encrypt, + **kwargs) diff --git a/cherrypy/lib/httputil.py b/cherrypy/lib/httputil.py index ad86cfba..585a4824 100644 --- a/cherrypy/lib/httputil.py +++ b/cherrypy/lib/httputil.py @@ -8,8 +8,9 @@ to a public caning. """ from binascii import b2a_base64 -from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou, reversed, sorted -from cherrypy._cpcompat import basestring, bytestr, iteritems, nativestr, unicodestr, unquote_qs +from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou +from cherrypy._cpcompat import basestring, bytestr, iteritems, nativestr +from cherrypy._cpcompat import reversed, sorted, unicodestr, unquote_qs response_codes = BaseHTTPRequestHandler.responses.copy() # From https://bitbucket.org/cherrypy/cherrypy/issue/361 @@ -213,7 +214,8 @@ class AcceptElement(HeaderElement): def header_elements(fieldname, fieldvalue): - """Return a sorted HeaderElement list from a comma-separated header string.""" + """Return a sorted HeaderElement list from a comma-separated header string. + """ if not fieldvalue: return [] @@ -381,7 +383,7 @@ class CaseInsensitiveDict(dict): if hasattr({}, 'has_key'): def has_key(self, key): - return dict.has_key(self, str(key).title()) + return str(key).title() in self def update(self, E): for k in E.keys(): diff --git a/cherrypy/lib/jsontools.py b/cherrypy/lib/jsontools.py index d3dfa5ad..1450806a 100644 --- a/cherrypy/lib/jsontools.py +++ b/cherrypy/lib/jsontools.py @@ -65,7 +65,8 @@ def json_handler(*args, **kwargs): return json_encode(value) -def json_out(content_type='application/json', debug=False, handler=json_handler): +def json_out(content_type='application/json', debug=False, + handler=json_handler): """Wrap request.handler to serialize its output to JSON. Sets Content-Type. If the given content_type is None, the Content-Type response header diff --git a/cherrypy/lib/profiler.py b/cherrypy/lib/profiler.py index 536ad217..5dac386e 100644 --- a/cherrypy/lib/profiler.py +++ b/cherrypy/lib/profiler.py @@ -35,7 +35,8 @@ module from the command line, it will call ``serve()`` for you. def new_func_strip_path(func_name): - """Make profiler output more readable by adding ``__init__`` modules' parents""" + """Make profiler output more readable by adding `__init__` modules' parents + """ filename, line, name = func_name if filename.endswith("__init__.py"): return os.path.basename(filename[:-12]) + filename[-12:], line, name @@ -126,7 +127,8 @@ class Profiler(object): runs = self.statfiles() runs.sort() for i in runs: - yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (i, i) + yield "<a href='report?filename=%s' target='main'>%s</a><br />" % ( + i, i) menu.exposed = True def report(self, filename): @@ -170,9 +172,11 @@ class make_app: """ if profile is None or pstats is None: - msg = ("Your installation of Python does not have a profile module. " - "If you're on Debian, try `sudo apt-get install python-profiler`. " - "See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.") + msg = ("Your installation of Python does not have a profile " + "module. If you're on Debian, try " + "`sudo apt-get install python-profiler`. " + "See http://www.cherrypy.org/wiki/ProfilingOnDebian " + "for details.") warnings.warn(msg) self.nextapp = nextapp @@ -194,8 +198,10 @@ class make_app: def serve(path=None, port=8080): if profile is None or pstats is None: msg = ("Your installation of Python does not have a profile module. " - "If you're on Debian, try `sudo apt-get install python-profiler`. " - "See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.") + "If you're on Debian, try " + "`sudo apt-get install python-profiler`. " + "See http://www.cherrypy.org/wiki/ProfilingOnDebian " + "for details.") warnings.warn(msg) import cherrypy diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py index 4afa7a63..68358bd9 100644 --- a/cherrypy/lib/sessions.py +++ b/cherrypy/lib/sessions.py @@ -8,10 +8,11 @@ You need to edit your config file to use sessions. Here's an example:: tools.sessions.storage_path = "/home/site/sessions" tools.sessions.timeout = 60 -This sets the session to be stored in files in the directory /home/site/sessions, -and the session timeout to 60 minutes. If you omit ``storage_type`` the sessions -will be saved in RAM. ``tools.sessions.on`` is the only required line for -working sessions, the rest are optional. +This sets the session to be stored in files in the directory +/home/site/sessions, and the session timeout to 60 minutes. If you omit +``storage_type`` the sessions will be saved in RAM. +``tools.sessions.on`` is the only required line for working sessions, +the rest are optional. By default, the session ID is passed in a cookie, so the client's browser must have cookies enabled for your site. @@ -30,9 +31,9 @@ for any requests that take a long time to process (streaming responses, expensive calculations, database lookups, API calls, etc), as other concurrent requests that also utilize sessions will hang until the session is unlocked. -If you want to control when the -session data is locked and unlocked, set ``tools.sessions.locking = 'explicit'``. -Then call ``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``. +If you want to control when the session data is locked and unlocked, +set ``tools.sessions.locking = 'explicit'``. Then call +``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``. Regardless of which mode you use, the session is guaranteed to be unlocked when the request is complete. @@ -478,7 +479,8 @@ class FileSession(Session): return os.path.exists(path) def _load(self, path=None): - assert self.locked, "The session load without being locked. Check your tools' priority levels." + assert self.locked, ("The session load without being locked. " + "Check your tools' priority levels.") if path is None: path = self._get_file_path() try: @@ -495,7 +497,8 @@ class FileSession(Session): return None def _save(self, expiration_time): - assert self.locked, "The session was saved without being locked. Check your tools' priority levels." + assert self.locked, ("The session was saved without being locked. " + "Check your tools' priority levels.") f = open(self._get_file_path(), "wb") try: pickle.dump((self._data, expiration_time), f, self.pickle_protocol) @@ -503,7 +506,8 @@ class FileSession(Session): f.close() def _delete(self): - assert self.locked, "The session deletion without being locked. Check your tools' priority levels." + assert self.locked, ("The session deletion without being locked. " + "Check your tools' priority levels.") try: os.unlink(self._get_file_path()) except OSError: @@ -911,8 +915,11 @@ def set_response_cookie(path=None, path_header=None, name='session_id', # Set response cookie cookie = cherrypy.serving.response.cookie cookie[name] = cherrypy.serving.session.id - cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_header) - or '/') + cookie[name]['path'] = ( + path or + cherrypy.serving.request.headers.get(path_header) or + '/' + ) # We'd like to use the "max-age" param as indicated in # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't diff --git a/cherrypy/lib/static.py b/cherrypy/lib/static.py index 83ffec88..d5bb9d02 100644 --- a/cherrypy/lib/static.py +++ b/cherrypy/lib/static.py @@ -20,7 +20,8 @@ from cherrypy._cpcompat import ntob, unquote from cherrypy.lib import cptools, httputil, file_generator_limited -def serve_file(path, content_type=None, disposition=None, name=None, debug=False): +def serve_file(path, content_type=None, disposition=None, name=None, + debug=False): """Set status, headers, and body in order to serve the given path. The Content-Type header will be set to the content_type arg, if provided. @@ -158,7 +159,8 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False): r = httputil.get_ranges(request.headers.get('Range'), content_length) if r == []: response.headers['Content-Range'] = "bytes */%s" % content_length - message = "Invalid Range (first-byte-pos greater than Content-Length)" + message = ("Invalid Range (first-byte-pos greater than " + "Content-Length)") if debug: cherrypy.log(message, 'TOOLS.STATIC') raise cherrypy.HTTPError(416, message) @@ -185,11 +187,11 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False): response.status = "206 Partial Content" try: # Python 3 - from email.generator import _make_boundary as choose_boundary + from email.generator import _make_boundary as make_boundary except ImportError: # Python 2 - from mimetools import choose_boundary - boundary = choose_boundary() + from mimetools import choose_boundary as make_boundary + boundary = make_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: @@ -207,11 +209,15 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False): start, stop), 'TOOLS.STATIC') yield ntob("--" + boundary, 'ascii') - yield ntob("\r\nContent-type: %s" % content_type, 'ascii') - yield ntob("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" - % (start, stop - 1, content_length), 'ascii') + yield ntob("\r\nContent-type: %s" % content_type, + 'ascii') + yield ntob( + "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % ( + start, stop - 1, content_length), + 'ascii') fileobj.seek(start) - for chunk in file_generator_limited(fileobj, stop - start): + gen = file_generator_limited(fileobj, stop - start) + for chunk in gen: yield chunk yield ntob("\r\n") # Final boundary @@ -362,7 +368,8 @@ def staticfile(filename, root=None, match="", content_types=None, debug=False): # If filename is relative, make absolute using "root". if not os.path.isabs(filename): if not root: - msg = "Static tool requires an absolute filename (got '%s')." % filename + msg = "Static tool requires an absolute filename (got '%s')." % ( + filename,) if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) diff --git a/cherrypy/process/plugins.py b/cherrypy/process/plugins.py index b5fba389..4d7dcbfd 100644 --- a/cherrypy/process/plugins.py +++ b/cherrypy/process/plugins.py @@ -7,7 +7,8 @@ import sys import time import threading -from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident, ntob, set, Timer, SetDaemonProperty +from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident +from cherrypy._cpcompat import ntob, set, Timer, SetDaemonProperty # _module__file__base is used by Autoreload to make # absolute any filenames retrieved from sys.modules which are not @@ -19,8 +20,8 @@ from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident, ntob, s # changes the current directory by executing os.chdir(), then the next time # Autoreload runs, it will not be able to find any filenames which are # not absolute paths, because the current directory is not the same as when the -# module was first imported. Autoreload will then wrongly conclude the file has -# "changed", and initiate the shutdown/re-exec sequence. +# module was first imported. Autoreload will then wrongly conclude the file +# has "changed", and initiate the shutdown/re-exec sequence. # See ticket #917. # For this workaround to have a decent probability of success, this module # needs to be imported as early as possible, before the app has much chance @@ -33,7 +34,8 @@ class SimplePlugin(object): """Plugin base class which auto-subscribes methods for known channels.""" bus = None - """A :class:`Bus <cherrypy.process.wspbus.Bus>`, usually cherrypy.engine.""" + """A :class:`Bus <cherrypy.process.wspbus.Bus>`, usually cherrypy.engine. + """ def __init__(self, bus): self.bus = bus @@ -75,9 +77,9 @@ class SignalHandler(object): if the process is attached to a TTY. This is because Unix window managers tend to send SIGHUP to terminal windows when the user closes them. - Feel free to add signals which are not available on every platform. The - :class:`SignalHandler` will ignore errors raised from attempting to register - handlers for unknown signals. + Feel free to add signals which are not available on every platform. + The :class:`SignalHandler` will ignore errors raised from attempting + to register handlers for unknown signals. """ handlers = {} @@ -250,8 +252,11 @@ class DropPrivileges(SimplePlugin): level=30) val = None self._umask = val - umask = property(_get_umask, _set_umask, - doc="""The default permission mode for newly created files and directories. + umask = property( + _get_umask, + _set_umask, + doc="""The default permission mode for newly created files and + directories. Usually expressed in octal format, for example, ``0644``. Availability: Unix, Windows. @@ -512,7 +517,9 @@ class Monitor(SimplePlugin): """The time in seconds between callback runs.""" thread = None - """A :class:`BackgroundTask<cherrypy.process.plugins.BackgroundTask>` thread.""" + """A :class:`BackgroundTask<cherrypy.process.plugins.BackgroundTask>` + thread. + """ def __init__(self, bus, callback, frequency=60, name=None): SimplePlugin.__init__(self, bus) @@ -567,9 +574,9 @@ class Autoreloader(Monitor): cherrypy.engine.autoreload.files.add(myFile) - If there are imported files you do *not* wish to monitor, you can adjust the - ``match`` attribute, a regular expression. For example, to stop monitoring - cherrypy itself:: + If there are imported files you do *not* wish to monitor, you can + adjust the ``match`` attribute, a regular expression. For example, + to stop monitoring cherrypy itself:: cherrypy.engine.autoreload.match = r'^(?!cherrypy).+' @@ -605,7 +612,10 @@ class Autoreloader(Monitor): files = set() for k, m in sys.modules.items(): if re.match(self.match, k): - if hasattr(m, '__loader__') and hasattr(m.__loader__, 'archive'): + if ( + hasattr(m, '__loader__') and + hasattr(m.__loader__, 'archive') + ): f = m.__loader__.archive else: f = getattr(m, '__file__', None) diff --git a/cherrypy/process/servers.py b/cherrypy/process/servers.py index cda1d470..fef37f77 100644 --- a/cherrypy/process/servers.py +++ b/cherrypy/process/servers.py @@ -13,7 +13,9 @@ protocols, etc.), you can manually register each one and then start them all with engine.start:: s1 = ServerAdapter(cherrypy.engine, MyWSGIServer(host='0.0.0.0', port=80)) - s2 = ServerAdapter(cherrypy.engine, another.HTTPServer(host='127.0.0.1', SSL=True)) + s2 = ServerAdapter(cherrypy.engine, + another.HTTPServer(host='127.0.0.1', + SSL=True)) s1.subscribe() s2.subscribe() cherrypy.engine.start() @@ -107,8 +109,8 @@ directive, configure your fastcgi script like the following:: } # end of $HTTP["url"] =~ "^/" Please see `Lighttpd FastCGI Docs -<http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ for an explanation -of the possible configuration options. +<http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ for +an explanation of the possible configuration options. """ import sys @@ -314,7 +316,8 @@ class FlupFCGIServer(object): # Forcibly stop the fcgi server main event loop. self.fcgiserver._keepGoing = False # Force all worker threads to die off. - self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount + self.fcgiserver._threadPool.maxSpare = ( + self.fcgiserver._threadPool._idleCount) self.ready = False @@ -363,7 +366,8 @@ def client_host(server_host): return '127.0.0.1' if server_host in ('::', '::0', '::0.0.0.0'): # :: is IN6ADDR_ANY, which should answer on localhost. - # ::0 and ::0.0.0.0 are non-canonical but common ways to write IN6ADDR_ANY. + # ::0 and ::0.0.0.0 are non-canonical but common + # ways to write IN6ADDR_ANY. return '::1' return server_host @@ -384,8 +388,9 @@ def check_port(host, port, timeout=1.0): socket.SOCK_STREAM) except socket.gaierror: if ':' in host: - info = [ - (socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))] + info = [( + socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0) + )] else: info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))] diff --git a/cherrypy/process/wspbus.py b/cherrypy/process/wspbus.py index cce3a553..5409d038 100644 --- a/cherrypy/process/wspbus.py +++ b/cherrypy/process/wspbus.py @@ -81,7 +81,8 @@ _startup_cwd = os.getcwd() class ChannelFailures(Exception): - """Exception raised when errors occur in a listener during Bus.publish().""" + """Exception raised when errors occur in a listener during Bus.publish(). + """ delimiter = '\n' def __init__(self, *args, **kwargs): @@ -325,11 +326,14 @@ class Bus(object): self.log("Waiting for child threads to terminate...") for t in threading.enumerate(): # Validate the we're not trying to join the MainThread - # that will cause a deadlock and the case exist when imlemented as a - # windows service and in any other case that another thread - # executes cherrypy.engine.exit() - if t != threading.currentThread() and t.isAlive() \ - and not isinstance(t, threading._MainThread): + # that will cause a deadlock and the case exist when + # implemented as a windows service and in any other case + # that another thread executes cherrypy.engine.exit() + if ( + t != threading.currentThread() and + t.isAlive() and + not isinstance(t, threading._MainThread) + ): # Note that any dummy (external) threads are always daemonic. if hasattr(threading.Thread, "daemon"): # Python 2.6+ diff --git a/cherrypy/test/benchmark.py b/cherrypy/test/benchmark.py index 1d02e6d0..cb3ce9f6 100644 --- a/cherrypy/test/benchmark.py +++ b/cherrypy/test/benchmark.py @@ -1,7 +1,7 @@ """CherryPy Benchmark Tool Usage: - benchmark.py --null --notests --help --cpmodpy --modpython --ab=path --apache=path + benchmark.py [options] --null: use a null Request object (to bench the HTTP server only) --notests: start the server but do not run the tests; this allows @@ -190,20 +190,21 @@ Percentage of the requests served within a certain time (ms) Finished 1000 requests """ - parse_patterns = [('complete_requests', 'Completed', - ntob(r'^Complete requests:\s*(\d+)')), - ('failed_requests', 'Failed', - ntob(r'^Failed requests:\s*(\d+)')), - ('requests_per_second', 'req/sec', - ntob(r'^Requests per second:\s*([0-9.]+)')), - ('time_per_request_concurrent', 'msec/req', - ntob( - r'^Time per request:\s*([0-9.]+).*concurrent requests\)$')), - ('transfer_rate', 'KB/sec', - ntob(r'^Transfer rate:\s*([0-9.]+)')), - ] - - def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, concurrency=10): + parse_patterns = [ + ('complete_requests', 'Completed', + ntob(r'^Complete requests:\s*(\d+)')), + ('failed_requests', 'Failed', + ntob(r'^Failed requests:\s*(\d+)')), + ('requests_per_second', 'req/sec', + ntob(r'^Requests per second:\s*([0-9.]+)')), + ('time_per_request_concurrent', 'msec/req', + ntob(r'^Time per request:\s*([0-9.]+).*concurrent requests\)$')), + ('transfer_rate', 'KB/sec', + ntob(r'^Transfer rate:\s*([0-9.]+)')) + ] + + def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, + concurrency=10): self.path = path self.requests = requests self.concurrency = concurrency @@ -306,7 +307,8 @@ def run_standard_benchmarks(): # modpython and other WSGI # def startup_modpython(req=None): - """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI).""" + """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI). + """ if cherrypy.engine.state == cherrypy._cpengine.STOPPED: if req: if "nullreq" in req.get_options(): diff --git a/cherrypy/test/helper.py b/cherrypy/test/helper.py index 07a3f76d..fbb70771 100644 --- a/cherrypy/test/helper.py +++ b/cherrypy/test/helper.py @@ -70,12 +70,13 @@ log_to_stderr = lambda msg, level: sys.stderr.write(msg + os.linesep) class LocalSupervisor(Supervisor): - """Base class for modeling/controlling servers which run in the same process. + """Base class for modeling/controlling servers which run in the same + process. When the server side runs in a different process, start/stop can dump all state between each test module easily. When the server side runs in the - same process as the client, however, we have to do a bit more work to ensure - config and mounted apps are reset between tests. + same process as the client, however, we have to do a bit more work to + ensure config and mounted apps are reset between tests. """ using_apache = False @@ -337,11 +338,13 @@ class CPWebCase(webtest.WebCase): def exit(self): sys.exit() - def getPage(self, url, headers=None, method="GET", body=None, protocol=None): + def getPage(self, url, headers=None, method="GET", body=None, + protocol=None): """Open the url. Return status, headers, body.""" if self.script_name: url = httputil.urljoin(self.script_name, url) - return webtest.WebCase.getPage(self, url, headers, method, body, protocol) + return webtest.WebCase.getPage(self, url, headers, method, body, + protocol) def skip(self, msg='skipped '): raise nose.SkipTest(msg) @@ -359,8 +362,9 @@ class CPWebCase(webtest.WebCase): # Stick a match-all group (.*) in to grab the traceback. esc = re.escape epage = esc(page) - epage = epage.replace(esc('<pre id="traceback"></pre>'), - esc('<pre id="traceback">') + '(.*)' + esc('</pre>')) + epage = epage.replace( + esc('<pre id="traceback"></pre>'), + esc('<pre id="traceback">') + '(.*)' + esc('</pre>')) m = re.match(ntob(epage, self.encoding), self.body, re.DOTALL) if not m: self._handlewebError( @@ -422,7 +426,8 @@ log.access_file: r'%(access_log)s' error_log = os.path.join(thisdir, 'test.error.log') access_log = os.path.join(thisdir, 'test.access.log') - def __init__(self, wait=False, daemonize=False, ssl=False, socket_host=None, socket_port=None): + def __init__(self, wait=False, daemonize=False, ssl=False, + socket_host=None, socket_port=None): self.wait = wait self.daemonize = daemonize self.ssl = ssl diff --git a/cherrypy/test/logtest.py b/cherrypy/test/logtest.py index 3482c9ec..cc59499a 100644 --- a/cherrypy/test/logtest.py +++ b/cherrypy/test/logtest.py @@ -54,7 +54,9 @@ class LogCase(object): if not self.interactive: raise self.failureException(msg) - p = " Show: [L]og [M]arker [P]attern; [I]gnore, [R]aise, or sys.e[X]it >> " + p = (" Show: " + "[L]og [M]arker [P]attern; " + "[I]gnore, [R]aise, or sys.e[X]it >> ") sys.stdout.write(p + ' ') # ARGH sys.stdout.flush() @@ -100,8 +102,8 @@ class LogCase(object): key = str(time.time()) self.lastmarker = key - open(self.logfile, 'ab+').write(ntob("%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. @@ -175,7 +177,11 @@ class LogCase(object): if lines not in data[sliceargs]: msg = "%r not found on log line %r" % (lines, sliceargs) self._handleLogError( - msg, [data[sliceargs], "--EXTRA CONTEXT--"] + data[sliceargs + 1:sliceargs + 6], marker, lines) + msg, + [data[sliceargs], "--EXTRA CONTEXT--"] + data[ + sliceargs + 1:sliceargs + 6], + marker, + lines) else: # Multiple args. Use __getslice__ and require lines to be list. if isinstance(lines, tuple): diff --git a/cherrypy/test/sessiondemo.py b/cherrypy/test/sessiondemo.py index 95531e37..f76b130a 100755 --- a/cherrypy/test/sessiondemo.py +++ b/cherrypy/test/sessiondemo.py @@ -106,7 +106,8 @@ class Root(object): 'Created new session because no session id was given.') if cherrypy.session.missing: changemsg.append( - 'Created new session due to missing (expired or malicious) session.') + 'Created new session due to missing ' + '(expired or malicious) session.') if cherrypy.session.regenerated: changemsg.append('Application generated a new session.') @@ -121,7 +122,9 @@ class Root(object): 'respcookie': cherrypy.response.cookie.output(), 'reqcookie': cherrypy.request.cookie.output(), 'sessiondata': copyitems(cherrypy.session), - 'servertime': datetime.utcnow().strftime("%Y/%m/%d %H:%M") + " UTC", + 'servertime': ( + datetime.utcnow().strftime("%Y/%m/%d %H:%M") + " UTC" + ), 'serverunixtime': calendar.timegm(datetime.utcnow().timetuple()), 'cpversion': cherrypy.__version__, 'pyversion': sys.version, diff --git a/cherrypy/test/test_auth_basic.py b/cherrypy/test/test_auth_basic.py index 757dfee7..7ba11dfc 100644 --- a/cherrypy/test/test_auth_basic.py +++ b/cherrypy/test/test_auth_basic.py @@ -20,13 +20,15 @@ class BasicAuthTest(helper.CPWebCase): class BasicProtected: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True class BasicProtected2: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True userpassdict = {'xuser': 'xpassword'} @@ -36,13 +38,19 @@ class BasicAuthTest(helper.CPWebCase): p = userhashdict.get(user) return p and p == md5(ntob(password)).hexdigest() or False - conf = {'/basic': {'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'wonderland', - 'tools.auth_basic.checkpassword': auth_basic.checkpassword_dict(userpassdict)}, - '/basic2': {'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'wonderland', - 'tools.auth_basic.checkpassword': checkpasshash}, - } + basic_checkpassword_dict = auth_basic.checkpassword_dict(userpassdict) + conf = { + '/basic': { + 'tools.auth_basic.on': True, + 'tools.auth_basic.realm': 'wonderland', + 'tools.auth_basic.checkpassword': basic_checkpassword_dict + }, + '/basic2': { + 'tools.auth_basic.on': True, + 'tools.auth_basic.realm': 'wonderland', + 'tools.auth_basic.checkpassword': checkpasshash + }, + } root = Root() root.basic = BasicProtected() diff --git a/cherrypy/test/test_auth_digest.py b/cherrypy/test/test_auth_digest.py index 1a4d6842..b46698d9 100644 --- a/cherrypy/test/test_auth_digest.py +++ b/cherrypy/test/test_auth_digest.py @@ -21,7 +21,8 @@ class DigestAuthTest(helper.CPWebCase): class DigestProtected: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True def fetch_users(): @@ -93,7 +94,15 @@ class DigestAuthTest(helper.CPWebCase): get_ha1 = auth_digest.get_ha1_dict_plain({'test': 'test'}) # Test user agent response with a wrong value for 'realm' - base_auth = 'Digest username="test", realm="wrong realm", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"' + base_auth = ('Digest username="test", ' + 'realm="wrong realm", ' + 'nonce="%s", ' + 'uri="/digest/", ' + 'algorithm=MD5, ' + 'response="%s", ' + 'qop=auth, ' + 'nc=%s, ' + 'cnonce="1522e61005789929"') auth_header = base_auth % ( nonce, '11111111111111111111111111111111', '00000001') @@ -107,7 +116,15 @@ class DigestAuthTest(helper.CPWebCase): self.assertStatus(401) # Test that must pass - base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"' + base_auth = ('Digest username="test", ' + 'realm="localhost", ' + 'nonce="%s", ' + 'uri="/digest/", ' + 'algorithm=MD5, ' + 'response="%s", ' + 'qop=auth, ' + 'nc=%s, ' + 'cnonce="1522e61005789929"') auth_header = base_auth % ( nonce, '11111111111111111111111111111111', '00000001') diff --git a/cherrypy/test/test_bus.py b/cherrypy/test/test_bus.py index 865fbac2..06ad6acb 100644 --- a/cherrypy/test/test_bus.py +++ b/cherrypy/test/test_bus.py @@ -102,8 +102,9 @@ class BusMethodTests(unittest.TestCase): b.start() try: # The start method MUST call all 'start' listeners. - self.assertEqual(set(self.responses), - set([msg % (i, 'start', None) for i in range(num)])) + self.assertEqual( + set(self.responses), + set([msg % (i, 'start', None) for i in range(num)])) # The start method MUST move the state to STARTED # (or EXITING, if errors occur) self.assertEqual(b.state, b.states.STARTED) @@ -144,8 +145,9 @@ class BusMethodTests(unittest.TestCase): b.graceful() # The graceful method MUST call all 'graceful' listeners. - self.assertEqual(set(self.responses), - set([msg % (i, 'graceful', None) for i in range(num)])) + self.assertEqual( + set(self.responses), + set([msg % (i, 'graceful', None) for i in range(num)])) # The graceful method MUST log its states. self.assertLog(['Bus graceful']) diff --git a/cherrypy/test/test_caching.py b/cherrypy/test/test_caching.py index f62cead8..be1950d6 100644 --- a/cherrypy/test/test_caching.py +++ b/cherrypy/test/test_caching.py @@ -12,9 +12,11 @@ import cherrypy from cherrypy._cpcompat import next, ntob, quote, xrange from cherrypy.lib import httputil -gif_bytes = ntob('GIF89a\x01\x00\x01\x00\x82\x00\x01\x99"\x1e\x00\x00\x00\x00\x00' - '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - '\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x02\x03\x02\x08\t\x00;') +gif_bytes = ntob( + 'GIF89a\x01\x00\x01\x00\x82\x00\x01\x99"\x1e\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + '\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x02\x03\x02\x08\t\x00;' +) from cherrypy.test import helper @@ -65,10 +67,13 @@ class CacheTest(helper.CPWebCase): class VaryHeaderCachingServer(object): - _cp_config = {'tools.caching.on': True, - 'tools.response_headers.on': True, - 'tools.response_headers.headers': [('Vary', 'Our-Varying-Header')], - } + _cp_config = { + 'tools.caching.on': True, + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [ + ('Vary', 'Our-Varying-Header') + ], + } def __init__(self): self.counter = count(1) diff --git a/cherrypy/test/test_config_server.py b/cherrypy/test/test_config_server.py index 302e99b5..40504d8f 100644 --- a/cherrypy/test/test_config_server.py +++ b/cherrypy/test/test_config_server.py @@ -102,9 +102,14 @@ class ServerConfigTests(helper.CPWebCase): ('From', lines256)]) # Test upload + cd = ( + 'Content-Disposition: form-data; ' + 'name="file"; ' + 'filename="hello.txt"' + ) body = '\r\n'.join([ '--x', - 'Content-Disposition: form-data; name="file"; filename="hello.txt"', + cd, 'Content-Type: text/plain', '', '%s', diff --git a/cherrypy/test/test_conn.py b/cherrypy/test/test_conn.py index 304f1e79..9aa071e4 100644 --- a/cherrypy/test/test_conn.py +++ b/cherrypy/test/test_conn.py @@ -7,8 +7,8 @@ timeout = 1 import cherrypy -from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, NotConnected, BadStatusLine -from cherrypy._cpcompat import ntob, urlopen, unicodestr +from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, NotConnected +from cherrypy._cpcompat import BadStatusLine, ntob, urlopen, unicodestr from cherrypy.test import webtest from cherrypy import _cperror diff --git a/cherrypy/test/test_core.py b/cherrypy/test/test_core.py index 534c0350..a09125da 100644 --- a/cherrypy/test/test_core.py +++ b/cherrypy/test/test_core.py @@ -48,8 +48,9 @@ class CoreRequestHandlingTest(helper.CPWebCase): class TestType(type): - """Metaclass which automatically exposes all functions in each subclass, - and adds an instance of the subclass as an attribute of root. + """Metaclass which automatically exposes all functions in each + subclass, and adds an instance of the subclass as an attribute + of root. """ def __init__(cls, name, bases, dct): type.__init__(cls, name, bases, dct) @@ -184,7 +185,8 @@ class CoreRequestHandlingTest(helper.CPWebCase): raise cherrypy.InternalRedirect( '/image/getImagesByUser?user_id=%s' % str(user_id)) - # We support Python 2.3, but the @-deco syntax would look like this: + # We support Python 2.3, but the @-deco syntax would look like + # this: # @tools.login_redir() def secure(self): return "Welcome!" @@ -234,7 +236,8 @@ class CoreRequestHandlingTest(helper.CPWebCase): def slice_file(self): path = os.path.join(os.getcwd(), os.path.dirname(__file__)) - return static.serve_file(os.path.join(path, "static/index.html")) + return static.serve_file( + os.path.join(path, "static/index.html")) class Cookies(Test): @@ -396,7 +399,8 @@ class CoreRequestHandlingTest(helper.CPWebCase): frag = "foo" self.getPage("/redirect/fragment/%s" % frag) self.assertMatchesBody( - r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag)) + r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % ( + frag, frag)) loc = self.assertHeader('Location') assert loc.endswith("#%s" % frag) self.assertStatus(('302 Found', '303 See Other')) @@ -404,7 +408,8 @@ class CoreRequestHandlingTest(helper.CPWebCase): # check injection protection # See https://bitbucket.org/cherrypy/cherrypy/issue/1003 self.getPage( - "/redirect/custom?code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval") + "/redirect/custom?" + "code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval") self.assertStatus(303) loc = self.assertHeader('Location') assert 'Set-Cookie' in loc diff --git a/cherrypy/test/test_dynamicobjectmapping.py b/cherrypy/test/test_dynamicobjectmapping.py index 5f0538e5..a64e9925 100644 --- a/cherrypy/test/test_dynamicobjectmapping.py +++ b/cherrypy/test/test_dynamicobjectmapping.py @@ -141,7 +141,8 @@ def setup_server(): def PUT(self, name): """ - Create a new user with the specified id, or edit it if it already exists + Create a new user with the specified id, or edit it if it already + exists """ if self.user: # Edit the current user @@ -233,7 +234,8 @@ def setup_server(): def index(self): if 'a' in cherrypy.request.params: raise Exception( - "Parameterized handler argument ended up in request.params") + "Parameterized handler argument ended up in " + "request.params") return self.a index.exposed = True diff --git a/cherrypy/test/test_encoding.py b/cherrypy/test/test_encoding.py index e15e24c3..3961b4d4 100644 --- a/cherrypy/test/test_encoding.py +++ b/cherrypy/test/test_encoding.py @@ -68,8 +68,9 @@ class EncodingTests(helper.CPWebCase): index.exposed = True def noshow(self): - # Test for ticket #147, where yield showed no exceptions (content- - # encoding was still gzip even though traceback wasn't zipped). + # Test for ticket #147, where yield showed no exceptions + # (content-encoding was still gzip even though traceback + # wasn't zipped). raise IndexError() yield "Here be dragons" noshow.exposed = True @@ -77,8 +78,9 @@ class EncodingTests(helper.CPWebCase): noshow._cp_config = {'tools.encode.on': False} def noshow_stream(self): - # Test for ticket #147, where yield showed no exceptions (content- - # encoding was still gzip even though traceback wasn't zipped). + # Test for ticket #147, where yield showed no exceptions + # (content-encoding was still gzip even though traceback + # wasn't zipped). raise IndexError() yield "Here be dragons" noshow_stream.exposed = True @@ -125,17 +127,19 @@ class EncodingTests(helper.CPWebCase): # Here, q is the POUND SIGN U+00A3 encoded in latin1 and then %HEX self.getPage("/reqparams?q=%A3") self.assertStatus(404) - self.assertErrorPage(404, - "The given query string could not be processed. Query " - "strings for this resource must be encoded with 'utf8'.") + self.assertErrorPage( + 404, + "The given query string could not be processed. Query " + "strings for this resource must be encoded with 'utf8'.") def test_urlencoded_decoding(self): # Test the decoding of an application/x-www-form-urlencoded entity. europoundUtf8 = europoundUnicode.encode('utf-8') body = ntob("param=") + europoundUtf8 - self.getPage('/', method='POST', - headers=[( - "Content-Type", "application/x-www-form-urlencoded"), + self.getPage('/', + method='POST', + headers=[ + ("Content-Type", "application/x-www-form-urlencoded"), ("Content-Length", str(len(body))), ], body=body), @@ -154,9 +158,11 @@ class EncodingTests(helper.CPWebCase): # ...and in utf16, which is not in the default attempt_charsets list: body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00") - self.getPage('/reqparams', method='POST', - headers=[( - "Content-Type", "application/x-www-form-urlencoded;charset=utf-16"), + self.getPage('/reqparams', + method='POST', + headers=[ + ("Content-Type", + "application/x-www-form-urlencoded;charset=utf-16"), ("Content-Length", str(len(body))), ], body=body), @@ -166,16 +172,19 @@ class EncodingTests(helper.CPWebCase): # Here, q is the POUND SIGN U+00A3 encoded in utf16, but # the Content-Type incorrectly labels it utf-8. body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00") - self.getPage('/reqparams', method='POST', - headers=[( - "Content-Type", "application/x-www-form-urlencoded;charset=utf-8"), + self.getPage('/reqparams', + method='POST', + headers=[ + ("Content-Type", + "application/x-www-form-urlencoded;charset=utf-8"), ("Content-Length", str(len(body))), ], body=body), self.assertStatus(400) - self.assertErrorPage(400, - "The request entity could not be decoded. The following charsets " - "were attempted: ['utf-8']") + self.assertErrorPage( + 400, + "The request entity could not be decoded. The following charsets " + "were attempted: ['utf-8']") def test_decode_tool(self): # An extra charset should be tried first, and succeed if it matches. @@ -210,24 +219,27 @@ class EncodingTests(helper.CPWebCase): ("Content-Length", str(len(body))), ], body=body), - self.assertErrorPage(400, - "The request entity could not be decoded. The following charsets " - "were attempted: ['utf-16']") + self.assertErrorPage( + 400, + "The request entity could not be decoded. The following charsets " + "were attempted: ['utf-16']") def test_multipart_decoding(self): # Test the decoding of a multipart entity when the charset (utf16) is # explicitly given. - body = ntob('\r\n'.join(['--X', - 'Content-Type: text/plain;charset=utf-16', - 'Content-Disposition: form-data; name="text"', - '', - '\xff\xfea\x00b\x00\x1c c\x00', - '--X', - 'Content-Type: text/plain;charset=utf-16', - 'Content-Disposition: form-data; name="submit"', - '', - '\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00', - '--X--'])) + body = ntob('\r\n'.join([ + '--X', + 'Content-Type: text/plain;charset=utf-16', + 'Content-Disposition: form-data; name="text"', + '', + '\xff\xfea\x00b\x00\x1c c\x00', + '--X', + 'Content-Type: text/plain;charset=utf-16', + 'Content-Disposition: form-data; name="submit"', + '', + '\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00', + '--X--' + ])) self.getPage('/reqparams', method='POST', headers=[( "Content-Type", "multipart/form-data;boundary=X"), @@ -239,15 +251,17 @@ class EncodingTests(helper.CPWebCase): def test_multipart_decoding_no_charset(self): # Test the decoding of a multipart entity when the charset (utf8) is # NOT explicitly given, but is in the list of charsets to attempt. - body = ntob('\r\n'.join(['--X', - 'Content-Disposition: form-data; name="text"', - '', - '\xe2\x80\x9c', - '--X', - 'Content-Disposition: form-data; name="submit"', - '', - 'Create', - '--X--'])) + body = ntob('\r\n'.join([ + '--X', + 'Content-Disposition: form-data; name="text"', + '', + '\xe2\x80\x9c', + '--X', + 'Content-Disposition: form-data; name="submit"', + '', + 'Create', + '--X--' + ])) self.getPage('/reqparams', method='POST', headers=[( "Content-Type", "multipart/form-data;boundary=X"), @@ -259,15 +273,17 @@ class EncodingTests(helper.CPWebCase): def test_multipart_decoding_no_successful_charset(self): # Test the decoding of a multipart entity when the charset (utf16) is # NOT explicitly given, and is NOT in the list of charsets to attempt. - body = ntob('\r\n'.join(['--X', - 'Content-Disposition: form-data; name="text"', - '', - '\xff\xfea\x00b\x00\x1c c\x00', - '--X', - 'Content-Disposition: form-data; name="submit"', - '', - '\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00', - '--X--'])) + body = ntob('\r\n'.join([ + '--X', + 'Content-Disposition: form-data; name="text"', + '', + '\xff\xfea\x00b\x00\x1c c\x00', + '--X', + 'Content-Disposition: form-data; name="submit"', + '', + '\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00', + '--X--' + ])) self.getPage('/reqparams', method='POST', headers=[( "Content-Type", "multipart/form-data;boundary=X"), @@ -275,9 +291,10 @@ class EncodingTests(helper.CPWebCase): ], body=body), self.assertStatus(400) - self.assertErrorPage(400, - "The request entity could not be decoded. The following charsets " - "were attempted: ['us-ascii', 'utf-8']") + self.assertErrorPage( + 400, + "The request entity could not be decoded. The following charsets " + "were attempted: ['us-ascii', 'utf-8']") def test_nontext(self): self.getPage('/nontext') diff --git a/cherrypy/test/test_http.py b/cherrypy/test/test_http.py index e85da8da..10df13b4 100644 --- a/cherrypy/test/test_http.py +++ b/cherrypy/test/test_http.py @@ -49,7 +49,9 @@ class HTTPTests(helper.CPWebCase): no_body._cp_config = {'request.process_request_body': False} def post_multipart(self, file): - """Return a summary ("a * 65536\nb * 65536") of the uploaded file.""" + """Return a summary ("a * 65536\nb * 65536") of the uploaded + file. + """ contents = file.file.read() summary = [] curchar = None diff --git a/cherrypy/test/test_httpauth.py b/cherrypy/test/test_httpauth.py index df382ca2..98a300f0 100644 --- a/cherrypy/test/test_httpauth.py +++ b/cherrypy/test/test_httpauth.py @@ -17,19 +17,22 @@ class HTTPAuthTest(helper.CPWebCase): class DigestProtected: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True class BasicProtected: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True class BasicProtected2: def index(self): - return "Hello %s, you've been authorized." % cherrypy.request.login + return "Hello %s, you've been authorized." % ( + cherrypy.request.login) index.exposed = True def fetch_users(): @@ -41,16 +44,26 @@ class HTTPAuthTest(helper.CPWebCase): def fetch_password(username): return sha(ntob('test')).hexdigest() - conf = {'/digest': {'tools.digest_auth.on': True, - 'tools.digest_auth.realm': 'localhost', - 'tools.digest_auth.users': fetch_users}, - '/basic': {'tools.basic_auth.on': True, - 'tools.basic_auth.realm': 'localhost', - 'tools.basic_auth.users': {'test': md5(ntob('test')).hexdigest()}}, - '/basic2': {'tools.basic_auth.on': True, - 'tools.basic_auth.realm': 'localhost', - 'tools.basic_auth.users': fetch_password, - 'tools.basic_auth.encrypt': sha_password_encrypter}} + conf = { + '/digest': { + 'tools.digest_auth.on': True, + 'tools.digest_auth.realm': 'localhost', + 'tools.digest_auth.users': fetch_users + }, + '/basic': { + 'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'localhost', + 'tools.basic_auth.users': { + 'test': md5(ntob('test')).hexdigest() + } + }, + '/basic2': { + 'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'localhost', + 'tools.basic_auth.users': fetch_password, + 'tools.basic_auth.encrypt': sha_password_encrypter + } + } root = Root() root.digest = DigestProtected() @@ -135,7 +148,18 @@ class HTTPAuthTest(helper.CPWebCase): ('qop', '"auth"', tokens['qop'])) # Test a wrong 'realm' value - base_auth = 'Digest username="test", realm="wrong realm", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"' + base_auth = ( + 'Digest ' + 'username="test", ' + 'realm="wrong realm", ' + 'nonce="%s", ' + 'uri="/digest/", ' + 'algorithm=MD5, ' + 'response="%s", ' + 'qop=auth, ' + 'nc=%s, ' + 'cnonce="1522e61005789929"' + ) auth = base_auth % (nonce, '', '00000001') params = httpauth.parseAuthorization(auth) @@ -146,7 +170,18 @@ class HTTPAuthTest(helper.CPWebCase): self.assertStatus(401) # Test that must pass - base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"' + base_auth = ( + 'Digest ' + 'username="test", ' + 'realm="localhost", ' + 'nonce="%s", ' + 'uri="/digest/", ' + 'algorithm=MD5, ' + 'response="%s", ' + 'qop=auth, ' + 'nc=%s, ' + 'cnonce="1522e61005789929"' + ) auth = base_auth % (nonce, '', '00000001') params = httpauth.parseAuthorization(auth) diff --git a/cherrypy/test/test_mime.py b/cherrypy/test/test_mime.py index 0c094f12..f5f2b9fb 100644 --- a/cherrypy/test/test_mime.py +++ b/cherrypy/test/test_mime.py @@ -35,7 +35,8 @@ class MultipartTest(helper.CPWebCase): def test_multipart(self): text_part = ntou("This is the text version") - html_part = ntou("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> + html_part = ntou( + """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type"> @@ -65,22 +66,24 @@ This is the <strong>HTML</strong> version self.assertBody(repr([text_part, html_part])) def test_multipart_form_data(self): - body = '\r\n'.join(['--X', - 'Content-Disposition: form-data; name="foo"', - '', - 'bar', - '--X', - # Test a param with more than one value. - # See - # https://bitbucket.org/cherrypy/cherrypy/issue/1028 - 'Content-Disposition: form-data; name="baz"', - '', - '111', - '--X', - 'Content-Disposition: form-data; name="baz"', - '', - '333', - '--X--']) + body = '\r\n'.join([ + '--X', + 'Content-Disposition: form-data; name="foo"', + '', + 'bar', + '--X', + # Test a param with more than one value. + # See + # https://bitbucket.org/cherrypy/cherrypy/issue/1028 + 'Content-Disposition: form-data; name="baz"', + '', + '111', + '--X', + 'Content-Disposition: form-data; name="baz"', + '', + '333', + '--X--' + ]) self.getPage('/multipart_form_data', method='POST', headers=[( "Content-Type", "multipart/form-data;boundary=X"), diff --git a/cherrypy/test/test_misc_tools.py b/cherrypy/test/test_misc_tools.py index aa18950e..df3e2e65 100644 --- a/cherrypy/test/test_misc_tools.py +++ b/cherrypy/test/test_misc_tools.py @@ -196,10 +196,11 @@ class AcceptTest(helper.CPWebCase): # Try unacceptable media types self.getPage('/accept/select', [('Accept', 'application/xml')]) - self.assertErrorPage(406, - "Your client sent this Accept header: application/xml. " - "But this resource only emits these media types: " - "text/html, text/plain.") + self.assertErrorPage( + 406, + "Your client sent this Accept header: application/xml. " + "But this resource only emits these media types: " + "text/html, text/plain.") class AutoVaryTest(helper.CPWebCase): @@ -208,4 +209,7 @@ class AutoVaryTest(helper.CPWebCase): def testAutoVary(self): self.getPage('/autovary/') self.assertHeader( - "Vary", 'Accept, Accept-Charset, Accept-Encoding, Host, If-Modified-Since, Range') + "Vary", + 'Accept, Accept-Charset, Accept-Encoding, ' + 'Host, If-Modified-Since, Range' + ) diff --git a/cherrypy/test/test_objectmapping.py b/cherrypy/test/test_objectmapping.py index 55c77b38..28362b63 100644 --- a/cherrypy/test/test_objectmapping.py +++ b/cherrypy/test/test_objectmapping.py @@ -78,7 +78,8 @@ class ObjectMappingTest(helper.CPWebCase): index.exposed = True def myMethod(self): - return "myMethod from dir1, path_info is:" + repr(cherrypy.request.path_info) + return "myMethod from dir1, path_info is:" + repr( + cherrypy.request.path_info) myMethod.exposed = True myMethod._cp_config = {'tools.trailing_slash.extra': True} @@ -234,7 +235,8 @@ class ObjectMappingTest(helper.CPWebCase): self.assertHeader('Location', '%s/dir1/' % self.base()) if not getattr(cherrypy.server, "using_apache", False): - # Test that we can use URL's which aren't all valid Python identifiers + # Test that we can use URL's which aren't all valid Python + # identifiers # This should also test the %XX-unquoting of URL's. self.getPage("/Von%20B%fclow?ID=14") self.assertBody("ID is 14") diff --git a/cherrypy/test/test_proxy.py b/cherrypy/test/test_proxy.py index d4961d18..49356448 100644 --- a/cherrypy/test/test_proxy.py +++ b/cherrypy/test/test_proxy.py @@ -84,7 +84,9 @@ class ProxyTest(helper.CPWebCase): headers=[('X-Forwarded-For', '192.168.0.20')]) self.assertBody("192.168.0.20") self.getPage("/remoteip", - headers=[('X-Forwarded-For', '67.15.36.43, 192.168.0.20')]) + headers=[ + ('X-Forwarded-For', '67.15.36.43, 192.168.0.20') + ]) self.assertBody("192.168.0.20") # Test X-Host (lighttpd; see https://trac.lighttpd.net/trac/ticket/418) @@ -104,8 +106,9 @@ class ProxyTest(helper.CPWebCase): for sn in script_names: # Test the value inside requests self.getPage(sn + "/newurl") - self.assertBody("Browse to <a href='%s://www.mydomain.test" % self.scheme - + sn + "/this/new/page'>this page</a>.") + self.assertBody( + "Browse to <a href='%s://www.mydomain.test" % self.scheme + + sn + "/this/new/page'>this page</a>.") self.getPage(sn + "/newurl", headers=[('X-Forwarded-Host', 'http://www.example.test')]) self.assertBody("Browse to <a href='http://www.example.test" diff --git a/cherrypy/test/test_request_obj.py b/cherrypy/test/test_request_obj.py index c94e848e..d9989e97 100644 --- a/cherrypy/test/test_request_obj.py +++ b/cherrypy/test/test_request_obj.py @@ -35,9 +35,9 @@ class RequestObjectTests(helper.CPWebCase): root = Root() class TestType(type): - - """Metaclass which automatically exposes all functions in each subclass, - and adds an instance of the subclass as an attribute of root. + """Metaclass which automatically exposes all functions in each + subclass, and adds an instance of the subclass as an attribute + of root. """ def __init__(cls, name, bases, dct): type.__init__(cls, name, bases, dct) @@ -115,7 +115,8 @@ class RequestObjectTests(helper.CPWebCase): raise_type_error_with_default_param.exposed = True def callable_error_page(status, **kwargs): - return "Error %s - Well, I'm very sorry but you haven't paid!" % status + return "Error %s - Well, I'm very sorry but you haven't paid!" % ( + status) class Error(Test): @@ -162,7 +163,9 @@ class RequestObjectTests(helper.CPWebCase): 'request.show_tracebacks': False} def rethrow(self): - """Test that an error raised here will be thrown out to the server.""" + """Test that an error raised here will be thrown out to + the server. + """ raise ValueError() rethrow._cp_config = {'request.throw_errors': True} @@ -182,9 +185,10 @@ class RequestObjectTests(helper.CPWebCase): def doubledheaders(self): # From https://bitbucket.org/cherrypy/cherrypy/issue/165: - # "header field names should not be case sensitive sayes the rfc. - # if i set a headerfield in complete lowercase i end up with two - # header fields, one in lowercase, the other in mixed-case." + # "header field names should not be case sensitive sayes the + # rfc. if i set a headerfield in complete lowercase i end up + # with two header fields, one in lowercase, the other in + # mixed-case." # Set the most common headers hMap = cherrypy.response.headers @@ -239,10 +243,11 @@ class RequestObjectTests(helper.CPWebCase): class Divorce: """HTTP Method handlers shouldn't collide with normal method names. - For example, a GET-handler shouldn't collide with a method named 'get'. + For example, a GET-handler shouldn't collide with a method named + 'get'. - If you build HTTP method dispatching into CherryPy, rewrite this class - to use your new dispatch mechanism and make sure that: + If you build HTTP method dispatching into CherryPy, rewrite this + class to use your new dispatch mechanism and make sure that: "GET /divorce HTTP/1.1" maps to divorce.index() and "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get() """ @@ -253,8 +258,9 @@ class RequestObjectTests(helper.CPWebCase): yield "<h1>Choose your document</h1>\n" yield "<ul>\n" for id, contents in self.documents.items(): - yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n" - % (id, id, contents)) + yield ( + " <li><a href='/divorce/get?ID=%s'>%s</a>:" + " %s</li>\n" % (id, id, contents)) yield "</ul>" index.exposed = True @@ -273,7 +279,9 @@ class RequestObjectTests(helper.CPWebCase): return existing appconf = { - '/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")}, + '/method': { + 'request.methods_with_bodies': ("POST", "PUT", "PROPFIND") + }, } cherrypy.tree.mount(root, config=appconf) setup_server = staticmethod(setup_server) @@ -346,17 +354,23 @@ class RequestObjectTests(helper.CPWebCase): '/paramerrors/one_positional_args?param1=foo', '/paramerrors/one_positional_args/foo', '/paramerrors/one_positional_args/foo/bar/baz', - '/paramerrors/one_positional_args_kwargs?param1=foo¶m2=bar', - '/paramerrors/one_positional_args_kwargs/foo?param2=bar¶m3=baz', - '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz', - '/paramerrors/one_positional_kwargs?param1=foo¶m2=bar¶m3=baz', - '/paramerrors/one_positional_kwargs/foo?param4=foo¶m2=bar¶m3=baz', + '/paramerrors/one_positional_args_kwargs?' + 'param1=foo¶m2=bar', + '/paramerrors/one_positional_args_kwargs/foo?' + 'param2=bar¶m3=baz', + '/paramerrors/one_positional_args_kwargs/foo/bar/baz?' + 'param2=bar¶m3=baz', + '/paramerrors/one_positional_kwargs?' + 'param1=foo¶m2=bar¶m3=baz', + '/paramerrors/one_positional_kwargs/foo?' + 'param4=foo¶m2=bar¶m3=baz', '/paramerrors/no_positional', '/paramerrors/no_positional_args/foo', '/paramerrors/no_positional_args/foo/bar/baz', '/paramerrors/no_positional_args_kwargs?param1=foo¶m2=bar', '/paramerrors/no_positional_args_kwargs/foo?param2=bar', - '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz', + '/paramerrors/no_positional_args_kwargs/foo/bar/baz?' + 'param2=bar¶m3=baz', '/paramerrors/no_positional_kwargs?param1=foo¶m2=bar', '/paramerrors/callable_object', ): @@ -383,9 +397,11 @@ class RequestObjectTests(helper.CPWebCase): error_msgs[2]), ('/paramerrors/one_positional_args/foo/bar/baz?param2=foo', error_msgs[3]), - ('/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar¶m3=baz', + ('/paramerrors/one_positional_args_kwargs/foo/bar/baz?' + 'param1=bar¶m3=baz', error_msgs[2]), - ('/paramerrors/one_positional_kwargs/foo?param1=foo¶m2=bar¶m3=baz', + ('/paramerrors/one_positional_kwargs/foo?' + 'param1=foo¶m2=bar¶m3=baz', error_msgs[2]), ('/paramerrors/no_positional/boo', error_msgs[1]), ('/paramerrors/no_positional?param1=foo', error_msgs[3]), @@ -521,13 +537,15 @@ class RequestObjectTests(helper.CPWebCase): self.getPage("/error/custom?err=401") self.assertStatus(401) self.assertBody( - "Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!") + "Error 401 Unauthorized - " + "Well, I'm very sorry but you haven't paid!") # Test default custom error page. self.getPage("/error/custom_default") self.assertStatus(500) self.assertBody( - "Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513)) + "Error 500 Internal Server Error - " + "Well, I'm very sorry but you haven't paid!".ljust(513)) # Test error in custom error page (ticket #305). # Note that the message is escaped for HTML (ticket #310). @@ -567,7 +585,9 @@ class RequestObjectTests(helper.CPWebCase): "audio/*;q=0.2") h = [ - ('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')] + ('Accept', + 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c') + ] self.getPage("/headerelements/get_elements?headername=Accept", h) self.assertStatus(200) self.assertBody("text/x-c\n" @@ -655,11 +675,12 @@ class RequestObjectTests(helper.CPWebCase): [('If-Match', ntou('=?utf-8?q?%s?=') % (c * 10))]) self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m") * 10) # Note: this is different output for Python3, but it decodes fine. - etag = self.assertHeader("ETag", - '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' - '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' - '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' - '4oSrbmdzdHLDtm0=?=') + etag = self.assertHeader( + "ETag", + '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' + '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' + '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' + '4oSrbmdzdHLDtm0=?=') self.assertEqual(httputil.decode_TEXT(etag), u * 10) def test_header_presence(self): diff --git a/cherrypy/test/test_routes.py b/cherrypy/test/test_routes.py index 312168c5..41f744f9 100644 --- a/cherrypy/test/test_routes.py +++ b/cherrypy/test/test_routes.py @@ -29,8 +29,12 @@ class RoutesDispatchTest(helper.CPWebCase): def index(self, **kwargs): return "Welcome to %s, pop. %s" % (self.name, self.population) - index._cp_config = {'tools.response_headers.on': True, - 'tools.response_headers.headers': [('Content-Language', 'en-GB')]} + index._cp_config = { + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [ + ('Content-Language', 'en-GB') + ] + } def update(self, **kwargs): self.population = kwargs['pop'] diff --git a/cherrypy/test/test_session.py b/cherrypy/test/test_session.py index 70155a50..355a2a3a 100755 --- a/cherrypy/test/test_session.py +++ b/cherrypy/test/test_session.py @@ -307,9 +307,10 @@ class SessionTest(helper.CPWebCase): # grab the cookie ID id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1] self.getPage('/testStr', - headers=[('Cookie', - 'session_id=maliciousid; ' - 'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')]) + headers=[ + ('Cookie', + 'session_id=maliciousid; ' + 'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')]) id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1] self.assertNotEqual(id1, id2) self.assertNotEqual(id2, 'maliciousid') diff --git a/cherrypy/test/test_sessionauthenticate.py b/cherrypy/test/test_sessionauthenticate.py index dd8f500d..2a6aa8d0 100644 --- a/cherrypy/test/test_sessionauthenticate.py +++ b/cherrypy/test/test_sessionauthenticate.py @@ -13,20 +13,21 @@ class SessionAuthenticateTest(helper.CPWebCase): def augment_params(): # A simple tool to add some things to request.params - # This is to check to make sure that session_auth can handle request - # params (ticket #780) + # This is to check to make sure that session_auth can handle + # request params (ticket #780) cherrypy.request.params["test"] = "test" - cherrypy.tools.augment_params = cherrypy.Tool('before_handler', - augment_params, None, priority=30) + cherrypy.tools.augment_params = cherrypy.Tool( + 'before_handler', augment_params, None, priority=30) class Test: - _cp_config = {'tools.sessions.on': True, - 'tools.session_auth.on': True, - 'tools.session_auth.check_username_and_password': check, - 'tools.augment_params.on': True, - } + _cp_config = { + 'tools.sessions.on': True, + 'tools.session_auth.on': True, + 'tools.session_auth.check_username_and_password': check, + 'tools.augment_params.on': True, + } def index(self, **kwargs): return "Hi %s, you are logged in" % cherrypy.request.login diff --git a/cherrypy/test/test_static.py b/cherrypy/test/test_static.py index 7e45b5df..21bc635d 100644 --- a/cherrypy/test/test_static.py +++ b/cherrypy/test/test_static.py @@ -252,14 +252,15 @@ class StaticTest(helper.CPWebCase): expected = len(body) if tell_position >= BIGFILE_SIZE: - # We can't exactly control how much content the server asks for. + # We can't exactly control how much content the server asks + # for. # Fudge it by only checking the first half of the reads. if expected < (BIGFILE_SIZE / 2): self.fail( - "The file should have advanced to position %r, but has " - "already advanced to the end of the file. It may not be " - "streamed as intended, or at the wrong chunk size (64k)" % - expected) + "The file should have advanced to position %r, but " + "has already advanced to the end of the file. It " + "may not be streamed as intended, or at the wrong " + "chunk size (64k)" % expected) elif tell_position < expected: self.fail( "The file should have advanced to position %r, but has " diff --git a/cherrypy/test/test_tools.py b/cherrypy/test/test_tools.py index 62815108..d650ca5e 100644 --- a/cherrypy/test/test_tools.py +++ b/cherrypy/test/test_tools.py @@ -162,9 +162,9 @@ class ToolTests(helper.CPWebCase): root = Root() class TestType(type): - - """Metaclass which automatically exposes all functions in each subclass, - and adds an instance of the subclass as an attribute of root. + """Metaclass which automatically exposes all functions in each + subclass, and adds an instance of the subclass as an attribute + of root. """ def __init__(cls, name, bases, dct): type.__init__(cls, name, bases, dct) @@ -195,7 +195,8 @@ class ToolTests(helper.CPWebCase): yield "confidential" # METHOD TWO: decorator using Tool() - # We support Python 2.3, but the @-deco syntax would look like this: + # We support Python 2.3, but the @-deco syntax would look like + # this: # @tools.check_access() def restricted(self): return "Welcome!" @@ -348,8 +349,10 @@ class ToolTests(helper.CPWebCase): zfile.write(expectedResult) zfile.close() - self.getPage("/euro", headers=[("Accept-Encoding", "gzip"), - ("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7")]) + self.getPage("/euro", + headers=[ + ("Accept-Encoding", "gzip"), + ("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7")]) self.assertInBody(zbuf.getvalue()[:3]) zbuf = BytesIO() diff --git a/cherrypy/test/test_tutorials.py b/cherrypy/test/test_tutorials.py index aeb0c1c9..ad89a9ea 100644 --- a/cherrypy/test/test_tutorials.py +++ b/cherrypy/test/test_tutorials.py @@ -52,7 +52,7 @@ class TutorialTest(helper.CPWebCase): def test02ExposeMethods(self): self.getPage("/load_tut_module/tut02_expose_methods") - self.getPage("/showMessage") + self.getPage("/show_msg") self.assertBody('Hello world!') def test03GetAndPost(self): @@ -82,7 +82,7 @@ class TutorialTest(helper.CPWebCase): <ul> <li><a href="http://del.icio.us">del.icio.us</a></li> - <li><a href="http://www.mornography.de">Hendrik's weblog</a></li> + <li><a href="http://www.cherrypy.org">CherryPy</a></li> </ul> <p>[<a href="../">Return to links page</a>]</p>''' @@ -123,14 +123,16 @@ class TutorialTest(helper.CPWebCase): self.getPage("/sessions") self.getPage('/') - self.assertBody("\n During your current session, you've viewed this" - "\n page 1 times! Your life is a patio of fun!" - "\n ") + self.assertBody( + "\n During your current session, you've viewed this" + "\n page 1 times! Your life is a patio of fun!" + "\n ") self.getPage('/', self.cookies) - self.assertBody("\n During your current session, you've viewed this" - "\n page 2 times! Your life is a patio of fun!" - "\n ") + self.assertBody( + "\n During your current session, you've viewed this" + "\n page 2 times! Your life is a patio of fun!" + "\n ") def test08GeneratorsAndYield(self): self.getPage("/load_tut_module/tut08_generators_and_yield") @@ -147,12 +149,12 @@ class TutorialTest(helper.CPWebCase): filesize = 5 h = [("Content-type", "multipart/form-data; boundary=x"), ("Content-Length", str(105 + filesize))] - b = '--x\n' + \ - 'Content-Disposition: form-data; name="myFile"; filename="hello.txt"\r\n' + \ - 'Content-Type: text/plain\r\n' + \ - '\r\n' + \ - 'a' * filesize + '\n' + \ - '--x--\n' + b = ('--x\n' + 'Content-Disposition: form-data; name="myFile"; ' + 'filename="hello.txt"\r\n' + 'Content-Type: text/plain\r\n' + '\r\n') + b += 'a' * filesize + '\n' + '--x--\n' self.getPage('/upload', h, "POST", b) self.assertBody('''<html> <body> diff --git a/cherrypy/test/test_virtualhost.py b/cherrypy/test/test_virtualhost.py index 0b9ac8ef..7c3d8c71 100644 --- a/cherrypy/test/test_virtualhost.py +++ b/cherrypy/test/test_virtualhost.py @@ -51,13 +51,16 @@ class VirtualHostTest(helper.CPWebCase): 'www.mydom4.com': '/dom4', } cherrypy.tree.mount(root, config={ - '/': {'request.dispatch': cherrypy.dispatch.VirtualHost(**hostmap)}, + '/': { + 'request.dispatch': cherrypy.dispatch.VirtualHost(**hostmap) + }, # Test static in config (section must include vhost prefix) - '/mydom2/static2': {'tools.staticdir.on': True, - 'tools.staticdir.root': curdir, - 'tools.staticdir.dir': 'static', - 'tools.staticdir.index': 'index.html', - }, + '/mydom2/static2': { + 'tools.staticdir.on': True, + 'tools.staticdir.root': curdir, + 'tools.staticdir.dir': 'static', + 'tools.staticdir.index': 'index.html', + }, }) setup_server = staticmethod(setup_server) diff --git a/cherrypy/test/test_wsgiapps.py b/cherrypy/test/test_wsgiapps.py index e1efdaa3..ac65c1eb 100644 --- a/cherrypy/test/test_wsgiapps.py +++ b/cherrypy/test/test_wsgiapps.py @@ -28,7 +28,9 @@ class WSGIGraftTests(helper.CPWebCase): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) - return [ntob('Hello'), ntob(''), ntob(' '), ntob(''), ntob('world')] + return [ + ntob('Hello'), ntob(''), ntob(' '), ntob(''), ntob('world') + ] class WSGIResponse(object): diff --git a/cherrypy/test/test_xmlrpc.py b/cherrypy/test/test_xmlrpc.py index d1b1d3f1..8f091ff1 100644 --- a/cherrypy/test/test_xmlrpc.py +++ b/cherrypy/test/test_xmlrpc.py @@ -2,9 +2,11 @@ import sys from cherrypy._cpcompat import py3k try: - from xmlrpclib import DateTime, Fault, ProtocolError, ServerProxy, SafeTransport + from xmlrpclib import DateTime, Fault, ProtocolError, ServerProxy + from xmlrpclib import SafeTransport except ImportError: - from xmlrpc.client import DateTime, Fault, ProtocolError, ServerProxy, SafeTransport + from xmlrpc.client import DateTime, Fault, ProtocolError, ServerProxy + from xmlrpc.client import SafeTransport if py3k: HTTPSTransport = SafeTransport @@ -16,7 +18,8 @@ if py3k: else: class HTTPSTransport(SafeTransport): - """Subclass of SafeTransport to fix sock.recv errors (by using file).""" + """Subclass of SafeTransport to fix sock.recv errors (by using file). + """ def request(self, host, handler, request_body, verbose=0): # issue XML-RPC request diff --git a/cherrypy/test/webtest.py b/cherrypy/test/webtest.py index 9f991d76..1fa3f969 100644 --- a/cherrypy/test/webtest.py +++ b/cherrypy/test/webtest.py @@ -27,7 +27,8 @@ import types from unittest import * from unittest import _TextTestResult -from cherrypy._cpcompat import basestring, ntob, py3k, HTTPConnection, HTTPSConnection, unicodestr +from cherrypy._cpcompat import basestring, ntob, py3k, HTTPConnection +from cherrypy._cpcompat import HTTPSConnection, unicodestr def interface(host): @@ -236,8 +237,10 @@ class WebCase(TestCase): or '::' (IN6ADDR_ANY), this will return the proper localhost.""" return interface(self.HOST) - def getPage(self, url, headers=None, method="GET", body=None, protocol=None): - """Open the url with debugging support. Return status, headers, body.""" + def getPage(self, url, headers=None, method="GET", body=None, + protocol=None): + """Open the url with debugging support. Return status, headers, body. + """ ServerError.on = False if isinstance(url, unicodestr): @@ -271,7 +274,9 @@ class WebCase(TestCase): if not self.interactive: raise self.failureException(msg) - p = " Show: [B]ody [H]eaders [S]tatus [U]RL; [I]gnore, [R]aise, or sys.e[X]it >> " + p = (" Show: " + "[B]ody [H]eaders [S]tatus [U]RL; " + "[I]gnore, [R]aise, or sys.e[X]it >> ") sys.stdout.write(p) sys.stdout.flush() while True: @@ -523,19 +528,25 @@ def openURL(url, headers=None, method="GET", body=None, # Replace the stdlib method, which only accepts ASCII url's def putrequest(self, method, url): - if self._HTTPConnection__response and self._HTTPConnection__response.isclosed(): + if ( + self._HTTPConnection__response and + self._HTTPConnection__response.isclosed() + ): self._HTTPConnection__response = None if self._HTTPConnection__state == http.client._CS_IDLE: - self._HTTPConnection__state = http.client._CS_REQ_STARTED + self._HTTPConnection__state = ( + http.client._CS_REQ_STARTED) else: raise http.client.CannotSendRequest() self._method = method if not url: url = ntob('/') - request = ntob(' ').join((method.encode("ASCII"), url, - self._http_vsn_str.encode("ASCII"))) + request = ntob(' ').join( + (method.encode("ASCII"), + url, + self._http_vsn_str.encode("ASCII"))) self._output(request) import types conn.putrequest = types.MethodType(putrequest, conn) diff --git a/cherrypy/tutorial/tut02_expose_methods.py b/cherrypy/tutorial/tut02_expose_methods.py index 6c6446eb..64a10f26 100644 --- a/cherrypy/tutorial/tut02_expose_methods.py +++ b/cherrypy/tutorial/tut02_expose_methods.py @@ -12,13 +12,13 @@ class HelloWorld: def index(self): # Let's link to another method here. - return 'We have an <a href="showMessage">important message</a> for you!' + return 'We have an <a href="show_msg">important message</a> for you!' index.exposed = True - def showMessage(self): + def show_msg(self): # Here's the important message! return "Hello world!" - showMessage.exposed = True + show_msg.exposed = True import os.path tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') diff --git a/cherrypy/tutorial/tut04_complex_site.py b/cherrypy/tutorial/tut04_complex_site.py index d870388e..e0660024 100644 --- a/cherrypy/tutorial/tut04_complex_site.py +++ b/cherrypy/tutorial/tut04_complex_site.py @@ -49,8 +49,12 @@ class LinksPage: <p>Here are some useful links:</p> <ul> - <li><a href="http://www.cherrypy.org">The CherryPy Homepage</a></li> - <li><a href="http://www.python.org">The Python Homepage</a></li> + <li> + <a href="http://www.cherrypy.org">The CherryPy Homepage</a> + </li> + <li> + <a href="http://www.python.org">The Python Homepage</a> + </li> </ul> <p>You can check out some extra useful @@ -70,7 +74,7 @@ class ExtraLinksPage: <ul> <li><a href="http://del.icio.us">del.icio.us</a></li> - <li><a href="http://www.mornography.de">Hendrik's weblog</a></li> + <li><a href="http://www.cherrypy.org">CherryPy</a></li> </ul> <p>[<a href="../">Return to links page</a>]</p>''' diff --git a/cherrypy/tutorial/tut10_http_errors.py b/cherrypy/tutorial/tut10_http_errors.py index a18777ef..c0e9893b 100644 --- a/cherrypy/tutorial/tut10_http_errors.py +++ b/cherrypy/tutorial/tut10_http_errors.py @@ -33,7 +33,11 @@ class HTTPErrorDemo(object): <html><body> <p>Toggle tracebacks <a href="toggleTracebacks">%s</a></p> <p><a href="/doesNotExist">Click me; I'm a broken link!</a></p> - <p><a href="/error?code=403">Use a custom error page from a file.</a></p> + <p> + <a href="/error?code=403"> + Use a custom error page from a file. + </a> + </p> <p>These errors are explicitly raised by the application:</p> <ul> <li><a href="/error?code=400">400</a></li> diff --git a/cherrypy/wsgiserver/ssl_builtin.py b/cherrypy/wsgiserver/ssl_builtin.py index 7d446c5f..2c74ad84 100644 --- a/cherrypy/wsgiserver/ssl_builtin.py +++ b/cherrypy/wsgiserver/ssl_builtin.py @@ -50,7 +50,8 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter): try: s = ssl.wrap_socket(sock, do_handshake_on_connect=True, server_side=True, certfile=self.certificate, - keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23) + keyfile=self.private_key, + ssl_version=ssl.PROTOCOL_SSLv23) except ssl.SSLError: e = sys.exc_info()[1] if e.errno == ssl.SSL_ERROR_EOF: diff --git a/cherrypy/wsgiserver/ssl_pyopenssl.py b/cherrypy/wsgiserver/ssl_pyopenssl.py index 234787d4..f8f2dafe 100644 --- a/cherrypy/wsgiserver/ssl_pyopenssl.py +++ b/cherrypy/wsgiserver/ssl_pyopenssl.py @@ -214,8 +214,10 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter): ssl_environ.update({ 'SSL_SERVER_M_VERSION': cert.get_version(), 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), - # 'SSL_SERVER_V_START': Validity of server's certificate (start time), - # 'SSL_SERVER_V_END': Validity of server's certificate (end time), + # 'SSL_SERVER_V_START': + # Validity of server's certificate (start time), + # 'SSL_SERVER_V_END': + # Validity of server's certificate (end time), }) for prefix, dn in [("I", cert.get_issuer()), diff --git a/cherrypy/wsgiserver/wsgiserver2.py b/cherrypy/wsgiserver/wsgiserver2.py index 22c3ee68..46b41fb1 100644 --- a/cherrypy/wsgiserver/wsgiserver2.py +++ b/cherrypy/wsgiserver/wsgiserver2.py @@ -133,7 +133,9 @@ if sys.version_info >= (3, 0): basestring = (bytes, str) def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """Return the given native string as a byte string in the given + encoding. + """ # In Python 3, the native string type is unicode return n.encode(encoding) else: @@ -142,7 +144,9 @@ else: basestring = basestring def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """Return the given native string as a byte string in the given + encoding. + """ # 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. @@ -195,13 +199,15 @@ socket_errors_to_ignore.append("The read operation timed out") socket_errors_nonblocking = plat_specific_errors( 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') -comma_separated_headers = [ntob(h) for h in - ['Accept', 'Accept-Charset', 'Accept-Encoding', - 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', - 'Connection', 'Content-Encoding', 'Content-Language', 'Expect', - 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE', - 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', - 'WWW-Authenticate']] +comma_separated_headers = [ + ntob(h) for h in + ['Accept', 'Accept-Charset', 'Accept-Encoding', + 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', + 'Connection', 'Content-Encoding', 'Content-Language', 'Expect', + 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE', + 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', + 'WWW-Authenticate'] +] import logging @@ -603,9 +609,10 @@ class HTTPRequest(object): try: success = self.read_request_line() except MaxSizeExceeded: - self.simple_response("414 Request-URI Too Long", - "The Request-URI sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "414 Request-URI Too Long", + "The Request-URI sent with the request exceeds the maximum " + "allowed bytes.") return else: if not success: @@ -614,9 +621,10 @@ class HTTPRequest(object): try: success = self.read_request_headers() except MaxSizeExceeded: - self.simple_response("413 Request Entity Too Large", - "The headers sent with the request exceed the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The headers sent with the request exceed the maximum " + "allowed bytes.") return else: if not success: @@ -734,9 +742,10 @@ class HTTPRequest(object): mrbs = self.server.max_request_body_size if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs: - self.simple_response("413 Request Entity Too Large", - "The entity sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The entity sent with the request exceeds the maximum " + "allowed bytes.") return False # Persistent connection support @@ -849,9 +858,10 @@ class HTTPRequest(object): cl = int(self.inheaders.get("Content-Length", 0)) if mrbs and mrbs < cl: if not self.sent_headers: - self.simple_response("413 Request Entity Too Large", - "The entity sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The entity sent with the request exceeds the maximum " + "allowed bytes.") return self.rfile = KnownLengthRFile(self.conn.rfile, cl) @@ -1027,13 +1037,15 @@ class CP_fileobject(socket._fileobject): if not _fileobject_uses_str_type: def read(self, size=-1): - # Use max, disallow tiny reads in a loop as they are very inefficient. - # We never leave read() with any leftover data from a new recv() call - # in our internal buffer. + # Use max, disallow tiny reads in a loop as they are very + # inefficient. + # We never leave read() with any leftover data from a new recv() + # call in our internal buffer. rbufsize = max(self._rbufsize, self.default_bufsize) - # Our use of StringIO rather than lists of string objects returned by - # recv() minimizes memory usage and fragmentation that occurs when - # rbufsize is large compared to the typical return value of recv(). + # Our use of StringIO rather than lists of string objects returned + # by recv() minimizes memory usage and fragmentation that occurs + # when rbufsize is large compared to the typical return value of + # recv(). buf = self._rbuf buf.seek(0, 2) # seek end if size < 0: @@ -1162,8 +1174,8 @@ class CP_fileobject(socket._fileobject): buf.write(data[:nl]) break else: - # Shortcut. Avoid data copy through buf when returning - # a substring of our first recv(). + # Shortcut. Avoid data copy through buf when + # returning a substring of our first recv(). return data[:nl] n = len(data) if n == size and not buf_len: @@ -1345,7 +1357,10 @@ class HTTPConnection(object): e = sys.exc_info()[1] errnum = e.args[0] # sadly SSL sockets return a different (longer) time out string - if errnum == 'timed out' or errnum == 'The read operation timed out': + if ( + errnum == 'timed out' or + errnum == 'The read operation timed out' + ): # Don't error if we're between requests; only error # if 1) no request has been started at all, or 2) we're # in the middle of a request. @@ -1379,9 +1394,10 @@ class HTTPConnection(object): # Unwrap our wfile self.wfile = CP_fileobject( self.socket._sock, "wb", self.wbufsize) - req.simple_response("400 Bad Request", - "The client sent a plain HTTP request, but " - "this server only speaks HTTPS on this port.") + req.simple_response( + "400 Bad Request", + "The client sent a plain HTTP request, but " + "this server only speaks HTTPS on this port.") self.linger = True except Exception: e = sys.exc_info()[1] @@ -1400,11 +1416,12 @@ class HTTPConnection(object): self.rfile.close() if not self.linger: - # Python's socket module does NOT call close on the kernel socket - # when you call socket.close(). We do so manually here because we - # want this server to send a FIN TCP segment immediately. Note this - # must be called *before* calling socket.close(), because the latter - # drops its reference to the kernel socket. + # Python's socket module does NOT call close on the kernel + # socket when you call socket.close(). We do so manually here + # because we want this server to send a FIN TCP segment + # immediately. Note this must be called *before* calling + # socket.close(), because the latter drops its reference to + # the kernel socket. if hasattr(self.socket, '_sock'): self.socket._sock.close() self.socket.close() @@ -1420,7 +1437,8 @@ class HTTPConnection(object): class TrueyZero(object): - """An object which equals and does math like the integer '0' but evals True.""" + """An object which equals and does math like the integer 0 but evals True. + """ def __add__(self, other): return other @@ -1464,12 +1482,30 @@ class WorkerThread(threading.Thread): self.start_time = None self.work_time = 0 self.stats = { - 'Requests': lambda s: self.requests_seen + ((self.start_time is None) and trueyzero or self.conn.requests_seen), - 'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and trueyzero or self.conn.rfile.bytes_read), - 'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and trueyzero or self.conn.wfile.bytes_written), - 'Work Time': lambda s: self.work_time + ((self.start_time is None) and trueyzero or time.time() - self.start_time), - 'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6), - 'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6), + 'Requests': lambda s: self.requests_seen + ( + (self.start_time is None) and + trueyzero or + self.conn.requests_seen + ), + 'Bytes Read': lambda s: self.bytes_read + ( + (self.start_time is None) and + trueyzero or + self.conn.rfile.bytes_read + ), + 'Bytes Written': lambda s: self.bytes_written + ( + (self.start_time is None) and + trueyzero or + self.conn.wfile.bytes_written + ), + 'Work Time': lambda s: self.work_time + ( + (self.start_time is None) and + trueyzero or + time.time() - self.start_time + ), + 'Read Throughput': lambda s: s['Bytes Read'](s) / ( + s['Work Time'](s) or 1e-6), + 'Write Throughput': lambda s: s['Bytes Written'](s) / ( + s['Work Time'](s) or 1e-6), } threading.Thread.__init__(self) @@ -1665,7 +1701,8 @@ class SSLAdapter(object): Required methods: * ``wrap(sock) -> (wrapped socket, ssl environ dict)`` - * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object`` + * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> + socket file object`` """ def __init__(self, certificate, private_key, certificate_chain=None): @@ -1694,7 +1731,8 @@ class HTTPServer(object): """The minimum number of worker threads to create (default 10).""" maxthreads = None - """The maximum number of worker threads to create (default -1 = no limit).""" + """The maximum number of worker threads to create (default -1 = no limit). + """ server_name = None """The name of the server; defaults to socket.gethostname().""" @@ -1706,10 +1744,13 @@ class HTTPServer(object): features used in the response.""" request_queue_size = 5 - """The 'backlog' arg to socket.listen(); max queued connections (default 5).""" + """The 'backlog' arg to socket.listen(); max queued connections + (default 5). + """ shutdown_timeout = 5 - """The total time, in seconds, to wait for worker threads to cleanly exit.""" + """The total time, in seconds, to wait for worker threads to cleanly exit. + """ timeout = 10 """The timeout in seconds for accepted connections (default 10).""" @@ -1723,7 +1764,8 @@ class HTTPServer(object): If None, this defaults to ``'%s Server' % self.version``.""" ready = False - """An internal flag which marks whether the socket is accepting connections.""" + """An internal flag which marks whether the socket is accepting connections + """ max_request_header_size = 0 """The maximum size, in bytes, for request headers, or 0 for no limit.""" @@ -1767,18 +1809,15 @@ class HTTPServer(object): 'Threads': lambda s: len(getattr(self.requests, "_threads", [])), 'Threads Idle': lambda s: getattr(self.requests, "idle", None), 'Socket Errors': 0, - 'Requests': lambda s: (not s['Enabled']) and -1 or sum([w['Requests'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Read'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Written'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Work Time': lambda s: (not s['Enabled']) and -1 or sum([w['Work Time'](w) for w - in s[ - 'Worker Threads'].values()], 0), + 'Requests': lambda s: (not s['Enabled']) and -1 or sum( + [w['Requests'](w) for w in s['Worker Threads'].values()], 0), + 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum( + [w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0), + 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum( + [w['Bytes Written'](w) for w in s['Worker Threads'].values()], + 0), + 'Work Time': lambda s: (not s['Enabled']) and -1 or sum( + [w['Work Time'](w) for w in s['Worker Threads'].values()], 0), 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum( [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6) for w in s['Worker Threads'].values()], 0), @@ -1818,8 +1857,10 @@ class HTTPServer(object): "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " "to listen on all active interfaces.") self._bind_addr = value - bind_addr = property(_get_bind_addr, _set_bind_addr, - doc="""The interface on which to listen for connections. + bind_addr = property( + _get_bind_addr, + _set_bind_addr, + doc="""The interface on which to listen for connections. For TCP sockets, a (host, port) tuple. Host values may be any IPv4 or IPv6 address, or any valid hostname. The string 'localhost' is a @@ -1884,8 +1925,9 @@ class HTTPServer(object): # addresses) host, port = self.bind_addr try: - info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + info = socket.getaddrinfo( + host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, socket.AI_PASSIVE) except socket.gaierror: if ':' in self.bind_addr[0]: info = [(socket.AF_INET6, socket.SOCK_STREAM, @@ -2100,8 +2142,9 @@ class HTTPServer(object): s = None try: s = socket.socket(af, socktype, proto) - # See http://groups.google.com/group/cherrypy-users/ - # browse_frm/thread/bbfe5eb39c904fe0 + # See + # http://groups.google.com/group/cherrypy-users/ + # browse_frm/thread/bbfe5eb39c904fe0 s.settimeout(1.0) s.connect((host, port)) s.close() @@ -2117,7 +2160,8 @@ class HTTPServer(object): class Gateway(object): - """A base class to interface HTTPServer with other systems, such as WSGI.""" + """A base class to interface HTTPServer with other systems, such as WSGI. + """ def __init__(self, req): self.req = req @@ -2160,7 +2204,7 @@ def get_ssl_adapter_class(name='pyopenssl'): return adapter -# -------------------------------- WSGI Stuff -------------------------------- # +# ------------------------------- WSGI Stuff -------------------------------- # class CherryPyWSGIServer(HTTPServer): @@ -2273,9 +2317,10 @@ class WSGIGateway(Gateway): if rbo is not None and chunklen > rbo: if not self.req.sent_headers: # Whew. We can send a 500 to the client. - self.req.simple_response("500 Internal Server Error", - "The requested resource returned more bytes than the " - "declared Content-Length.") + self.req.simple_response( + "500 Internal Server Error", + "The requested resource returned more bytes than the " + "declared Content-Length.") else: # Dang. We have probably already sent data. Truncate the chunk # to fit (so the client doesn't hang) and raise an error later. @@ -2355,8 +2400,8 @@ class WSGIGateway_u0(WSGIGateway_10): """A Gateway class to interface HTTPServer with WSGI u.0. - WSGI u.0 is an experimental protocol, which uses unicode for keys and values - in both Python 2 and Python 3. + WSGI u.0 is an experimental protocol, which uses unicode for keys and + values in both Python 2 and Python 3. """ def get_environ(self): diff --git a/cherrypy/wsgiserver/wsgiserver3.py b/cherrypy/wsgiserver/wsgiserver3.py index c368afc1..156b716c 100644 --- a/cherrypy/wsgiserver/wsgiserver3.py +++ b/cherrypy/wsgiserver/wsgiserver3.py @@ -107,7 +107,9 @@ if sys.version_info >= (3, 0): basestring = (bytes, str) def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """Return the given native string as a byte string in the given + encoding. + """ # In Python 3, the native string type is unicode return n.encode(encoding) else: @@ -116,7 +118,9 @@ else: basestring = basestring def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given encoding.""" + """Return the given native string as a byte string in the given + encoding. + """ # 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. @@ -169,13 +173,15 @@ socket_errors_to_ignore.append("The read operation timed out") socket_errors_nonblocking = plat_specific_errors( 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') -comma_separated_headers = [ntob(h) for h in - ['Accept', 'Accept-Charset', 'Accept-Encoding', - 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', - 'Connection', 'Content-Encoding', 'Content-Language', 'Expect', - 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE', - 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', - 'WWW-Authenticate']] +comma_separated_headers = [ + ntob(h) for h in + ['Accept', 'Accept-Charset', 'Accept-Encoding', + 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', + 'Connection', 'Content-Encoding', 'Content-Language', 'Expect', + 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE', + 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', + 'WWW-Authenticate'] +] import logging @@ -577,9 +583,10 @@ class HTTPRequest(object): try: success = self.read_request_line() except MaxSizeExceeded: - self.simple_response("414 Request-URI Too Long", - "The Request-URI sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "414 Request-URI Too Long", + "The Request-URI sent with the request exceeds the maximum " + "allowed bytes.") return else: if not success: @@ -588,9 +595,10 @@ class HTTPRequest(object): try: success = self.read_request_headers() except MaxSizeExceeded: - self.simple_response("413 Request Entity Too Large", - "The headers sent with the request exceed the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The headers sent with the request exceed the maximum " + "allowed bytes.") return else: if not success: @@ -711,9 +719,10 @@ class HTTPRequest(object): mrbs = self.server.max_request_body_size if mrbs and int(self.inheaders.get(b"Content-Length", 0)) > mrbs: - self.simple_response("413 Request Entity Too Large", - "The entity sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The entity sent with the request exceeds the maximum " + "allowed bytes.") return False # Persistent connection support @@ -837,9 +846,10 @@ class HTTPRequest(object): cl = int(self.inheaders.get(b"Content-Length", 0)) if mrbs and mrbs < cl: if not self.sent_headers: - self.simple_response("413 Request Entity Too Large", - "The entity sent with the request exceeds the maximum " - "allowed bytes.") + self.simple_response( + "413 Request Entity Too Large", + "The entity sent with the request exceeds the " + "maximum allowed bytes.") return self.rfile = KnownLengthRFile(self.conn.rfile, cl) @@ -948,8 +958,10 @@ class HTTPRequest(object): self.rfile.read(remaining) if b"date" not in hkeys: - self.outheaders.append( - (b"Date", email.utils.formatdate(usegmt=True).encode('ISO-8859-1'))) + self.outheaders.append(( + b"Date", + email.utils.formatdate(usegmt=True).encode('ISO-8859-1') + )) if b"server" not in hkeys: self.outheaders.append( @@ -1060,7 +1072,10 @@ class HTTPConnection(object): e = sys.exc_info()[1] errnum = e.args[0] # sadly SSL sockets return a different (longer) time out string - if errnum == 'timed out' or errnum == 'The read operation timed out': + if ( + errnum == 'timed out' or + errnum == 'The read operation timed out' + ): # Don't error if we're between requests; only error # if 1) no request has been started at all, or 2) we're # in the middle of a request. @@ -1094,9 +1109,10 @@ class HTTPConnection(object): # Unwrap our wfile self.wfile = CP_makefile( self.socket._sock, "wb", self.wbufsize) - req.simple_response("400 Bad Request", - "The client sent a plain HTTP request, but " - "this server only speaks HTTPS on this port.") + req.simple_response( + "400 Bad Request", + "The client sent a plain HTTP request, but this server " + "only speaks HTTPS on this port.") self.linger = True except Exception: e = sys.exc_info()[1] @@ -1115,12 +1131,14 @@ class HTTPConnection(object): self.rfile.close() if not self.linger: - # Python's socket module does NOT call close on the kernel socket - # when you call socket.close(). We do so manually here because we - # want this server to send a FIN TCP segment immediately. Note this - # must be called *before* calling socket.close(), because the latter - # drops its reference to the kernel socket. - # Python 3 *probably* fixed this with socket._real_close; hard to tell. + # Python's socket module does NOT call close on the kernel + # socket when you call socket.close(). We do so manually here + # because we want this server to send a FIN TCP segment + # immediately. Note this must be called *before* calling + # socket.close(), because the latter drops its reference to + # the kernel socket. + # Python 3 *probably* fixed this with socket._real_close; + # hard to tell. # self.socket._sock.close() self.socket.close() else: @@ -1135,7 +1153,8 @@ class HTTPConnection(object): class TrueyZero(object): - """An object which equals and does math like the integer '0' but evals True.""" + """An object which equals and does math like the integer 0 but evals True. + """ def __add__(self, other): return other @@ -1179,12 +1198,30 @@ class WorkerThread(threading.Thread): self.start_time = None self.work_time = 0 self.stats = { - 'Requests': lambda s: self.requests_seen + ((self.start_time is None) and trueyzero or self.conn.requests_seen), - 'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and trueyzero or self.conn.rfile.bytes_read), - 'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and trueyzero or self.conn.wfile.bytes_written), - 'Work Time': lambda s: self.work_time + ((self.start_time is None) and trueyzero or time.time() - self.start_time), - 'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6), - 'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6), + 'Requests': lambda s: self.requests_seen + ( + (self.start_time is None) and + trueyzero or + self.conn.requests_seen + ), + 'Bytes Read': lambda s: self.bytes_read + ( + (self.start_time is None) and + trueyzero or + self.conn.rfile.bytes_read + ), + 'Bytes Written': lambda s: self.bytes_written + ( + (self.start_time is None) and + trueyzero or + self.conn.wfile.bytes_written + ), + 'Work Time': lambda s: self.work_time + ( + (self.start_time is None) and + trueyzero or + time.time() - self.start_time + ), + 'Read Throughput': lambda s: s['Bytes Read'](s) / ( + s['Work Time'](s) or 1e-6), + 'Write Throughput': lambda s: s['Bytes Written'](s) / ( + s['Work Time'](s) or 1e-6), } threading.Thread.__init__(self) @@ -1375,7 +1412,8 @@ class SSLAdapter(object): Required methods: * ``wrap(sock) -> (wrapped socket, ssl environ dict)`` - * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object`` + * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> + socket file object`` """ def __init__(self, certificate, private_key, certificate_chain=None): @@ -1404,7 +1442,8 @@ class HTTPServer(object): """The minimum number of worker threads to create (default 10).""" maxthreads = None - """The maximum number of worker threads to create (default -1 = no limit).""" + """The maximum number of worker threads to create (default -1 = no limit). + """ server_name = None """The name of the server; defaults to socket.gethostname().""" @@ -1416,10 +1455,13 @@ class HTTPServer(object): features used in the response.""" request_queue_size = 5 - """The 'backlog' arg to socket.listen(); max queued connections (default 5).""" + """The 'backlog' arg to socket.listen(); max queued connections + (default 5). + """ shutdown_timeout = 5 - """The total time, in seconds, to wait for worker threads to cleanly exit.""" + """The total time, in seconds, to wait for worker threads to cleanly exit. + """ timeout = 10 """The timeout in seconds for accepted connections (default 10).""" @@ -1433,7 +1475,9 @@ class HTTPServer(object): If None, this defaults to ``'%s Server' % self.version``.""" ready = False - """An internal flag which marks whether the socket is accepting connections.""" + """An internal flag which marks whether the socket is accepting + connections. + """ max_request_header_size = 0 """The maximum size, in bytes, for request headers, or 0 for no limit.""" @@ -1477,18 +1521,15 @@ class HTTPServer(object): 'Threads': lambda s: len(getattr(self.requests, "_threads", [])), 'Threads Idle': lambda s: getattr(self.requests, "idle", None), 'Socket Errors': 0, - 'Requests': lambda s: (not s['Enabled']) and -1 or sum([w['Requests'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Read'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Written'](w) for w - in s[ - 'Worker Threads'].values()], 0), - 'Work Time': lambda s: (not s['Enabled']) and -1 or sum([w['Work Time'](w) for w - in s[ - 'Worker Threads'].values()], 0), + 'Requests': lambda s: (not s['Enabled']) and -1 or sum( + [w['Requests'](w) for w in s['Worker Threads'].values()], 0), + 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum( + [w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0), + 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum( + [w['Bytes Written'](w) for w in s['Worker Threads'].values()], + 0), + 'Work Time': lambda s: (not s['Enabled']) and -1 or sum( + [w['Work Time'](w) for w in s['Worker Threads'].values()], 0), 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum( [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6) for w in s['Worker Threads'].values()], 0), @@ -1528,8 +1569,10 @@ class HTTPServer(object): "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " "to listen on all active interfaces.") self._bind_addr = value - bind_addr = property(_get_bind_addr, _set_bind_addr, - doc="""The interface on which to listen for connections. + bind_addr = property( + _get_bind_addr, + _set_bind_addr, + doc="""The interface on which to listen for connections. For TCP sockets, a (host, port) tuple. Host values may be any IPv4 or IPv6 address, or any valid hostname. The string 'localhost' is a @@ -1576,7 +1619,8 @@ class HTTPServer(object): host, port = self.bind_addr try: info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + socket.SOCK_STREAM, 0, + socket.AI_PASSIVE) except socket.gaierror: if ':' in self.bind_addr[0]: info = [(socket.AF_INET6, socket.SOCK_STREAM, @@ -1790,8 +1834,9 @@ class HTTPServer(object): s = None try: s = socket.socket(af, socktype, proto) - # See http://groups.google.com/group/cherrypy-users/ - # browse_frm/thread/bbfe5eb39c904fe0 + # See + # http://groups.google.com/group/cherrypy-users/ + # browse_frm/thread/bbfe5eb39c904fe0 s.settimeout(1.0) s.connect((host, port)) s.close() @@ -1807,7 +1852,8 @@ class HTTPServer(object): class Gateway(object): - """A base class to interface HTTPServer with other systems, such as WSGI.""" + """A base class to interface HTTPServer with other systems, such as WSGI. + """ def __init__(self, req): self.req = req @@ -1849,7 +1895,7 @@ def get_ssl_adapter_class(name='builtin'): return adapter -# -------------------------------- WSGI Stuff -------------------------------- # +# ------------------------------- WSGI Stuff -------------------------------- # class CherryPyWSGIServer(HTTPServer): @@ -1971,8 +2017,9 @@ class WSGIGateway(Gateway): if not self.req.sent_headers: # Whew. We can send a 500 to the client. self.req.simple_response("500 Internal Server Error", - "The requested resource returned more bytes than the " - "declared Content-Length.") + "The requested resource returned " + "more bytes than the declared " + "Content-Length.") else: # Dang. We have probably already sent data. Truncate the chunk # to fit (so the client doesn't hang) and raise an error later. @@ -2052,8 +2099,8 @@ class WSGIGateway_u0(WSGIGateway_10): """A Gateway class to interface HTTPServer with WSGI u.0. - WSGI u.0 is an experimental protocol, which uses unicode for keys and values - in both Python 2 and Python 3. + WSGI u.0 is an experimental protocol, which uses unicode for keys + and values in both Python 2 and Python 3. """ def get_environ(self): |