summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2014-09-13 23:50:14 -0400
committerJason R. Coombs <jaraco@jaraco.com>2014-09-13 23:50:14 -0400
commit92b0b0e4e9f60638354c9f85e8ccc51d73ca20cb (patch)
tree4783e10820033435392140d2adfb4018da5003ff
parent8cfc428509554116f145795bbd27d9333ab59acf (diff)
parent0529de1c4bbd75ac2052a8fece69846b9345cc7b (diff)
downloadcherrypy-92b0b0e4e9f60638354c9f85e8ccc51d73ca20cb.tar.gz
Merged in shroom/cherrypy (pull request #50)
Fix race condition in session clean up.
-rw-r--r--.hgignore1
-rw-r--r--CHANGES.txt32
-rw-r--r--CONTRIBUTING.txt6
-rw-r--r--cherrypy/__init__.py50
-rw-r--r--cherrypy/_cpchecker.py85
-rw-r--r--cherrypy/_cpcompat.py77
-rw-r--r--cherrypy/_cpcompat_subprocess.py136
-rw-r--r--cherrypy/_cpconfig.py39
-rw-r--r--cherrypy/_cpdispatch.py99
-rw-r--r--cherrypy/_cperror.py135
-rw-r--r--cherrypy/_cplogging.py55
-rw-r--r--cherrypy/_cpmodpy.py51
-rw-r--r--cherrypy/_cpnative_server.py25
-rw-r--r--cherrypy/_cpreqbody.py182
-rw-r--r--cherrypy/_cprequest.py52
-rw-r--r--cherrypy/_cpserver.py55
-rw-r--r--cherrypy/_cpthreadinglocal.py6
-rw-r--r--cherrypy/_cptools.py49
-rw-r--r--cherrypy/_cptree.py38
-rw-r--r--cherrypy/_cpwsgi.py70
-rw-r--r--cherrypy/_cpwsgi_server.py23
-rwxr-xr-xcherrypy/cherryd29
-rw-r--r--cherrypy/lib/__init__.py40
-rw-r--r--cherrypy/lib/auth.py28
-rw-r--r--cherrypy/lib/auth_basic.py15
-rw-r--r--cherrypy/lib/auth_digest.py139
-rw-r--r--cherrypy/lib/caching.py37
-rw-r--r--cherrypy/lib/covercp.py52
-rw-r--r--cherrypy/lib/cpstats.py112
-rw-r--r--cherrypy/lib/cptools.py78
-rw-r--r--cherrypy/lib/encoding.py70
-rw-r--r--cherrypy/lib/gctools.py21
-rw-r--r--cherrypy/lib/http.py1
-rw-r--r--cherrypy/lib/httpauth.py127
-rw-r--r--cherrypy/lib/httputil.py58
-rw-r--r--cherrypy/lib/jsontools.py16
-rw-r--r--cherrypy/lib/lockfile.py8
-rw-r--r--cherrypy/lib/locking.py47
-rw-r--r--cherrypy/lib/profiler.py26
-rw-r--r--cherrypy/lib/reprconf.py38
-rw-r--r--cherrypy/lib/sessions.py114
-rw-r--r--cherrypy/lib/static.py69
-rw-r--r--cherrypy/lib/xmlrpcutil.py4
-rw-r--r--cherrypy/process/plugins.py81
-rw-r--r--cherrypy/process/servers.py27
-rw-r--r--cherrypy/process/win32.py8
-rw-r--r--cherrypy/process/wspbus.py29
-rw-r--r--cherrypy/scaffold/__init__.py8
-rw-r--r--cherrypy/test/__init__.py3
-rw-r--r--cherrypy/test/_test_decorators.py2
-rw-r--r--cherrypy/test/_test_states_demo.py4
-rw-r--r--cherrypy/test/benchmark.py71
-rw-r--r--cherrypy/test/checkerdemo.py7
-rw-r--r--cherrypy/test/helper.py80
-rw-r--r--cherrypy/test/logtest.py25
-rw-r--r--cherrypy/test/modfastcgi.py6
-rw-r--r--cherrypy/test/modfcgid.py2
-rw-r--r--cherrypy/test/modpy.py8
-rw-r--r--cherrypy/test/modwsgi.py8
-rwxr-xr-xcherrypy/test/sessiondemo.py17
-rw-r--r--cherrypy/test/static/404.html5
-rw-r--r--cherrypy/test/test_auth_basic.py46
-rw-r--r--cherrypy/test/test_auth_digest.py52
-rw-r--r--cherrypy/test/test_bus.py35
-rw-r--r--cherrypy/test/test_caching.py50
-rw-r--r--cherrypy/test/test_compat.py2
-rw-r--r--cherrypy/test/test_config.py45
-rw-r--r--cherrypy/test/test_config_server.py15
-rw-r--r--cherrypy/test/test_conn.py219
-rw-r--r--cherrypy/test/test_core.py130
-rw-r--r--cherrypy/test/test_dynamicobjectmapping.py53
-rw-r--r--cherrypy/test/test_encoding.py198
-rw-r--r--cherrypy/test/test_etags.py5
-rw-r--r--cherrypy/test/test_http.py75
-rw-r--r--cherrypy/test/test_httpauth.py84
-rw-r--r--cherrypy/test/test_iterator.py181
-rw-r--r--cherrypy/test/test_json.py6
-rw-r--r--cherrypy/test/test_logging.py24
-rw-r--r--cherrypy/test/test_mime.py69
-rw-r--r--cherrypy/test/test_misc_tools.py28
-rw-r--r--cherrypy/test/test_objectmapping.py57
-rw-r--r--cherrypy/test/test_proxy.py32
-rw-r--r--cherrypy/test/test_refleaks.py2
-rw-r--r--cherrypy/test/test_request_obj.py241
-rw-r--r--cherrypy/test/test_routes.py16
-rwxr-xr-xcherrypy/test/test_session.py31
-rw-r--r--cherrypy/test/test_sessionauthenticate.py21
-rw-r--r--cherrypy/test/test_states.py64
-rw-r--r--cherrypy/test/test_static.py85
-rw-r--r--cherrypy/test/test_tools.py52
-rw-r--r--cherrypy/test/test_tutorials.py38
-rw-r--r--cherrypy/test/test_virtualhost.py22
-rw-r--r--cherrypy/test/test_wsgi_ns.py11
-rw-r--r--cherrypy/test/test_wsgi_vhost.py5
-rw-r--r--cherrypy/test/test_wsgiapps.py44
-rw-r--r--cherrypy/test/test_xmlrpc.py25
-rw-r--r--cherrypy/test/webtest.py57
-rw-r--r--cherrypy/tutorial/bonus-sqlobject.py168
-rw-r--r--cherrypy/tutorial/tut01_helloworld.py2
-rw-r--r--cherrypy/tutorial/tut02_expose_methods.py7
-rw-r--r--cherrypy/tutorial/tut03_get_and_post.py2
-rw-r--r--cherrypy/tutorial/tut04_complex_site.py15
-rw-r--r--cherrypy/tutorial/tut05_derived_objects.py1
-rw-r--r--cherrypy/tutorial/tut06_default_method.py1
-rw-r--r--cherrypy/tutorial/tut07_sessions.py1
-rw-r--r--cherrypy/tutorial/tut08_generators_and_yield.py1
-rw-r--r--cherrypy/tutorial/tut10_http_errors.py11
-rw-r--r--cherrypy/wsgiserver/ssl_builtin.py13
-rw-r--r--cherrypy/wsgiserver/ssl_pyopenssl.py22
-rw-r--r--cherrypy/wsgiserver/wsgiserver2.py357
-rw-r--r--cherrypy/wsgiserver/wsgiserver3.py316
-rw-r--r--docs/_static/bgsides.png (renamed from sphinx/source/_static/bgsides.png)bin6498 -> 6498 bytes
-rw-r--r--docs/_static/cpdocmain.css (renamed from sphinx/source/_static/cpdocmain.css)0
-rw-r--r--docs/_static/images/cpreturn.gif (renamed from sphinx/source/progguide/cpreturn.gif)bin6088 -> 6088 bytes
-rw-r--r--docs/_static/images/cpyield.gif (renamed from sphinx/source/progguide/cpyield.gif)bin9112 -> 9112 bytes
-rw-r--r--docs/_static/images/sushibelt.JPGbin0 -> 161186 bytes
-rw-r--r--docs/advanced.rst664
-rw-r--r--docs/basics.rst732
-rw-r--r--docs/conf.py (renamed from sphinx/source/conf.py)113
-rw-r--r--docs/config.rst (renamed from sphinx/source/tutorial/config.rst)120
-rw-r--r--docs/contribute.rst6
-rw-r--r--docs/deploy.rst602
-rw-r--r--docs/extend.rst691
-rw-r--r--docs/glossary.rst34
-rw-r--r--docs/index.rst51
-rw-r--r--docs/install.rst156
-rw-r--r--docs/intro.rst150
-rw-r--r--docs/pkg/cherrypy.lib.rst166
-rw-r--r--docs/pkg/cherrypy.process.rst46
-rw-r--r--docs/pkg/cherrypy.rst162
-rw-r--r--docs/pkg/cherrypy.scaffold.rst10
-rw-r--r--docs/pkg/cherrypy.test.rst390
-rw-r--r--docs/pkg/cherrypy.tutorial.rst94
-rw-r--r--docs/pkg/cherrypy.wsgiserver.rst46
-rw-r--r--docs/pkg/modules.rst7
-rw-r--r--docs/tutorials.rst933
-rw-r--r--docs/util/convert-trac.py134
-rw-r--r--docs/util/test-doc.py (renamed from sphinx/util/test-doc.py)11
-rw-r--r--ez_setup.py134
-rw-r--r--man/cherryd.1 (renamed from docs/cherryd.1)0
-rw-r--r--release.py146
-rw-r--r--setup.cfg5
-rw-r--r--setup.py76
-rw-r--r--sphinx/Makefile89
-rw-r--r--sphinx/make.bat113
-rw-r--r--sphinx/source/appendix/cherrypyspeed.rst251
-rw-r--r--sphinx/source/appendix/faq.rst207
-rw-r--r--sphinx/source/appendix/index.rst11
-rw-r--r--sphinx/source/appendix/success.rst87
-rw-r--r--sphinx/source/deployguide/apache.rst4
-rw-r--r--sphinx/source/deployguide/cherryd.rst56
-rw-r--r--sphinx/source/deployguide/index.rst48
-rw-r--r--sphinx/source/index.rst51
-rw-r--r--sphinx/source/intro/index.rst84
-rw-r--r--sphinx/source/intro/install.rst109
-rw-r--r--sphinx/source/intro/license.rst32
-rw-r--r--sphinx/source/intro/whycherrypy.rst105
-rw-r--r--sphinx/source/progguide/REST.rst255
-rw-r--r--sphinx/source/progguide/choosingtemplate.rst151
-rw-r--r--sphinx/source/progguide/cookies.rst51
-rw-r--r--sphinx/source/progguide/customheaders.rst76
-rw-r--r--sphinx/source/progguide/extending/customplugins.rst131
-rw-r--r--sphinx/source/progguide/extending/customtools.rst282
-rw-r--r--sphinx/source/progguide/extending/index.rst15
-rw-r--r--sphinx/source/progguide/files/downloading.rst50
-rw-r--r--sphinx/source/progguide/files/favicon.rst18
-rw-r--r--sphinx/source/progguide/files/index.rst11
-rw-r--r--sphinx/source/progguide/files/static.rst298
-rw-r--r--sphinx/source/progguide/files/uploading.rst75
-rw-r--r--sphinx/source/progguide/index.rst36
-rw-r--r--sphinx/source/progguide/responsetimeouts.rst41
-rw-r--r--sphinx/source/progguide/security.rst59
-rw-r--r--sphinx/source/progguide/streaming.rst83
-rw-r--r--sphinx/source/refman/_cpchecker.rst12
-rw-r--r--sphinx/source/refman/_cpconfig.rst18
-rw-r--r--sphinx/source/refman/_cpdispatch.rst31
-rw-r--r--sphinx/source/refman/_cperror.rst32
-rw-r--r--sphinx/source/refman/_cplogging.rst15
-rw-r--r--sphinx/source/refman/_cpreqbody.rst35
-rw-r--r--sphinx/source/refman/_cprequest.rst22
-rw-r--r--sphinx/source/refman/_cpserver.rst12
-rw-r--r--sphinx/source/refman/_cptools.rst39
-rw-r--r--sphinx/source/refman/_cptree.rst15
-rw-r--r--sphinx/source/refman/_cpwsgi.rst24
-rw-r--r--sphinx/source/refman/cherrypy.rst40
-rw-r--r--sphinx/source/refman/cperrors.gifbin12971 -> 0 bytes
-rw-r--r--sphinx/source/refman/index.rst12
-rw-r--r--sphinx/source/refman/lib/auth.rst14
-rw-r--r--sphinx/source/refman/lib/auth_basic.rst12
-rw-r--r--sphinx/source/refman/lib/auth_digest.rst26
-rw-r--r--sphinx/source/refman/lib/caching.rst26
-rw-r--r--sphinx/source/refman/lib/covercp.rst18
-rw-r--r--sphinx/source/refman/lib/cpstats.rst13
-rw-r--r--sphinx/source/refman/lib/cptools.rst49
-rw-r--r--sphinx/source/refman/lib/encoding.rst22
-rw-r--r--sphinx/source/refman/lib/httpauth.rst23
-rw-r--r--sphinx/source/refman/lib/httputil.rst41
-rw-r--r--sphinx/source/refman/lib/index.rst10
-rw-r--r--sphinx/source/refman/lib/jsontools.rst16
-rw-r--r--sphinx/source/refman/lib/profiler.rst24
-rw-r--r--sphinx/source/refman/lib/reprconf.rst29
-rw-r--r--sphinx/source/refman/lib/sessions.rst41
-rw-r--r--sphinx/source/refman/lib/static.rst19
-rw-r--r--sphinx/source/refman/lib/xmlrpc.rst17
-rw-r--r--sphinx/source/refman/process/index.rst10
-rw-r--r--sphinx/source/refman/process/plugins/daemonizer.rst47
-rw-r--r--sphinx/source/refman/process/plugins/dropprivileges.rst25
-rw-r--r--sphinx/source/refman/process/plugins/index.rst46
-rw-r--r--sphinx/source/refman/process/plugins/pidfile.rst20
-rw-r--r--sphinx/source/refman/process/plugins/signalhandler.rst39
-rw-r--r--sphinx/source/refman/process/servers.rst31
-rw-r--r--sphinx/source/refman/process/win32.rst17
-rw-r--r--sphinx/source/refman/process/wspbus.rst15
-rw-r--r--sphinx/source/refman/wsgiserver/index.rst9
-rw-r--r--sphinx/source/refman/wsgiserver/init.rst84
-rw-r--r--sphinx/source/refman/wsgiserver/ssl_builtin.rst12
-rw-r--r--sphinx/source/refman/wsgiserver/ssl_pyopenssl.rst18
-rw-r--r--sphinx/source/tutorial/REST.rst308
-rw-r--r--sphinx/source/tutorial/basics.rst148
-rw-r--r--sphinx/source/tutorial/dispatching.rst433
-rw-r--r--sphinx/source/tutorial/engine.rst76
-rw-r--r--sphinx/source/tutorial/exposing.rst76
-rw-r--r--sphinx/source/tutorial/files/songs.py75
-rw-r--r--sphinx/source/tutorial/index.rst58
-rw-r--r--sphinx/source/tutorial/tools.rst105
-rw-r--r--sphinx/util/convert-trac.py117
-rw-r--r--tox.ini8
227 files changed, 9137 insertions, 7728 deletions
diff --git a/.hgignore b/.hgignore
index e0e944cb..d207c898 100644
--- a/.hgignore
+++ b/.hgignore
@@ -12,3 +12,4 @@ bin
*.kdev4
include
sphinx/source/_build
+.tox
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 00000000..b6abc792
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,32 @@
+-----
+3.6.0
+-----
+
+* Fixed HTTP range headers for negative length larger than content size.
+* Disabled universal wheel generation as wsgiserver has Python duality.
+* Pull Request #42: Correct TypeError in ``check_auth`` when encrypt is used.
+* Pull Request #59: Correct signature of HandlerWrapperTool.
+* Pull Request #60: Fix error in SessionAuth where login_screen was
+ incorrectly used.
+* Issue #1077: Support keyword-only arguments in dispatchers (Python 3).
+* Issue #1019: Allow logging host name in the access log.
+
+-----
+3.5.0
+-----
+
+* Issue #1301: When the incoming queue is full, now reject additional
+ connections. This functionality was added to CherryPy 3.0, but
+ unintentionally lost in 3.1.
+
+-----
+3.4.0
+-----
+
+* Miscellaneous quality improvements.
+
+-----
+3.3.0
+-----
+
+CherryPy adopts semver.
diff --git a/CONTRIBUTING.txt b/CONTRIBUTING.txt
new file mode 100644
index 00000000..6c653f75
--- /dev/null
+++ b/CONTRIBUTING.txt
@@ -0,0 +1,6 @@
+CherryPy is a community-maintained, open-source project hosted at Bitbucket.
+
+To contribute, first read `How to write the perfect pull request
+<http://blog.jaraco.com/2014/04/how-to-write-perfect-pull-request.html>`_
+and file your contribution with the `CherryPy Project page
+<https://bitbucket.org/cherrypy/cherrypy>`_.
diff --git a/cherrypy/__init__.py b/cherrypy/__init__.py
index f5d3b004..ce34861b 100644
--- a/cherrypy/__init__.py
+++ b/cherrypy/__init__.py
@@ -56,7 +56,7 @@ with customized or extended components. The core API's are:
These API's are described in the `CherryPy specification <https://bitbucket.org/cherrypy/cherrypy/wiki/CherryPySpec>`_.
"""
-__version__ = "3.2.4"
+__version__ = "3.5.1"
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
from cherrypy._cpcompat import basestring, unicodestr, set
@@ -93,6 +93,7 @@ except ImportError:
engine.listeners['before_request'] = set()
engine.listeners['after_request'] = set()
+
class _TimeoutMonitor(process.plugins.Monitor):
def __init__(self, bus):
@@ -125,6 +126,7 @@ engine.signal_handler = process.plugins.SignalHandler(engine)
class _HandleSignalsPlugin(object):
+
"""Handle signals from other processes based on the configured
platform handlers above."""
@@ -175,7 +177,9 @@ def quickstart(root=None, script_name="", config=None):
from cherrypy._cpcompat import threadlocal as _local
+
class _Serving(_local):
+
"""An interface for registering request and response objects.
Rather than have a separate "thread local" object for the request and
@@ -270,7 +274,10 @@ request = _ThreadLocalProxy('request')
response = _ThreadLocalProxy('response')
# Create thread_data object as a thread-specific all-purpose storage
+
+
class _ThreadData(_local):
+
"""A container for thread-specific data."""
thread_data = _ThreadData()
@@ -295,7 +302,9 @@ except ImportError:
from cherrypy import _cplogging
+
class _GlobalLogManager(_cplogging.LogManager):
+
"""A site-wide LogManager; routes to app.log or global log as appropriate.
This :class:`LogManager<cherrypy._cplogging.LogManager>` implements
@@ -306,8 +315,10 @@ class _GlobalLogManager(_cplogging.LogManager):
"""
def __call__(self, *args, **kwargs):
- """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
+ """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'):
log = request.app.log
else:
@@ -315,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:
@@ -329,6 +341,7 @@ log.error_file = ''
# Using an access file makes CP about 10% slower. Leave off by default.
log.access_file = ''
+
def _buslog(msg, level):
log.error(msg, 'ENGINE', severity=level)
engine.subscribe('log', _buslog)
@@ -348,7 +361,8 @@ def expose(func=None, alias=None):
parents[a.replace(".", "_")] = func
return func
- import sys, types
+ import sys
+ import types
if isinstance(func, (types.FunctionType, types.MethodType)):
if alias is None:
# @expose
@@ -375,6 +389,7 @@ def expose(func=None, alias=None):
alias = func
return expose_
+
def popargs(*args, **kwargs):
"""A decorator for _cp_dispatch
(cherrypy.dispatch.Dispatcher.dispatch_method_name).
@@ -454,34 +469,34 @@ def popargs(*args, **kwargs):
"""
- #Since keyword arg comes after *args, we have to process it ourselves
- #for lower versions of python.
+ # Since keyword arg comes after *args, we have to process it ourselves
+ # for lower versions of python.
handler = None
handler_call = False
- for k,v in kwargs.items():
+ for k, v in kwargs.items():
if k == 'handler':
handler = v
else:
raise TypeError(
- "cherrypy.popargs() got an unexpected keyword argument '{0}'" \
+ "cherrypy.popargs() got an unexpected keyword argument '{0}'"
.format(k)
- )
+ )
import inspect
if handler is not None \
- and (hasattr(handler, '__call__') or inspect.isclass(handler)):
+ and (hasattr(handler, '__call__') or inspect.isclass(handler)):
handler_call = True
def decorated(cls_or_self=None, vpath=None):
if inspect.isclass(cls_or_self):
- #cherrypy.popargs is a class decorator
+ # cherrypy.popargs is a class decorator
cls = cls_or_self
setattr(cls, dispatch.Dispatcher.dispatch_method_name, decorated)
return cls
- #We're in the actual function
+ # We're in the actual function
self = cls_or_self
parms = {}
for arg in args:
@@ -498,9 +513,9 @@ def popargs(*args, **kwargs):
request.params.update(parms)
- #If we are the ultimate handler, then to prevent our _cp_dispatch
- #from being called again, we will resolve remaining elements through
- #getattr() directly.
+ # If we are the ultimate handler, then to prevent our _cp_dispatch
+ # from being called again, we will resolve remaining elements through
+ # getattr() directly.
if vpath:
return getattr(self, vpath.pop(0), None)
else:
@@ -508,6 +523,7 @@ def popargs(*args, **kwargs):
return decorated
+
def url(path="", qs="", script_name=None, base=None, relative=None):
"""Create an absolute URL for the given path.
@@ -625,7 +641,7 @@ config.defaults = {
'tools.log_headers.on': True,
'tools.trailing_slash.on': True,
'tools.encode.on': True
- }
+}
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
# Must reset to get our defaults applied.
diff --git a/cherrypy/_cpchecker.py b/cherrypy/_cpchecker.py
index 3205ed09..4ef82597 100644
--- a/cherrypy/_cpchecker.py
+++ b/cherrypy/_cpchecker.py
@@ -6,6 +6,7 @@ from cherrypy._cpcompat import iteritems, copykeys, builtins
class Checker(object):
+
"""A checker for CherryPy sites and their mounted applications.
When this object is called at engine startup, it executes each
@@ -22,7 +23,6 @@ class Checker(object):
on = True
"""If True (the default), run all checks; if False, turn off all checks."""
-
def __init__(self):
self._populate_known_types()
@@ -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
@@ -61,8 +62,9 @@ class Checker(object):
key_atoms = key.strip("/").split("/")
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))
+ "The application mounted at %r has config "
+ "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."""
@@ -76,13 +78,15 @@ class Checker(object):
for key, value in iteritems(entries):
for n in ("engine.", "server.", "tree.", "checker."):
if key.startswith(n):
- msg.append("[%s] %s = %s" % (section, key, value))
+ msg.append("[%s] %s = %s" %
+ (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):
@@ -102,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
@@ -111,7 +117,7 @@ class Checker(object):
for key in app.config.keys():
if key.startswith("[") or key.endswith("]"):
warnings.warn(
- "The application mounted at %r has config " \
+ "The application mounted at %r has config "
"section names with extraneous brackets: %r. "
"Config *files* need brackets; config *dicts* "
"(e.g. passed to tree.mount) do not." % (sn, key))
@@ -144,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:
@@ -165,9 +175,7 @@ class Checker(object):
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
% (msg, section, root, dir))
-
# -------------------------- Compatibility -------------------------- #
-
obsolete = {
'server.default_content_type': 'tools.response_headers.headers',
'log_access_file': 'log.access_file',
@@ -180,7 +188,7 @@ class Checker(object):
'throw_errors': 'request.throw_errors',
'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
'cherrypy.Application(Root())))'),
- }
+ }
deprecated = {}
@@ -213,9 +221,7 @@ class Checker(object):
continue
self._compat(app.config)
-
# ------------------------ Known Namespaces ------------------------ #
-
extra_config_namespaces = []
def _known_ns(self, app):
@@ -235,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):
@@ -258,11 +268,7 @@ class Checker(object):
continue
self._known_ns(app)
-
-
-
# -------------------------- Config Types -------------------------- #
-
known_config_types = {}
def _populate_known_types(self):
@@ -314,14 +320,13 @@ class Checker(object):
continue
self._known_types(app.config)
-
# -------------------- Specific config warnings -------------------- #
-
def check_localhost(self):
"""Warn if any socket_host is 'localhost'. See #711."""
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 68c82b69..8a98b38b 100644
--- a/cherrypy/_cpcompat.py
+++ b/cherrypy/_cpcompat.py
@@ -26,16 +26,23 @@ if sys.version_info >= (3, 0):
unicodestr = str
nativestr = unicodestr
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
+
def tonative(n, encoding='ISO-8859-1'):
"""Return the given string as a native string in the given encoding."""
# In Python 3, the native string type is unicode
@@ -53,29 +60,36 @@ else:
unicodestr = unicode
nativestr = bytestr
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
# was intended.
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'):
"""Return the given string as a native string in the given encoding."""
# In Python 2, the native string type is bytes.
@@ -91,6 +105,7 @@ else:
# bytes:
BytesIO = StringIO
+
def assert_native(n):
if not isinstance(n, nativestr):
raise TypeError("n must be a native str (got %s)" % type(n).__name__)
@@ -109,6 +124,7 @@ except ImportError:
# the legacy API of base64
from base64 import decodestring as _base64_decodebytes
+
def base64_decode(n, encoding='ISO-8859-1'):
"""Return the native string base64-decoded (as a native string)."""
if isinstance(n, unicodestr):
@@ -210,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
@@ -243,16 +261,19 @@ if hasattr(threading.Thread, "daemon"):
# Python 2.6+
def get_daemon(t):
return t.daemon
+
def set_daemon(t, val):
t.daemon = val
else:
def get_daemon(t):
return t.isDaemon()
+
def set_daemon(t, val):
t.setDaemon(val)
try:
from email.utils import formatdate
+
def HTTPDate(timeval=None):
return formatdate(timeval, usegmt=True)
except ImportError:
@@ -261,16 +282,22 @@ except ImportError:
try:
# Python 3
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
+
def unquote_qs(atom, encoding, errors='strict'):
return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)
try:
- # Prefer simplejson, which is usually more advanced than the builtin module.
+ # Prefer simplejson, which is usually more advanced than the builtin
+ # module.
import simplejson as json
json_decode = json.JSONDecoder().decode
_json_encode = json.JSONEncoder().iterencode
@@ -282,20 +309,22 @@ except ImportError:
_json_encode = json.JSONEncoder().iterencode
else:
json = None
+
def json_decode(s):
raise ValueError('No JSON library is available')
+
def _json_encode(s):
raise ValueError('No JSON library is available')
finally:
if json and py3k:
- # The two Python 3 implementations (simplejson/json)
+ # The two Python 3 implementations (simplejson/json)
# outputs str. We need bytes.
def json_encode(value):
for chunk in _json_encode(value):
yield chunk.encode('utf8')
else:
json_encode = _json_encode
-
+
try:
import cPickle as pickle
@@ -307,11 +336,13 @@ except ImportError:
try:
os.urandom(20)
import binascii
+
def random20():
return binascii.hexlify(os.urandom(20)).decode('ascii')
except (AttributeError, NotImplementedError):
import random
# os.urandom not available until Python 2.4. Fall back to random.random.
+
def random20():
return sha('%s' % random.random()).hexdigest()
@@ -328,7 +359,7 @@ except NameError:
def next(i):
return i.next()
-if sys.version_info >= (3,3):
+if sys.version_info >= (3, 3):
Timer = threading.Timer
Event = threading.Event
else:
@@ -338,17 +369,15 @@ else:
# Prior to Python 2.6, the Thread class did not have a .daemon property.
# This mix-in adds that property.
+
+
class SetDaemonProperty:
+
def __get_daemon(self):
return self.isDaemon()
+
def __set_daemon(self, daemon):
self.setDaemon(daemon)
- if sys.version_info < (2,6):
+ if sys.version_info < (2, 6):
daemon = property(__get_daemon, __set_daemon)
-
-# Use subprocess module from Python 2.7 on Python 2.3-2.6
-if sys.version_info < (2,7):
- import cherrypy._cpcompat_subprocess as subprocess
-else:
- import subprocess
diff --git a/cherrypy/_cpcompat_subprocess.py b/cherrypy/_cpcompat_subprocess.py
index f058ab6a..ce363725 100644
--- a/cherrypy/_cpcompat_subprocess.py
+++ b/cherrypy/_cpcompat_subprocess.py
@@ -404,30 +404,38 @@ except NameError:
from sets import Set as set
# Exception classes used by this module.
+
+
class CalledProcessError(Exception):
+
"""This exception is raised when a process run by check_call() or
check_output() returns a non-zero exit status.
The exit status will be stored in the returncode attribute;
check_output() will also store the output in the output attribute.
"""
+
def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
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:
import threading
import msvcrt
import _subprocess
+
class STARTUPINFO:
dwFlags = 0
hStdInput = None
hStdOutput = None
hStdError = None
wShowWindow = 0
+
class pywintypes:
error = IOError
else:
@@ -447,9 +455,9 @@ __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call",
if mswindows:
from _subprocess import CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, \
- STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, \
- STD_ERROR_HANDLE, SW_HIDE, \
- STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW
+ STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, \
+ STD_ERROR_HANDLE, SW_HIDE, \
+ STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW
__all__.extend(["CREATE_NEW_CONSOLE", "CREATE_NEW_PROCESS_GROUP",
"STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE",
@@ -462,6 +470,7 @@ except:
_active = []
+
def _cleanup():
for inst in _active[:]:
res = inst._internal_poll(_deadstate=sys.maxint)
@@ -599,7 +608,7 @@ def list2cmdline(seq):
bs_buf.append(c)
elif c == '"':
# Double backslashes.
- result.append('\\' * len(bs_buf)*2)
+ result.append('\\' * len(bs_buf) * 2)
bs_buf = []
result.append('\\"')
else:
@@ -621,6 +630,7 @@ def list2cmdline(seq):
class Popen(object):
+
def __init__(self, args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
@@ -640,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:
@@ -704,13 +715,11 @@ class Popen(object):
else:
self.stderr = os.fdopen(errread, 'rb', bufsize)
-
def _translate_newlines(self, data):
data = data.replace("\r\n", "\n")
data = data.replace("\r", "\n")
return data
-
def __del__(self, _maxint=sys.maxint, _active=_active):
# If __init__ hasn't had a chance to execute (e.g. if it
# was passed an undeclared keyword argument), we don't
@@ -724,7 +733,6 @@ class Popen(object):
# Child is still running, keep us alive until we can wait on it.
_active.append(self)
-
def communicate(self, input=None):
"""Interact with process: Send data to stdin. Read data from
stdout and stderr, until end-of-file is reached. Wait for
@@ -758,11 +766,9 @@ class Popen(object):
return self._communicate(input)
-
def poll(self):
return self._internal_poll()
-
if mswindows:
#
# Windows methods
@@ -779,7 +785,8 @@ class Popen(object):
errread, errwrite = None, None
if stdin is None:
- p2cread = _subprocess.GetStdHandle(_subprocess.STD_INPUT_HANDLE)
+ p2cread = _subprocess.GetStdHandle(
+ _subprocess.STD_INPUT_HANDLE)
if p2cread is None:
p2cread, _ = _subprocess.CreatePipe(None, 0)
elif stdin == PIPE:
@@ -792,7 +799,8 @@ class Popen(object):
p2cread = self._make_inheritable(p2cread)
if stdout is None:
- c2pwrite = _subprocess.GetStdHandle(_subprocess.STD_OUTPUT_HANDLE)
+ c2pwrite = _subprocess.GetStdHandle(
+ _subprocess.STD_OUTPUT_HANDLE)
if c2pwrite is None:
_, c2pwrite = _subprocess.CreatePipe(None, 0)
elif stdout == PIPE:
@@ -805,7 +813,8 @@ class Popen(object):
c2pwrite = self._make_inheritable(c2pwrite)
if stderr is None:
- errwrite = _subprocess.GetStdHandle(_subprocess.STD_ERROR_HANDLE)
+ errwrite = _subprocess.GetStdHandle(
+ _subprocess.STD_ERROR_HANDLE)
if errwrite is None:
_, errwrite = _subprocess.CreatePipe(None, 0)
elif stderr == PIPE:
@@ -823,19 +832,22 @@ class Popen(object):
c2pread, c2pwrite,
errread, errwrite)
-
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"""
w9xpopen = os.path.join(
- os.path.dirname(_subprocess.GetModuleFileName(0)),
- "w9xpopen.exe")
+ os.path.dirname(_subprocess.GetModuleFileName(0)),
+ "w9xpopen.exe")
if not os.path.exists(w9xpopen):
# Eeek - file-not-found - possibly an embedding
# situation - see if we can locate it in sys.exec_prefix
@@ -847,7 +859,6 @@ class Popen(object):
"shell or platform.")
return w9xpopen
-
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines,
startupinfo, creationflags, shell,
@@ -872,7 +883,7 @@ class Popen(object):
startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = _subprocess.SW_HIDE
comspec = os.environ.get("COMSPEC", "cmd.exe")
- args = '{} /c "{}"'.format (comspec, args)
+ args = '{} /c "{}"'.format(comspec, args)
if (_subprocess.GetVersion() >= 0x80000000 or
os.path.basename(comspec).lower() == "command.com"):
# Win9x, or using command.com on NT. We need to
@@ -892,14 +903,16 @@ class Popen(object):
# Start the process
try:
try:
- hp, ht, pid, tid = _subprocess.CreateProcess(executable, args,
- # no special security
- None, None,
- int(not close_fds),
- creationflags,
- env,
- cwd,
- startupinfo)
+ hp, ht, pid, tid = _subprocess.CreateProcess(
+ executable, args,
+ # no special
+ # security
+ None, None,
+ int(not close_fds),
+ creationflags,
+ env,
+ cwd,
+ startupinfo)
except pywintypes.error, e:
# Translate pywintypes.error to WindowsError, which is
# a subclass of OSError. FIXME: We should really
@@ -926,10 +939,12 @@ class Popen(object):
self.pid = pid
ht.Close()
- def _internal_poll(self, _deadstate=None,
+ def _internal_poll(
+ self, _deadstate=None,
_WaitForSingleObject=_subprocess.WaitForSingleObject,
_WAIT_OBJECT_0=_subprocess.WAIT_OBJECT_0,
- _GetExitCodeProcess=_subprocess.GetExitCodeProcess):
+ _GetExitCodeProcess=_subprocess.GetExitCodeProcess
+ ):
"""Check if child process has terminated. Returns returncode
attribute.
@@ -942,7 +957,6 @@ class Popen(object):
self.returncode = _GetExitCodeProcess(self._handle)
return self.returncode
-
def wait(self):
"""Wait for child process to terminate. Returns returncode
attribute."""
@@ -952,14 +966,12 @@ class Popen(object):
self.returncode = _subprocess.GetExitCodeProcess(self._handle)
return self.returncode
-
def _readerthread(self, fh, buffer):
buffer.append(fh.read())
-
def _communicate(self, input):
- stdout = None # Return
- stderr = None # Return
+ stdout = None # Return
+ stderr = None # Return
if self.stdout:
stdout = []
@@ -1074,7 +1086,6 @@ class Popen(object):
c2pread, c2pwrite,
errread, errwrite)
-
def _set_cloexec_flag(self, fd, cloexec=True):
try:
cloexec_flag = fcntl.FD_CLOEXEC
@@ -1087,19 +1098,18 @@ class Popen(object):
else:
fcntl.fcntl(fd, fcntl.F_SETFD, old & ~cloexec_flag)
-
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)
self._set_cloexec_flag(w)
return r, w
-
def _close_fds(self, but):
if hasattr(os, 'closerange'):
os.closerange(3, but)
@@ -1113,7 +1123,6 @@ class Popen(object):
except:
pass
-
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines,
startupinfo, creationflags, shell,
@@ -1143,7 +1152,8 @@ class Popen(object):
try:
gc_was_enabled = gc.isenabled()
# Disable gc to avoid bug where gc -> file_dealloc ->
- # write to stderr -> hang. http://bugs.python.org/issue1336
+ # write to stderr -> hang.
+ # http://bugs.python.org/issue1336
gc.disable()
try:
self.pid = os.fork()
@@ -1210,15 +1220,16 @@ class Popen(object):
except:
exc_type, exc_value, tb = sys.exc_info()
- # Save the traceback and attach it to the exception object
+ # Save the traceback and attach it to the exception
+ # object
exc_lines = traceback.format_exception(exc_type,
exc_value,
tb)
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
@@ -1254,10 +1265,9 @@ class Popen(object):
os.close(fd)
raise child_exception
-
def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED,
- _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED,
- _WEXITSTATUS=os.WEXITSTATUS):
+ _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED,
+ _WEXITSTATUS=os.WEXITSTATUS):
# This method is called (indirectly) by __del__, so it cannot
# refer to anything outside of its local scope."""
if _WIFSIGNALED(sts):
@@ -1268,9 +1278,8 @@ class Popen(object):
# Should never happen
raise RuntimeError("Unknown child exit status!")
-
def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
- _WNOHANG=os.WNOHANG, _os_error=os.error):
+ _WNOHANG=os.WNOHANG, _os_error=os.error):
"""Check if child process has terminated. Returns returncode
attribute.
@@ -1288,7 +1297,6 @@ class Popen(object):
self.returncode = _deadstate
return self.returncode
-
def wait(self):
"""Wait for child process to terminate. Returns returncode
attribute."""
@@ -1305,7 +1313,6 @@ class Popen(object):
self._handle_exitstatus(sts)
return self.returncode
-
def _communicate(self, input):
if self.stdin:
# Flush stdio buffer. This might block, if the user has
@@ -1338,14 +1345,14 @@ class Popen(object):
self.wait()
return (stdout, stderr)
-
def _communicate_with_poll(self, input):
- stdout = None # Return
- stderr = None # Return
+ stdout = None # Return
+ stderr = None # Return
fd2file = {}
fd2output = {}
poller = select.poll()
+
def register_and_append(file_obj, eventmask):
poller.register(file_obj.fileno(), eventmask)
fd2file[file_obj.fileno()] = file_obj
@@ -1377,7 +1384,7 @@ class Popen(object):
for fd, mode in ready:
if mode & select.POLLOUT:
- chunk = input[input_offset : input_offset + _PIPE_BUF]
+ chunk = input[input_offset: input_offset + _PIPE_BUF]
try:
input_offset += os.write(fd, chunk)
except OSError, e:
@@ -1399,12 +1406,11 @@ class Popen(object):
return (stdout, stderr)
-
def _communicate_with_select(self, input):
read_set = []
write_set = []
- stdout = None # Return
- stderr = None # Return
+ stdout = None # Return
+ stderr = None # Return
if self.stdin and input:
write_set.append(self.stdin)
@@ -1418,14 +1424,15 @@ class Popen(object):
input_offset = 0
while read_set or write_set:
try:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
+ rlist, wlist, xlist = select.select(
+ read_set, write_set, [])
except select.error, e:
if e.args[0] == errno.EINTR:
continue
raise
if self.stdin in wlist:
- chunk = input[input_offset : input_offset + _PIPE_BUF]
+ chunk = input[input_offset: input_offset + _PIPE_BUF]
try:
bytes_written = os.write(self.stdin.fileno(), chunk)
except OSError, e:
@@ -1456,7 +1463,6 @@ class Popen(object):
return (stdout, stderr)
-
def send_signal(self, sig):
"""Send a signal to the process
"""
diff --git a/cherrypy/_cpconfig.py b/cherrypy/_cpconfig.py
index e1ab28ce..c11bc1d1 100644
--- a/cherrypy/_cpconfig.py
+++ b/cherrypy/_cpconfig.py
@@ -125,6 +125,7 @@ from cherrypy.lib import reprconf
# Deprecated in CherryPy 3.2--remove in 3.3
NamespaceSet = reprconf.NamespaceSet
+
def merge(base, other):
"""Merge one app config (from a dict, file, or filename) into another.
@@ -146,6 +147,7 @@ def merge(base, other):
class Config(reprconf.Config):
+
"""The 'global' configuration data for the entire CherryPy process."""
def update(self, config):
@@ -171,6 +173,7 @@ class Config(reprconf.Config):
raise TypeError(
"The cherrypy.config decorator does not accept positional "
"arguments; you must use keyword arguments.")
+
def tool_decorator(f):
if not hasattr(f, "_cp_config"):
f._cp_config = {}
@@ -188,7 +191,7 @@ Config.environments = environments = {
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
- },
+ },
"production": {
'engine.autoreload.on': False,
'checker.on': False,
@@ -196,7 +199,7 @@ Config.environments = environments = {
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
'log.screen': False,
- },
+ },
"embedded": {
# For use with CherryPy embedded in another deployment stack.
'engine.autoreload.on': False,
@@ -207,7 +210,7 @@ Config.environments = environments = {
'log.screen': False,
'engine.SIGHUP': None,
'engine.SIGTERM': None,
- },
+ },
"test_suite": {
'engine.autoreload.on': False,
'checker.on': False,
@@ -215,8 +218,8 @@ Config.environments = environments = {
'request.show_tracebacks': True,
'request.show_mismatched_params': True,
'log.screen': False,
- },
- }
+ },
+}
# Sphinx end config.environments
@@ -247,17 +250,23 @@ def _server_namespace_handler(k, v):
setattr(cherrypy.server, k, v)
Config.namespaces["server"] = _server_namespace_handler
+
def _engine_namespace_handler(k, v):
"""Backward compatibility handler for the "engine" namespace."""
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'}
+ deprecated = {
+ '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:
@@ -283,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)
@@ -297,10 +309,9 @@ def _tree_namespace_handler(k, v):
if isinstance(v, dict):
for script_name, app in v.items():
cherrypy.tree.graft(app, script_name)
- cherrypy.engine.log("Mounted: %s on %s" % (app, script_name or "/"))
+ cherrypy.engine.log("Mounted: %s on %s" %
+ (app, script_name or "/"))
else:
cherrypy.tree.graft(v, v.script_name)
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
Config.namespaces["tree"] = _tree_namespace_handler
-
-
diff --git a/cherrypy/_cpdispatch.py b/cherrypy/_cpdispatch.py
index bed35b59..1c2d7df8 100644
--- a/cherrypy/_cpdispatch.py
+++ b/cherrypy/_cpdispatch.py
@@ -22,6 +22,7 @@ from cherrypy._cpcompat import set
class PageHandler(object):
+
"""Callable which sets response.body."""
def __init__(self, callable, *args, **kwargs):
@@ -35,8 +36,12 @@ class PageHandler(object):
def set_args(self, args):
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
@@ -44,8 +49,12 @@ class PageHandler(object):
def set_kwargs(self, kwargs):
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:
@@ -72,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.
@@ -83,10 +93,11 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
show_mismatched_params = getattr(
cherrypy.serving.request, 'show_mismatched_params', False)
try:
- (args, varargs, varkw, defaults) = inspect.getargspec(callable)
+ (args, varargs, varkw, defaults) = getargspec(callable)
except TypeError:
if isinstance(callable, object) and hasattr(callable, '__call__'):
- (args, varargs, varkw, defaults) = inspect.getargspec(callable.__call__)
+ (args, varargs, varkw,
+ defaults) = getargspec(callable.__call__)
else:
# If it wasn't one of our own types, re-raise
# the original error
@@ -143,7 +154,7 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
# arguments it's definitely a 404.
message = None
if show_mismatched_params:
- message="Missing parameters: %s" % ",".join(missing_args)
+ message = "Missing parameters: %s" % ",".join(missing_args)
raise cherrypy.HTTPError(404, message=message)
# the extra positional arguments come from the path - 404 Not Found
@@ -165,8 +176,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
message = None
if show_mismatched_params:
- message="Multiple values for parameters: "\
- "%s" % ",".join(multiple_args)
+ message = "Multiple values for parameters: "\
+ "%s" % ",".join(multiple_args)
raise cherrypy.HTTPError(error, message=message)
if not varkw and varkw_usage > 0:
@@ -176,8 +187,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
if extra_qs_params:
message = None
if show_mismatched_params:
- message="Unexpected query string "\
- "parameters: %s" % ", ".join(extra_qs_params)
+ message = "Unexpected query string "\
+ "parameters: %s" % ", ".join(extra_qs_params)
raise cherrypy.HTTPError(404, message=message)
# If there were any extra body parameters, it's a 400 Not Found
@@ -185,8 +196,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
if extra_body_params:
message = None
if show_mismatched_params:
- message="Unexpected body parameters: "\
- "%s" % ", ".join(extra_body_params)
+ message = "Unexpected body parameters: "\
+ "%s" % ", ".join(extra_body_params)
raise cherrypy.HTTPError(400, message=message)
@@ -194,10 +205,16 @@ try:
import inspect
except ImportError:
test_callable_spec = lambda callable, args, kwargs: None
-
+else:
+ getargspec = inspect.getargspec
+ # Python 3 requires using getfullargspec if keyword-only arguments are present
+ if hasattr(inspect, 'getfullargspec'):
+ def getargspec(callable):
+ return inspect.getfullargspec(callable)[:4]
class LateParamPageHandler(PageHandler):
+
"""When passing cherrypy.request.params to the page handler, we do not
want to capture that dict too early; we want to give tools like the
decoding tool a chance to modify the params dict in-between the lookup
@@ -224,17 +241,22 @@ class LateParamPageHandler(PageHandler):
if sys.version_info < (3, 0):
punctuation_to_underscores = string.maketrans(
string.punctuation, '_' * len(string.punctuation))
+
def validate_translator(t):
if not isinstance(t, str) or len(t) != 256:
- raise ValueError("The translate argument must be a str of len 256.")
+ raise ValueError(
+ "The translate argument must be a str of len 256.")
else:
punctuation_to_underscores = str.maketrans(
string.punctuation, '_' * len(string.punctuation))
+
def validate_translator(t):
if not isinstance(t, dict):
raise ValueError("The translate argument must be a dict.")
+
class Dispatcher(object):
+
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
The tree is rooted at cherrypy.request.app.root, and each hierarchical
@@ -323,31 +345,31 @@ class Dispatcher(object):
if dispatch and hasattr(dispatch, '__call__') and not \
getattr(dispatch, 'exposed', False) and \
pre_len > 1:
- #Don't expose the hidden 'index' token to _cp_dispatch
- #We skip this if pre_len == 1 since it makes no sense
- #to call a dispatcher when we have no tokens left.
+ # Don't expose the hidden 'index' token to _cp_dispatch
+ # We skip this if pre_len == 1 since it makes no sense
+ # to call a dispatcher when we have no tokens left.
index_name = iternames.pop()
subnode = dispatch(vpath=iternames)
iternames.append(index_name)
else:
- #We didn't find a path, but keep processing in case there
- #is a default() handler.
+ # We didn't find a path, but keep processing in case there
+ # is a default() handler.
iternames.pop(0)
else:
- #We found the path, remove the vpath entry
+ # We found the path, remove the vpath entry
iternames.pop(0)
segleft = len(iternames)
if segleft > pre_len:
- #No path segment was removed. Raise an error.
+ # No path segment was removed. Raise an error.
raise cherrypy.CherryPyException(
"A vpath segment was added. Custom dispatchers may only "
+ "remove elements. While trying to process "
+ "{0} in {1}".format(name, fullpath)
- )
+ )
elif segleft == pre_len:
- #Assume that the handler used the current path segment, but
- #did not pop it. This allows things like
- #return getattr(self, vpath[0], None)
+ # Assume that the handler used the current path segment, but
+ # did not pop it. This allows things like
+ # return getattr(self, vpath[0], None)
iternames.pop(0)
segleft -= 1
node = subnode
@@ -372,14 +394,16 @@ 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.
for name, obj, conf, segleft in object_trail:
base.update(conf)
if 'tools.staticdir.dir' in conf:
- base['tools.staticdir.section'] = '/' + '/'.join(fullpath[0:fullpath_len - segleft])
+ base['tools.staticdir.section'] = '/' + \
+ '/'.join(fullpath[0:fullpath_len - segleft])
return base
# Try successive objects (reverse order)
@@ -396,13 +420,15 @@ class Dispatcher(object):
if getattr(defhandler, 'exposed', False):
# Insert any extra _cp_config from the default handler.
conf = getattr(defhandler, "_cp_config", {})
- object_trail.insert(i+1, ["default", defhandler, conf, segleft])
+ object_trail.insert(
+ i + 1, ["default", defhandler, conf, segleft])
request.config = set_conf()
# See https://bitbucket.org/cherrypy/cherrypy/issue/613
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.
@@ -426,6 +452,7 @@ class Dispatcher(object):
class MethodDispatcher(Dispatcher):
+
"""Additional dispatch based on cherrypy.request.method.upper().
Methods named GET, POST, etc will be called on an exposed class.
@@ -469,6 +496,7 @@ class MethodDispatcher(Dispatcher):
class RoutesDispatcher(object):
+
"""A Routes based dispatcher for CherryPy."""
def __init__(self, full_result=False, **mapper_options):
@@ -584,13 +612,15 @@ class RoutesDispatcher(object):
def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
from cherrypy.lib import xmlrpcutil
+
def xmlrpc_dispatch(path_info):
path_info = xmlrpcutil.patched_path(path_info)
return next_dispatcher(path_info)
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.
@@ -630,6 +660,7 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domai
headers may contain the port number.
"""
from cherrypy.lib import httputil
+
def vhost_dispatch(path_info):
request = cherrypy.serving.request
header = request.headers.get
@@ -644,7 +675,8 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domai
result = next_dispatcher(path_info)
- # Touch up staticdir config. See https://bitbucket.org/cherrypy/cherrypy/issue/614.
+ # Touch up staticdir config. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/614.
section = request.config.get('tools.staticdir.section')
if section:
section = section[len(prefix):]
@@ -652,4 +684,3 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domai
return result
return vhost_dispatch
-
diff --git a/cherrypy/_cperror.py b/cherrypy/_cperror.py
index 2f627c43..6256595b 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,40 +99,50 @@ 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
class CherryPyException(Exception):
+
"""A base class for CherryPy exceptions."""
pass
class TimeoutError(CherryPyException):
+
"""Exception raised when Response.timed_out is detected."""
pass
class InternalRedirect(CherryPyException):
+
"""Exception raised to switch to the handler for a different URL.
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=""):
@@ -152,6 +168,7 @@ class InternalRedirect(CherryPyException):
class HTTPRedirect(CherryPyException):
+
"""Exception raised when the request should be redirected.
This exception will force a HTTP redirect to the URL or URL's you give it.
@@ -222,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.
@@ -240,13 +258,16 @@ 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]
- msgs = [msg % (u, u) for u in self.urls]
+ 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>.'
+ from xml.sax import saxutils
+ msgs = [msg % (saxutils.quoteattr(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
# (allow finalize to set it).
@@ -311,24 +332,27 @@ def clean_headers(status):
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."""
@@ -352,7 +376,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.
@@ -369,11 +394,11 @@ class HTTPError(CherryPyException):
tb = None
if cherrypy.serving.request.show_tracebacks:
tb = format_exc()
- response.headers['Content-Type'] = "text/html;charset=utf-8"
+
response.headers.pop('Content-Length', None)
content = self.get_error_page(self.status, traceback=tb,
- message=self._message).encode('utf-8')
+ message=self._message)
response.body = content
_be_ie_unfriendly(self.code)
@@ -387,6 +412,7 @@ class HTTPError(CherryPyException):
class NotFound(HTTPError):
+
"""Exception raised when a URL could not be mapped to any handler (404).
This is equivalent to raising
@@ -402,7 +428,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>
@@ -425,12 +452,15 @@ _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>
'''
+
def get_error_page(status, **kwargs):
"""Return an HTML page, containing a pretty error response.
@@ -464,13 +494,33 @@ def get_error_page(status, **kwargs):
# Use a custom template or callable for the error page?
pages = cherrypy.serving.request.error_page
error_page = pages.get(code) or pages.get('default')
+
+ # Default template, can be overridden below.
+ template = _HTTPErrorTemplate
if error_page:
try:
if hasattr(error_page, '__call__'):
- return error_page(**kwargs)
+ # The caller function may be setting headers manually,
+ # so we delegate to it completely. We may be returning
+ # an iterator as well as a string here.
+ #
+ # We *must* make sure any content is not unicode.
+ result = error_page(**kwargs)
+ if cherrypy.lib.is_iterator(result):
+ from cherrypy.lib.encoding import UTF8StreamEncoder
+ return UTF8StreamEncoder(result)
+ elif isinstance(result, cherrypy._cpcompat.unicodestr):
+ return result.encode('utf-8')
+ else:
+ if not isinstance(result, cherrypy._cpcompat.bytestr):
+ raise ValueError('error page function did not '
+ 'return a bytestring, unicodestring or an '
+ 'iterator - returned object of type %s.'
+ % (type(result).__name__))
+ return result
else:
- data = open(error_page, 'rb').read()
- return tonative(data) % kwargs
+ # Load the template from this path.
+ template = tonative(open(error_page, 'rb').read())
except:
e = _format_exception(*_exc_info())[-1]
m = kwargs['message']
@@ -479,14 +529,18 @@ def get_error_page(status, **kwargs):
m += "In addition, the custom error page failed:\n<br />%s" % e
kwargs['message'] = m
- return _HTTPErrorTemplate % kwargs
+ response = cherrypy.serving.response
+ response.headers['Content-Type'] = "text/html;charset=utf-8"
+ result = template % kwargs
+ return result.encode('utf-8')
+
_ie_friendly_error_sizes = {
400: 512, 403: 256, 404: 512, 405: 256,
406: 512, 408: 512, 409: 512, 410: 256,
500: 512, 501: 512, 505: 512,
- }
+}
def _be_ie_unfriendly(status):
@@ -525,6 +579,7 @@ def format_exc(exc=None):
finally:
del exc
+
def bare_error(extrabody=None):
"""Produce status, headers, body for a critical error.
@@ -550,7 +605,5 @@ def bare_error(extrabody=None):
return (ntob("500 Internal Server Error"),
[(ntob('Content-Type'), ntob('text/plain')),
- (ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
+ (ntob('Content-Length'), ntob(str(len(body)), 'ISO-8859-1'))],
[body])
-
-
diff --git a/cherrypy/_cplogging.py b/cherrypy/_cplogging.py
index cecbcd50..554fd7ef 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
@@ -55,6 +56,13 @@ errors! The format of access messages is highly formalized, but the error log
isn't--it receives messages from a variety of sources (including full error
tracebacks, if enabled).
+If you are logging the access log and error log to the same source, then there
+is a possibility that a specially crafted error message may replicate an access
+log message as described in CWE-117. In this case it is the application
+developer's responsibility to manually escape data before using CherryPy's log()
+functionality, or they may create an application that is vulnerable to CWE-117.
+This would be achieved by using a custom handler escape any special characters,
+and attached as described below.
Custom Handlers
===============
@@ -113,6 +121,7 @@ from cherrypy._cpcompat import ntob, py3k
class NullHandler(logging.Handler):
+
"""A no-op logging handler to silence the logging.lastResort handler."""
def handle(self, record):
@@ -126,6 +135,7 @@ class NullHandler(logging.Handler):
class LogManager(object):
+
"""An object to assist both simple and advanced logging.
``cherrypy.log`` is an instance of this class.
@@ -166,8 +176,10 @@ class LogManager(object):
self.error_log = logging.getLogger("%s.error" % logger_root)
self.access_log = logging.getLogger("%s.access" % logger_root)
else:
- self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
- self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
+ self.error_log = logging.getLogger(
+ "%s.error.%s" % (logger_root, appid))
+ self.access_log = logging.getLogger(
+ "%s.access.%s" % (logger_root, appid))
self.error_log.setLevel(logging.INFO)
self.access_log.setLevel(logging.INFO)
@@ -187,7 +199,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
@@ -207,9 +220,10 @@ 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;
it collects the data itself from
:class:`cherrypy.request<cherrypy._cprequest.Request>`.
@@ -242,6 +256,7 @@ class LogManager(object):
'b': dict.get(outheaders, 'Content-Length', '') or "-",
'f': dict.get(inheaders, 'Referer', ''),
'a': dict.get(inheaders, 'User-Agent', ''),
+ 'o': dict.get(inheaders, 'Host', '-'),
}
if py3k:
for k, v in atoms.items():
@@ -261,7 +276,8 @@ class LogManager(object):
atoms[k] = v
try:
- self.access_log.log(logging.INFO, self.access_log_format.format(**atoms))
+ self.access_log.log(
+ logging.INFO, self.access_log_format.format(**atoms))
except:
self(traceback=True)
else:
@@ -277,7 +293,8 @@ class LogManager(object):
atoms[k] = v.replace('"', '\\"')
try:
- self.access_log.log(logging.INFO, self.access_log_format % atoms)
+ self.access_log.log(
+ logging.INFO, self.access_log_format % atoms)
except:
self(traceback=True)
@@ -295,15 +312,13 @@ class LogManager(object):
if getattr(h, "_cpbuiltin", None) == key:
return h
-
# ------------------------- Screen handlers ------------------------- #
-
def _set_screen_handler(self, log, enable, stream=None):
h = self._get_builtin_handler(log, "screen")
if enable:
if not h:
if stream is None:
- stream=sys.stderr
+ stream = sys.stderr
h = logging.StreamHandler(stream)
h.setFormatter(logfmt)
h._cpbuiltin = "screen"
@@ -320,7 +335,7 @@ class LogManager(object):
self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
screen = property(_get_screen, _set_screen,
- doc="""Turn stderr/stdout logging on or off.
+ doc="""Turn stderr/stdout logging on or off.
If you set this to True, it'll add the appropriate StreamHandler for
you. If you set it to False, it will remove the handler.
@@ -354,10 +369,11 @@ class LogManager(object):
if h:
return h.baseFilename
return ''
+
def _set_error_file(self, newvalue):
self._set_file_handler(self.error_log, newvalue)
error_file = property(_get_error_file, _set_error_file,
- doc="""The filename for self.error_log.
+ doc="""The filename for self.error_log.
If you set this to a string, it'll add the appropriate FileHandler for
you. If you set it to ``None`` or ``''``, it will remove the handler.
@@ -368,10 +384,11 @@ class LogManager(object):
if h:
return h.baseFilename
return ''
+
def _set_access_file(self, newvalue):
self._set_file_handler(self.access_log, newvalue)
access_file = property(_get_access_file, _set_access_file,
- doc="""The filename for self.access_log.
+ doc="""The filename for self.access_log.
If you set this to a string, it'll add the appropriate FileHandler for
you. If you set it to ``None`` or ``''``, it will remove the handler.
@@ -396,7 +413,7 @@ class LogManager(object):
def _set_wsgi(self, newvalue):
self._set_wsgi_handler(self.error_log, newvalue)
wsgi = property(_get_wsgi, _set_wsgi,
- doc="""Write errors to wsgi.errors.
+ doc="""Write errors to wsgi.errors.
If you set this to True, it'll add the appropriate
:class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
@@ -406,6 +423,7 @@ class LogManager(object):
class WSGIErrorHandler(logging.Handler):
+
"A handler class which writes logging records to environ['wsgi.errors']."
def flush(self):
@@ -428,7 +446,8 @@ class WSGIErrorHandler(logging.Handler):
msg = self.format(record)
fs = "%s\n"
import types
- if not hasattr(types, "UnicodeType"): #if no unicode support...
+ # if no unicode support...
+ if not hasattr(types, "UnicodeType"):
stream.write(fs % msg)
else:
try:
diff --git a/cherrypy/_cpmodpy.py b/cherrypy/_cpmodpy.py
index 66f98309..02154d69 100644
--- a/cherrypy/_cpmodpy.py
+++ b/cherrypy/_cpmodpy.py
@@ -35,11 +35,11 @@ Listen 8080
LoadModule python_module /usr/lib/apache2/modules/mod_python.so
<Location "/">
- PythonPath "sys.path+['/path/to/my/application']"
- SetHandler python-program
- PythonHandler cherrypy._cpmodpy::handler
- PythonOption cherrypy.setup myapp::setup_server
- PythonDebug On
+ PythonPath "sys.path+['/path/to/my/application']"
+ SetHandler python-program
+ PythonHandler cherrypy._cpmodpy::handler
+ PythonOption cherrypy.setup myapp::setup_server
+ PythonDebug On
</Location>
# End
@@ -67,11 +67,11 @@ from cherrypy.lib import httputil
# ------------------------------ Request-handling
-
def setup(req):
from mod_python import apache
- # Run any setup functions defined by a "PythonOption cherrypy.setup" directive.
+ # Run any setup functions defined by a "PythonOption cherrypy.setup"
+ # directive.
options = req.get_options()
if 'cherrypy.setup' in options:
for function in options['cherrypy.setup'].split():
@@ -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)
@@ -124,6 +124,7 @@ def setup(req):
class _ReadOnlyRequest:
expose = ('read', 'readline', 'readlines')
+
def __init__(self, req):
for method in self.expose:
self.__dict__[method] = getattr(req, method)
@@ -132,6 +133,8 @@ class _ReadOnlyRequest:
recursive = False
_isSetUp = False
+
+
def handler(req):
from mod_python import apache
try:
@@ -142,9 +145,11 @@ def handler(req):
# Obtain a Request object from CherryPy
local = req.connection.local_addr
- local = httputil.Host(local[0], local[1], req.connection.local_host or "")
+ local = httputil.Host(
+ local[0], local[1], req.connection.local_host or "")
remote = req.connection.remote_addr
- remote = httputil.Host(remote[0], remote[1], req.connection.remote_host or "")
+ remote = httputil.Host(
+ remote[0], remote[1], req.connection.remote_host or "")
scheme = req.parsed_uri[0] or 'http'
req.get_basic_auth_pw()
@@ -210,10 +215,12 @@ 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.
+ # Add the *previous* path_info + qs to
+ # redirections.
if qs:
qs = "?" + qs
redirections.append(sn + path + qs)
@@ -224,8 +231,9 @@ def handler(req):
qs = ir.query_string
rfile = BytesIO()
- send_response(req, response.output_status, response.header_list,
- response.body, response.stream)
+ send_response(
+ req, response.output_status, response.header_list,
+ response.body, response.stream)
finally:
app.release_serving()
except:
@@ -260,14 +268,12 @@ def send_response(req, status, headers, body, stream=False):
req.write(seg)
-
# --------------- Startup tools for CherryPy + mod_python --------------- #
-
-
import os
import re
try:
import subprocess
+
def popen(fullcmd):
p = subprocess.Popen(fullcmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
@@ -284,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:
@@ -341,4 +351,3 @@ LoadModule python_module modules/mod_python.so
def stop(self):
os.popen("apache -k stop")
self.ready = False
-
diff --git a/cherrypy/_cpnative_server.py b/cherrypy/_cpnative_server.py
index 401bce0a..e303573d 100644
--- a/cherrypy/_cpnative_server.py
+++ b/cherrypy/_cpnative_server.py
@@ -46,9 +46,11 @@ class NativeGateway(wsgiserver.Gateway):
request.app = app
request.prev = prev
- # Run the CherryPy Request object and obtain the response
+ # Run the CherryPy Request object and obtain the
+ # response
try:
- request.run(method, path, qs, req.request_protocol, headers, rfile)
+ request.run(method, path, qs,
+ req.request_protocol, headers, rfile)
break
except cherrypy.InternalRedirect:
ir = sys.exc_info()[1]
@@ -57,10 +59,12 @@ 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.
+ # Add the *previous* path_info + qs to
+ # redirections.
if qs:
qs = "?" + qs
redirections.append(sn + path + qs)
@@ -78,7 +82,7 @@ class NativeGateway(wsgiserver.Gateway):
app.release_serving()
except:
tb = format_exc()
- #print tb
+ # print tb
cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
s, h, b = bare_error()
self.send_response(s, h, b)
@@ -102,6 +106,7 @@ class NativeGateway(wsgiserver.Gateway):
class CPHTTPServer(wsgiserver.HTTPServer):
+
"""Wrapper for wsgiserver.HTTPServer.
wsgiserver has been designed to not reference CherryPy in any way,
@@ -123,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
@@ -145,5 +152,3 @@ class CPHTTPServer(wsgiserver.HTTPServer):
self.server_adapter.ssl_certificate,
self.server_adapter.ssl_private_key,
self.server_adapter.ssl_certificate_chain)
-
-
diff --git a/cherrypy/_cpreqbody.py b/cherrypy/_cpreqbody.py
index b49c0843..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."""
@@ -209,8 +216,10 @@ def process_multipart(entity):
if part.fp.done:
break
+
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 = []
@@ -235,6 +244,7 @@ def process_multipart_form_data(entity):
entity.parts = kept_parts
+
def _old_process_multipart(entity):
"""The behavior of 3.2 and lower. Deprecated and will be changed in 3.3."""
process_multipart(entity)
@@ -263,11 +273,9 @@ def _old_process_multipart(entity):
params[key] = value
-
-# --------------------------------- Entities --------------------------------- #
-
-
+# -------------------------------- Entities --------------------------------- #
class Entity(object):
+
"""An HTTP request body, or MIME multipart body.
This class collects information about the HTTP request entity. When a
@@ -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.
@@ -414,7 +426,8 @@ class Entity(object):
self.content_type = httputil.HeaderElement.from_str(
self.default_content_type)
- # Copy the class 'attempt_charsets', prepending any Content-Type charset
+ # Copy the class 'attempt_charsets', prepending any Content-Type
+ # charset
dec = self.content_type.params.get("charset", None)
if dec:
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
@@ -426,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:
@@ -444,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)
@@ -473,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)
@@ -515,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.
@@ -523,6 +550,7 @@ class Entity(object):
class Part(Entity):
+
"""A MIME part entity, part of a multipart entity."""
# "The default character set, which must be assumed in the absence of a
@@ -554,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):
@@ -607,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("")
@@ -617,7 +646,7 @@ class Part(Entity):
lines = []
seen = 0
while True:
- line = self.fp.readline(1<<16)
+ line = self.fp.readline(1 << 16)
if not line:
raise EOFError("Illegal end of multipart body.")
if line.startswith(ntob("--")) and prev_lf:
@@ -664,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()
@@ -683,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)
@@ -696,23 +732,30 @@ try:
except ValueError:
# Python 2.4 and lower
class Infinity(object):
+
def __cmp__(self, other):
return 1
+
def __sub__(self, other):
return self
inf = Infinity()
-comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
- '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
@@ -736,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:
@@ -889,13 +932,15 @@ class SizedReader:
class RequestBody(Entity):
+
"""The entity of the HTTP request."""
bufsize = 8 * 1024
"""The buffer size used when reading the socket."""
# Don't parse the request body at all if the client didn't provide
- # a Content-Type header. See https://bitbucket.org/cherrypy/cherrypy/issue/790
+ # a Content-Type header. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/790
default_content_type = ''
"""This defines a default ``Content-Type`` to use if no Content-Type header
is given. The empty string is used for RequestBody, which results in the
@@ -907,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)
@@ -952,7 +999,8 @@ class RequestBody(Entity):
# add them in here.
request_params = self.request_params
for key, value in self.params.items():
- # Python 2 only: keyword arguments must be byte strings (type 'str').
+ # Python 2 only: keyword arguments must be byte strings (type
+ # 'str').
if sys.version_info < (3, 0):
if isinstance(key, unicode):
key = key.encode('ISO-8859-1')
diff --git a/cherrypy/_cprequest.py b/cherrypy/_cprequest.py
index 3dd2873b..290bd2eb 100644
--- a/cherrypy/_cprequest.py
+++ b/cherrypy/_cprequest.py
@@ -13,6 +13,7 @@ from cherrypy.lib import httputil, file_generator
class Hook(object):
+
"""A callback and its metadata: failsafe, priority, and kwargs."""
callback = None
@@ -71,6 +72,7 @@ class Hook(object):
class HookMap(dict):
+
"""A map of call points to lists of callbacks (Hook objects)."""
def __new__(cls, points=None):
@@ -122,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
@@ -139,14 +145,17 @@ def hooks_namespace(k, v):
v = Hook(v)
cherrypy.serving.request.hooks[hookpoint].append(v)
+
def request_namespace(k, v):
"""Attach request attributes declared in config."""
- # Provides config entries to set request.body attrs (like attempt_charsets).
+ # Provides config entries to set request.body attrs (like
+ # attempt_charsets).
if k[:5] == 'body.':
setattr(cherrypy.serving.request.body, k[5:], v)
else:
setattr(cherrypy.serving.request, k, v)
+
def response_namespace(k, v):
"""Attach response attributes declared in config."""
# Provides config entries to set default response headers
@@ -156,6 +165,7 @@ def response_namespace(k, v):
else:
setattr(cherrypy.serving.response, k, v)
+
def error_page_namespace(k, v):
"""Attach error pages declared in config."""
if k != 'default':
@@ -170,6 +180,7 @@ hookpoints = ['on_start_resource', 'before_request_body',
class Request(object):
+
"""An HTTP request.
This object represents the metadata of an HTTP request message;
@@ -422,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.
@@ -707,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:
@@ -741,7 +753,8 @@ class Request(object):
# First, see if there is a custom dispatch at this URI. Custom
# dispatchers can only be specified in app.config, not in _cp_config
# (since custom dispatchers may not even have an app.root).
- dispatch = self.app.find_config(path, "request.dispatch", self.dispatch)
+ dispatch = self.app.find_config(
+ path, "request.dispatch", self.dispatch)
# dispatch() should set self.handler and self.config
dispatch(path)
@@ -763,13 +776,13 @@ class Request(object):
def _get_body_params(self):
warnings.warn(
- "body_params is deprecated in CherryPy 3.2, will be removed in "
- "CherryPy 3.3.",
- DeprecationWarning
- )
+ "body_params is deprecated in CherryPy 3.2, will be removed in "
+ "CherryPy 3.3.",
+ DeprecationWarning
+ )
return self.body.params
body_params = property(_get_body_params,
- doc= """
+ doc="""
If the request Content-Type is 'application/x-www-form-urlencoded' or
multipart, this will be a dict of the params pulled from the entity
body; that is, it will be the portion of request.params that come
@@ -783,6 +796,7 @@ class Request(object):
class ResponseBody(object):
+
"""The body of the HTTP response (the response entity)."""
if py3k:
@@ -825,6 +839,7 @@ class ResponseBody(object):
class Response(object):
+
"""An HTTP Response, including status, headers, and body."""
status = ""
@@ -892,7 +907,8 @@ class Response(object):
newbody = []
for chunk in self.body:
if py3k and not isinstance(chunk, bytes):
- raise TypeError("Chunk %s is not of type 'bytes'." % repr(chunk))
+ raise TypeError("Chunk %s is not of type 'bytes'." %
+ repr(chunk))
newbody.append(chunk)
newbody = ntob('').join(newbody)
@@ -909,7 +925,8 @@ class Response(object):
headers = self.headers
self.status = "%s %s" % (code, reason)
- self.output_status = ntob(str(code), 'ascii') + ntob(" ") + headers.encode(reason)
+ self.output_status = ntob(str(code), 'ascii') + \
+ ntob(" ") + headers.encode(reason)
if self.stream:
# The upshot: wsgiserver will chunk the response if
@@ -954,6 +971,3 @@ class Response(object):
"""
if time.time() > self.time + self.timeout:
self.timed_out = True
-
-
-
diff --git a/cherrypy/_cpserver.py b/cherrypy/_cpserver.py
index efbe5244..a31e7428 100644
--- a/cherrypy/_cpserver.py
+++ b/cherrypy/_cpserver.py
@@ -12,6 +12,7 @@ from cherrypy.process.servers import *
class Server(ServerAdapter):
+
"""An adapter for an HTTP server.
You can set attributes (like socket_host and socket_port)
@@ -26,15 +27,19 @@ class Server(ServerAdapter):
"""The TCP port on which to listen for connections."""
_socket_host = '127.0.0.1'
+
def _get_socket_host(self):
return self._socket_host
+
def _set_socket_host(self, value):
if value == '':
raise ValueError("The empty string ('') is not an allowed value. "
"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,
+ 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.
@@ -56,6 +61,14 @@ class Server(ServerAdapter):
socket_timeout = 10
"""The timeout in seconds for accepted connections (default 10)."""
+
+ accepted_queue_size = -1
+ """The maximum number of requests which will be queued up before
+ the server refuses to accept it (default -1, meaning no limit)."""
+
+ accepted_queue_timeout = 10
+ """The timeout in seconds for attempting to add a request to the
+ queue when the queue is full (default 10)."""
shutdown_timeout = 5
"""The time to wait for HTTP worker threads to clean up."""
@@ -69,11 +82,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,
@@ -100,17 +115,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."""
@@ -157,6 +174,7 @@ class Server(ServerAdapter):
if self.socket_host is None and self.socket_port is None:
return None
return (self.socket_host, self.socket_port)
+
def _set_bind_addr(self, value):
if value is None:
self.socket_file = None
@@ -174,11 +192,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
@@ -202,4 +224,3 @@ class Server(ServerAdapter):
host += ":%s" % port
return "%s://%s" % (scheme, host)
-
diff --git a/cherrypy/_cpthreadinglocal.py b/cherrypy/_cpthreadinglocal.py
index 34c17ac4..238c3224 100644
--- a/cherrypy/_cpthreadinglocal.py
+++ b/cherrypy/_cpthreadinglocal.py
@@ -137,6 +137,7 @@ affects what we see:
# Threading import is at end
+
class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock'
@@ -158,6 +159,7 @@ class _localbase(object):
return self
+
def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = currentThread().__dict__.get(key)
@@ -175,6 +177,7 @@ def _patch(self):
else:
object.__setattr__(self, '__dict__', d)
+
class local(_localbase):
def __getattribute__(self, name):
@@ -204,7 +207,6 @@ class local(_localbase):
finally:
lock.release()
-
def __del__():
threading_enumerate = enumerate
__getattribute__ = object.__getattribute__
@@ -231,7 +233,7 @@ class local(_localbase):
try:
del __dict__[key]
except KeyError:
- pass # didn't have anything in this thread
+ pass # didn't have anything in this thread
return __del__
__del__ = __del__()
diff --git a/cherrypy/_cptools.py b/cherrypy/_cptools.py
index 2923147d..06a56e87 100644
--- a/cherrypy/_cptools.py
+++ b/cherrypy/_cptools.py
@@ -43,10 +43,14 @@ 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):
+
"""A registered function for use with CherryPy request-processing hooks.
help(tool.callable) should give you more information about this Tool.
@@ -64,6 +68,7 @@ class Tool(object):
def _get_on(self):
raise AttributeError(_attr_error)
+
def _set_on(self, value):
raise AttributeError(_attr_error)
on = property(_get_on, _set_on)
@@ -117,6 +122,7 @@ class Tool(object):
raise TypeError("The %r Tool does not accept positional "
"arguments; you must use keyword arguments."
% self._name)
+
def tool_decorator(f):
if not hasattr(f, "_cp_config"):
f._cp_config = {}
@@ -142,6 +148,7 @@ class Tool(object):
class HandlerTool(Tool):
+
"""Tool which is called 'before main', that may skip normal handlers.
If the tool successfully handles the request (by setting response.body),
@@ -191,6 +198,7 @@ class HandlerTool(Tool):
class HandlerWrapperTool(Tool):
+
"""Tool which wraps request.handler in a provided wrapper function.
The 'newhandler' arg must be a handler wrapper function that takes a
@@ -209,20 +217,23 @@ 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
self._priority = priority
- def callable(self, debug=False):
+ def callable(self, *args, **kwargs):
innerfunc = cherrypy.serving.request.handler
+
def wrap(*args, **kwargs):
return self.newhandler(innerfunc, *args, **kwargs)
cherrypy.serving.request.handler = wrap
class ErrorTool(Tool):
+
"""Tool which is used to replace the default request.error_response."""
def __init__(self, callable, name=None):
@@ -249,6 +260,7 @@ from cherrypy.lib import auth_basic, auth_digest
class SessionTool(Tool):
+
"""Session Tool for CherryPy.
sessions.locking
@@ -258,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
@@ -314,9 +327,8 @@ class SessionTool(Tool):
_sessions.set_response_cookie(**conf)
-
-
class XMLRPCController(object):
+
"""A Controller (page handler collection) for XML-RPC.
To use it, have your controllers subclass this base class (it will
@@ -387,6 +399,7 @@ class SessionAuthTool(HandlerTool):
class CachingTool(Tool):
+
"""Caching Tool for CherryPy."""
def _wrapper(self, **kwargs):
@@ -397,7 +410,7 @@ class CachingTool(Tool):
if request.cacheable:
# Note the devious technique here of adding hooks on the fly
request.hooks.attach('before_finalize', _caching.tee_output,
- priority = 90)
+ priority=90)
_wrapper.priority = 20
def _setup(self):
@@ -409,8 +422,8 @@ class CachingTool(Tool):
priority=p, **conf)
-
class Toolbox(object):
+
"""A collection of Tools.
This object also functions as a config namespace handler for itself.
@@ -431,6 +444,7 @@ class Toolbox(object):
def __enter__(self):
"""Populate request.toolmaps from tools specified in config."""
cherrypy.serving.request.toolmaps[self.namespace] = map = {}
+
def populate(k, v):
toolname, arg = k.split(".", 1)
bucket = map.setdefault(toolname, {})
@@ -459,6 +473,7 @@ class DeprecatedTool(Tool):
def __call__(self, *args, **kwargs):
warnings.warn(self.warnmsg)
+
def tool_decorator(f):
return f
return tool_decorator
@@ -487,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 20df811c..a31b2793 100644
--- a/cherrypy/_cptree.py
+++ b/cherrypy/_cptree.py
@@ -1,7 +1,6 @@
"""CherryPy Application and Tree objects."""
import os
-import sys
import cherrypy
from cherrypy._cpcompat import ntou, py3k
@@ -10,6 +9,7 @@ from cherrypy.lib import httputil
class Application(object):
+
"""A CherryPy Application.
Servers and gateways should not instantiate Request objects directly.
@@ -62,10 +62,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
@@ -77,11 +77,15 @@ class Application(object):
If script_name is explicitly set to None, then the script_name will be
provided for each call from request.wsgi_environ['SCRIPT_NAME'].
"""
+
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:
value = value.rstrip("/")
@@ -148,6 +152,7 @@ class Application(object):
class Tree(object):
+
"""A registry of CherryPy applications, mounted at diverse points.
An instance of this class may also be used as a WSGI callable
@@ -201,8 +206,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)
@@ -273,7 +279,8 @@ class Tree(object):
# Python 2/WSGI u.0: all strings MUST be of type unicode
enc = environ[ntou('wsgi.url_encoding')]
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
- environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
+ environ[ntou('PATH_INFO')] = path[
+ len(sn.rstrip("/")):].decode(enc)
else:
# Python 2/WSGI 1.x: all strings MUST be of type str
environ['SCRIPT_NAME'] = sn
@@ -283,5 +290,10 @@ class Tree(object):
# Python 3/WSGI u.0: all strings MUST be full unicode
environ['SCRIPT_NAME'] = sn
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
- # for the wsgi 1.0 the environment is already encoded by the Gateway.
+ else:
+ # Python 3/WSGI 1.x: all strings MUST be ISO-8859-1 str
+ environ['SCRIPT_NAME'] = sn.encode(
+ 'utf-8').decode('ISO-8859-1')
+ environ['PATH_INFO'] = path[
+ len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
return app(environ, start_response)
diff --git a/cherrypy/_cpwsgi.py b/cherrypy/_cpwsgi.py
index fdc19249..f6db68b0 100644
--- a/cherrypy/_cpwsgi.py
+++ b/cherrypy/_cpwsgi.py
@@ -13,10 +13,11 @@ import cherrypy as _cherrypy
from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
from cherrypy import _cperror
from cherrypy.lib import httputil
-
+from cherrypy.lib import is_closable_iterator
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')]
@@ -31,6 +32,7 @@ def downgrade_wsgi_ux_to_1x(environ):
class VirtualHost(object):
+
"""Select a different WSGI application based on the Host header.
This can be useful when running multiple sites within one CP server.
@@ -82,6 +84,7 @@ class VirtualHost(object):
class InternalRedirector(object):
+
"""WSGI middleware that handles raised cherrypy.InternalRedirect."""
def __init__(self, nextapp, recursive=False):
@@ -107,7 +110,8 @@ class InternalRedirector(object):
redirections.append(old_uri)
if not self.recursive:
- # Check to see if the new URI has been redirected to already
+ # Check to see if the new URI has been redirected to
+ # already
new_uri = sn + ir.path
if ir.query_string:
new_uri += "?" + ir.query_string
@@ -126,6 +130,7 @@ class InternalRedirector(object):
class ExceptionTrapper(object):
+
"""WSGI middleware that traps exceptions."""
def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
@@ -133,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):
@@ -146,7 +156,8 @@ class _TrappedResponse(object):
self.start_response = start_response
self.throws = throws
self.started_response = False
- self.response = self.trap(self.nextapp, self.environ, self.start_response)
+ self.response = self.trap(
+ self.nextapp, self.environ, self.start_response)
self.iter_response = iter(self.response)
def __iter__(self):
@@ -210,6 +221,7 @@ class _TrappedResponse(object):
class AppResponse(object):
+
"""WSGI response iterable for CherryPy applications."""
def __init__(self, environ, start_response, cpapp):
@@ -230,16 +242,20 @@ class AppResponse(object):
outheaders = []
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)
+ raise TypeError(
+ "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)
+ raise TypeError(
+ "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]
@@ -262,14 +278,26 @@ class AppResponse(object):
def close(self):
"""Close and de-reference the current request and response. (Core)"""
+ streaming = _cherrypy.serving.response.stream
self.cpapp.release_serving()
+ # We avoid the expense of examining the iterator to see if it's
+ # closable unless we are streaming the response, as that's the
+ # only situation where we are going to have an iterator which
+ # may not have been exhausted yet.
+ if streaming and is_closable_iterator(self.iter_response):
+ iter_close = self.iter_response.close
+ try:
+ iter_close()
+ except Exception:
+ _cherrypy.log(traceback=True, severity=40)
+
def run(self):
"""Create a Request object using environ."""
env = self.environ.get
local = httputil.Host('', int(env('SERVER_PORT', 80)),
- env('SERVER_NAME', ''))
+ env('SERVER_NAME', ''))
remote = httputil.Host(env('REMOTE_ADDR', ''),
int(env('REMOTE_PORT', -1) or -1),
env('REMOTE_HOST', ''))
@@ -293,16 +321,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)
@@ -339,6 +368,7 @@ class AppResponse(object):
class CPWSGIApp(object):
+
"""A WSGI application object for a CherryPy Application."""
pipeline = [('ExceptionTrapper', ExceptionTrapper),
@@ -361,7 +391,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
@@ -405,4 +436,3 @@ class CPWSGIApp(object):
name, arg = k.split(".", 1)
bucket = self.config.setdefault(name, {})
bucket[arg] = v
-
diff --git a/cherrypy/_cpwsgi_server.py b/cherrypy/_cpwsgi_server.py
index f8db23f2..874e2e9f 100644
--- a/cherrypy/_cpwsgi_server.py
+++ b/cherrypy/_cpwsgi_server.py
@@ -8,6 +8,7 @@ from cherrypy import wsgiserver
class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
+
"""Wrapper for wsgiserver.CherryPyWSGIServer.
wsgiserver has been designed to not reference CherryPy in any way,
@@ -18,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
@@ -30,10 +35,12 @@ class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
s.__init__(self, server_adapter.bind_addr, cherrypy.tree,
self.server_adapter.thread_pool,
server_name,
- max = self.server_adapter.thread_pool_max,
- request_queue_size = self.server_adapter.socket_queue_size,
- timeout = self.server_adapter.socket_timeout,
- shutdown_timeout = self.server_adapter.shutdown_timeout,
+ max=self.server_adapter.thread_pool_max,
+ request_queue_size=self.server_adapter.socket_queue_size,
+ timeout=self.server_adapter.socket_timeout,
+ shutdown_timeout=self.server_adapter.shutdown_timeout,
+ accepted_queue_size=self.server_adapter.accepted_queue_size,
+ accepted_queue_timeout=self.server_adapter.accepted_queue_timeout,
)
self.protocol = self.server_adapter.protocol_version
self.nodelay = self.server_adapter.nodelay
@@ -56,8 +63,8 @@ class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
self.server_adapter.ssl_private_key,
self.server_adapter.ssl_certificate_chain)
- self.stats['Enabled'] = getattr(self.server_adapter, 'statistics', False)
+ self.stats['Enabled'] = getattr(
+ self.server_adapter, 'statistics', False)
def error_log(self, msg="", level=20, traceback=False):
cherrypy.engine.log(msg, level, traceback)
-
diff --git a/cherrypy/cherryd b/cherrypy/cherryd
index adb2a02e..5afb27ad 100755
--- a/cherrypy/cherryd
+++ b/cherrypy/cherryd
@@ -7,6 +7,7 @@ import cherrypy
from cherrypy.process import plugins, servers
from cherrypy import Application
+
def start(configfiles=None, daemonize=False, environment=None,
fastcgi=False, scgi=False, pidfile=None, imports=None,
cgi=False):
@@ -14,7 +15,7 @@ def start(configfiles=None, daemonize=False, environment=None,
sys.path = [''] + sys.path
for i in imports or []:
exec("import %s" % i)
-
+
for c in configfiles or []:
cherrypy.config.update(c)
# If there's only one app mounted, merge config into it.
@@ -22,26 +23,26 @@ def start(configfiles=None, daemonize=False, environment=None,
for app in cherrypy.tree.apps.values():
if isinstance(app, Application):
app.merge(c)
-
+
engine = cherrypy.engine
-
+
if environment is not None:
cherrypy.config.update({'environment': environment})
-
+
# Only daemonize if asked to.
if daemonize:
# Don't print anything to stdout/sterr.
cherrypy.config.update({'log.screen': False})
plugins.Daemonizer(engine).subscribe()
-
+
if pidfile:
plugins.PIDFile(engine, pidfile).subscribe()
-
+
if hasattr(engine, "signal_handler"):
engine.signal_handler.subscribe()
if hasattr(engine, "console_control_handler"):
engine.console_control_handler.subscribe()
-
+
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
cherrypy.log.error("You may only specify one of the cgi, fastcgi, and "
"scgi options.", 'ENGINE')
@@ -51,7 +52,7 @@ def start(configfiles=None, daemonize=False, environment=None,
cherrypy.config.update({'engine.autoreload_on': False})
# Turn off the default HTTP server (which is subscribed by default).
cherrypy.server.unsubscribe()
-
+
addr = cherrypy.server.bind_addr
if fastcgi:
f = servers.FlupFCGIServer(application=cherrypy.tree,
@@ -64,7 +65,7 @@ def start(configfiles=None, daemonize=False, environment=None,
bindAddress=addr)
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
s.subscribe()
-
+
# Always start the engine; this will start all other services
try:
engine.start()
@@ -77,7 +78,7 @@ def start(configfiles=None, daemonize=False, environment=None,
if __name__ == '__main__':
from optparse import OptionParser
-
+
p = OptionParser()
p.add_option('-c', '--config', action="append", dest='config',
help="specify config file(s)")
@@ -86,7 +87,8 @@ if __name__ == '__main__':
p.add_option('-e', '--environment', dest='environment', default=None,
help="apply the given config environment")
p.add_option('-f', action="store_true", dest='fastcgi',
- help="start a fastcgi server instead of the default HTTP server")
+ help="start a fastcgi server instead of the default HTTP "
+ "server")
p.add_option('-s', action="store_true", dest='scgi',
help="start a scgi server instead of the default HTTP server")
p.add_option('-x', action="store_true", dest='cgi',
@@ -98,12 +100,11 @@ if __name__ == '__main__':
p.add_option('-P', '--Path', action="append", dest='Path',
help="add the given paths to sys.path")
options, args = p.parse_args()
-
+
if options.Path:
for p in options.Path:
sys.path.insert(0, p)
-
+
start(options.config, options.daemonize,
options.environment, options.fastcgi, options.scgi,
options.pidfile, options.imports, options.cgi)
-
diff --git a/cherrypy/lib/__init__.py b/cherrypy/lib/__init__.py
index bb72204b..a75a53da 100644
--- a/cherrypy/lib/__init__.py
+++ b/cherrypy/lib/__init__.py
@@ -3,7 +3,45 @@
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
from cherrypy.lib.reprconf import unrepr, modules, attributes
+def is_iterator(obj):
+ '''Returns a boolean indicating if the object provided implements
+ the iterator protocol (i.e. like a generator). This will return
+ false for objects which iterable, but not iterators themselves.'''
+ from types import GeneratorType
+ if isinstance(obj, GeneratorType):
+ return True
+ elif not hasattr(obj, '__iter__'):
+ return False
+ else:
+ # Types which implement the protocol must return themselves when
+ # invoking 'iter' upon them.
+ return iter(obj) is obj
+
+def is_closable_iterator(obj):
+
+ # Not an iterator.
+ if not is_iterator(obj):
+ return False
+
+ # A generator - the easiest thing to deal with.
+ import inspect
+ if inspect.isgenerator(obj):
+ return True
+
+ # A custom iterator. Look for a close method...
+ if not (hasattr(obj, 'close') and callable(obj.close)):
+ return False
+
+ # ... which doesn't require any arguments.
+ try:
+ inspect.getcallargs(obj.close)
+ except TypeError:
+ return False
+ else:
+ return True
+
class file_generator(object):
+
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
def __init__(self, input, chunkSize=65536):
@@ -23,6 +61,7 @@ class file_generator(object):
raise StopIteration()
next = __next__
+
def file_generator_limited(fileobj, count, chunk_size=65536):
"""Yield the given file object in chunks, stopping after `count`
bytes has been emitted. Default chunk size is 64kB. (Core)
@@ -36,6 +75,7 @@ def file_generator_limited(fileobj, count, chunk_size=65536):
remaining -= chunklen
yield chunk
+
def set_vary_header(response, header_name):
"Add a Vary header to a response"
varies = response.headers.get("Vary", "")
diff --git a/cherrypy/lib/auth.py b/cherrypy/lib/auth.py
index 0f22b9be..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
@@ -17,10 +18,11 @@ def check_auth(users, encrypt=None, realm=None):
if hasattr(users, '__call__'):
try:
# backward compatibility
- users = users() # expect it to return a dictionary
+ users = users() # expect it to return a dictionary
if not isinstance(users, dict):
- raise ValueError("Authentication users must be a dictionary")
+ raise ValueError(
+ "Authentication users must be a dictionary")
# fetch the user password
password = users.get(ah["username"], None)
@@ -44,6 +46,7 @@ def check_auth(users, encrypt=None, realm=None):
request.login = False
return False
+
def basic_auth(realm, users, encrypt=None, debug=False):
"""If auth fails, raise 401 with a basic authentication header.
@@ -51,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.
@@ -64,9 +68,12 @@ def basic_auth(realm, users, encrypt=None, debug=False):
return
# inform the user-agent this path is protected
- cherrypy.serving.response.headers['www-authenticate'] = httpauth.basicAuth(realm)
+ cherrypy.serving.response.headers[
+ 'www-authenticate'] = httpauth.basicAuth(realm)
+
+ raise cherrypy.HTTPError(
+ 401, "You are not authorized to access that resource")
- raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
def digest_auth(realm, users, debug=False):
"""If auth fails, raise 401 with a digest authentication header.
@@ -74,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:
@@ -82,6 +90,8 @@ def digest_auth(realm, users, debug=False):
return
# inform the user-agent this path is protected
- cherrypy.serving.response.headers['www-authenticate'] = httpauth.digestAuth(realm)
+ cherrypy.serving.response.headers[
+ 'www-authenticate'] = httpauth.digestAuth(realm)
- raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
+ raise cherrypy.HTTPError(
+ 401, "You are not authorized to access that resource")
diff --git a/cherrypy/lib/auth_basic.py b/cherrypy/lib/auth_basic.py
index cc9c53f2..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::
@@ -77,11 +78,13 @@ def basic_auth(realm, checkpassword, debug=False):
if debug:
cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
request.login = username
- return # successful authentication
- except (ValueError, binascii.Error): # split() error, base64.decodestring() error
+ return # successful authentication
+ # split() error, base64.decodestring() error
+ except (ValueError, binascii.Error):
raise cherrypy.HTTPError(400, 'Bad Request')
# Respond with 401 status and a WWW-Authenticate header
- cherrypy.serving.response.headers['www-authenticate'] = 'Basic realm="%s"' % realm
- raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
-
+ cherrypy.serving.response.headers[
+ 'www-authenticate'] = 'Basic realm="%s"' % realm
+ raise cherrypy.HTTPError(
+ 401, "You are not authorized to access that resource")
diff --git a/cherrypy/lib/auth_digest.py b/cherrypy/lib/auth_digest.py
index f4c876e5..e06535dc 100644
--- a/cherrypy/lib/auth_digest.py
+++ b/cherrypy/lib/auth_digest.py
@@ -41,6 +41,8 @@ def TRACE(msg):
# Three helper functions for users of the tool, providing three variants
# of get_ha1() functions for three different kinds of credential stores.
+
+
def get_ha1_dict_plain(user_password_dict):
"""Returns a get_ha1 function which obtains a plaintext password from a
dictionary of the form: {username : password}.
@@ -57,6 +59,7 @@ def get_ha1_dict_plain(user_password_dict):
return get_ha1
+
def get_ha1_dict(user_ha1_dict):
"""Returns a get_ha1 function which obtains a HA1 password hash from a
dictionary of the form: {username : HA1}.
@@ -71,6 +74,7 @@ def get_ha1_dict(user_ha1_dict):
return get_ha1
+
def get_ha1_file_htdigest(filename):
"""Returns a get_ha1 function which obtains a HA1 password hash from a
flat file with lines of the same format as that produced by the Apache
@@ -99,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.
@@ -125,6 +130,7 @@ def H(s):
class HttpDigestAuthorization (object):
+
"""Class to parse a Digest Authorization header and perform re-calculation
of the digest.
"""
@@ -135,7 +141,7 @@ class HttpDigestAuthorization (object):
def __init__(self, auth_header, http_method, debug=False):
self.http_method = http_method
self.debug = debug
- scheme, params = auth_header.split(" ", 1)
+ scheme, params = auth_header.split(" ", 1)
self.scheme = scheme.lower()
if self.scheme != 'digest':
raise ValueError('Authorization scheme is not "Digest"')
@@ -151,84 +157,95 @@ class HttpDigestAuthorization (object):
self.nonce = paramsd.get('nonce')
self.uri = paramsd.get('uri')
self.method = paramsd.get('method')
- self.response = paramsd.get('response') # the response digest
+ self.response = paramsd.get('response') # the response digest
self.algorithm = paramsd.get('algorithm', 'MD5').upper()
self.cnonce = paramsd.get('cnonce')
self.opaque = paramsd.get('opaque')
- self.qop = paramsd.get('qop') # qop
- self.nc = paramsd.get('nc') # nonce count
+ self.qop = paramsd.get('qop') # qop
+ self.nc = paramsd.get('nc') # nonce count
# 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.response
+ 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.response
+ )
if not has_reqd:
- raise ValueError(self.errmsg("Not all required parameters are present."))
+ raise ValueError(
+ self.errmsg("Not all required parameters are present."))
if self.qop:
if self.qop not in valid_qops:
- raise ValueError(self.errmsg("Unsupported value for qop: '%s'" % self.qop))
+ raise ValueError(
+ 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"))
+ raise ValueError(
+ 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"))
-
+ raise ValueError(
+ 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)
- s_timestamp, s_hashpart = synthesize_nonce(s, key, timestamp).split(':', 1)
+ s_timestamp, s_hashpart = synthesize_nonce(
+ s, key, timestamp).split(':', 1)
is_valid = s_hashpart == hashpart
if self.debug:
TRACE('validate_nonce: %s' % is_valid)
return is_valid
- except ValueError: # split() error
+ except ValueError: # split() error
pass
return False
-
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)
if int(timestamp) + max_age_seconds > int(time.time()):
return False
- except ValueError: # int() error
+ except ValueError: # int() error
pass
if self.debug:
TRACE("nonce is stale")
return True
-
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:
@@ -238,11 +255,11 @@ class HttpDigestAuthorization (object):
elif self.qop == "auth-int":
a2 = "%s:%s:%s" % (self.http_method, self.uri, H(entity_body))
else:
- # in theory, this should never happen, since I validate qop in __init__()
+ # in theory, this should never happen, since I validate qop in
+ # __init__()
raise ValueError(self.errmsg("Unrecognized value for qop!"))
return H(a2)
-
def request_digest(self, ha1, entity_body=''):
"""Calculates the Request-Digest. See :rfc:`2617` section 3.2.2.1.
@@ -253,22 +270,24 @@ 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)
# Request-Digest -- RFC 2617 3.2.2.1
if self.qop:
- req = "%s:%s:%s:%s:%s" % (self.nonce, self.nc, self.cnonce, self.qop, ha2)
+ req = "%s:%s:%s:%s:%s" % (
+ self.nonce, self.nc, self.cnonce, self.qop, ha2)
else:
req = "%s:%s" % (self.nonce, ha2)
# 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
@@ -282,8 +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)
@@ -293,7 +312,7 @@ def www_authenticate(realm, key, algorithm='MD5', nonce=None, qop=qop_auth, stal
if nonce is None:
nonce = synthesize_nonce(realm, key)
s = 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % (
- realm, nonce, algorithm, qop)
+ realm, nonce, algorithm, qop)
if stale:
s += ', stale="true"'
return s
@@ -303,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.
@@ -322,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
@@ -331,9 +351,11 @@ def digest_auth(realm, get_ha1, key, debug=False):
nonce_is_stale = False
if auth_header is not None:
try:
- auth = HttpDigestAuthorization(auth_header, request.method, debug=debug)
+ auth = HttpDigestAuthorization(
+ auth_header, request.method, debug=debug)
except ValueError:
- raise cherrypy.HTTPError(400, "The Authorization header could not be parsed.")
+ raise cherrypy.HTTPError(
+ 400, "The Authorization header could not be parsed.")
if debug:
TRACE(str(auth))
@@ -341,19 +363,22 @@ 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 digest == auth.response: # authenticated
if debug:
TRACE("digest matches auth.response")
# Now check if nonce is stale.
- # The choice of ten minutes' lifetime for nonce is somewhat arbitrary
+ # The choice of ten minutes' lifetime for nonce is somewhat
+ # arbitrary
nonce_is_stale = auth.is_nonce_stale(max_age_seconds=600)
if not nonce_is_stale:
request.login = auth.username
if debug:
- TRACE("authentication of %s successful" % auth.username)
+ TRACE("authentication of %s successful" %
+ auth.username)
return
# Respond with 401 status and a WWW-Authenticate header
@@ -361,5 +386,5 @@ def digest_auth(realm, get_ha1, key, debug=False):
if debug:
TRACE(header)
cherrypy.serving.response.headers['WWW-Authenticate'] = header
- raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
-
+ raise cherrypy.HTTPError(
+ 401, "You are not authorized to access that resource")
diff --git a/cherrypy/lib/caching.py b/cherrypy/lib/caching.py
index 42626fc5..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
=====
@@ -43,6 +43,7 @@ from cherrypy._cpcompat import copyitems, ntob, set_daemon, sorted, Event
class Cache(object):
+
"""Base class for Cache implementations."""
def get(self):
@@ -62,11 +63,9 @@ class Cache(object):
raise NotImplemented
-
-# ------------------------------- Memory Cache ------------------------------- #
-
-
+# ------------------------------ Memory Cache ------------------------------- #
class AntiStampedeCache(dict):
+
"""A storage system for cached items which reduces stampede collisions."""
def wait(self, key, timeout=5, debug=False):
@@ -90,7 +89,8 @@ class AntiStampedeCache(dict):
# Wait until it's done or times out.
if debug:
- cherrypy.log('Waiting up to %s seconds' % timeout, 'TOOLS.CACHING')
+ cherrypy.log('Waiting up to %s seconds' %
+ timeout, 'TOOLS.CACHING')
value.wait(timeout)
if value.result is not None:
# The other thread finished its calculation. Use it.
@@ -128,6 +128,7 @@ class AntiStampedeCache(dict):
class MemoryCache(Cache):
+
"""An in-memory cache for varying response content.
Each key in self.store is a URI, and each value is an AntiStampedeCache.
@@ -152,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."""
@@ -325,13 +327,15 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
directive = atoms.pop(0)
if directive == 'max-age':
if len(atoms) != 1 or not atoms[0].isdigit():
- raise cherrypy.HTTPError(400, "Invalid Cache-Control header")
+ raise cherrypy.HTTPError(
+ 400, "Invalid Cache-Control header")
max_age = int(atoms[0])
break
elif directive == 'no-cache':
if debug:
- cherrypy.log('Ignoring cache due to Cache-Control: no-cache',
- 'TOOLS.CACHING')
+ cherrypy.log(
+ 'Ignoring cache due to Cache-Control: no-cache',
+ 'TOOLS.CACHING')
request.cached = False
request.cacheable = True
return False
@@ -348,7 +352,8 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
request.cacheable = True
return False
- # Copy the response headers. See https://bitbucket.org/cherrypy/cherrypy/issue/721.
+ # Copy the response headers. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/721.
response.headers = rh = httputil.HeaderMap()
for k in h:
dict.__setitem__(rh, k, dict.__getitem__(h, k))
@@ -387,7 +392,7 @@ def tee_output():
def tee(body):
"""Tee response.body into a list."""
if ('no-cache' in response.headers.values('Pragma') or
- 'no-store' in response.headers.values('Cache-Control')):
+ 'no-store' in response.headers.values('Cache-Control')):
for chunk in body:
yield chunk
return
diff --git a/cherrypy/lib/covercp.py b/cherrypy/lib/covercp.py
index 656d99da..a74ec342 100644
--- a/cherrypy/lib/covercp.py
+++ b/cherrypy/lib/covercp.py
@@ -24,13 +24,15 @@ import re
import sys
import cgi
from cherrypy._cpcompat import quote_plus
-import os, os.path
+import os
+import os.path
localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
the_coverage = None
try:
from coverage import coverage
the_coverage = coverage(data_file=localFile)
+
def start():
the_coverage.start()
except ImportError:
@@ -39,7 +41,9 @@ except ImportError:
the_coverage = None
import warnings
- warnings.warn("No code coverage will be performed; coverage.py could not be imported.")
+ warnings.warn(
+ "No code coverage will be performed; "
+ "coverage.py could not be imported.")
def start():
pass
@@ -118,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"/>
@@ -173,7 +180,10 @@ 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):
s = len(statements)
@@ -182,6 +192,7 @@ def _percent(statements, missing):
return int(round(100.0 * e / s))
return 0
+
def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
coverage=the_coverage):
@@ -194,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" % \
- (newpath, quote_plus(exclude), name)
-
- for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude, coverage=coverage):
+ 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
+ ):
yield chunk
# Now list the files
@@ -217,7 +234,7 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
pass
else:
pc = _percent(statements, missing)
- pc_str = ("%3d%% " % pc).replace(' ','&nbsp;')
+ pc_str = ("%3d%% " % pc).replace(' ', '&nbsp;')
if pc < float(pct) or pc == -1:
pc_str = "<span class='fail'>%s</span>" % pc_str
else:
@@ -226,10 +243,12 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1),
pc_str, newpath, name)
+
def _skip_file(path, exclude):
if exclude:
return bool(re.search(exclude, path))
+
def _graft(path, tree):
d = tree
@@ -249,6 +268,7 @@ def _graft(path, tree):
if node:
d = d.setdefault(node, {})
+
def get_tree(base, exclude, coverage=the_coverage):
"""Return covered module names as a nested dict."""
tree = {}
@@ -258,6 +278,7 @@ def get_tree(base, exclude, coverage=the_coverage):
_graft(path, tree)
return tree
+
class CoverStats(object):
def __init__(self, coverage, root=None):
@@ -301,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>"
@@ -331,7 +353,8 @@ class CoverStats(object):
yield template % (lineno, cgi.escape(line))
def report(self, name):
- filename, statements, excluded, missing, _ = self.coverage.analysis2(name)
+ filename, statements, excluded, missing, _ = self.coverage.analysis2(
+ name)
pc = _percent(statements, missing)
yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name),
fullpath=name,
@@ -350,7 +373,7 @@ def serve(path=localFile, port=8080, root=None):
if coverage is None:
raise ImportError("The coverage module could not be imported.")
from coverage import coverage
- cov = coverage(data_file = path)
+ cov = coverage(data_file=path)
cov.load()
import cherrypy
@@ -362,4 +385,3 @@ def serve(path=localFile, port=8080, root=None):
if __name__ == "__main__":
serve(*tuple(sys.argv[1:]))
-
diff --git a/cherrypy/lib/cpstats.py b/cherrypy/lib/cpstats.py
index a9ceacdb..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.
@@ -68,7 +69,7 @@ Each namespace, then, is a dict of named statistical values, such as
good on a report: spaces and capitalization are just fine.
In addition to scalars, values in a namespace MAY be a (third-layer)
-dict, or a list, called a "collection". For example, the CherryPy
+dict, or a list, called a "collection". For example, the CherryPy
:class:`StatsTool` keeps track of what each request is doing (or has most
recently done) in a 'Requests' collection, where each key is a thread ID; each
value in the subdict MUST be a fourth dict (whew!) of statistical data about
@@ -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,10 +187,12 @@ To format statistics reports::
"""
-# -------------------------------- Statistics -------------------------------- #
+# ------------------------------- Statistics -------------------------------- #
import logging
-if not hasattr(logging, 'statistics'): logging.statistics = {}
+if not hasattr(logging, 'statistics'):
+ logging.statistics = {}
+
def extrapolate_statistics(scope):
"""Return an extrapolated copy of the given scope."""
@@ -204,7 +208,7 @@ def extrapolate_statistics(scope):
return c
-# --------------------- CherryPy Applications Statistics --------------------- #
+# -------------------- CherryPy Applications Statistics --------------------- #
import threading
import time
@@ -214,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),
@@ -231,12 +243,13 @@ appstats.update({
'Total Time': 0,
'Uptime': lambda s: time.time() - s['Start Time'],
'Requests': {},
- })
+})
proc_time = lambda s: time.time() - s['Start Time']
class ByteCountWrapper(object):
+
"""Wraps a file-like object, counting the number of bytes read."""
def __init__(self, rfile):
@@ -282,6 +295,7 @@ average_uriset_time = lambda s: s['Count'] and (s['Sum'] / s['Count']) or 0
class StatsTool(cherrypy.Tool):
+
"""Record various information about the current request."""
def __init__(self):
@@ -318,10 +332,11 @@ class StatsTool(cherrypy.Tool):
'Request-Line': request.request_line,
'Response Status': None,
'Start Time': time.time(),
- }
+ }
- def record_stop(self, uriset=None, slow_queries=1.0, slow_queries_count=100,
- debug=False, **kwargs):
+ def record_stop(
+ self, uriset=None, slow_queries=1.0, slow_queries_count=100,
+ debug=False, **kwargs):
"""Record the end of a request."""
resp = cherrypy.serving.response
w = appstats['Requests'][threading._get_ident()]
@@ -337,7 +352,8 @@ class StatsTool(cherrypy.Tool):
w['Bytes Written'] = cl
appstats['Total Bytes Written'] += cl
- w['Response Status'] = getattr(resp, 'output_status', None) or resp.status
+ w['Response Status'] = getattr(
+ resp, 'output_status', None) or resp.status
w['End Time'] = time.time()
p = w['End Time'] - w['Start Time']
@@ -391,6 +407,7 @@ missing = object()
locale_date = lambda v: time.strftime('%c', time.gmtime(v))
iso_format = lambda v: time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(v))
+
def pause_resume(ns):
def _pause_resume(enabled):
pause_disabled = ''
@@ -430,20 +447,20 @@ class StatsPage(object):
'End Time': None,
'Processing Time': '%.3f',
'Start Time': iso_format,
- },
+ },
'URI Set Tracking': {
'Avg': '%.3f',
'Max': '%.3f',
'Min': '%.3f',
'Sum': '%.3f',
- },
+ },
'Requests': {
'Bytes Read': '%s',
'Bytes Written': '%s',
'End Time': None,
'Processing Time': '%.3f',
'Start Time': None,
- },
+ },
},
'CherryPy WSGIServer': {
'Enabled': pause_resume('CherryPy WSGIServer'),
@@ -452,7 +469,6 @@ class StatsPage(object):
},
}
-
def index(self):
# Transform the raw data into pretty output for HTML
yield """
@@ -503,18 +519,25 @@ table.stats2 th {
""" % title
for i, (key, value) in enumerate(scalars):
colnum = i % 3
- if colnum == 0: yield """
+ if colnum == 0:
+ yield """
<tr>"""
- yield """
- <th>%(key)s</th><td id='%(title)s-%(key)s'>%(value)s</td>""" % vars()
- if colnum == 2: yield """
+ yield (
+ """
+ <th>%(key)s</th><td id='%(title)s-%(key)s'>%(value)s</td>""" %
+ vars()
+ )
+ if colnum == 2:
+ yield """
</tr>"""
- if colnum == 0: yield """
+ if colnum == 0:
+ yield """
<th></th><td></td>
<th></th><td></td>
</tr>"""
- elif colnum == 1: yield """
+ elif colnum == 1:
+ yield """
<th></th><td></td>
</tr>"""
yield """
@@ -662,4 +685,3 @@ table.stats2 th {
resume.exposed = True
resume.cp_config = {'tools.allow.on': True,
'tools.allow.methods': ['POST']}
-
diff --git a/cherrypy/lib/cptools.py b/cherrypy/lib/cptools.py
index 5ae40b02..f376282c 100644
--- a/cherrypy/lib/cptools.py
+++ b/cherrypy/lib/cptools.py
@@ -6,6 +6,7 @@ import re
import cherrypy
from cherrypy._cpcompat import basestring, md5, set, unicodestr
from cherrypy.lib import httputil as _httputil
+from cherrypy.lib import is_iterator
# Conditional HTTP request support #
@@ -79,13 +80,15 @@ def validate_etags(autotags=False, debug=False):
'TOOLS.ETAGS')
if conditions == ["*"] or etag in conditions:
if debug:
- cherrypy.log('request.method: %s' % request.method, 'TOOLS.ETAGS')
+ cherrypy.log('request.method: %s' %
+ request.method, 'TOOLS.ETAGS')
if request.method in ("GET", "HEAD"):
raise cherrypy.HTTPRedirect([], 304)
else:
raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r "
"matched %r" % (etag, conditions))
+
def validate_since():
"""Validate the current Last-Modified against If-Modified-Since headers.
@@ -207,8 +210,8 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY')
if xff:
if remote == 'X-Forwarded-For':
- # See http://bob.pythonmac.org/archives/2005/09/23/apache-x-forwarded-for-caveat/
- xff = xff.split(',')[-1].strip()
+ #Bug #1268
+ xff = xff.split(',')[0].strip()
request.remote.ip = xff
@@ -277,6 +280,7 @@ def referer(pattern, accept=True, accept_missing=False, error=403,
class SessionAuth(object):
+
"""Assert that the user is logged in."""
session_key = "username"
@@ -298,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")
@@ -337,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
@@ -345,51 +354,51 @@ Message: %(error_msg)s
username = sess.get(self.session_key)
if not username:
sess[self.session_key] = username = self.anonymous()
- if self.debug:
- cherrypy.log('No session[username], trying anonymous', 'TOOLS.SESSAUTH')
+ self._debug_message('No session[username], trying anonymous')
if not username:
url = cherrypy.url(qs=request.query_string)
- if self.debug:
- cherrypy.log('No username, routing to login_screen with '
- 'from_page %r' % url, 'TOOLS.SESSAUTH')
+ self._debug_message(
+ 'No username, routing to login_screen with from_page %(url)r',
+ locals(),
+ )
response.body = self.login_screen(url)
if "Content-Length" in response.headers:
# Delete Content-Length header so finalize() recalcs it.
del response.headers["Content-Length"]
return True
- if self.debug:
- cherrypy.log('Setting request.login to %r' % username, 'TOOLS.SESSAUTH')
+ self._debug_message('Setting request.login to %(username)r', locals())
request.login = username
self.on_check(username)
+ def _debug_message(self, template, context={}):
+ if not self.debug:
+ return
+ cherrypy.log(template % context, 'TOOLS.SESSAUTH')
+
def run(self):
request = cherrypy.serving.request
response = cherrypy.serving.response
path = request.path_info
if path.endswith('login_screen'):
- if self.debug:
- cherrypy.log('routing %r to login_screen' % path, 'TOOLS.SESSAUTH')
- return self.login_screen(**request.params)
+ self._debug_message('routing %(path)r to login_screen', locals())
+ response.body = self.login_screen()
+ return True
elif path.endswith('do_login'):
if request.method != 'POST':
response.headers['Allow'] = "POST"
- if self.debug:
- cherrypy.log('do_login requires POST', 'TOOLS.SESSAUTH')
+ self._debug_message('do_login requires POST')
raise cherrypy.HTTPError(405)
- if self.debug:
- cherrypy.log('routing %r to do_login' % path, 'TOOLS.SESSAUTH')
+ self._debug_message('routing %(path)r to do_login', locals())
return self.do_login(**request.params)
elif path.endswith('do_logout'):
if request.method != 'POST':
response.headers['Allow'] = "POST"
raise cherrypy.HTTPError(405)
- if self.debug:
- cherrypy.log('routing %r to do_logout' % path, 'TOOLS.SESSAUTH')
+ self._debug_message('routing %(path)r to do_logout', locals())
return self.do_logout(**request.params)
else:
- if self.debug:
- cherrypy.log('No special path, running do_check', 'TOOLS.SESSAUTH')
+ self._debug_message('No special path, running do_check')
return self.do_check()
@@ -411,11 +420,13 @@ def log_traceback(severity=logging.ERROR, debug=False):
"""Write the last error's traceback to the cherrypy error log."""
cherrypy.log("", "HTTP", severity=severity, traceback=True)
+
def log_request_headers(debug=False):
"""Write request headers to the cherrypy error log."""
h = [" %s: %s" % (k, v) for k, v in cherrypy.serving.request.header_list]
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), "HTTP")
+
def log_hooks(debug=False):
"""Write request.hooks to the cherrypy error log."""
request = cherrypy.serving.request
@@ -437,6 +448,7 @@ def log_hooks(debug=False):
cherrypy.log('\nRequest Hooks for ' + cherrypy.url() +
':\n' + '\n'.join(msg), "HTTP")
+
def redirect(url='', internal=True, debug=False):
"""Raise InternalRedirect or HTTPRedirect to the given url."""
if debug:
@@ -448,6 +460,7 @@ def redirect(url='', internal=True, debug=False):
else:
raise cherrypy.HTTPRedirect(url)
+
def trailing_slash(missing=True, extra=False, status=None, debug=False):
"""Redirect if path_info has (missing|extra) trailing slash."""
request = cherrypy.serving.request
@@ -469,17 +482,17 @@ def trailing_slash(missing=True, extra=False, status=None, debug=False):
new_url = cherrypy.url(pi[:-1], request.query_string)
raise cherrypy.HTTPRedirect(new_url, status=status or 301)
+
def flatten(debug=False):
"""Wrap response.body in a generator that recursively iterates over body.
This allows cherrypy.response.body to consist of 'nested generators';
that is, a set of generators that yield generators.
"""
- import types
def flattener(input):
numchunks = 0
for x in input:
- if not isinstance(x, types.GeneratorType):
+ if not is_iterator(x):
numchunks += 1
yield x
else:
@@ -592,7 +605,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
@@ -605,12 +619,12 @@ def autovary(ignore=None, debug=False):
resp_h = cherrypy.serving.response.headers
v = set([e.value for e in resp_h.elements('Vary')])
if debug:
- cherrypy.log('Accessed headers: %s' % request.headers.accessed_headers,
- 'TOOLS.AUTOVARY')
+ cherrypy.log(
+ 'Accessed headers: %s' % request.headers.accessed_headers,
+ 'TOOLS.AUTOVARY')
v = v.union(request.headers.accessed_headers)
v = v.difference(ignore)
v = list(v)
v.sort()
resp_h['Vary'] = ', '.join(v)
request.hooks.attach('before_finalize', set_response_header, 95)
-
diff --git a/cherrypy/lib/encoding.py b/cherrypy/lib/encoding.py
index 31a6d3ab..a4c2cbd6 100644
--- a/cherrypy/lib/encoding.py
+++ b/cherrypy/lib/encoding.py
@@ -4,6 +4,7 @@ import time
import cherrypy
from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr
from cherrypy.lib import file_generator
+from cherrypy.lib import is_closable_iterator
from cherrypy.lib import set_vary_header
@@ -14,13 +15,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
@@ -33,6 +34,31 @@ def decode(encoding=None, default_encoding='utf-8'):
default_encoding = [default_encoding]
body.attempt_charsets = body.attempt_charsets + default_encoding
+class UTF8StreamEncoder:
+ def __init__(self, iterator):
+ self._iterator = iterator
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ return self.__next__()
+
+ def __next__(self):
+ res = next(self._iterator)
+ if isinstance(res, unicodestr):
+ res = res.encode('utf-8')
+ return res
+
+ def close(self):
+ if is_closable_iterator(self._iterator):
+ self._iterator.close()
+
+ def __getattr__(self, attr):
+ if attr.startswith('__'):
+ raise AttributeError(self, attr)
+ return getattr(self._iterator, attr)
+
class ResponseEncoder:
@@ -96,7 +122,8 @@ class ResponseEncoder:
response = cherrypy.serving.response
if self.debug:
- cherrypy.log('response.stream %r' % response.stream, 'TOOLS.ENCODE')
+ cherrypy.log('response.stream %r' %
+ response.stream, 'TOOLS.ENCODE')
if response.stream:
encoder = self.encode_stream
else:
@@ -125,10 +152,12 @@ class ResponseEncoder:
# If specified, force this encoding to be used, or fail.
encoding = self.encoding.lower()
if self.debug:
- cherrypy.log('Specified encoding %r' % encoding, 'TOOLS.ENCODE')
+ cherrypy.log('Specified encoding %r' %
+ encoding, 'TOOLS.ENCODE')
if (not charsets) or "*" in charsets or encoding in charsets:
if self.debug:
- cherrypy.log('Attempting encoding %r' % encoding, 'TOOLS.ENCODE')
+ cherrypy.log('Attempting encoding %r' %
+ encoding, 'TOOLS.ENCODE')
if encoder(encoding):
return encoding
else:
@@ -140,7 +169,8 @@ class ResponseEncoder:
if encoder(self.default_encoding):
return self.default_encoding
else:
- raise cherrypy.HTTPError(500, self.failmsg % self.default_encoding)
+ raise cherrypy.HTTPError(500, self.failmsg %
+ self.default_encoding)
else:
for element in encs:
if element.qvalue > 0:
@@ -178,7 +208,8 @@ class ResponseEncoder:
msg = "Your client did not send an Accept-Charset header."
else:
msg = "Your client sent this Accept-Charset header: %s." % ac
- msg += " We tried these charsets: %s." % ", ".join(self.attempted_charsets)
+ _charsets = ", ".join(sorted(self.attempted_charsets))
+ msg += " We tried these charsets: %s." % (_charsets,)
raise cherrypy.HTTPError(406, msg)
def __call__(self, *args, **kwargs):
@@ -201,24 +232,27 @@ class ResponseEncoder:
ct = response.headers.elements("Content-Type")
if self.debug:
- cherrypy.log('Content-Type: %r' % [str(h) for h in ct], 'TOOLS.ENCODE')
+ cherrypy.log('Content-Type: %r' % [str(h)
+ for h in ct], 'TOOLS.ENCODE')
if ct and self.add_charset:
ct = ct[0]
if self.text_only:
if ct.value.lower().startswith("text/"):
if self.debug:
- cherrypy.log('Content-Type %s starts with "text/"' % ct,
- 'TOOLS.ENCODE')
+ cherrypy.log(
+ 'Content-Type %s starts with "text/"' % ct,
+ 'TOOLS.ENCODE')
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:
if self.debug:
- cherrypy.log('Finding because not text_only', 'TOOLS.ENCODE')
+ cherrypy.log('Finding because not text_only',
+ 'TOOLS.ENCODE')
do_find = True
if do_find:
@@ -233,6 +267,7 @@ class ResponseEncoder:
# GZIP
+
def compress(body, compress_level):
"""Compress 'body' at the given compress_level."""
import zlib
@@ -262,6 +297,7 @@ def compress(body, compress_level):
# ISIZE: 4 bytes
yield struct.pack("<L", size & int('FFFFFFFF', 16))
+
def decompress(body):
import gzip
@@ -274,7 +310,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
@@ -382,4 +419,3 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'], debug=False):
if debug:
cherrypy.log('No acceptable encoding found.', context='GZIP')
cherrypy.HTTPError(406, "identity, gzip").set_response()
-
diff --git a/cherrypy/lib/gctools.py b/cherrypy/lib/gctools.py
index e6af40b5..4b616c59 100644
--- a/cherrypy/lib/gctools.py
+++ b/cherrypy/lib/gctools.py
@@ -15,6 +15,7 @@ from cherrypy.process.plugins import SimplePlugin
class ReferrerTree(object):
+
"""An object which gathers all referrers of an object to a given depth."""
peek_length = 40
@@ -89,6 +90,7 @@ class ReferrerTree(object):
def format(self, tree):
"""Return a list of string reprs from a nested list of referrers."""
output = []
+
def ascend(branch, depth=1):
for parent, grandparents in branch:
output.append((" " * depth) + self._format(parent))
@@ -111,7 +113,7 @@ class RequestCounter(SimplePlugin):
self.count += 1
def after_request(self):
- self.count -=1
+ self.count -= 1
request_counter = RequestCounter(cherrypy.engine)
request_counter.subscribe()
@@ -129,15 +131,17 @@ def get_context(obj):
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!"
@@ -211,4 +215,3 @@ class GCRoot(object):
return "\n".join(output)
stats.exposed = True
-
diff --git a/cherrypy/lib/http.py b/cherrypy/lib/http.py
index 4661d69e..12043ad1 100644
--- a/cherrypy/lib/http.py
+++ b/cherrypy/lib/http.py
@@ -4,4 +4,3 @@ warnings.warn('cherrypy.lib.http has been deprecated and will be removed '
DeprecationWarning)
from cherrypy.lib.httputil import *
-
diff --git a/cherrypy/lib/httpauth.py b/cherrypy/lib/httpauth.py
index 88dc2ef9..0897ea2a 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
@@ -57,7 +60,7 @@ __all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
"parseAuthorization", "SUPPORTED_ALGORITHM", "md5SessionKey",
"calculateNonce", "SUPPORTED_QOP")
-################################################################################
+##########################################################################
import time
from cherrypy._cpcompat import base64_decode, ntob, md5
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
@@ -70,16 +73,17 @@ AUTH_INT = "auth-int"
SUPPORTED_ALGORITHM = (MD5, MD5_SESS)
SUPPORTED_QOP = (AUTH, AUTH_INT)
-################################################################################
+##########################################################################
# doAuth
#
DIGEST_AUTH_ENCODERS = {
MD5: lambda val: md5(ntob(val)).hexdigest(),
MD5_SESS: lambda val: md5(ntob(val)).hexdigest(),
-# SHA: lambda val: sha.new(ntob(val)).hexdigest (),
+ # SHA: lambda val: sha.new(ntob(val)).hexdigest (),
}
-def calculateNonce (realm, algorithm = MD5):
+
+def calculateNonce(realm, algorithm=MD5):
"""This is an auxaliary function that calculates 'nonce' value. It is used
to handle sessions."""
@@ -89,44 +93,47 @@ def calculateNonce (realm, algorithm = MD5):
try:
encoder = DIGEST_AUTH_ENCODERS[algorithm]
except KeyError:
- raise NotImplementedError ("The chosen algorithm (%s) does not have "\
- "an implementation yet" % algorithm)
+ raise NotImplementedError("The chosen algorithm (%s) does not have "
+ "an implementation yet" % algorithm)
+
+ return encoder("%d:%s" % (time.time(), realm))
- return encoder ("%d:%s" % (time.time(), realm))
-def digestAuth (realm, algorithm = MD5, nonce = None, qop = AUTH):
+def digestAuth(realm, algorithm=MD5, nonce=None, qop=AUTH):
"""Challenges the client for a Digest authentication."""
global SUPPORTED_ALGORITHM, DIGEST_AUTH_ENCODERS, SUPPORTED_QOP
assert algorithm in SUPPORTED_ALGORITHM
assert qop in SUPPORTED_QOP
if nonce is None:
- nonce = calculateNonce (realm, algorithm)
+ nonce = calculateNonce(realm, algorithm)
return 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % (
realm, nonce, algorithm, qop
)
-def basicAuth (realm):
+
+def basicAuth(realm):
"""Challengenes the client for a Basic authentication."""
assert '"' not in realm, "Realms cannot contain the \" (quote) character."
return 'Basic realm="%s"' % realm
-def doAuth (realm):
+
+def doAuth(realm):
"""'doAuth' function returns the challenge string b giving priority over
Digest and fallback to Basic authentication when the browser doesn't
support the first one.
This should be set in the HTTP header under the key 'WWW-Authenticate'."""
- return digestAuth (realm) + " " + basicAuth (realm)
+ return digestAuth(realm) + " " + basicAuth(realm)
-################################################################################
+##########################################################################
# Parse authorization parameters
#
-def _parseDigestAuthorization (auth_params):
+def _parseDigestAuthorization(auth_params):
# Convert the auth params to a dict
items = parse_http_list(auth_params)
params = parse_keqv_list(items)
@@ -140,8 +147,8 @@ def _parseDigestAuthorization (auth_params):
return None
# If qop is sent then cnonce and nc MUST be present
- if "qop" in params and not ("cnonce" in params \
- and "nc" in params):
+ if "qop" in params and not ("cnonce" in params
+ and "nc" in params):
return None
# If qop is not sent, neither cnonce nor nc can be present
@@ -152,7 +159,7 @@ def _parseDigestAuthorization (auth_params):
return params
-def _parseBasicAuthorization (auth_params):
+def _parseBasicAuthorization(auth_params):
username, password = base64_decode(auth_params).split(":", 1)
return {"username": username, "password": password}
@@ -161,18 +168,19 @@ AUTH_SCHEMES = {
"digest": _parseDigestAuthorization,
}
-def parseAuthorization (credentials):
+
+def parseAuthorization(credentials):
"""parseAuthorization will convert the value of the 'Authorization' key in
the HTTP header to a map itself. If the parsing fails 'None' is returned.
"""
global AUTH_SCHEMES
- auth_scheme, auth_params = credentials.split(" ", 1)
- auth_scheme = auth_scheme.lower ()
+ auth_scheme, auth_params = credentials.split(" ", 1)
+ auth_scheme = auth_scheme.lower()
parser = AUTH_SCHEMES[auth_scheme]
- params = parser (auth_params)
+ params = parser(auth_params)
if params is None:
return
@@ -182,10 +190,10 @@ def parseAuthorization (credentials):
return params
-################################################################################
+##########################################################################
# Check provided response for a valid password
#
-def md5SessionKey (params, password):
+def md5SessionKey(params, password):
"""
If the "algorithm" directive's value is "MD5-sess", then A1
[the session key] is calculated only once - on the first request by the
@@ -210,10 +218,11 @@ def md5SessionKey (params, password):
params_copy[key] = params[key]
params_copy["algorithm"] = MD5_SESS
- return _A1 (params_copy, password)
+ return _A1(params_copy, password)
+
def _A1(params, password):
- algorithm = params.get ("algorithm", MD5)
+ algorithm = params.get("algorithm", MD5)
H = DIGEST_AUTH_ENCODERS[algorithm]
if algorithm == MD5:
@@ -227,7 +236,7 @@ def _A1(params, password):
# This is A1 if qop is set
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
# ":" unq(nonce-value) ":" unq(cnonce-value)
- h_a1 = H ("%s:%s:%s" % (params["username"], params["realm"], password))
+ h_a1 = H("%s:%s:%s" % (params["username"], params["realm"], password))
return "%s:%s:%s" % (h_a1, params["nonce"], params["cnonce"])
@@ -235,13 +244,13 @@ def _A2(params, method, kwargs):
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
# A2 = Method ":" digest-uri-value
- qop = params.get ("qop", "auth")
+ qop = params.get("qop", "auth")
if qop == "auth":
return method + ":" + params["uri"]
elif qop == "auth-int":
# If the "qop" value is "auth-int", then A2 is:
# A2 = Method ":" digest-uri-value ":" H(entity-body)
- entity_body = kwargs.get ("entity_body", "")
+ entity_body = kwargs.get("entity_body", "")
H = kwargs["H"]
return "%s:%s:%s" % (
@@ -251,20 +260,22 @@ def _A2(params, method, kwargs):
)
else:
- raise NotImplementedError ("The 'qop' method is unknown: %s" % qop)
+ 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
"""
params = auth_map
- algorithm = params.get ("algorithm", MD5)
+ algorithm = params.get("algorithm", MD5)
H = DIGEST_AUTH_ENCODERS[algorithm]
KD = lambda secret, data: H(secret + ":" + data)
- qop = params.get ("qop", None)
+ qop = params.get("qop", None)
H_A2 = H(_A2(params, method, kwargs))
@@ -297,7 +308,8 @@ def _computeDigestResponse(auth_map, password, method = "GET", A1 = None,**kwarg
return KD(H_A1, request)
-def _checkDigestResponse(auth_map, password, method = "GET", A1 = None, **kwargs):
+
+def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs):
"""This function is used to verify the response given by the client when
he tries to authenticate.
Optional arguments:
@@ -312,43 +324,48 @@ def _checkDigestResponse(auth_map, password, method = "GET", A1 = None, **kwargs
if auth_map['realm'] != kwargs.get('realm', None):
return False
- response = _computeDigestResponse(auth_map, password, method, A1,**kwargs)
+ response = _computeDigestResponse(
+ auth_map, password, method, A1, **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
+ pass_through = lambda password, username=None: password
+ encrypt = encrypt or pass_through
try:
- return encrypt(auth_map["password"], auth_map["username"]) == password
+ candidate = encrypt(auth_map["password"], auth_map["username"])
except TypeError:
- return encrypt(auth_map["password"]) == password
+ # if encrypt only takes one parameter, it's the password
+ candidate = encrypt(auth_map["password"])
+ return candidate == password
AUTH_RESPONSES = {
"basic": _checkBasicResponse,
"digest": _checkDigestResponse,
}
-def checkResponse (auth_map, password, method = "GET", encrypt=None, **kwargs):
+
+def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs):
"""'checkResponse' compares the auth_map with the password and optionally
other arguments that each implementation might need.
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 5d5cffbf..69a18d45 100644
--- a/cherrypy/lib/httputil.py
+++ b/cherrypy/lib/httputil.py
@@ -8,24 +8,24 @@ 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
response_codes[500] = ('Internal Server Error',
- 'The server encountered an unexpected condition '
- 'which prevented it from fulfilling the request.')
+ 'The server encountered an unexpected condition '
+ 'which prevented it from fulfilling the request.')
response_codes[503] = ('Service Unavailable',
- 'The server is currently unable to handle the '
- 'request due to a temporary overloading or '
- 'maintenance of the server.')
+ 'The server is currently unable to handle the '
+ 'request due to a temporary overloading or '
+ 'maintenance of the server.')
import re
import urllib
-
def urljoin(*atoms):
"""Return the given path \*atoms, joined into a single URL.
@@ -38,6 +38,7 @@ def urljoin(*atoms):
# Special-case the final url of "", and return "/" instead.
return url or "/"
+
def urljoin_bytes(*atoms):
"""Return the given path *atoms, joined into a single URL.
@@ -50,10 +51,12 @@ def urljoin_bytes(*atoms):
# Special-case the final url of "", and return "/" instead.
return url or ntob("/")
+
def protocol_from_http(protocol_str):
"""Return a protocol tuple from the given 'HTTP/x.y' string."""
return int(protocol_str[5]), int(protocol_str[7])
+
def get_ranges(headervalue, content_length):
"""Return a list of (start, stop) indices from a Range header, or None.
@@ -100,12 +103,20 @@ def get_ranges(headervalue, content_length):
# See rfc quote above.
return None
# Negative subscript (last N bytes)
- result.append((content_length - int(stop), content_length))
+ #
+ # RFC 2616 Section 14.35.1:
+ # If the entity is shorter than the specified suffix-length,
+ # the entire entity-body is used.
+ if int(stop) > content_length:
+ result.append((0, content_length))
+ else:
+ result.append((content_length - int(stop), content_length))
return result
class HeaderElement(object):
+
"""An element (with parameters) from an HTTP header's element list."""
def __init__(self, value, params=None):
@@ -160,7 +171,9 @@ class HeaderElement(object):
q_separator = re.compile(r'; *q *=')
+
class AcceptElement(HeaderElement):
+
"""An element (with parameters) from an Accept* header's element list.
AcceptElement objects are comparable; the more-preferred object will be
@@ -206,14 +219,15 @@ class AcceptElement(HeaderElement):
else:
return self.qvalue < other.qvalue
-
+RE_HEADER_SPLIT = re.compile(',(?=(?:[^"]*"[^"]*")*[^"]*$)')
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 []
result = []
- for element in fieldvalue.split(","):
+ for element in RE_HEADER_SPLIT.split(fieldvalue):
if fieldname.startswith("Accept") or fieldname == 'TE':
hv = AcceptElement.from_str(element)
else:
@@ -222,6 +236,7 @@ def header_elements(fieldname, fieldvalue):
return list(reversed(sorted(result)))
+
def decode_TEXT(value):
r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
try:
@@ -237,6 +252,7 @@ def decode_TEXT(value):
decodedvalue += atom
return decodedvalue
+
def valid_status(status):
"""Return legal HTTP status Code, Reason-phrase and Message.
@@ -332,6 +348,7 @@ def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
+
def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
"""Build a params dictionary from a query_string.
@@ -350,6 +367,7 @@ def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
class CaseInsensitiveDict(dict):
+
"""A case-insensitive dict subclass.
Each key is changed on entry to str(key).title().
@@ -372,7 +390,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():
@@ -404,13 +422,15 @@ class CaseInsensitiveDict(dict):
# replaced with a single SP before interpretation of the TEXT value."
if nativestr == bytestr:
header_translate_table = ''.join([chr(i) for i in xrange(256)])
- header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+ header_translate_deletechars = ''.join(
+ [chr(i) for i in xrange(32)]) + chr(127)
else:
header_translate_table = None
header_translate_deletechars = bytes(range(32)) + bytes([127])
class HeaderMap(CaseInsensitiveDict):
+
"""A dict subclass for HTTP request and response headers.
Each key is changed on entry to str(key).title(). This allows headers
@@ -419,7 +439,7 @@ class HeaderMap(CaseInsensitiveDict):
Values are header values (decoded according to :rfc:`2047` if necessary).
"""
- protocol=(1, 1)
+ protocol = (1, 1)
encodings = ["ISO-8859-1"]
# Someday, when http-bis is done, this will probably get dropped
@@ -460,8 +480,10 @@ class HeaderMap(CaseInsensitiveDict):
# See header_translate_* constants above.
# Replace only if you really know what you're doing.
- k = k.translate(header_translate_table, header_translate_deletechars)
- v = v.translate(header_translate_table, header_translate_deletechars)
+ k = k.translate(header_translate_table,
+ header_translate_deletechars)
+ v = v.translate(header_translate_table,
+ header_translate_deletechars)
yield (k, v)
encode_header_items = classmethod(encode_header_items)
@@ -488,7 +510,9 @@ class HeaderMap(CaseInsensitiveDict):
(v, cls.encodings))
encode = classmethod(encode)
+
class Host(object):
+
"""An internet address.
name
diff --git a/cherrypy/lib/jsontools.py b/cherrypy/lib/jsontools.py
index 3f8cf9dd..90b3ff8a 100644
--- a/cherrypy/lib/jsontools.py
+++ b/cherrypy/lib/jsontools.py
@@ -1,6 +1,6 @@
-import sys
import cherrypy
-from cherrypy._cpcompat import basestring, ntou, json, json_encode, json_decode
+from cherrypy._cpcompat import basestring, ntou, json_encode, json_decode
+
def json_processor(entity):
"""Read application/json data into request.json."""
@@ -13,8 +13,9 @@ def json_processor(entity):
except ValueError:
raise cherrypy.HTTPError(400, 'Invalid JSON document')
+
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
- force=True, debug=False, processor = json_processor):
+ force=True, debug=False, processor=json_processor):
"""Add a processor to parse JSON request entities:
The default processor places the parsed data into request.json.
@@ -57,11 +58,14 @@ def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
cherrypy.log('Adding body processor for %s' % ct, 'TOOLS.JSON_IN')
request.body.processors[ct] = processor
+
def json_handler(*args, **kwargs):
value = cherrypy.serving.request._json_inner_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
@@ -87,6 +91,6 @@ def json_out(content_type='application/json', debug=False, handler=json_handler)
request.handler = handler
if content_type is not None:
if debug:
- cherrypy.log('Setting Content-Type to %s' % content_type, 'TOOLS.JSON_OUT')
+ cherrypy.log('Setting Content-Type to %s' %
+ content_type, 'TOOLS.JSON_OUT')
cherrypy.serving.response.headers['Content-Type'] = content_type
-
diff --git a/cherrypy/lib/lockfile.py b/cherrypy/lib/lockfile.py
index 92aea14d..b9d8e027 100644
--- a/cherrypy/lib/lockfile.py
+++ b/cherrypy/lib/lockfile.py
@@ -16,6 +16,7 @@ except ImportError:
class LockError(Exception):
+
"Could not obtain a lock"
msg = "Unable to lock %r"
@@ -25,6 +26,7 @@ class LockError(Exception):
class UnlockError(LockError):
+
"Could not release a lock"
msg = "Unable to unlock %r"
@@ -32,6 +34,7 @@ class UnlockError(LockError):
# first, a default, naive locking implementation
class LockFile(object):
+
"""
A default, naive locking implementation. Always fails if the file
already exists.
@@ -53,6 +56,7 @@ class LockFile(object):
class SystemLockFile(object):
+
"""
An abstract base class for platform-specific locking.
"""
@@ -99,7 +103,7 @@ class SystemLockFile(object):
pass
#@abc.abstract_method
- #def _lock_file(self):
+ # def _lock_file(self):
# """Attempt to obtain the lock on self.fp. Raise LockError if not
# acquired."""
@@ -109,6 +113,7 @@ class SystemLockFile(object):
class WindowsLockFile(SystemLockFile):
+
def _lock_file(self):
# Lock just the first byte
try:
@@ -128,6 +133,7 @@ if 'msvcrt' in globals():
class UnixLockFile(SystemLockFile):
+
def _lock_file(self):
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
try:
diff --git a/cherrypy/lib/locking.py b/cherrypy/lib/locking.py
new file mode 100644
index 00000000..72dda9b3
--- /dev/null
+++ b/cherrypy/lib/locking.py
@@ -0,0 +1,47 @@
+import datetime
+
+
+class NeverExpires(object):
+ def expired(self):
+ return False
+
+
+class Timer(object):
+ """
+ A simple timer that will indicate when an expiration time has passed.
+ """
+ def __init__(self, expiration):
+ "Create a timer that expires at `expiration` (UTC datetime)"
+ self.expiration = expiration
+
+ @classmethod
+ def after(cls, elapsed):
+ """
+ Return a timer that will expire after `elapsed` passes.
+ """
+ return cls(datetime.datetime.utcnow() + elapsed)
+
+ def expired(self):
+ return datetime.datetime.utcnow() >= self.expiration
+
+
+class LockTimeout(Exception):
+ "An exception when a lock could not be acquired before a timeout period"
+
+
+class LockChecker(object):
+ """
+ Keep track of the time and detect if a timeout has expired
+ """
+ def __init__(self, session_id, timeout):
+ self.session_id = session_id
+ if timeout:
+ self.timer = Timer.after(timeout)
+ else:
+ self.timer = NeverExpires()
+
+ def expired(self):
+ if self.timer.expired():
+ raise LockTimeout(
+ "Timeout acquiring lock for %(session_id)s" % vars(self))
+ return False
diff --git a/cherrypy/lib/profiler.py b/cherrypy/lib/profiler.py
index 39129bbe..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
@@ -49,7 +50,8 @@ except ImportError:
profile = None
pstats = None
-import os, os.path
+import os
+import os.path
import sys
import warnings
@@ -57,6 +59,7 @@ from cherrypy._cpcompat import StringIO
_count = 0
+
class Profiler(object):
def __init__(self, path=None):
@@ -124,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):
@@ -150,6 +154,7 @@ class ProfileAggregator(Profiler):
class make_app:
+
def __init__(self, nextapp, path=None, aggregate=False):
"""Make a WSGI middleware app which wraps 'nextapp' with profiling.
@@ -167,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
@@ -191,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
@@ -205,4 +214,3 @@ def serve(path=None, port=8080):
if __name__ == "__main__":
serve(*tuple(sys.argv[1:]))
-
diff --git a/cherrypy/lib/reprconf.py b/cherrypy/lib/reprconf.py
index 3ec47e7f..6e70b5ec 100644
--- a/cherrypy/lib/reprconf.py
+++ b/cherrypy/lib/reprconf.py
@@ -44,6 +44,7 @@ except ImportError:
import operator as _operator
import sys
+
def as_dict(config):
"""Return a dict from 'config' whether it is a dict, file, or filename."""
if isinstance(config, basestring):
@@ -54,6 +55,7 @@ def as_dict(config):
class NamespaceSet(dict):
+
"""A dict of config namespace names and handlers.
Each config entry should begin with a namespace name; the corresponding
@@ -129,6 +131,7 @@ class NamespaceSet(dict):
class Config(dict):
+
"""A dict-like set of configuration data, with defaults and namespaces.
May take a file, filename, or dict.
@@ -180,6 +183,7 @@ class Config(dict):
class Parser(ConfigParser):
+
"""Sub-class of ConfigParser that keeps the case of options and that
raises an exception if the file cannot be read.
"""
@@ -260,13 +264,31 @@ class _Builder2:
return expr[subs]
def build_CallFunc(self, o):
- children = map(self.build, o.getChildren())
- callee = children.pop(0)
- kwargs = children.pop() or {}
- starargs = children.pop() or ()
- args = tuple(children) + tuple(starargs)
+ children = o.getChildren()
+ # Build callee from first child
+ callee = self.build(children[0])
+ # Build args and kwargs from remaining children
+ args = []
+ kwargs = {}
+ for child in children[1:]:
+ class_name = child.__class__.__name__
+ # None is ignored
+ if class_name == 'NoneType':
+ continue
+ # Keywords become kwargs
+ if class_name == 'Keyword':
+ kwargs.update(self.build(child))
+ # Everything else becomes args
+ else :
+ args.append(self.build(child))
return callee(*args, **kwargs)
+ def build_Keyword(self, o):
+ key, value_obj = o.getChildren()
+ value = self.build(value_obj)
+ kw_dict = {key: value}
+ return kw_dict
+
def build_List(self, o):
return map(self.build, o.getChildren())
@@ -415,6 +437,9 @@ class _Builder3:
raise TypeError("unrepr could not resolve the name %s" % repr(name))
+ def build_NameConstant(self, o):
+ return o.value
+
def build_UnaryOp(self, o):
op, operand = map(self.build, [o.op, o.operand])
return op(operand)
@@ -457,6 +482,7 @@ def modules(modulePath):
__import__(modulePath)
return sys.modules[modulePath]
+
def attributes(full_attribute_name):
"""Load a module and retrieve an attribute of that module."""
@@ -475,5 +501,3 @@ def attributes(full_attribute_name):
# Return a reference to the attribute.
return attr
-
-
diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py
index e2076c56..37556363 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.
@@ -99,10 +100,14 @@ import cherrypy
from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
from cherrypy.lib import httputil
from cherrypy.lib import lockfile
+from cherrypy.lib import locking
+from cherrypy.lib import is_iterator
missing = object()
+
class Session(object):
+
"""A CherryPy dict-like Session object (one per request)."""
_id = None
@@ -112,6 +117,7 @@ class Session(object):
def _get_id(self):
return self._id
+
def _set_id(self, value):
self._id = value
for o in self.id_observers:
@@ -238,7 +244,7 @@ class Session(object):
# If session data has never been loaded then it's never been
# accessed: no need to save it
if self.loaded:
- t = datetime.timedelta(seconds = self.timeout * 60)
+ t = datetime.timedelta(seconds=self.timeout * 60)
expiration_time = self.now() + t
if self.debug:
cherrypy.log('Saving session %r with expiry %s' %
@@ -298,15 +304,18 @@ class Session(object):
# -------------------- Application accessor methods -------------------- #
def __getitem__(self, key):
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data[key]
def __setitem__(self, key, value):
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
self._data[key] = value
def __delitem__(self, key):
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
del self._data[key]
def pop(self, key, default=missing):
@@ -314,55 +323,65 @@ class Session(object):
If key is not found, default is returned if given,
otherwise KeyError is raised.
"""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
if default is missing:
return self._data.pop(key)
else:
return self._data.pop(key, default)
def __contains__(self, key):
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return key in self._data
if hasattr({}, 'has_key'):
def has_key(self, key):
"""D.has_key(k) -> True if D has a key k, else False."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return key in self._data
def get(self, key, default=None):
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data.get(key, default)
def update(self, d):
"""D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
self._data.update(d)
def setdefault(self, key, default=None):
"""D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data.setdefault(key, default)
def clear(self):
"""D.clear() -> None. Remove all items from D."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
self._data.clear()
def keys(self):
"""D.keys() -> list of D's keys."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data.keys()
def items(self):
"""D.items() -> list of D's (key, value) pairs, as 2-tuples."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data.items()
def values(self):
"""D.values() -> list of D's values."""
- if not self.loaded: self.load()
+ if not self.loaded:
+ self.load()
return self._data.values()
@@ -423,6 +442,7 @@ class RamSession(Session):
class FileSession(Session):
+
"""Implementation of the File backend for sessions
storage_path
@@ -430,6 +450,10 @@ class FileSession(Session):
will be saved as pickle.dump(data, expiration_time) in its own file;
the filename will be self.SESSION_PREFIX + self.id.
+ lock_timeout
+ A timedelta or numeric seconds indicating how long
+ to block acquiring a lock. If None (default), acquiring a lock
+ will block indefinitely.
"""
SESSION_PREFIX = 'session-'
@@ -439,8 +463,17 @@ class FileSession(Session):
def __init__(self, id=None, **kwargs):
# The 'storage_path' arg is required for file-based sessions.
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
+ kwargs.setdefault('lock_timeout', None)
+
Session.__init__(self, id=id, **kwargs)
+ # validate self.lock_timeout
+ if isinstance(self.lock_timeout, (int, float)):
+ self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout)
+ if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))):
+ raise ValueError("Lock timeout must be numeric seconds or "
+ "a timedelta instance.")
+
def setup(cls, **kwargs):
"""Set up the storage system for file-based sessions.
@@ -465,7 +498,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:
@@ -477,11 +511,13 @@ class FileSession(Session):
except (IOError, EOFError):
e = sys.exc_info()[1]
if self.debug:
- cherrypy.log("Error loading the session pickle: %s" % e, 'TOOLS.SESSIONS')
+ cherrypy.log("Error loading the session pickle: %s" %
+ e, 'TOOLS.SESSIONS')
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)
@@ -489,7 +525,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:
@@ -500,7 +537,8 @@ class FileSession(Session):
if path is None:
path = self._get_file_path()
path += self.LOCK_SUFFIX
- while True:
+ checker = locking.LockChecker(self.id, self.lock_timeout)
+ while not checker.expired():
try:
self.lock = lockfile.LockFile(path)
except lockfile.LockError:
@@ -523,7 +561,7 @@ class FileSession(Session):
# Iterate over all session files in self.storage_path
for fname in os.listdir(self.storage_path):
if (fname.startswith(self.SESSION_PREFIX)
- and not fname.endswith(self.LOCK_SUFFIX)):
+ and not fname.endswith(self.LOCK_SUFFIX)):
# We have a session file: lock and load it and check
# if it's expired. If it fails, nevermind.
path = os.path.join(self.storage_path, fname)
@@ -554,6 +592,7 @@ class FileSession(Session):
class PostgresqlSession(Session):
+
""" Implementation of the PostgreSQL backend for sessions. It assumes
a table like this::
@@ -665,6 +704,7 @@ class MemcachedSession(Session):
def _get_id(self):
return self._id
+
def _set_id(self, value):
# This encode() call is where we differ from the superclass.
# Memcache keys MUST be byte strings, not unicode.
@@ -696,7 +736,8 @@ class MemcachedSession(Session):
self.mc_lock.acquire()
try:
if not self.cache.set(self.id, (self._data, expiration_time), td):
- raise AssertionError("Session data for id %r not set." % self.id)
+ raise AssertionError(
+ "Session data for id %r not set." % self.id)
finally:
self.mc_lock.release()
@@ -742,11 +783,12 @@ def save():
else:
# If the body is not being streamed, we save the data now
# (so we can release the lock).
- if isinstance(response.body, types.GeneratorType):
+ if is_iterator(response.body):
response.collapse_body()
cherrypy.session.save()
save.failsafe = True
+
def close():
"""Close the session object for this request."""
sess = getattr(cherrypy.serving, "session", None)
@@ -838,6 +880,7 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
kwargs['clean_freq'] = clean_freq
cherrypy.serving.session = sess = storage_class(id, **kwargs)
sess.debug = debug
+
def update_cookie(id):
"""Update the cookie every time the session id changes."""
cherrypy.serving.response.cookie[name] = id
@@ -892,8 +935,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
@@ -912,11 +958,11 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
raise ValueError("The httponly cookie token is not supported.")
cookie[name]['httponly'] = 1
+
def expire():
"""Expire the current session cookie."""
- name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id')
+ name = cherrypy.serving.request.config.get(
+ 'tools.sessions.name', 'session_id')
one_year = 60 * 60 * 24 * 365
e = time.time() - one_year
cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)
-
-
diff --git a/cherrypy/lib/static.py b/cherrypy/lib/static.py
index f55dec1d..a630dae6 100644
--- a/cherrypy/lib/static.py
+++ b/cherrypy/lib/static.py
@@ -1,26 +1,27 @@
+import os
+import re
+import stat
+import mimetypes
+
try:
from io import UnsupportedOperation
except ImportError:
UnsupportedOperation = object()
-import logging
-import mimetypes
-mimetypes.init()
-mimetypes.types_map['.dwg']='image/x-dwg'
-mimetypes.types_map['.ico']='image/x-icon'
-mimetypes.types_map['.bz2']='application/x-bzip2'
-mimetypes.types_map['.gz']='application/x-gzip'
-
-import os
-import re
-import stat
-import time
import cherrypy
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):
+mimetypes.init()
+mimetypes.types_map['.dwg'] = 'image/x-dwg'
+mimetypes.types_map['.ico'] = 'image/x-icon'
+mimetypes.types_map['.bz2'] = 'application/x-bzip2'
+mimetypes.types_map['.gz'] = 'application/x-gzip'
+
+
+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.
@@ -92,6 +93,7 @@ def serve_file(path, content_type=None, disposition=None, name=None, debug=False
fileobj = open(path, 'rb')
return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
+
def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
debug=False):
"""Set status, headers, and body in order to serve the given file object.
@@ -145,6 +147,7 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
+
def _serve_fileobj(fileobj, content_type, content_length, debug=False):
"""Internal. Set response.body to the given file object, perhaps ranged."""
response = cherrypy.serving.response
@@ -156,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)
@@ -169,8 +173,9 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
stop = content_length
r_len = stop - start
if debug:
- cherrypy.log('Single part; start: %r, stop: %r' % (start, stop),
- 'TOOLS.STATIC')
+ cherrypy.log(
+ 'Single part; start: %r, stop: %r' % (start, stop),
+ 'TOOLS.STATIC')
response.status = "206 Partial Content"
response.headers['Content-Range'] = (
"bytes %s-%s/%s" % (start, stop - 1, content_length))
@@ -182,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:
@@ -199,14 +204,20 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
for start, stop in r:
if debug:
- cherrypy.log('Multipart; start: %r, stop: %r' % (start, stop),
- 'TOOLS.STATIC')
+ cherrypy.log(
+ 'Multipart; start: %r, stop: %r' % (
+ 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
@@ -226,6 +237,7 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
response.body = fileobj
return response.body
+
def serve_download(path, name=None):
"""Serve 'path' as an application/x-download attachment."""
# This is such a common idiom I felt it deserved its own wrapper.
@@ -252,6 +264,7 @@ def _attempt(filename, content_types, debug=False):
cherrypy.log('NotFound', 'TOOLS.STATICFILE')
return False
+
def staticdir(section, dir, root="", match="", content_types=None, index="",
debug=False):
"""Serve a static resource from the given (root +) dir.
@@ -314,7 +327,7 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
# have ".." or similar uplevel attacks in it. Check that the final
# filename is a child of dir.
if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
- raise cherrypy.HTTPError(403) # Forbidden
+ raise cherrypy.HTTPError(403) # Forbidden
handled = _attempt(filename, content_types)
if not handled:
@@ -325,6 +338,7 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
request.is_index = filename[-1] in (r"\/")
return handled
+
def staticfile(filename, root=None, match="", content_types=None, debug=False):
"""Serve a static resource from the given (root +) filename.
@@ -354,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/lib/xmlrpcutil.py b/cherrypy/lib/xmlrpcutil.py
index 9a44464b..9fc9564f 100644
--- a/cherrypy/lib/xmlrpcutil.py
+++ b/cherrypy/lib/xmlrpcutil.py
@@ -3,6 +3,7 @@ import sys
import cherrypy
from cherrypy._cpcompat import ntob
+
def get_xmlrpclib():
try:
import xmlrpc.client as x
@@ -10,6 +11,7 @@ def get_xmlrpclib():
import xmlrpclib as x
return x
+
def process_body():
"""Return (params, method) from request body."""
try:
@@ -48,8 +50,8 @@ def respond(body, encoding='utf-8', allow_none=0):
encoding=encoding,
allow_none=allow_none))
+
def on_error(*args, **kwargs):
body = str(sys.exc_info()[1])
xmlrpclib = get_xmlrpclib()
_set_response(xmlrpclib.dumps(xmlrpclib.Fault(1, body)))
-
diff --git a/cherrypy/process/plugins.py b/cherrypy/process/plugins.py
index 99bf9005..c787ba92 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
@@ -29,10 +30,12 @@ _module__file__base = os.getcwd()
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
@@ -54,8 +57,8 @@ class SimplePlugin(object):
self.bus.unsubscribe(channel, method)
-
class SignalHandler(object):
+
"""Register bus channels (and listeners) for system signals.
You can modify what signals your application listens for, and what it does
@@ -74,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 = {}
@@ -187,12 +190,14 @@ class SignalHandler(object):
try:
- import pwd, grp
+ import pwd
+ import grp
except ImportError:
pwd, grp = None, None
class DropPrivileges(SimplePlugin):
+
"""Drop privileges. uid/gid arguments not available on Windows.
Special thanks to `Gavin Baker <http://antonym.org/2005/12/dropping-privileges-in-python.html>`_
@@ -207,6 +212,7 @@ class DropPrivileges(SimplePlugin):
def _get_uid(self):
return self._uid
+
def _set_uid(self, val):
if val is not None:
if pwd is None:
@@ -217,10 +223,11 @@ class DropPrivileges(SimplePlugin):
val = pwd.getpwnam(val)[2]
self._uid = val
uid = property(_get_uid, _set_uid,
- doc="The uid under which to run. Availability: Unix.")
+ doc="The uid under which to run. Availability: Unix.")
def _get_gid(self):
return self._gid
+
def _set_gid(self, val):
if val is not None:
if grp is None:
@@ -231,10 +238,11 @@ class DropPrivileges(SimplePlugin):
val = grp.getgrnam(val)[2]
self._gid = val
gid = property(_get_gid, _set_gid,
- doc="The gid under which to run. Availability: Unix.")
+ doc="The gid under which to run. Availability: Unix.")
def _get_umask(self):
return self._umask
+
def _set_umask(self, val):
if val is not None:
try:
@@ -244,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.
@@ -299,6 +310,7 @@ class DropPrivileges(SimplePlugin):
class Daemonizer(SimplePlugin):
+
"""Daemonize the running script.
Use this with a Web Site Process Bus via::
@@ -368,7 +380,7 @@ class Daemonizer(SimplePlugin):
pid = os.fork()
if pid > 0:
self.bus.log('Forking twice.')
- os._exit(0) # Exit second parent
+ os._exit(0) # Exit second parent
except OSError:
exc = sys.exc_info()[1]
sys.exit("%s: fork #2 failed: (%d) %s\n"
@@ -394,6 +406,7 @@ class Daemonizer(SimplePlugin):
class PIDFile(SimplePlugin):
+
"""Maintain a PID file via a WSPBus."""
def __init__(self, bus, pidfile):
@@ -422,6 +435,7 @@ class PIDFile(SimplePlugin):
class PerpetualTimer(Timer):
+
"""A responsive subclass of threading.Timer whose run() method repeats.
Use this timer only when you really need a very interruptible timer;
@@ -451,6 +465,7 @@ class PerpetualTimer(Timer):
class BackgroundTask(SetDaemonProperty, threading.Thread):
+
"""A subclass of threading.Thread whose run() method repeats.
Use this class for most repeating tasks. It uses time.sleep() to wait
@@ -492,6 +507,7 @@ class BackgroundTask(SetDaemonProperty, threading.Thread):
class Monitor(SimplePlugin):
+
"""WSPBus listener to periodically run a callback in its own thread."""
callback = None
@@ -501,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)
@@ -516,7 +534,7 @@ class Monitor(SimplePlugin):
threadname = self.name or self.__class__.__name__
if self.thread is None:
self.thread = BackgroundTask(self.frequency, self.callback,
- bus = self.bus)
+ bus=self.bus)
self.thread.setName(threadname)
self.thread.start()
self.bus.log("Started monitor thread %r." % threadname)
@@ -527,7 +545,8 @@ class Monitor(SimplePlugin):
def stop(self):
"""Stop our callback's background task thread."""
if self.thread is None:
- self.bus.log("No thread running for %s." % self.name or self.__class__.__name__)
+ self.bus.log("No thread running for %s." %
+ self.name or self.__class__.__name__)
else:
if self.thread is not threading.currentThread():
name = self.thread.getName()
@@ -545,6 +564,7 @@ class Monitor(SimplePlugin):
class Autoreloader(Monitor):
+
"""Monitor which re-executes the process when files change.
This :ref:`plugin<plugins>` restarts the process (via :func:`os.execv`)
@@ -554,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).+'
@@ -590,15 +610,20 @@ class Autoreloader(Monitor):
def sysfiles(self):
"""Return a Set of sys.modules filenames to monitor."""
files = set()
- for k, m in sys.modules.items():
+ for k, m in list(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)
if f is not None and not os.path.isabs(f):
- # ensure absolute paths so a os.chdir() in the app doesn't break me
- f = os.path.normpath(os.path.join(_module__file__base, f))
+ # ensure absolute paths so a os.chdir() in the app
+ # doesn't break me
+ f = os.path.normpath(
+ os.path.join(_module__file__base, f))
files.add(f)
return files
@@ -626,14 +651,17 @@ class Autoreloader(Monitor):
else:
if mtime is None or mtime > oldtime:
# The file has been deleted or modified.
- self.bus.log("Restarting because %s changed." % filename)
+ self.bus.log("Restarting because %s changed." %
+ filename)
self.thread.cancel()
- self.bus.log("Stopped thread %r." % self.thread.getName())
+ self.bus.log("Stopped thread %r." %
+ self.thread.getName())
self.bus.restart()
return
class ThreadManager(SimplePlugin):
+
"""Manager for HTTP request threads.
If you have control over thread creation and destruction, publish to
@@ -687,4 +715,3 @@ class ThreadManager(SimplePlugin):
self.bus.publish('stop_thread', i)
self.threads.clear()
graceful = stop
-
diff --git a/cherrypy/process/servers.py b/cherrypy/process/servers.py
index da355879..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
@@ -117,6 +119,7 @@ import warnings
class ServerAdapter(object):
+
"""Adapter for an HTTP server.
If you need to start more than one HTTP server (to serve on multiple
@@ -249,6 +252,7 @@ class ServerAdapter(object):
class FlupCGIServer(object):
+
"""Adapter for a flup.server.cgi.WSGIServer."""
def __init__(self, *args, **kwargs):
@@ -272,6 +276,7 @@ class FlupCGIServer(object):
class FlupFCGIServer(object):
+
"""Adapter for a flup.server.fcgi.WSGIServer."""
def __init__(self, *args, **kwargs):
@@ -311,11 +316,13 @@ 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
class FlupSCGIServer(object):
+
"""Adapter for a flup.server.scgi.WSGIServer."""
def __init__(self, *args, **kwargs):
@@ -359,10 +366,12 @@ 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
+
def check_port(host, port, timeout=1.0):
"""Raise an error if the given port is not free on the given host."""
if not host:
@@ -379,7 +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))]
@@ -406,6 +417,7 @@ def check_port(host, port, timeout=1.0):
free_port_timeout = 0.1
occupied_port_timeout = 1.0
+
def wait_for_free_port(host, port, timeout=None):
"""Wait for the specified port to become free (drop requests)."""
if not host:
@@ -425,6 +437,7 @@ def wait_for_free_port(host, port, timeout=None):
raise IOError("Port %r not free on %r" % (port, host))
+
def wait_for_occupied_port(host, port, timeout=None):
"""Wait for the specified port to become active (receive requests)."""
if not host:
@@ -447,6 +460,6 @@ def wait_for_occupied_port(host, port, timeout=None):
# On systems where a loopback interface is not available and the
# server is bound to all interfaces, it's difficult to determine
# whether the server is in fact occupying the port. In this case,
- # just issue a warning and move on. See issue #1100.
+ # just issue a warning and move on. See issue #1100.
msg = "Unable to verify that the server is bound on %r" % port
warnings.warn(msg)
diff --git a/cherrypy/process/win32.py b/cherrypy/process/win32.py
index 6f135177..4afd3f14 100644
--- a/cherrypy/process/win32.py
+++ b/cherrypy/process/win32.py
@@ -11,6 +11,7 @@ from cherrypy.process import wspbus, plugins
class ConsoleCtrlHandler(plugins.SimplePlugin):
+
"""A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
def __init__(self, bus):
@@ -68,6 +69,7 @@ class ConsoleCtrlHandler(plugins.SimplePlugin):
class Win32Bus(wspbus.Bus):
+
"""A Web Site Process Bus implementation for Win32.
Instead of time.sleep, this bus blocks using native win32event objects.
@@ -90,6 +92,7 @@ class Win32Bus(wspbus.Bus):
def _get_state(self):
return self._state
+
def _set_state(self, value):
self._state = value
event = self._get_state_event(value)
@@ -106,7 +109,8 @@ class Win32Bus(wspbus.Bus):
# Don't wait for an event that beat us to the punch ;)
if self.state not in state:
events = tuple([self._get_state_event(s) for s in state])
- win32event.WaitForMultipleObjects(events, 0, win32event.INFINITE)
+ win32event.WaitForMultipleObjects(
+ events, 0, win32event.INFINITE)
else:
# Don't wait for an event that beat us to the punch ;)
if self.state != state:
@@ -115,6 +119,7 @@ class Win32Bus(wspbus.Bus):
class _ControlCodes(dict):
+
"""Control codes used to "signal" a service via ControlService.
User-defined control codes are in the range 128-255. We generally use
@@ -145,6 +150,7 @@ def signal_child(service, command):
class PyWebService(win32serviceutil.ServiceFramework):
+
"""Python Web Service."""
_svc_name_ = "Python Web Service"
diff --git a/cherrypy/process/wspbus.py b/cherrypy/process/wspbus.py
index df5d6885..5409d038 100644
--- a/cherrypy/process/wspbus.py
+++ b/cherrypy/process/wspbus.py
@@ -78,8 +78,11 @@ from cherrypy._cpcompat import set
# sys.executable is a relative-path, and/or cause other problems).
_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):
@@ -107,9 +110,13 @@ class ChannelFailures(Exception):
__nonzero__ = __bool__
# Use a flag to indicate the state of the bus.
+
+
class _StateEnum(object):
+
class State(object):
name = None
+
def __repr__(self):
return "states.%s" % self.name
@@ -137,6 +144,7 @@ else:
class Bus(object):
+
"""Process state-machine and messenger for HTTP site deployment.
All listeners for a given channel are guaranteed to be called even
@@ -266,14 +274,14 @@ class Bus(object):
# signal handler, console handler, or atexit handler), so we
# can't just let exceptions propagate out unhandled.
# Assume it's been logged and just die.
- os._exit(70) # EX_SOFTWARE
+ os._exit(70) # EX_SOFTWARE
if exitstate == states.STARTING:
# exit() was called before start() finished, possibly due to
# Ctrl-C because a start listener got stuck. In this case,
# we could get stuck in a loop where Ctrl-C never exits the
# process, so we just call os.exit here.
- os._exit(70) # EX_SOFTWARE
+ os._exit(70) # EX_SOFTWARE
def restart(self):
"""Restart the process (may close connections).
@@ -318,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+
@@ -394,7 +405,7 @@ class Bus(object):
Set self.max_cloexec_files to 0 to disable this behavior.
"""
- for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
+ for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
try:
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
except IOError:
diff --git a/cherrypy/scaffold/__init__.py b/cherrypy/scaffold/__init__.py
index 9a04796d..50de34bb 100644
--- a/cherrypy/scaffold/__init__.py
+++ b/cherrypy/scaffold/__init__.py
@@ -47,11 +47,11 @@ Or, just look at the pretty picture:<br />
other.exposed = True
files = cherrypy.tools.staticdir.handler(
- section="/files",
- dir=os.path.join(local_dir, "static"),
- # Ignore .php files, etc.
+ section="/files",
+ dir=os.path.join(local_dir, "static"),
+ # Ignore .php files, etc.
match=r'\.(css|gif|html?|ico|jpe?g|js|png|swf|xml)$',
- )
+ )
root = Root()
diff --git a/cherrypy/test/__init__.py b/cherrypy/test/__init__.py
index 77036070..0927c170 100644
--- a/cherrypy/test/__init__.py
+++ b/cherrypy/test/__init__.py
@@ -11,15 +11,18 @@ interactive input.
import os
import sys
+
def newexit():
os._exit(1)
+
def setup():
# We want to monkey patch sys.exit so that we can get some
# information about where exit is being called.
newexit._old = sys.exit
sys.exit = newexit
+
def teardown():
try:
sys.exit = sys.exit._old
diff --git a/cherrypy/test/_test_decorators.py b/cherrypy/test/_test_decorators.py
index e4209523..666ea6d9 100644
--- a/cherrypy/test/_test_decorators.py
+++ b/cherrypy/test/_test_decorators.py
@@ -37,5 +37,3 @@ class ToolExamples(object):
# the _cp_config attribute added by the Tool decorator. You have
# to write _cp_config[k] = v or _cp_config.update(...) instead.
blah._cp_config['response.stream'] = True
-
-
diff --git a/cherrypy/test/_test_states_demo.py b/cherrypy/test/_test_states_demo.py
index 4ae081ef..f90f14f9 100644
--- a/cherrypy/test/_test_states_demo.py
+++ b/cherrypy/test/_test_states_demo.py
@@ -57,9 +57,11 @@ def starterror():
zerodiv = 1 / 0
cherrypy.engine.subscribe('start', starterror, priority=6)
+
def log_test_case_name():
if cherrypy.config.get('test_case_name', False):
- cherrypy.log("STARTED FROM: %s" % cherrypy.config.get('test_case_name'))
+ cherrypy.log("STARTED FROM: %s" %
+ cherrypy.config.get('test_case_name'))
cherrypy.engine.subscribe('start', log_test_case_name, priority=6)
diff --git a/cherrypy/test/benchmark.py b/cherrypy/test/benchmark.py
index e26d41d6..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
@@ -47,6 +47,7 @@ __all__ = ['ABSession', 'Root', 'print_report',
size_cache = {}
+
class Root:
def index(self):
@@ -86,7 +87,7 @@ cherrypy.config.update({
'server.max_request_header_size': 0,
'server.max_request_body_size': 0,
'engine.deadlock_poll_freq': 0,
- })
+})
# Cheat mode on ;)
del cherrypy.config['tools.log_tracebacks.on']
@@ -98,12 +99,13 @@ appconf = {
'tools.staticdir.on': True,
'tools.staticdir.dir': 'static',
'tools.staticdir.root': curdir,
- },
- }
+ },
+}
app = cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf)
class NullRequest:
+
"""A null HTTP request class, returning 200 and an empty body."""
def __init__(self, local, remote, scheme="http"):
@@ -128,6 +130,7 @@ class NullResponse:
class ABSession:
+
"""A session of 'ab', the Apache HTTP server benchmarking tool.
Example output from ab:
@@ -187,19 +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
@@ -209,7 +214,8 @@ Finished 1000 requests
assert self.concurrency > 0
assert self.requests > 0
# Don't use "localhost".
- # Cf http://mail.python.org/pipermail/python-win32/2008-March/007050.html
+ # Cf
+ # http://mail.python.org/pipermail/python-win32/2008-March/007050.html
return ("-k -n %s -c %s http://127.0.0.1:%s%s" %
(self.requests, self.concurrency, port, self.path))
@@ -261,8 +267,9 @@ def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
# Add a row of averages.
yield ["Average"] + [str(avg[attr] / len(concurrency)) for attr in attrs]
+
def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
- concurrency=50):
+ concurrency=50):
sess = ABSession(concurrency=concurrency)
attrs, names, patterns = list(zip(*sess.parse_patterns))
yield ('bytes',) + names
@@ -271,6 +278,7 @@ def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
sess.run()
yield [sz] + [getattr(sess, attr) for attr in attrs]
+
def print_report(rows):
for row in rows:
print("")
@@ -282,24 +290,25 @@ def print_report(rows):
def run_standard_benchmarks():
print("")
print("Client Thread Report (1000 requests, 14 byte response body, "
- "%s server threads):" % cherrypy.server.thread_pool)
+ "%s server threads):" % cherrypy.server.thread_pool)
print_report(thread_report())
print("")
print("Client Thread Report (1000 requests, 14 bytes via staticdir, "
- "%s server threads):" % cherrypy.server.thread_pool)
+ "%s server threads):" % cherrypy.server.thread_pool)
print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
print("")
print("Size Report (1000 requests, 50 client threads, "
- "%s server threads):" % cherrypy.server.thread_pool)
+ "%s server threads):" % cherrypy.server.thread_pool)
print_report(size_report())
# 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():
@@ -312,7 +321,7 @@ def startup_modpython(req=None):
cherrypy.engine.start()
if cherrypy.engine.state == cherrypy._cpengine.STARTING:
cherrypy.engine.wait()
- return 0 # apache.OK
+ return 0 # apache.OK
def run_modpython(use_wsgi=False):
@@ -329,11 +338,14 @@ def run_modpython(use_wsgi=False):
s = _cpmodpy.ModPythonServer
if use_wsgi:
pyopts.append(("wsgi.application", "cherrypy::tree"))
- pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython"))
+ pyopts.append(
+ ("wsgi.startup", "cherrypy.test.benchmark::startup_modpython"))
handler = "modpython_gateway::handler"
- s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH, handler=handler)
+ s = s(port=54583, opts=pyopts,
+ apache_path=APACHE_PATH, handler=handler)
else:
- pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython"))
+ pyopts.append(
+ ("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython"))
s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH)
try:
@@ -343,7 +355,6 @@ def run_modpython(use_wsgi=False):
s.stop()
-
if __name__ == '__main__':
longopts = ['cpmodpy', 'modpython', 'null', 'notests',
'help', 'ab=', 'apache=']
@@ -367,7 +378,7 @@ if __name__ == '__main__':
def run():
port = cherrypy.server.socket_port
print("You may now open http://127.0.0.1:%s%s/" %
- (port, SCRIPT_NAME))
+ (port, SCRIPT_NAME))
if "--null" in opts:
print("Using null Request object")
@@ -389,7 +400,9 @@ if __name__ == '__main__':
print("Starting CherryPy app server...")
class NullWriter(object):
+
"""Suppresses the printing of socket errors."""
+
def write(self, data):
pass
sys.stderr = NullWriter()
diff --git a/cherrypy/test/checkerdemo.py b/cherrypy/test/checkerdemo.py
index 32a7dee2..68fb222b 100644
--- a/cherrypy/test/checkerdemo.py
+++ b/cherrypy/test/checkerdemo.py
@@ -9,6 +9,7 @@ import os
import cherrypy
thisdir = os.path.dirname(os.path.abspath(__file__))
+
class Root:
pass
@@ -19,13 +20,13 @@ if __name__ == '__main__':
},
# This entry should be OK.
'/base/static': {'tools.staticdir.on': True,
- 'tools.staticdir.dir': 'static'},
+ 'tools.staticdir.dir': 'static'},
# Warn on missing folder.
'/base/js': {'tools.staticdir.on': True,
- 'tools.staticdir.dir': 'js'},
+ 'tools.staticdir.dir': 'js'},
# Warn on dir with an abs path even though we provide root.
'/base/static2': {'tools.staticdir.on': True,
- 'tools.staticdir.dir': '/static'},
+ 'tools.staticdir.dir': '/static'},
# Warn on dir with a relative path with no root.
'/static3': {'tools.staticdir.on': True,
'tools.staticdir.dir': 'static'},
diff --git a/cherrypy/test/helper.py b/cherrypy/test/helper.py
index 0628b788..b6ed3277 100644
--- a/cherrypy/test/helper.py
+++ b/cherrypy/test/helper.py
@@ -14,17 +14,23 @@ import warnings
import cherrypy
from cherrypy._cpcompat import basestring, copyitems, HTTPSConnection, ntob
-from cherrypy._cpcompat import subprocess
from cherrypy.lib import httputil
from cherrypy.lib import gctools
from cherrypy.lib.reprconf import unrepr
from cherrypy.test import webtest
+# Use subprocess module from Python 2.7 on Python 2.3-2.6
+if sys.version_info < (2, 7):
+ import cherrypy._cpcompat_subprocess as subprocess
+else:
+ import subprocess
+
import nose
_testconfig = None
-def get_tst_config(overconf = {}):
+
+def get_tst_config(overconf={}):
global _testconfig
if _testconfig is None:
conf = {
@@ -52,7 +58,9 @@ def get_tst_config(overconf = {}):
return conf
+
class Supervisor(object):
+
"""Base class for modeling and controlling servers during testing."""
def __init__(self, **kwargs):
@@ -64,13 +72,16 @@ class Supervisor(object):
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
@@ -120,6 +131,7 @@ class LocalSupervisor(Supervisor):
class NativeServerSupervisor(LocalSupervisor):
+
"""Server supervisor for the builtin HTTP server."""
httpserver_class = "cherrypy._cpnative_server.CPHTTPServer"
@@ -131,6 +143,7 @@ class NativeServerSupervisor(LocalSupervisor):
class LocalWSGISupervisor(LocalSupervisor):
+
"""Server supervisor for the builtin WSGI server."""
httpserver_class = "cherrypy._cpwsgi_server.CPWSGIServer"
@@ -153,7 +166,8 @@ class LocalWSGISupervisor(LocalSupervisor):
try:
import wsgiconq
except ImportError:
- warnings.warn("Error importing wsgiconq. pyconquer will not run.")
+ warnings.warn(
+ "Error importing wsgiconq. pyconquer will not run.")
else:
app = wsgiconq.WSGILogger(app, c_calls=True)
@@ -161,9 +175,10 @@ class LocalWSGISupervisor(LocalSupervisor):
try:
from wsgiref import validate
except ImportError:
- warnings.warn("Error importing wsgiref. The validator will not run.")
+ warnings.warn(
+ "Error importing wsgiref. The validator will not run.")
else:
- #wraps the app in the validator
+ # wraps the app in the validator
app = validate.validator(app)
return app
@@ -175,6 +190,7 @@ def get_cpmodpy_supervisor(**options):
sup.template = modpy.conf_cpmodpy
return sup
+
def get_modpygw_supervisor(**options):
from cherrypy.test import modpy
sup = modpy.ModPythonSupervisor(**options)
@@ -182,18 +198,22 @@ def get_modpygw_supervisor(**options):
sup.using_wsgi = True
return sup
+
def get_modwsgi_supervisor(**options):
from cherrypy.test import modwsgi
return modwsgi.ModWSGISupervisor(**options)
+
def get_modfcgid_supervisor(**options):
from cherrypy.test import modfcgid
return modfcgid.ModFCGISupervisor(**options)
+
def get_modfastcgi_supervisor(**options):
from cherrypy.test import modfastcgi
return modfastcgi.ModFCGISupervisor(**options)
+
def get_wsgi_u_supervisor(**options):
cherrypy.server.wsgi_version = ('u', 0)
return LocalWSGISupervisor(**options)
@@ -261,14 +281,15 @@ class CPWebCase(webtest.WebCase):
def setup_class(cls):
''
- #Creates a server
+ # Creates a server
conf = get_tst_config()
- supervisor_factory = cls.available_servers.get(conf.get('server', 'wsgi'))
+ supervisor_factory = cls.available_servers.get(
+ conf.get('server', 'wsgi'))
if supervisor_factory is None:
raise RuntimeError('Unknown server in config: %s' % conf['server'])
supervisor = supervisor_factory(**conf)
- #Copied from "run_test_suite"
+ # Copied from "run_test_suite"
cherrypy.config.reset()
baseconf = cls._setup_server(supervisor, conf)
cherrypy.config.update(baseconf)
@@ -303,14 +324,15 @@ class CPWebCase(webtest.WebCase):
self.assertBody("Statistics:")
# Tell nose to run this last in each class.
# Prefer sys.maxint for Python 2.3, which didn't have float('inf')
- test_gc.compat_co_firstlineno = getattr(sys, 'maxint', None) or float('inf')
+ test_gc.compat_co_firstlineno = getattr(
+ sys, 'maxint', None) or float('inf')
def prefix(self):
return self.script_name.rstrip("/")
def base(self):
if ((self.scheme == "http" and self.PORT == 80) or
- (self.scheme == "https" and self.PORT == 443)):
+ (self.scheme == "https" and self.PORT == 443)):
port = ""
else:
port = ":%s" % self.PORT
@@ -321,11 +343,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)
@@ -341,13 +365,16 @@ class CPWebCase(webtest.WebCase):
# First, test the response body without checking the traceback.
# 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>'))
- m = re.match(ntob(epage, self.encoding), self.body, re.DOTALL)
+ def esc(text):
+ return re.escape(ntob(text))
+ epage = re.escape(page)
+ epage = epage.replace(
+ esc('<pre id="traceback"></pre>'),
+ esc('<pre id="traceback">') + ntob('(.*)') + esc('</pre>'))
+ m = re.match(epage, self.body, re.DOTALL)
if not m:
- self._handlewebError('Error page does not match; expected:\n' + page)
+ self._handlewebError(
+ 'Error page does not match; expected:\n' + page)
return
# Now test the pattern against the traceback
@@ -405,7 +432,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
@@ -429,7 +457,7 @@ server.ssl_private_key: r'%s'
'access_log': self.access_log,
'ssl': ssl,
'extra': extra,
- }
+ }
f = open(self.config_file, 'wb')
f.write(ntob(conf, 'utf-8'))
f.close()
@@ -455,10 +483,12 @@ server.ssl_private_key: r'%s'
args.append('-d')
env = os.environ.copy()
- # Make sure we import the cherrypy package in which this module is defined.
+ # Make sure we import the cherrypy package in which this module is
+ # defined.
grandparentdir = os.path.abspath(os.path.join(thisdir, '..', '..'))
if env.get('PYTHONPATH', ''):
- env['PYTHONPATH'] = os.pathsep.join((grandparentdir, env['PYTHONPATH']))
+ env['PYTHONPATH'] = os.pathsep.join(
+ (grandparentdir, env['PYTHONPATH']))
else:
env['PYTHONPATH'] = grandparentdir
self._proc = subprocess.Popen([sys.executable] + args, env=env)
diff --git a/cherrypy/test/logtest.py b/cherrypy/test/logtest.py
index cbc0c330..cc59499a 100644
--- a/cherrypy/test/logtest.py
+++ b/cherrypy/test/logtest.py
@@ -10,11 +10,14 @@ from cherrypy._cpcompat import basestring, ntob, unicodestr
try:
# On Windows, msvcrt.getch reads a single char without output.
import msvcrt
+
def getchar():
return msvcrt.getch()
except ImportError:
# Unix getchr
- import tty, termios
+ import tty
+ import termios
+
def getchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
@@ -27,6 +30,7 @@ except ImportError:
class LogCase(object):
+
"""unittest.TestCase mixin for testing log messages.
logfile: a filename for the desired log. Yes, I know modes are evil,
@@ -50,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()
@@ -96,7 +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.
@@ -104,8 +111,8 @@ class LogCase(object):
If marker is None, self.lastmarker is used. If the log hasn't
been marked (using self.markLog), the entire log will be returned.
"""
-## # Give the logger time to finish writing?
-## time.sleep(0.5)
+# Give the logger time to finish writing?
+# time.sleep(0.5)
logfile = self.logfile
marker = marker or self.lastmarker
@@ -169,7 +176,12 @@ class LogCase(object):
lines = lines.encode('utf-8')
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)
+ self._handleLogError(
+ 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):
@@ -185,4 +197,3 @@ class LogCase(object):
if line not in logline:
msg = "%r not found in log" % line
self._handleLogError(msg, data[start:stop], marker, line)
-
diff --git a/cherrypy/test/modfastcgi.py b/cherrypy/test/modfastcgi.py
index 085d2be1..53645474 100644
--- a/cherrypy/test/modfastcgi.py
+++ b/cherrypy/test/modfastcgi.py
@@ -80,10 +80,12 @@ RewriteRule ^(.*)$ /fastcgi.pyc [L]
FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000
"""
+
def erase_script_name(environ, start_response):
environ['SCRIPT_NAME'] = ''
return cherrypy.tree(environ, start_response)
+
class ModFCGISupervisor(helper.LocalWSGISupervisor):
httpserver_class = "cherrypy.process.servers.FlupFCGIServer"
@@ -131,5 +133,5 @@ class ModFCGISupervisor(helper.LocalWSGISupervisor):
helper.LocalWSGISupervisor.stop(self)
def sync_apps(self):
- cherrypy.server.httpserver.fcgiserver.application = self.get_app(erase_script_name)
-
+ cherrypy.server.httpserver.fcgiserver.application = self.get_app(
+ erase_script_name)
diff --git a/cherrypy/test/modfcgid.py b/cherrypy/test/modfcgid.py
index 526ba409..6f8a3584 100644
--- a/cherrypy/test/modfcgid.py
+++ b/cherrypy/test/modfcgid.py
@@ -77,6 +77,7 @@ RewriteRule ^(.*)$ /fastcgi.pyc [L]
FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000
"""
+
class ModFCGISupervisor(helper.LocalSupervisor):
using_apache = True
@@ -122,4 +123,3 @@ class ModFCGISupervisor(helper.LocalSupervisor):
def sync_apps(self):
cherrypy.server.httpserver.fcgiserver.application = self.get_app()
-
diff --git a/cherrypy/test/modpy.py b/cherrypy/test/modpy.py
index 70f7ad43..829628b7 100644
--- a/cherrypy/test/modpy.py
+++ b/cherrypy/test/modpy.py
@@ -91,6 +91,7 @@ PythonOption socket_host %(host)s
PythonDebug On
"""
+
class ModPythonSupervisor(helper.Supervisor):
using_apache = True
@@ -123,6 +124,8 @@ class ModPythonSupervisor(helper.Supervisor):
loaded = False
+
+
def wsgisetup(req):
global loaded
if not loaded:
@@ -134,7 +137,7 @@ def wsgisetup(req):
"log.error_file": os.path.join(curdir, "test.log"),
"environment": "test_suite",
"server.socket_host": options['socket_host'],
- })
+ })
modname = options['testmod']
mod = __import__(modname, globals(), locals(), [''])
@@ -157,7 +160,6 @@ def cpmodpysetup(req):
"log.error_file": os.path.join(curdir, "test.log"),
"environment": "test_suite",
"server.socket_host": options['socket_host'],
- })
+ })
from mod_python import apache
return apache.OK
-
diff --git a/cherrypy/test/modwsgi.py b/cherrypy/test/modwsgi.py
index d3b873b7..043fb6e8 100644
--- a/cherrypy/test/modwsgi.py
+++ b/cherrypy/test/modwsgi.py
@@ -89,11 +89,12 @@ SetEnv testmod %(testmod)s
class ModWSGISupervisor(helper.Supervisor):
+
"""Server Controller for ModWSGI and CherryPy."""
using_apache = True
using_wsgi = True
- template=conf_modwsgi
+ template = conf_modwsgi
def __str__(self):
return "ModWSGI Server on %s:%s" % (self.host, self.port)
@@ -128,6 +129,8 @@ class ModWSGISupervisor(helper.Supervisor):
loaded = False
+
+
def application(environ, start_response):
import cherrypy
global loaded
@@ -143,6 +146,5 @@ def application(environ, start_response):
"environment": "test_suite",
"engine.SIGHUP": None,
"engine.SIGTERM": None,
- })
+ })
return cherrypy.tree(environ, start_response)
-
diff --git a/cherrypy/test/sessiondemo.py b/cherrypy/test/sessiondemo.py
index dd9b656e..f76b130a 100755
--- a/cherrypy/test/sessiondemo.py
+++ b/cherrypy/test/sessiondemo.py
@@ -95,15 +95,19 @@ function init() {
</body></html>
"""
+
class Root(object):
def page(self):
changemsg = []
if cherrypy.session.id != cherrypy.session.originalid:
if cherrypy.session.originalid is None:
- changemsg.append('Created new session because no session id was given.')
+ changemsg.append(
+ '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.')
+ changemsg.append(
+ 'Created new session due to missing '
+ '(expired or malicious) session.')
if cherrypy.session.regenerated:
changemsg.append('Application generated a new session.')
@@ -118,12 +122,14 @@ 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,
'expires': expires,
- }
+ }
def index(self):
# Must modify data or the session will not be saved.
@@ -148,6 +154,5 @@ if __name__ == '__main__':
#'environment': 'production',
'log.screen': True,
'tools.sessions.on': True,
- })
+ })
cherrypy.quickstart(Root())
-
diff --git a/cherrypy/test/static/404.html b/cherrypy/test/static/404.html
new file mode 100644
index 00000000..01b17b09
--- /dev/null
+++ b/cherrypy/test/static/404.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <h1>I couldn't find that thing you were looking for!</h1>
+ </body>
+</html>
diff --git a/cherrypy/test/test_auth_basic.py b/cherrypy/test/test_auth_basic.py
index 3a9781d8..7ba11dfc 100644
--- a/cherrypy/test/test_auth_basic.py
+++ b/cherrypy/test/test_auth_basic.py
@@ -12,34 +12,45 @@ class BasicAuthTest(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self):
return "This is public."
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
- userpassdict = {'xuser' : 'xpassword'}
- userhashdict = {'xuser' : md5(ntob('xpassword')).hexdigest()}
+ userpassdict = {'xuser': 'xpassword'}
+ userhashdict = {'xuser': md5(ntob('xpassword')).hexdigest()}
def checkpasshash(realm, user, password):
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()
@@ -58,10 +69,12 @@ class BasicAuthTest(helper.CPWebCase):
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="wonderland"')
- self.getPage('/basic/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
+ self.getPage('/basic/',
+ [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
self.assertStatus(401)
- self.getPage('/basic/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
+ self.getPage('/basic/',
+ [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
self.assertStatus('200 OK')
self.assertBody("Hello xuser, you've been authorized.")
@@ -70,10 +83,11 @@ class BasicAuthTest(helper.CPWebCase):
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="wonderland"')
- self.getPage('/basic2/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
+ self.getPage('/basic2/',
+ [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
self.assertStatus(401)
- self.getPage('/basic2/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
+ self.getPage('/basic2/',
+ [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
self.assertStatus('200 OK')
self.assertBody("Hello xuser, you've been authorized.")
-
diff --git a/cherrypy/test/test_auth_digest.py b/cherrypy/test/test_auth_digest.py
index 3cd570e4..b46698d9 100644
--- a/cherrypy/test/test_auth_digest.py
+++ b/cherrypy/test/test_auth_digest.py
@@ -8,23 +8,26 @@ from cherrypy.lib import auth_digest
from cherrypy.test import helper
+
class DigestAuthTest(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self):
return "This is public."
index.exposed = True
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():
return {'test': 'test'}
-
get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(fetch_users())
conf = {'/digest': {'tools.auth_digest.on': True,
'tools.auth_digest.realm': 'localhost',
@@ -55,7 +58,8 @@ class DigestAuthTest(helper.CPWebCase):
break
if value is None:
- self._handlewebError("Digest authentification scheme was not found")
+ self._handlewebError(
+ "Digest authentification scheme was not found")
value = value[7:]
items = value.split(', ')
@@ -70,7 +74,8 @@ class DigestAuthTest(helper.CPWebCase):
if 'realm' not in tokens:
self._handlewebError(missing_msg % 'realm')
elif tokens['realm'] != '"localhost"':
- self._handlewebError(bad_value_msg % ('realm', '"localhost"', tokens['realm']))
+ self._handlewebError(bad_value_msg %
+ ('realm', '"localhost"', tokens['realm']))
if 'nonce' not in tokens:
self._handlewebError(missing_msg % 'nonce')
else:
@@ -78,18 +83,29 @@ class DigestAuthTest(helper.CPWebCase):
if 'algorithm' not in tokens:
self._handlewebError(missing_msg % 'algorithm')
elif tokens['algorithm'] != '"MD5"':
- self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
+ self._handlewebError(bad_value_msg %
+ ('algorithm', '"MD5"', tokens['algorithm']))
if 'qop' not in tokens:
self._handlewebError(missing_msg % 'qop')
elif tokens['qop'] != '"auth"':
- self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
+ self._handlewebError(bad_value_msg %
+ ('qop', '"auth"', tokens['qop']))
- get_ha1 = auth_digest.get_ha1_dict_plain({'test' : 'test'})
+ 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"'
-
- auth_header = base_auth % (nonce, '11111111111111111111111111111111', '00000001')
+ 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')
auth = auth_digest.HttpDigestAuthorization(auth_header, 'GET')
# calculate the response digest
ha1 = get_ha1(auth.realm, 'test')
@@ -100,9 +116,18 @@ 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"'
-
- auth_header = base_auth % (nonce, '11111111111111111111111111111111', '00000001')
+ 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')
auth = auth_digest.HttpDigestAuthorization(auth_header, 'GET')
# calculate the response digest
ha1 = get_ha1('localhost', 'test')
@@ -112,4 +137,3 @@ class DigestAuthTest(helper.CPWebCase):
self.getPage('/digest/', [('Authorization', auth_header)])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")
-
diff --git a/cherrypy/test/test_bus.py b/cherrypy/test/test_bus.py
index 51c10220..06ad6acb 100644
--- a/cherrypy/test/test_bus.py
+++ b/cherrypy/test/test_bus.py
@@ -24,7 +24,8 @@ class PublishSubscribeTests(unittest.TestCase):
for channel in b.listeners:
for index, priority in enumerate([100, 50, 0, 51]):
- b.subscribe(channel, self.get_listener(channel, index), priority)
+ b.subscribe(channel,
+ self.get_listener(channel, index), priority)
for channel in b.listeners:
b.publish(channel)
@@ -42,11 +43,13 @@ class PublishSubscribeTests(unittest.TestCase):
custom_listeners = ('hugh', 'louis', 'dewey')
for channel in custom_listeners:
for index, priority in enumerate([None, 10, 60, 40]):
- b.subscribe(channel, self.get_listener(channel, index), priority)
+ b.subscribe(channel,
+ self.get_listener(channel, index), priority)
for channel in custom_listeners:
b.publish(channel, 'ah so')
- expected.extend([msg % (i, channel, 'ah so') for i in (1, 3, 0, 2)])
+ expected.extend([msg % (i, channel, 'ah so')
+ for i in (1, 3, 0, 2)])
b.publish(channel)
expected.extend([msg % (i, channel, None) for i in (1, 3, 0, 2)])
@@ -74,6 +77,7 @@ class BusMethodTests(unittest.TestCase):
def log(self, bus):
self._log_entries = []
+
def logit(msg, level):
self._log_entries.append(msg)
bus.subscribe('log', logit)
@@ -98,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)
@@ -140,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'])
@@ -165,7 +171,8 @@ class BusMethodTests(unittest.TestCase):
# The exit method MUST move the state to EXITING
self.assertEqual(b.state, b.states.EXITING)
# The exit method MUST log its states.
- self.assertLog(['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED'])
+ self.assertLog(
+ ['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED'])
def test_wait(self):
b = wspbus.Bus()
@@ -176,7 +183,8 @@ class BusMethodTests(unittest.TestCase):
for method, states in [('start', [b.states.STARTED]),
('stop', [b.states.STOPPED]),
- ('start', [b.states.STARTING, b.states.STARTED]),
+ ('start',
+ [b.states.STARTING, b.states.STARTED]),
('exit', [b.states.EXITING]),
]:
threading.Thread(target=f, args=(method,)).start()
@@ -193,6 +201,7 @@ class BusMethodTests(unittest.TestCase):
def f():
time.sleep(0.2)
b.exit()
+
def g():
time.sleep(0.4)
threading.Thread(target=f).start()
@@ -204,10 +213,12 @@ class BusMethodTests(unittest.TestCase):
# The block method MUST wait for the EXITING state.
self.assertEqual(b.state, b.states.EXITING)
- # The block method MUST wait for ALL non-main, non-daemon threads to finish.
+ # The block method MUST wait for ALL non-main, non-daemon threads to
+ # finish.
threads = [t for t in threading.enumerate() if not get_daemon(t)]
self.assertEqual(len(threads), 1)
- # The last message will mention an indeterminable thread name; ignore it
+ # The last message will mention an indeterminable thread name; ignore
+ # it
self.assertEqual(self._log_entries[:-1],
['Bus STOPPING', 'Bus STOPPED',
'Bus EXITING', 'Bus EXITED',
@@ -218,8 +229,10 @@ class BusMethodTests(unittest.TestCase):
self.log(b)
try:
events = []
+
def f(*args, **kwargs):
events.append(("f", args, kwargs))
+
def g():
events.append("g")
b.subscribe("start", g)
diff --git a/cherrypy/test/test_caching.py b/cherrypy/test/test_caching.py
index 725cf84c..be1950d6 100644
--- a/cherrypy/test/test_caching.py
+++ b/cherrypy/test/test_caching.py
@@ -12,14 +12,16 @@ 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
+
class CacheTest(helper.CPWebCase):
def setup_server():
@@ -45,7 +47,8 @@ class CacheTest(helper.CPWebCase):
control.exposed = True
def a_gif(self):
- cherrypy.response.headers['Last-Modified'] = httputil.HTTPDate()
+ cherrypy.response.headers[
+ 'Last-Modified'] = httputil.HTTPDate()
return gif_bytes
a_gif.exposed = True
@@ -64,10 +67,13 @@ class CacheTest(helper.CPWebCase):
class VaryHeaderCachingServer(object):
- _cp_config = {'tools.caching.on': True,
+ _cp_config = {
+ 'tools.caching.on': True,
'tools.response_headers.on': True,
- 'tools.response_headers.headers': [('Vary', 'Our-Varying-Header')],
- }
+ 'tools.response_headers.headers': [
+ ('Vary', 'Our-Varying-Header')
+ ],
+ }
def __init__(self):
self.counter = count(1)
@@ -104,15 +110,18 @@ class CacheTest(helper.CPWebCase):
cacheable.exposed = True
def specific(self):
- cherrypy.response.headers['Etag'] = 'need_this_to_make_me_cacheable'
+ cherrypy.response.headers[
+ 'Etag'] = 'need_this_to_make_me_cacheable'
return "I am being specific"
specific.exposed = True
specific._cp_config = {'tools.expires.secs': 86400}
- class Foo(object):pass
+ class Foo(object):
+ pass
def wrongtype(self):
- cherrypy.response.headers['Etag'] = 'need_this_to_make_me_cacheable'
+ cherrypy.response.headers[
+ 'Etag'] = 'need_this_to_make_me_cacheable'
return "Woops"
wrongtype.exposed = True
wrongtype._cp_config = {'tools.expires.secs': Foo()}
@@ -138,7 +147,8 @@ class CacheTest(helper.CPWebCase):
# POST, PUT, DELETE should not be cached.
self.getPage("/", method="POST")
self.assertBody('visit #2')
- # Because gzip is turned on, the Vary header should always Vary for content-encoding
+ # Because gzip is turned on, the Vary header should always Vary for
+ # content-encoding
self.assertHeader('Vary', 'Accept-Encoding')
# The previous request should have invalidated the cache,
# so this request will recalc the response.
@@ -155,14 +165,16 @@ class CacheTest(helper.CPWebCase):
self.getPage("/", method="GET", headers=[('Accept-Encoding', 'gzip')])
self.assertHeader('Content-Encoding', 'gzip')
self.assertHeader('Vary')
- self.assertEqual(cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
+ self.assertEqual(
+ cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
# Now check that a second request gets the gzip header and gzipped body
# This also tests a bug in 3.0 to 3.0.2 whereby the cached, gzipped
# response body was being gzipped a second time.
self.getPage("/", method="GET", headers=[('Accept-Encoding', 'gzip')])
self.assertHeader('Content-Encoding', 'gzip')
- self.assertEqual(cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
+ self.assertEqual(
+ cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
# Now check that a third request that doesn't accept gzip
# skips the cache (because the 'Vary' header denies it).
@@ -179,11 +191,13 @@ class CacheTest(helper.CPWebCase):
# Now check that different 'Vary'-fields don't evict each other.
# This test creates 2 requests with different 'Our-Varying-Header'
# and then tests if the first one still exists.
- self.getPage("/varying_headers/", headers=[('Our-Varying-Header', 'request 2')])
+ self.getPage("/varying_headers/",
+ headers=[('Our-Varying-Header', 'request 2')])
self.assertStatus("200 OK")
self.assertBody('visit #2')
- self.getPage("/varying_headers/", headers=[('Our-Varying-Header', 'request 2')])
+ self.getPage("/varying_headers/",
+ headers=[('Our-Varying-Header', 'request 2')])
self.assertStatus("200 OK")
self.assertBody('visit #2')
@@ -286,10 +300,11 @@ class CacheTest(helper.CPWebCase):
self.getPage("/long_process?seconds=%d" % SECONDS)
self.assertBody('success!')
self.getPage("/clear_cache?path=" +
- quote('/long_process?seconds=%d' % SECONDS, safe=''))
+ quote('/long_process?seconds=%d' % SECONDS, safe=''))
self.assertStatus(200)
start = datetime.datetime.now()
+
def run():
self.getPage("/long_process?seconds=%d" % SECONDS)
# The response should be the same every time
@@ -325,4 +340,3 @@ class CacheTest(helper.CPWebCase):
self.assertBody('visit #4')
self.getPage("/control")
self.assertBody('visit #4')
-
diff --git a/cherrypy/test/test_compat.py b/cherrypy/test/test_compat.py
index d3eeeb1d..62cb3b4f 100644
--- a/cherrypy/test/test_compat.py
+++ b/cherrypy/test/test_compat.py
@@ -4,7 +4,9 @@ import nose
from cherrypy import _cpcompat as compat
+
class StringTester(unittest.TestCase):
+
def test_ntob_non_native(self):
"""
ntob should raise an Exception on unicode.
diff --git a/cherrypy/test/test_config.py b/cherrypy/test/test_config.py
index db3a55f0..f9831b12 100644
--- a/cherrypy/test/test_config.py
+++ b/cherrypy/test/test_config.py
@@ -1,12 +1,14 @@
"""Tests for the CherryPy configuration system."""
-import os, sys
-localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
-
-from cherrypy._cpcompat import ntob, StringIO
+import os
+import sys
import unittest
import cherrypy
+import cherrypy._cpcompat as compat
+
+localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
+
def setup_server():
@@ -41,7 +43,7 @@ def setup_server():
plain._cp_config = {'request.body.attempt_charsets': ['utf-16']}
favicon_ico = cherrypy.tools.staticfile.handler(
- filename=os.path.join(localDir, '../favicon.ico'))
+ filename=os.path.join(localDir, '../favicon.ico'))
class Foo:
@@ -72,10 +74,10 @@ def setup_server():
return str(cherrypy.request.config.get(key, "None"))
index.exposed = True
-
def raw_namespace(key, value):
if key == 'input.map':
handler = cherrypy.request.handler
+
def wrapper():
params = cherrypy.request.params
for name, coercer in list(value.items()):
@@ -87,6 +89,7 @@ def setup_server():
cherrypy.request.handler = wrapper
elif key == 'output':
handler = cherrypy.request.handler
+
def wrapper():
# 'value' is a type (like int or str).
return value(handler())
@@ -101,12 +104,18 @@ def setup_server():
incr.exposed = True
incr._cp_config = {'raw.input.map': {'num': int}}
- ioconf = StringIO("""
+ if not compat.py3k:
+ thing3 = "thing3: unicode('test', errors='ignore')"
+ else:
+ thing3 = ''
+
+ ioconf = compat.StringIO("""
[/]
neg: -1234
filename: os.path.join(sys.prefix, "hello.py")
thing1: cherrypy.lib.httputil.response_codes[404]
thing2: __import__('cherrypy.tutorial', globals(), locals(), ['']).thing2
+%s
complex: 3+2j
mul: 6*3
ones: "11"
@@ -115,7 +124,7 @@ stradd: %%(ones)s + %%(twos)s + "33"
[/favicon.ico]
tools.staticfile.filename = %r
-""" % os.path.join(localDir, 'static/dirback.jpg'))
+""" % (thing3, os.path.join(localDir, 'static/dirback.jpg')))
root = Root()
root.foo = Foo()
@@ -133,6 +142,7 @@ tools.staticfile.filename = %r
from cherrypy.test import helper
+
class ConfigTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -147,8 +157,9 @@ class ConfigTests(helper.CPWebCase):
('/foo/', 'bax', 'None'),
('/foo/bar', 'baz', "'that2'"),
('/foo/nex', 'baz', 'that2'),
- # If 'foo' == 'this', then the mount point '/another' leaks into '/'.
- ('/another/','foo', 'None'),
+ # If 'foo' == 'this', then the mount point '/another' leaks into
+ # '/'.
+ ('/another/', 'foo', 'None'),
]
for path, key, expected in tests:
self.getPage(path + "?key=" + key)
@@ -171,7 +182,7 @@ class ConfigTests(helper.CPWebCase):
# From Foo.bar._cp_config
'foo': 'this3',
'bax': 'this4',
- }
+ }
for key, expected in expectedconf.items():
self.getPage("/foo/bar?key=" + key)
self.assertBody(repr(expected))
@@ -193,6 +204,10 @@ class ConfigTests(helper.CPWebCase):
from cherrypy.tutorial import thing2
self.assertBody(repr(thing2))
+ if not compat.py3k:
+ self.getPage("/repr?key=thing3")
+ self.assertBody(repr(unicode('test')))
+
self.getPage("/repr?key=complex")
self.assertBody("(3+2j)")
@@ -226,7 +241,7 @@ class ConfigTests(helper.CPWebCase):
self.getPage("/plain", method='POST', headers=[
('Content-Type', 'application/x-www-form-urlencoded'),
('Content-Length', '13')],
- body=ntob('\xff\xfex\x00=\xff\xfea\x00b\x00c\x00'))
+ body=compat.ntob('\xff\xfex\x00=\xff\xfea\x00b\x00c\x00'))
self.assertBody("abc")
@@ -248,9 +263,9 @@ class VariableSubstitutionTests(unittest.TestCase):
""")
- fp = StringIO(conf)
+ fp = compat.StringIO(conf)
cherrypy.config.update(fp)
self.assertEqual(cherrypy.config["my"]["my.dir"], "/some/dir/my/dir")
- self.assertEqual(cherrypy.config["my"]["my.dir2"], "/some/dir/my/dir/dir2")
-
+ self.assertEqual(cherrypy.config["my"]
+ ["my.dir2"], "/some/dir/my/dir/dir2")
diff --git a/cherrypy/test/test_config_server.py b/cherrypy/test/test_config_server.py
index 489904fa..40504d8f 100644
--- a/cherrypy/test/test_config_server.py
+++ b/cherrypy/test/test_config_server.py
@@ -1,6 +1,7 @@
"""Tests for the CherryPy configuration system."""
-import os, sys
+import os
+import sys
localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import socket
import time
@@ -12,11 +13,13 @@ import cherrypy
from cherrypy.test import helper
+
class ServerConfigTests(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self):
return cherrypy.request.wsgi_environ['SERVER_PORT']
index.exposed = True
@@ -46,7 +49,7 @@ class ServerConfigTests(helper.CPWebCase):
# Test non-numeric <servername>
# Also test default server.instance = builtin server
'server.yetanother.socket_port': 9878,
- })
+ })
setup_server = staticmethod(setup_server)
PORT = 9876
@@ -99,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',
@@ -118,4 +126,3 @@ class ServerConfigTests(helper.CPWebCase):
("Content-Length", "%s" % len(b))]
self.getPage('/upload', h, "POST", b)
self.assertStatus(413)
-
diff --git a/cherrypy/test/test_conn.py b/cherrypy/test/test_conn.py
index 8f2512b7..502a3ab3 100644
--- a/cherrypy/test/test_conn.py
+++ b/cherrypy/test/test_conn.py
@@ -1,5 +1,6 @@
"""Tests for TCP connection handling, including proper and timely close."""
+import httplib
import socket
import sys
import time
@@ -7,14 +8,15 @@ 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, tonative, urlopen, unicodestr
from cherrypy.test import webtest
from cherrypy import _cperror
pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN'
+
def setup_server():
def raise500():
@@ -93,11 +95,12 @@ def setup_server():
cherrypy.config.update({
'server.max_request_body_size': 1001,
'server.socket_timeout': timeout,
- })
+ })
from cherrypy.test import helper
+
class ConnectionCloseTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -131,10 +134,22 @@ class ConnectionCloseTests(helper.CPWebCase):
self.assertRaises(NotConnected, self.getPage, "/")
def test_Streaming_no_len(self):
- self._streaming(set_cl=False)
+ try:
+ self._streaming(set_cl=False)
+ finally:
+ try:
+ self.HTTP_CONN.close()
+ except (TypeError, AttributeError):
+ pass
def test_Streaming_with_len(self):
- self._streaming(set_cl=True)
+ try:
+ self._streaming(set_cl=True)
+ finally:
+ try:
+ self.HTTP_CONN.close()
+ except (TypeError, AttributeError):
+ pass
def _streaming(self, set_cl):
if cherrypy.server.protocol_version == "HTTP/1.1":
@@ -179,10 +194,12 @@ class ConnectionCloseTests(helper.CPWebCase):
else:
self.assertHeader("Connection", "close")
- # Make another request on the same connection, which should error.
+ # Make another request on the same connection, which should
+ # error.
self.assertRaises(NotConnected, self.getPage, "/")
- # Try HEAD. See https://bitbucket.org/cherrypy/cherrypy/issue/864.
+ # Try HEAD. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/864.
self.getPage("/stream", method='HEAD')
self.assertStatus('200 OK')
self.assertBody('')
@@ -220,7 +237,8 @@ class ConnectionCloseTests(helper.CPWebCase):
self.assertNoHeader("Connection", "Keep-Alive")
self.assertNoHeader("Transfer-Encoding")
- # Make another request on the same connection, which should error.
+ # Make another request on the same connection, which should
+ # error.
self.assertRaises(NotConnected, self.getPage, "/")
def test_HTTP10_KeepAlive(self):
@@ -235,7 +253,7 @@ class ConnectionCloseTests(helper.CPWebCase):
self.assertStatus('200 OK')
self.assertBody(pov)
# Apache, for example, may emit a Connection header even for HTTP/1.0
-## self.assertNoHeader("Connection")
+# self.assertNoHeader("Connection")
# Test a keep-alive HTTP/1.0 request.
self.persistent = True
@@ -250,7 +268,7 @@ class ConnectionCloseTests(helper.CPWebCase):
self.assertStatus('200 OK')
self.assertBody(pov)
# Apache, for example, may emit a Connection header even for HTTP/1.0
-## self.assertNoHeader("Connection")
+# self.assertNoHeader("Connection")
class PipelineTests(helper.CPWebCase):
@@ -361,7 +379,6 @@ class PipelineTests(helper.CPWebCase):
self.body = response.read()
self.assertBody(pov)
-
# Make another request on the same socket,
# but timeout on the headers
conn.send(ntob('GET /hello HTTP/1.1'))
@@ -444,47 +461,53 @@ class PipelineTests(helper.CPWebCase):
# Try a page without an Expect request header first.
# Note that httplib's response.begin automatically ignores
# 100 Continue responses, so we must manually check for it.
- conn.putrequest("POST", "/upload", skip_host=True)
- conn.putheader("Host", self.HOST)
- conn.putheader("Content-Type", "text/plain")
- conn.putheader("Content-Length", "4")
- conn.endheaders()
- conn.send(ntob("d'oh"))
- response = conn.response_class(conn.sock, method="POST")
- version, status, reason = response._read_status()
- self.assertNotEqual(status, 100)
- conn.close()
+ try:
+ conn.putrequest("POST", "/upload", skip_host=True)
+ conn.putheader("Host", self.HOST)
+ conn.putheader("Content-Type", "text/plain")
+ conn.putheader("Content-Length", "4")
+ conn.endheaders()
+ conn.send(ntob("d'oh"))
+ response = conn.response_class(conn.sock, method="POST")
+ version, status, reason = response._read_status()
+ self.assertNotEqual(status, 100)
+ finally:
+ conn.close()
# Now try a page with an Expect header...
- conn.connect()
- conn.putrequest("POST", "/upload", skip_host=True)
- conn.putheader("Host", self.HOST)
- conn.putheader("Content-Type", "text/plain")
- conn.putheader("Content-Length", "17")
- conn.putheader("Expect", "100-continue")
- conn.endheaders()
- response = conn.response_class(conn.sock, method="POST")
-
- # ...assert and then skip the 100 response
- version, status, reason = response._read_status()
- self.assertEqual(status, 100)
- while True:
- line = response.fp.readline().strip()
- if line:
- self.fail("100 Continue should not output any headers. Got %r" % line)
- else:
- break
+ try:
+ conn.connect()
+ conn.putrequest("POST", "/upload", skip_host=True)
+ conn.putheader("Host", self.HOST)
+ conn.putheader("Content-Type", "text/plain")
+ conn.putheader("Content-Length", "17")
+ conn.putheader("Expect", "100-continue")
+ conn.endheaders()
+ response = conn.response_class(conn.sock, method="POST")
- # ...send the body
- body = ntob("I am a small file")
- conn.send(body)
+ # ...assert and then skip the 100 response
+ version, status, reason = response._read_status()
+ self.assertEqual(status, 100)
+ while True:
+ line = response.fp.readline().strip()
+ if line:
+ self.fail(
+ "100 Continue should not output any headers. Got %r" %
+ line)
+ else:
+ break
- # ...get the final response
- response.begin()
- self.status, self.headers, self.body = webtest.shb(response)
- self.assertStatus(200)
- self.assertBody("thanks for '%s'" % body)
- conn.close()
+ # ...send the body
+ body = ntob("I am a small file")
+ conn.send(body)
+
+ # ...get the final response
+ response.begin()
+ self.status, self.headers, self.body = webtest.shb(response)
+ self.assertStatus(200)
+ self.assertBody("thanks for '%s'" % body)
+ finally:
+ conn.close()
class ConnectionTests(helper.CPWebCase):
@@ -596,7 +619,7 @@ class ConnectionTests(helper.CPWebCase):
return self.skip()
if (hasattr(self, 'harness') and
- "modpython" in self.harness.__class__.__name__.lower()):
+ "modpython" in self.harness.__class__.__name__.lower()):
# mod_python forbids chunked encoding
return self.skip()
@@ -608,8 +631,8 @@ class ConnectionTests(helper.CPWebCase):
# Try a normal chunked request (with extensions)
body = ntob("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n"
- "Content-Type: application/json\r\n"
- "\r\n")
+ "Content-Type: application/json\r\n"
+ "\r\n")
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Transfer-Encoding", "chunked")
@@ -680,8 +703,9 @@ class ConnectionTests(helper.CPWebCase):
# the actual bytes in the response body.
self.persistent = True
conn = self.HTTP_CONN
- conn.putrequest("GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
- skip_host=True)
+ conn.putrequest(
+ "GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
+ skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.getresponse()
@@ -692,7 +716,7 @@ class ConnectionTests(helper.CPWebCase):
def test_598(self):
remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
- (self.scheme, self.HOST, self.PORT,))
+ (self.scheme, self.HOST, self.PORT,))
buf = remote_data_conn.read(512)
time.sleep(timeout * 0.6)
remaining = (1024 * 1024) - 512
@@ -710,6 +734,92 @@ class ConnectionTests(helper.CPWebCase):
remote_data_conn.close()
+def setup_upload_server():
+
+ class Root:
+ def upload(self):
+ if not cherrypy.request.method == 'POST':
+ raise AssertionError("'POST' != request.method %r" %
+ cherrypy.request.method)
+ return "thanks for '%s'" % tonative(cherrypy.request.body.read())
+ upload.exposed = True
+
+ cherrypy.tree.mount(Root())
+ cherrypy.config.update({
+ 'server.max_request_body_size': 1001,
+ 'server.socket_timeout': 10,
+ 'server.accepted_queue_size': 5,
+ 'server.accepted_queue_timeout': 0.1,
+ })
+
+import errno
+socket_reset_errors = []
+# Not all of these names will be defined for every platform.
+for _ in ("ECONNRESET", "WSAECONNRESET"):
+ if _ in dir(errno):
+ socket_reset_errors.append(getattr(errno, _))
+
+class LimitedRequestQueueTests(helper.CPWebCase):
+ setup_server = staticmethod(setup_upload_server)
+
+ def test_queue_full(self):
+ conns = []
+ overflow_conn = None
+
+ try:
+ # Make 15 initial requests and leave them open, which should use
+ # all of wsgiserver's WorkerThreads and fill its Queue.
+ import time
+ for i in range(15):
+ conn = self.HTTP_CONN(self.HOST, self.PORT)
+ conn.putrequest("POST", "/upload", skip_host=True)
+ conn.putheader("Host", self.HOST)
+ conn.putheader("Content-Type", "text/plain")
+ conn.putheader("Content-Length", "4")
+ conn.endheaders()
+ conns.append(conn)
+
+ # Now try a 16th conn, which should be closed by the server immediately.
+ overflow_conn = self.HTTP_CONN(self.HOST, self.PORT)
+ # Manually connect since httplib won't let us set a timeout
+ for res in socket.getaddrinfo(self.HOST, self.PORT, 0,
+ socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ overflow_conn.sock = socket.socket(af, socktype, proto)
+ overflow_conn.sock.settimeout(5)
+ overflow_conn.sock.connect(sa)
+ break
+
+ overflow_conn.putrequest("GET", "/", skip_host=True)
+ overflow_conn.putheader("Host", self.HOST)
+ overflow_conn.endheaders()
+ response = overflow_conn.response_class(overflow_conn.sock, method="GET")
+ try:
+ response.begin()
+ except socket.error as exc:
+ if exc.args[0] in socket_reset_errors:
+ pass # Expected.
+ else:
+ raise AssertionError("Overflow conn did not get RST. "
+ "Got %s instead" % repr(exc.args))
+ except httplib.BadStatusLine:
+ # This is a special case in OS X. Linux and Windows will
+ # RST correctly.
+ assert sys.platform == 'darwin'
+ else:
+ raise AssertionError("Overflow conn did not get RST ")
+ finally:
+ for conn in conns:
+ conn.send(ntob("done"))
+ response = conn.response_class(conn.sock, method="POST")
+ response.begin()
+ self.body = response.read()
+ self.assertBody("thanks for 'done'")
+ self.assertEqual(response.status, 200)
+ conn.close()
+ if overflow_conn:
+ overflow_conn.close()
+
class BadRequestTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -731,4 +841,3 @@ class BadRequestTests(helper.CPWebCase):
self.body = response.read()
self.assertBody("HTTP requires CRLF terminators")
conn.close()
-
diff --git a/cherrypy/test/test_core.py b/cherrypy/test/test_core.py
index 9dfd990d..ae4728de 100644
--- a/cherrypy/test/test_core.py
+++ b/cherrypy/test/test_core.py
@@ -17,6 +17,7 @@ favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico")
from cherrypy.test import helper
+
class CoreRequestHandlingTest(helper.CPWebCase):
def setup_server():
@@ -45,10 +46,11 @@ class CoreRequestHandlingTest(helper.CPWebCase):
from cherrypy.test._test_decorators import ExposeExamples
root.expose_dec = ExposeExamples()
-
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)
@@ -58,7 +60,6 @@ class CoreRequestHandlingTest(helper.CPWebCase):
setattr(root, name.lower(), cls())
Test = TestType('Test', (object, ), {})
-
class URL(Test):
_cp_config = {'tools.trailing_slash.on': False}
@@ -73,11 +74,10 @@ class CoreRequestHandlingTest(helper.CPWebCase):
relative = bool(relative)
return cherrypy.url(path_info, relative=relative)
-
def log_status():
Status.statuses.append(cherrypy.response.status)
- cherrypy.tools.log_status = cherrypy.Tool('on_end_resource', log_status)
-
+ cherrypy.tools.log_status = cherrypy.Tool(
+ 'on_end_resource', log_status)
class Status(Test):
@@ -106,11 +106,11 @@ class CoreRequestHandlingTest(helper.CPWebCase):
return "bad news"
statuses = []
+
def on_end_resource_stage(self):
return repr(self.statuses)
on_end_resource_stage._cp_config = {'tools.log_status.on': True}
-
class Redirect(Test):
class Error:
@@ -146,6 +146,9 @@ class CoreRequestHandlingTest(helper.CPWebCase):
def fragment(self, frag):
raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
+ def url_with_quote(self):
+ raise cherrypy.HTTPRedirect("/some\"url/that'we/want")
+
def login_redir():
if not getattr(cherrypy.request, "login", None):
raise cherrypy.InternalRedirect("/internalredirect/login")
@@ -174,16 +177,19 @@ class CoreRequestHandlingTest(helper.CPWebCase):
def petshop(self, user_id):
if user_id == "parrot":
# Trade it for a slug when redirecting
- raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug')
+ raise cherrypy.InternalRedirect(
+ '/image/getImagesByUser?user_id=slug')
elif user_id == "terrier":
# Trade it for a fish when redirecting
- raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish')
+ raise cherrypy.InternalRedirect(
+ '/image/getImagesByUser?user_id=fish')
else:
# This should pass the user_id through to getImagesByUser
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!"
@@ -202,13 +208,11 @@ class CoreRequestHandlingTest(helper.CPWebCase):
return "whatever"
early_ir._cp_config = {'hooks.before_request_body': redir_custom}
-
class Image(Test):
def getImagesByUser(self, user_id):
return "0 images for %s" % user_id
-
class Flatten(Test):
def as_string(self):
@@ -228,7 +232,6 @@ class CoreRequestHandlingTest(helper.CPWebCase):
for chunk in self.as_yield():
yield chunk
-
class Ranges(Test):
def get_ranges(self, bytes):
@@ -236,8 +239,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):
@@ -249,7 +252,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
def multiple(self, names):
for name in names:
cookie = cherrypy.request.cookie[name]
- # Python2's SimpleCookie.__setitem__ won't take unicode keys.
+ # Python2's SimpleCookie.__setitem__ won't take unicode
+ # keys.
cherrypy.response.cookie[str(name)] = cookie.value
def append_headers(header_list, debug=False):
@@ -258,7 +262,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
"Extending response headers with %s" % repr(header_list),
"TOOLS.APPEND_HEADERS")
cherrypy.serving.response.header_list.extend(header_list)
- cherrypy.tools.append_headers = cherrypy.Tool('on_end_resource', append_headers)
+ cherrypy.tools.append_headers = cherrypy.Tool(
+ 'on_end_resource', append_headers)
class MultiHeader(Test):
@@ -267,16 +272,15 @@ class CoreRequestHandlingTest(helper.CPWebCase):
header_list = cherrypy.tools.append_headers(header_list=[
(ntob('WWW-Authenticate'), ntob('Negotiate')),
(ntob('WWW-Authenticate'), ntob('Basic realm="foo"')),
- ])(header_list)
+ ])(header_list)
def commas(self):
- cherrypy.response.headers['WWW-Authenticate'] = 'Negotiate,Basic realm="foo"'
-
+ cherrypy.response.headers[
+ 'WWW-Authenticate'] = 'Negotiate,Basic realm="foo"'
cherrypy.tree.mount(root)
setup_server = staticmethod(setup_server)
-
def testStatus(self):
self.getPage("/status/")
self.assertBody('normal')
@@ -313,15 +317,15 @@ class CoreRequestHandlingTest(helper.CPWebCase):
# Make sure GET params are preserved.
self.getPage("/redirect?id=3")
self.assertStatus(301)
- self.assertInBody("<a href='%s/redirect/?id=3'>"
- "%s/redirect/?id=3</a>" % (self.base(), self.base()))
+ self.assertMatchesBody('<a href=([\'"])%s/redirect/[?]id=3\\1>'
+ "%s/redirect/[?]id=3</a>" % (self.base(), self.base()))
if self.prefix():
# Corner case: the "trailing slash" redirect could be tricky if
# we're using a virtual root and the URI is "/vroot" (no slash).
self.getPage("")
self.assertStatus(301)
- self.assertInBody("<a href='%s/'>%s/</a>" %
+ self.assertMatchesBody("<a href=(['\"])%s/\\1>%s/</a>" %
(self.base(), self.base()))
# Test that requests for NON-index methods WITH a trailing slash
@@ -329,8 +333,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
# Make sure GET params are preserved.
self.getPage("/redirect/by_code/?code=307")
self.assertStatus(301)
- self.assertInBody("<a href='%s/redirect/by_code?code=307'>"
- "%s/redirect/by_code?code=307</a>"
+ self.assertMatchesBody("<a href=(['\"])%s/redirect/by_code[?]code=307\\1>"
+ "%s/redirect/by_code[?]code=307</a>"
% (self.base(), self.base()))
# If the trailing_slash tool is off, CP should just continue
@@ -347,23 +351,28 @@ class CoreRequestHandlingTest(helper.CPWebCase):
self.assertStatus(200)
self.getPage("/redirect/by_code?code=300")
- self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
self.assertStatus(300)
self.getPage("/redirect/by_code?code=301")
- self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
self.assertStatus(301)
self.getPage("/redirect/by_code?code=302")
- self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
self.assertStatus(302)
self.getPage("/redirect/by_code?code=303")
- self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
self.assertStatus(303)
self.getPage("/redirect/by_code?code=307")
- self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
self.assertStatus(307)
self.getPage("/redirect/nomodify")
@@ -392,19 +401,42 @@ class CoreRequestHandlingTest(helper.CPWebCase):
# http://skrb.org/ietf/http_errata.html#location-fragments
frag = "foo"
self.getPage("/redirect/fragment/%s" % frag)
- self.assertMatchesBody(r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag))
+ self.assertMatchesBody(
+ r"<a href=(['\"])(.*)\/some\/url\#%s\1>\2\/some\/url\#%s</a>" % (
+ frag, frag))
loc = self.assertHeader('Location')
assert loc.endswith("#%s" % frag)
self.assertStatus(('302 Found', '303 See Other'))
# check injection protection
# See https://bitbucket.org/cherrypy/cherrypy/issue/1003
- self.getPage("/redirect/custom?code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval")
+ self.getPage(
+ "/redirect/custom?"
+ "code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval")
self.assertStatus(303)
loc = self.assertHeader('Location')
assert 'Set-Cookie' in loc
self.assertNoHeader('Set-Cookie')
+ def assertValidXHTML():
+ from xml.etree import ElementTree
+ try:
+ ElementTree.fromstring('<html><body>%s</body></html>' % self.body)
+ except ElementTree.ParseError as e:
+ self._handlewebError('automatically generated redirect '
+ 'did not generate well-formed html')
+
+ # check redirects to URLs generated valid HTML - we check this
+ # by seeing if it appears as valid XHTML.
+ self.getPage("/redirect/by_code?code=303")
+ self.assertStatus(303)
+ assertValidXHTML()
+
+ # do the same with a url containing quote characters.
+ self.getPage("/redirect/url_with_quote")
+ self.assertStatus(303)
+ assertValidXHTML()
+
def test_InternalRedirect(self):
# InternalRedirect
self.getPage("/internalredirect/")
@@ -412,7 +444,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
self.assertStatus(200)
# Test passthrough
- self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film")
+ self.getPage(
+ "/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film")
self.assertBody('0 images for Sir-not-appearing-in-this-film')
self.assertStatus(200)
@@ -463,6 +496,12 @@ class CoreRequestHandlingTest(helper.CPWebCase):
self.getPage("/ranges/get_ranges?bytes=2-4,-1")
self.assertBody("[(2, 5), (7, 8)]")
+ # Test a suffix-byte-range longer than the content
+ # length. Note that in this test, the content length
+ # is 8 bytes.
+ self.getPage("/ranges/get_ranges?bytes=-100")
+ self.assertBody("[(0, 8)]")
+
# Get a partial file.
if cherrypy.server.protocol_version == "HTTP/1.1":
self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
@@ -519,7 +558,7 @@ class CoreRequestHandlingTest(helper.CPWebCase):
if sys.version_info >= (2, 5):
header_value = lambda x: x
else:
- header_value = lambda x: x+';'
+ header_value = lambda x: x + ';'
self.getPage("/cookies/single?name=First",
[('Cookie', 'First=Dinsdale;')])
@@ -532,7 +571,7 @@ class CoreRequestHandlingTest(helper.CPWebCase):
self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
self.getPage("/cookies/single?name=Something-With%2CComma",
- [('Cookie', 'Something-With,Comma=some-value')])
+ [('Cookie', 'Something-With,Comma=some-value')])
self.assertStatus(400)
def testDefaultContentType(self):
@@ -545,10 +584,11 @@ class CoreRequestHandlingTest(helper.CPWebCase):
def test_multiple_headers(self):
self.getPage('/multiheader/header_list')
- self.assertEqual([(k, v) for k, v in self.headers if k == 'WWW-Authenticate'],
- [('WWW-Authenticate', 'Negotiate'),
- ('WWW-Authenticate', 'Basic realm="foo"'),
- ])
+ self.assertEqual(
+ [(k, v) for k, v in self.headers if k == 'WWW-Authenticate'],
+ [('WWW-Authenticate', 'Negotiate'),
+ ('WWW-Authenticate', 'Basic realm="foo"'),
+ ])
self.getPage('/multiheader/commas')
self.assertHeader('WWW-Authenticate', 'Negotiate,Basic realm="foo"')
@@ -666,9 +706,11 @@ class ErrorTests(helper.CPWebCase):
def break_header():
# Add a header after finalize that is invalid
cherrypy.serving.response.header_list.append((2, 3))
- cherrypy.tools.break_header = cherrypy.Tool('on_end_resource', break_header)
+ cherrypy.tools.break_header = cherrypy.Tool(
+ 'on_end_resource', break_header)
class Root:
+
def index(self):
return "hello"
index.exposed = True
@@ -684,5 +726,5 @@ class ErrorTests(helper.CPWebCase):
def test_start_response_error(self):
self.getPage("/start_response_error")
self.assertStatus(500)
- self.assertInBody("TypeError: response.header_list key 2 is not a byte string.")
-
+ self.assertInBody(
+ "TypeError: response.header_list key 2 is not a byte string.")
diff --git a/cherrypy/test/test_dynamicobjectmapping.py b/cherrypy/test/test_dynamicobjectmapping.py
index 0bd128f8..a64e9925 100644
--- a/cherrypy/test/test_dynamicobjectmapping.py
+++ b/cherrypy/test/test_dynamicobjectmapping.py
@@ -6,9 +6,9 @@ from cherrypy.test import helper
script_names = ["", "/foo", "/users/fred/blog", "/corp/blog"]
-
def setup_server():
class SubSubRoot:
+
def index(self):
return "SubSubRoot index"
index.exposed = True
@@ -31,6 +31,7 @@ def setup_server():
}
class SubRoot:
+
def index(self):
return "SubRoot index"
index.exposed = True
@@ -50,7 +51,9 @@ def setup_server():
'1': SubRoot(),
'2': SubRoot(),
}
+
class Root:
+
def index(self):
return "index"
index.exposed = True
@@ -70,12 +73,14 @@ def setup_server():
# DynamicNodeAndMethodDispatcher example.
# This example exposes a fairly naive HTTP api
class User(object):
+
def __init__(self, id, name):
self.id = id
self.name = name
def __unicode__(self):
return unicode(self.name)
+
def __str__(self):
return str(self.name)
@@ -111,6 +116,7 @@ def setup_server():
class UserInstanceNode(object):
exposed = True
+
def __init__(self, id):
self.id = id
self.user = user_lookup.get(id, None)
@@ -135,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
@@ -154,9 +161,10 @@ def setup_server():
del self.user
return "DELETE %d" % id
-
class ABHandler:
+
class CustomDispatch:
+
def index(self, a, b):
return "custom"
index.exposed = True
@@ -168,7 +176,7 @@ def setup_server():
return self.CustomDispatch()
def index(self, a, b=None):
- body = [ 'a:' + str(a) ]
+ body = ['a:' + str(a)]
if b is not None:
body.append(',b:' + str(b))
return ''.join(body)
@@ -179,6 +187,7 @@ def setup_server():
delete.exposed = True
class IndexOnly:
+
def _cp_dispatch(self, vpath):
"""Make sure that popping ALL of vpath still shows the index
handler.
@@ -192,7 +201,9 @@ def setup_server():
index.exposed = True
class DecoratedPopArgs:
+
"""Test _cp_dispatch with @cherrypy.popargs."""
+
def index(self):
return "no params"
index.exposed = True
@@ -200,9 +211,11 @@ def setup_server():
def hi(self):
return "hi was not interpreted as 'a' param"
hi.exposed = True
- DecoratedPopArgs = cherrypy.popargs('a', 'b', handler=ABHandler())(DecoratedPopArgs)
+ DecoratedPopArgs = cherrypy.popargs(
+ 'a', 'b', handler=ABHandler())(DecoratedPopArgs)
class NonDecoratedPopArgs:
+
"""Test _cp_dispatch = cherrypy.popargs()"""
_cp_dispatch = cherrypy.popargs('a')
@@ -212,6 +225,7 @@ def setup_server():
index.exposed = True
class ParameterizedHandler:
+
"""Special handler created for each request"""
def __init__(self, a):
@@ -219,13 +233,17 @@ def setup_server():
def index(self):
if 'a' in cherrypy.request.params:
- raise Exception("Parameterized handler argument ended up in request.params")
+ raise Exception(
+ "Parameterized handler argument ended up in "
+ "request.params")
return self.a
index.exposed = True
class ParameterizedPopArgs:
+
"""Test cherrypy.popargs() with a function call handler"""
- ParameterizedPopArgs = cherrypy.popargs('a', handler=ParameterizedHandler)(ParameterizedPopArgs)
+ ParameterizedPopArgs = cherrypy.popargs(
+ 'a', handler=ParameterizedHandler)(ParameterizedPopArgs)
Root.decorated = DecoratedPopArgs()
Root.undecorated = NonDecoratedPopArgs()
@@ -237,14 +255,15 @@ def setup_server():
md = cherrypy.dispatch.MethodDispatcher('dynamic_dispatch')
for url in script_names:
conf = {'/': {
- 'user': (url or "/").split("/")[-2],
- },
- '/users': {
- 'request.dispatch': md
- },
- }
+ 'user': (url or "/").split("/")[-2],
+ },
+ '/users': {
+ 'request.dispatch': md
+ },
+ }
cherrypy.tree.mount(Root(), url, conf)
+
class DynamicObjectMappingTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -354,12 +373,14 @@ class DynamicObjectMappingTest(helper.CPWebCase):
self.assertHeader('Allow', headers)
# Make sure POSTs update already existings resources
- self.getPage("/users/%d" % id, method='POST', body="name=%s" % updatedname)
+ self.getPage("/users/%d" %
+ id, method='POST', body="name=%s" % updatedname)
self.assertBody("POST %d" % id)
self.assertHeader('Allow', headers)
# Make sure PUTs Update already existing resources.
- self.getPage("/users/%d" % id, method='PUT', body="name=%s" % updatedname)
+ self.getPage("/users/%d" %
+ id, method='PUT', body="name=%s" % updatedname)
self.assertBody("PUT %d" % id)
self.assertHeader('Allow', headers)
@@ -368,7 +389,6 @@ class DynamicObjectMappingTest(helper.CPWebCase):
self.assertBody("DELETE %d" % id)
self.assertHeader('Allow', headers)
-
# GET acts like a container
self.getPage("/users")
self.assertBody("[]")
@@ -401,4 +421,3 @@ class DynamicObjectMappingTest(helper.CPWebCase):
self.getPage("/parameter_test/argument2/")
self.assertBody("argument2")
-
diff --git a/cherrypy/test/test_encoding.py b/cherrypy/test/test_encoding.py
index 055bfc06..3961b4d4 100644
--- a/cherrypy/test/test_encoding.py
+++ b/cherrypy/test/test_encoding.py
@@ -18,8 +18,10 @@ class EncodingTests(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self, param):
- assert param == europoundUnicode, "%r != %r" % (param, europoundUnicode)
+ assert param == europoundUnicode, "%r != %r" % (
+ param, europoundUnicode)
yield europoundUnicode
index.exposed = True
@@ -38,17 +40,21 @@ class EncodingTests(helper.CPWebCase):
# should not fail.
cherrypy.response.cookie['candy'] = 'bar'
cherrypy.response.cookie['candy']['domain'] = 'cherrypy.org'
- cherrypy.response.headers['Some-Header'] = 'My d\xc3\xb6g has fleas'
+ cherrypy.response.headers[
+ 'Some-Header'] = 'My d\xc3\xb6g has fleas'
return 'Any content'
cookies_and_headers.exposed = True
def reqparams(self, *args, **kwargs):
- return ntob(', ').join([": ".join((k, v)).encode('utf8')
- for k, v in cherrypy.request.params.items()])
+ return ntob(', ').join(
+ [": ".join((k, v)).encode('utf8')
+ for k, v in sorted(cherrypy.request.params.items())]
+ )
reqparams.exposed = True
def nontext(self, *args, **kwargs):
- cherrypy.response.headers['Content-Type'] = 'application/binary'
+ cherrypy.response.headers[
+ 'Content-Type'] = 'application/binary'
return '\x00\x01\x02\x03'
nontext.exposed = True
nontext._cp_config = {'tools.encode.text_only': False,
@@ -56,13 +62,15 @@ class EncodingTests(helper.CPWebCase):
}
class GZIP:
+
def index(self):
yield "Hello, world"
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
@@ -70,14 +78,16 @@ 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
noshow_stream._cp_config = {'response.stream': True}
class Decode:
+
def extra_charset(self, *args, **kwargs):
return ', '.join([": ".join((k, v))
for k, v in cherrypy.request.params.items()])
@@ -85,7 +95,7 @@ class EncodingTests(helper.CPWebCase):
extra_charset._cp_config = {
'tools.decode.on': True,
'tools.decode.default_encoding': ['utf-16'],
- }
+ }
def force_charset(self, *args, **kwargs):
return ', '.join([": ".join((k, v))
@@ -94,7 +104,7 @@ class EncodingTests(helper.CPWebCase):
force_charset._cp_config = {
'tools.decode.on': True,
'tools.decode.encoding': 'utf-16',
- }
+ }
root = Root()
root.gzip = GZIP()
@@ -117,18 +127,21 @@ 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,
+ 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"),
- ("Content-Length", str(len(body))),
- ],
+ body = ntob("param=") + europoundUtf8
+ self.getPage('/',
+ method='POST',
+ headers=[
+ ("Content-Type", "application/x-www-form-urlencoded"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertBody(europoundUtf8)
@@ -136,18 +149,22 @@ class EncodingTests(helper.CPWebCase):
# Here, q is the POUND SIGN U+00A3 encoded in utf8
body = ntob("q=\xc2\xa3")
self.getPage('/reqparams', method='POST',
- headers=[("Content-Type", "application/x-www-form-urlencoded"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "application/x-www-form-urlencoded"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
# ...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"),
- ("Content-Length", str(len(body))),
- ],
+ self.getPage('/reqparams',
+ method='POST',
+ headers=[
+ ("Content-Type",
+ "application/x-www-form-urlencoded;charset=utf-16"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
@@ -155,13 +172,17 @@ 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"),
- ("Content-Length", str(len(body))),
- ],
+ 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,
+ self.assertErrorPage(
+ 400,
"The request entity could not be decoded. The following charsets "
"were attempted: ['utf-8']")
@@ -170,9 +191,10 @@ class EncodingTests(helper.CPWebCase):
# Here, we add utf-16 as a charset and pass a utf-16 body.
body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00")
self.getPage('/decode/extra_charset', method='POST',
- headers=[("Content-Type", "application/x-www-form-urlencoded"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "application/x-www-form-urlencoded"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
@@ -181,9 +203,10 @@ class EncodingTests(helper.CPWebCase):
# Here, we add utf-16 as a charset but still pass a utf-8 body.
body = ntob("q=\xc2\xa3")
self.getPage('/decode/extra_charset', method='POST',
- headers=[("Content-Type", "application/x-www-form-urlencoded"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "application/x-www-form-urlencoded"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
@@ -191,73 +214,85 @@ class EncodingTests(helper.CPWebCase):
# Here, we force utf-16 as a charset but still pass a utf-8 body.
body = ntob("q=\xc2\xa3")
self.getPage('/decode/force_charset', method='POST',
- headers=[("Content-Type", "application/x-www-form-urlencoded"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "application/x-www-form-urlencoded"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
- self.assertErrorPage(400,
+ 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"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "multipart/form-data;boundary=X"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
- self.assertBody(ntob("text: ab\xe2\x80\x9cc, submit: Create"))
+ self.assertBody(ntob("submit: Create, text: ab\xe2\x80\x9cc"))
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"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "multipart/form-data;boundary=X"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
- self.assertBody(ntob("text: \xe2\x80\x9c, submit: Create"))
+ self.assertBody(ntob("submit: Create, text: \xe2\x80\x9c"))
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"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "multipart/form-data;boundary=X"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
self.assertStatus(400)
- self.assertErrorPage(400,
+ self.assertErrorPage(
+ 400,
"The request entity could not be decoded. The following charsets "
"were attempted: ['us-ascii', 'utf-8']")
@@ -345,7 +380,7 @@ class EncodingTests(helper.CPWebCase):
# and 2) we may have already written some of the body.
# The fix is to never stream yields when using gzip.
if (cherrypy.server.protocol_version == "HTTP/1.0" or
- getattr(cherrypy.server, "using_apache", False)):
+ getattr(cherrypy.server, "using_apache", False)):
self.getPage('/gzip/noshow_stream',
headers=[("Accept-Encoding", "gzip")])
self.assertHeader('Content-Encoding', 'gzip')
@@ -360,4 +395,3 @@ class EncodingTests(helper.CPWebCase):
def test_UnicodeHeaders(self):
self.getPage('/cookies_and_headers')
self.assertBody('Any content')
-
diff --git a/cherrypy/test/test_etags.py b/cherrypy/test/test_etags.py
index b6f2811f..4a263f36 100644
--- a/cherrypy/test/test_etags.py
+++ b/cherrypy/test/test_etags.py
@@ -7,6 +7,7 @@ class ETagTest(helper.CPWebCase):
def setup_server():
class Root:
+
def resource(self):
return "Oh wah ta goo Siam."
resource.exposed = True
@@ -51,7 +52,8 @@ class ETagTest(helper.CPWebCase):
# Test If-None-Match (both valid and invalid)
self.getPage("/resource", headers=[('If-None-Match', etag)])
self.assertStatus(304)
- self.getPage("/resource", method='POST', headers=[('If-None-Match', etag)])
+ self.getPage("/resource", method='POST',
+ headers=[('If-None-Match', etag)])
self.assertStatus("412 Precondition Failed")
self.getPage("/resource", headers=[('If-None-Match', "*")])
self.assertStatus(304)
@@ -80,4 +82,3 @@ class ETagTest(helper.CPWebCase):
self.getPage("/unicoded", headers=[('If-Match', etag1)])
self.assertStatus(200)
self.assertHeader('ETag', etag1)
-
diff --git a/cherrypy/test/test_http.py b/cherrypy/test/test_http.py
index 549c62ef..ff96afa2 100644
--- a/cherrypy/test/test_http.py
+++ b/cherrypy/test/test_http.py
@@ -31,14 +31,20 @@ def encode_multipart_formdata(files):
return content_type, body
-
-
from cherrypy.test import helper
+
class HTTPTests(helper.CPWebCase):
+
+ def make_connection(self):
+ if self.scheme == "https":
+ return HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
+ else:
+ return HTTPConnection('%s:%s' % (self.interface(), self.PORT))
def setup_server():
class Root:
+
def index(self, *args, **kwargs):
return "Hello world!"
index.exposed = True
@@ -49,7 +55,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
@@ -59,15 +67,22 @@ class HTTPTests(helper.CPWebCase):
count += 1
else:
if count:
- if py3k: curchar = chr(curchar)
+ if py3k:
+ curchar = chr(curchar)
summary.append("%s * %d" % (curchar, count))
count = 1
curchar = c
if count:
- if py3k: curchar = chr(curchar)
+ if py3k:
+ curchar = chr(curchar)
summary.append("%s * %d" % (curchar, count))
return ", ".join(summary)
post_multipart.exposed = True
+
+ @cherrypy.expose
+ def post_filename(self, myfile):
+ '''Return the name of the file which was uploaded.'''
+ return myfile.filename
cherrypy.tree.mount(Root())
cherrypy.config.update({'server.max_request_body_size': 30000000})
@@ -81,10 +96,7 @@ class HTTPTests(helper.CPWebCase):
# Send a message with neither header and no body. Even though
# the request is of method POST, this should be OK because we set
# request.process_request_body to False for our handler.
- if self.scheme == "https":
- c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
- else:
- c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+ c = self.make_connection()
c.request("POST", "/no_body")
response = c.getresponse()
self.body = response.fp.read()
@@ -111,15 +123,12 @@ class HTTPTests(helper.CPWebCase):
contents = "".join([c * 65536 for c in alphabet])
# encode as multipart form data
- files=[('file', 'file.txt', contents)]
+ files = [('file', 'file.txt', contents)]
content_type, body = encode_multipart_formdata(files)
body = body.encode('Latin-1')
# post file
- if self.scheme == 'https':
- c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
- else:
- c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+ c = self.make_connection()
c.putrequest('POST', '/post_multipart')
c.putheader('Content-Type', content_type)
c.putheader('Content-Length', str(len(body)))
@@ -131,16 +140,37 @@ class HTTPTests(helper.CPWebCase):
self.status = str(response.status)
self.assertStatus(200)
self.assertBody(", ".join(["%s * 65536" % c for c in alphabet]))
+
+ def test_post_filename_with_commas(self):
+ '''Testing that we can handle filenames with commas. This was
+ reported as a bug in:
+ https://bitbucket.org/cherrypy/cherrypy/issue/1146/'''
+ # We'll upload a bunch of files with differing names.
+ for fname in ['boop.csv', 'foo, bar.csv', 'bar, xxxx.csv', 'file"name.csv']:
+ files = [('myfile', fname, 'yunyeenyunyue')]
+ content_type, body = encode_multipart_formdata(files)
+ body = body.encode('Latin-1')
+
+ # post file
+ c = self.make_connection()
+ c.putrequest('POST', '/post_filename')
+ c.putheader('Content-Type', content_type)
+ c.putheader('Content-Length', str(len(body)))
+ c.endheaders()
+ c.send(body)
+
+ response = c.getresponse()
+ self.body = response.fp.read()
+ self.status = str(response.status)
+ self.assertStatus(200)
+ self.assertBody(fname)
def test_malformed_request_line(self):
if getattr(cherrypy.server, "using_apache", False):
return self.skip("skipped due to known Apache differences...")
# Test missing version in Request-Line
- if self.scheme == 'https':
- c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
- else:
- c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+ c = self.make_connection()
c._output(ntob('GET /'))
c._send_output()
if hasattr(c, 'strict'):
@@ -161,10 +191,7 @@ class HTTPTests(helper.CPWebCase):
self.assertBody("Hello world!")
def test_malformed_header(self):
- if self.scheme == 'https':
- c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
- else:
- c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+ c = self.make_connection()
c.putrequest('GET', '/')
c.putheader('Content-Type', 'text/plain')
# See https://bitbucket.org/cherrypy/cherrypy/issue/941
@@ -208,11 +235,11 @@ class HTTPTests(helper.CPWebCase):
try:
response.begin()
self.assertEqual(response.status, 400)
- self.assertEqual(response.fp.read(22), ntob("Malformed Request-Line"))
+ self.assertEqual(response.fp.read(22),
+ ntob("Malformed Request-Line"))
c.close()
except socket.error:
e = sys.exc_info()[1]
# "Connection reset by peer" is also acceptable.
if e.errno != errno.ECONNRESET:
raise
-
diff --git a/cherrypy/test/test_httpauth.py b/cherrypy/test/test_httpauth.py
index 5f4acbb4..98a300f0 100644
--- a/cherrypy/test/test_httpauth.py
+++ b/cherrypy/test/test_httpauth.py
@@ -4,27 +4,35 @@ from cherrypy.lib import httpauth
from cherrypy.test import helper
+
class HTTPAuthTest(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self):
return "This is public."
index.exposed = True
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():
@@ -36,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()
@@ -54,7 +72,6 @@ class HTTPAuthTest(helper.CPWebCase):
cherrypy.tree.mount(root, config=conf)
setup_server = staticmethod(setup_server)
-
def testPublic(self):
self.getPage("/")
self.assertStatus('200 OK')
@@ -97,7 +114,8 @@ class HTTPAuthTest(helper.CPWebCase):
break
if value is None:
- self._handlewebError("Digest authentification scheme was not found")
+ self._handlewebError(
+ "Digest authentification scheme was not found")
value = value[7:]
items = value.split(', ')
@@ -112,7 +130,8 @@ class HTTPAuthTest(helper.CPWebCase):
if 'realm' not in tokens:
self._handlewebError(missing_msg % 'realm')
elif tokens['realm'] != '"localhost"':
- self._handlewebError(bad_value_msg % ('realm', '"localhost"', tokens['realm']))
+ self._handlewebError(bad_value_msg %
+ ('realm', '"localhost"', tokens['realm']))
if 'nonce' not in tokens:
self._handlewebError(missing_msg % 'nonce')
else:
@@ -120,14 +139,27 @@ class HTTPAuthTest(helper.CPWebCase):
if 'algorithm' not in tokens:
self._handlewebError(missing_msg % 'algorithm')
elif tokens['algorithm'] != '"MD5"':
- self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
+ self._handlewebError(bad_value_msg %
+ ('algorithm', '"MD5"', tokens['algorithm']))
if 'qop' not in tokens:
self._handlewebError(missing_msg % 'qop')
elif tokens['qop'] != '"auth"':
- self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
+ self._handlewebError(bad_value_msg %
+ ('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)
@@ -138,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)
@@ -148,4 +191,3 @@ class HTTPAuthTest(helper.CPWebCase):
self.getPage('/digest/', [('Authorization', auth)])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")
-
diff --git a/cherrypy/test/test_iterator.py b/cherrypy/test/test_iterator.py
new file mode 100644
index 00000000..dcf4bc94
--- /dev/null
+++ b/cherrypy/test/test_iterator.py
@@ -0,0 +1,181 @@
+import cherrypy
+from cherrypy._cpcompat import unicodestr
+
+class IteratorBase(object):
+
+ created = 0
+ datachunk = u'butternut squash' * 256
+
+ @classmethod
+ def incr(cls):
+ cls.created += 1
+
+ @classmethod
+ def decr(cls):
+ cls.created -= 1
+
+class OurGenerator(IteratorBase):
+
+ def __iter__(self):
+ self.incr()
+ try:
+ for i in range(1024):
+ yield self.datachunk
+ finally:
+ self.decr()
+
+class OurIterator(IteratorBase):
+
+ started = False
+ closed_off = False
+ count = 0
+
+ def increment(self):
+ self.incr()
+
+ def decrement(self):
+ if not self.closed_off:
+ self.closed_off = True
+ self.decr()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if not self.started:
+ self.started = True
+ self.increment()
+ self.count += 1
+ if self.count > 1024:
+ raise StopIteration
+ return self.datachunk
+
+ next = __next__
+
+ def __del__(self):
+ self.decrement()
+
+class OurClosableIterator(OurIterator):
+
+ def close(self):
+ self.decrement()
+
+class OurNotClosableIterator(OurIterator):
+
+ # We can't close something which requires an additional argument.
+ def close(self, somearg):
+ self.decrement()
+
+class OurUnclosableIterator(OurIterator):
+ close = 'close' # not callable!
+
+from cherrypy.test import helper
+class IteratorTest(helper.CPWebCase):
+
+ @staticmethod
+ def setup_server():
+
+ class Root(object):
+
+ @cherrypy.expose
+ def count(self, clsname):
+ cherrypy.response.headers['Content-Type'] = 'text/plain'
+ return unicodestr(globals()[clsname].created)
+
+ @cherrypy.expose
+ def getall(self, clsname):
+ cherrypy.response.headers['Content-Type'] = 'text/plain'
+ return globals()[clsname]()
+
+ @cherrypy.expose
+ def stream(self, clsname):
+ return self.getall(clsname)
+ stream._cp_config = {'response.stream': True}
+
+ cherrypy.tree.mount(Root())
+
+ def test_iterator(self):
+ if cherrypy.server.protocol_version != "HTTP/1.1":
+ return self.skip()
+
+ self.PROTOCOL = "HTTP/1.1"
+
+ # Check the counts of all the classes, they should be zero.
+ closables = ['OurClosableIterator', 'OurGenerator']
+ unclosables = ['OurUnclosableIterator', 'OurNotClosableIterator']
+ all_classes = closables + unclosables
+
+ import random
+ random.shuffle(all_classes)
+
+ for clsname in all_classes:
+ self.getPage("/count/" + clsname)
+ self.assertStatus(200)
+ self.assertBody('0')
+
+ # We should also be able to read the entire content body
+ # successfully, though we don't need to, we just want to
+ # check the header.
+ for clsname in all_classes:
+ itr_conn = self.get_conn()
+ itr_conn.putrequest("GET", "/getall/" + clsname)
+ itr_conn.endheaders()
+ response = itr_conn.getresponse()
+ self.assertEqual(response.status, 200)
+ headers = response.getheaders()
+ for header_name, header_value in headers:
+ if header_name.lower() == 'content-length':
+ assert header_value == unicodestr(1024 * 16 * 256), header_value
+ break
+ else:
+ raise AssertionError('No Content-Length header found')
+
+ # As the response should be fully consumed by CherryPy
+ # before sending back, the count should still be at zero
+ # by the time the response has been sent.
+ self.getPage("/count/" + clsname)
+ self.assertStatus(200)
+ self.assertBody('0')
+
+ # Now we do the same check with streaming - some classes will
+ # be automatically closed, while others cannot.
+ stream_counts = {}
+ for clsname in all_classes:
+ itr_conn = self.get_conn()
+ itr_conn.putrequest("GET", "/stream/" + clsname)
+ itr_conn.endheaders()
+ response = itr_conn.getresponse()
+ self.assertEqual(response.status, 200)
+ response.fp.read(65536)
+
+ # Let's check the count - this should always be one.
+ self.getPage("/count/" + clsname)
+ self.assertBody('1')
+
+ # Now if we close the connection, the count should go back
+ # to zero.
+ itr_conn.close()
+ self.getPage("/count/" + clsname)
+
+ # If this is a response which should be easily closed, then
+ # we will test to see if the value has gone back down to
+ # zero.
+ if clsname in closables:
+
+ # Sometimes we try to get the answer too quickly - we
+ # will wait for 100 ms before asking again if we didn't
+ # get the answer we wanted.
+ if self.body != '0':
+ import time
+ time.sleep(0.1)
+ self.getPage("/count/" + clsname)
+
+ stream_counts[clsname] = int(self.body)
+
+ # Check that we closed off the classes which should provide
+ # easy mechanisms for doing so.
+ for clsname in closables:
+ assert stream_counts[clsname] == 0, (
+ 'did not close off stream response correctly, expected '
+ 'count of zero for %s: %s' % (clsname, stream_counts)
+ )
diff --git a/cherrypy/test/test_json.py b/cherrypy/test/test_json.py
index 3f30fa23..8776d998 100644
--- a/cherrypy/test/test_json.py
+++ b/cherrypy/test/test_json.py
@@ -3,9 +3,12 @@ from cherrypy.test import helper
from cherrypy._cpcompat import json
+
class JsonTest(helper.CPWebCase):
+
def setup_server():
class Root(object):
+
def plain(self):
return 'hello'
plain.exposed = True
@@ -95,6 +98,3 @@ class JsonTest(helper.CPWebCase):
self.getPage("/json_cached") # 2'nd time to hit cache
self.assertStatus(200, '"hello"')
-
-
-
diff --git a/cherrypy/test/test_logging.py b/cherrypy/test/test_logging.py
index e01e99c6..0c029ad7 100644
--- a/cherrypy/test/test_logging.py
+++ b/cherrypy/test/test_logging.py
@@ -54,16 +54,15 @@ def setup_server():
root = Root()
-
cherrypy.config.update({'log.error_file': error_log,
'log.access_file': access_log,
})
cherrypy.tree.mount(root)
-
from cherrypy.test import helper, logtest
+
class AccessLogTests(helper.CPWebCase, logtest.LogCase):
setup_server = staticmethod(setup_server)
@@ -106,6 +105,26 @@ class AccessLogTests(helper.CPWebCase, logtest.LogCase):
self.assertLog(-1, '] "GET %s/as_yield HTTP/1.1" 200 - "" ""'
% self.prefix())
+ def testCustomLogFormat(self):
+ '''Test a customized access_log_format string,
+ which is a feature of _cplogging.LogManager.access() '''
+
+ original_logformat = cherrypy._cplogging.LogManager.access_log_format
+ cherrypy._cplogging.LogManager.access_log_format = \
+ '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}" {o}' \
+ if py3k else \
+ '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(o)s'
+
+ self.markLog()
+ self.getPage("/as_string", headers=[('Referer', 'REFERER'),
+ ('User-Agent', 'USERAGENT'),
+ ('Host', 'HOST')])
+ self.assertLog(-1, '%s - - [' % self.interface())
+ self.assertLog(-1, '] "GET /as_string HTTP/1.1" '
+ '200 7 "REFERER" "USERAGENT" HOST')
+
+ cherrypy._cplogging.LogManager.access_log_format = original_logformat
+
def testEscapedOutput(self):
# Test unicode in access log pieces.
self.markLog()
@@ -154,4 +173,3 @@ class ErrorLogTests(helper.CPWebCase, logtest.LogCase):
self.assertLog(-3, 'raise ValueError()')
finally:
ignore.pop()
-
diff --git a/cherrypy/test/test_mime.py b/cherrypy/test/test_mime.py
index 03cd078e..f5f2b9fb 100644
--- a/cherrypy/test/test_mime.py
+++ b/cherrypy/test/test_mime.py
@@ -3,6 +3,7 @@
import cherrypy
from cherrypy._cpcompat import ntob, ntou, sorted
+
def setup_server():
class Root:
@@ -28,12 +29,14 @@ def setup_server():
from cherrypy.test import helper
+
class MultipartTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
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">
@@ -58,32 +61,37 @@ This is the <strong>HTML</strong> version
headers = [
('Content-Type', 'multipart/mixed; boundary=123456789'),
('Content-Length', str(len(body))),
- ]
+ ]
self.getPage('/multipart', headers, "POST", body)
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"),
- ("Content-Length", str(len(body))),
- ],
+ headers=[(
+ "Content-Type", "multipart/form-data;boundary=X"),
+ ("Content-Length", str(len(body))),
+ ],
body=body),
- self.assertBody(repr([('baz', [ntou('111'), ntou('333')]), ('foo', ntou('bar'))]))
+ self.assertBody(
+ repr([('baz', [ntou('111'), ntou('333')]), ('foo', ntou('bar'))]))
class SafeMultipartHandlingTest(helper.CPWebCase):
@@ -93,13 +101,13 @@ class SafeMultipartHandlingTest(helper.CPWebCase):
headers = [
('Accept', 'text/*'),
('Content-Type', 'multipart/form-data; '
- 'boundary=----------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6'),
+ 'boundary=----------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6'),
('User-Agent', 'Shockwave Flash'),
('Host', 'www.example.com:54583'),
('Content-Length', '499'),
('Connection', 'Keep-Alive'),
('Cache-Control', 'no-cache'),
- ]
+ ]
filedata = ntob('<?xml version="1.0" encoding="UTF-8"?>\r\n'
'<projectDescription>\r\n'
'</projectDescription>\r\n')
@@ -115,14 +123,13 @@ class SafeMultipartHandlingTest(helper.CPWebCase):
'\r\n')
+ filedata +
ntob('\r\n'
- '------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6\r\n'
- 'Content-Disposition: form-data; name="Upload"\r\n'
- '\r\n'
- 'Submit Query\r\n'
- # Flash apps omit the trailing \r\n on the last line:
- '------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6--'
- ))
+ '------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6\r\n'
+ 'Content-Disposition: form-data; name="Upload"\r\n'
+ '\r\n'
+ 'Submit Query\r\n'
+ # Flash apps omit the trailing \r\n on the last line:
+ '------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6--'
+ ))
self.getPage('/flashupload', headers, "POST", body)
self.assertBody("Upload: Submit Query, Filename: .project, "
"Filedata: %r" % filedata)
-
diff --git a/cherrypy/test/test_misc_tools.py b/cherrypy/test/test_misc_tools.py
index 82e6a5db..df3e2e65 100644
--- a/cherrypy/test/test_misc_tools.py
+++ b/cherrypy/test/test_misc_tools.py
@@ -8,6 +8,7 @@ from cherrypy import tools
def setup_server():
class Root:
+
def index(self):
yield "Hello, world"
index.exposed = True
@@ -22,8 +23,7 @@ def setup_server():
'tools.response_headers.headers': [("Content-Language", "fr"),
('Content-Type', 'text/plain')],
'tools.log_hooks.on': True,
- }
-
+ }
class Accept:
_cp_config = {'tools.accept.on': True}
@@ -52,12 +52,14 @@ def setup_server():
select.exposed = True
class Referer:
+
def accept(self):
return "Accepted!"
accept.exposed = True
reject = accept
class AutoVary:
+
def index(self):
# Read a header directly with 'get'
ae = cherrypy.request.headers.get('Accept-Encoding')
@@ -96,6 +98,7 @@ def setup_server():
from cherrypy.test import helper
+
class ResponseHeadersTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -142,7 +145,8 @@ class AcceptTest(helper.CPWebCase):
self.assertInBody('<title>Unknown Blog</title>')
# Specify exact media type
- self.getPage('/accept/feed', headers=[('Accept', 'application/atom+xml')])
+ self.getPage('/accept/feed',
+ headers=[('Accept', 'application/atom+xml')])
self.assertStatus(200)
self.assertInBody('<title>Unknown Blog</title>')
@@ -176,7 +180,8 @@ class AcceptTest(helper.CPWebCase):
self.getPage('/accept/select', [('Accept', 'text/plain')])
self.assertStatus(200)
self.assertBody('PAGE TITLE')
- self.getPage('/accept/select', [('Accept', 'text/plain, text/*;q=0.5')])
+ self.getPage('/accept/select',
+ [('Accept', 'text/plain, text/*;q=0.5')])
self.assertStatus(200)
self.assertBody('PAGE TITLE')
@@ -191,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):
@@ -203,5 +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 86a0df33..e80a7a71 100644
--- a/cherrypy/test/test_objectmapping.py
+++ b/cherrypy/test/test_objectmapping.py
@@ -1,3 +1,4 @@
+import sys
import cherrypy
from cherrypy._cpcompat import ntou
from cherrypy._cptree import Application
@@ -10,6 +11,7 @@ class ObjectMappingTest(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self, name="world"):
return name
index.exposed = True
@@ -54,8 +56,8 @@ class ObjectMappingTest(helper.CPWebCase):
mapped_func.exposed = True
setattr(Root, "Von B\xfclow", mapped_func)
-
class Exposing:
+
def base(self):
return "expose works!"
cherrypy.expose(base)
@@ -63,20 +65,22 @@ class ObjectMappingTest(helper.CPWebCase):
cherrypy.expose(base, "2")
class ExposingNewStyle(object):
+
def base(self):
return "expose works!"
cherrypy.expose(base)
cherrypy.expose(base, "1")
cherrypy.expose(base, "2")
-
class Dir1:
+
def index(self):
return "index for dir1"
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}
@@ -84,8 +88,8 @@ class ObjectMappingTest(helper.CPWebCase):
return "default for dir1, param is:" + repr(params)
default.exposed = True
-
class Dir2:
+
def index(self):
return "index for dir2, path is:" + cherrypy.request.path_info
index.exposed = True
@@ -102,16 +106,18 @@ class ObjectMappingTest(helper.CPWebCase):
return "/".join(vpath)
posparam.exposed = True
-
class Dir3:
+
def default(self):
return "default for dir3, not exposed"
class Dir4:
+
def index(self):
return "index for dir4, not exposed"
class DefNoIndex:
+
def default(self, *args):
raise cherrypy.HTTPRedirect("contact")
default.exposed = True
@@ -150,8 +156,8 @@ class ObjectMappingTest(helper.CPWebCase):
}
cherrypy.tree.mount(Root(), url, conf)
-
class Isolated:
+
def index(self):
return "made it!"
index.exposed = True
@@ -165,10 +171,10 @@ class ObjectMappingTest(helper.CPWebCase):
def GET(self):
return "milk"
- cherrypy.tree.mount(AnotherApp(), "/app", {'/': {'request.dispatch': d}})
+ cherrypy.tree.mount(AnotherApp(), "/app",
+ {'/': {'request.dispatch': d}})
setup_server = staticmethod(setup_server)
-
def testObjectMapping(self):
for url in script_names:
prefix = self.script_name = url
@@ -177,10 +183,12 @@ class ObjectMappingTest(helper.CPWebCase):
self.assertBody('world')
self.getPage("/dir1/myMethod")
- self.assertBody("myMethod from dir1, path_info is:'/dir1/myMethod'")
+ self.assertBody(
+ "myMethod from dir1, path_info is:'/dir1/myMethod'")
self.getPage("/this/method/does/not/exist")
- self.assertBody("default:('this', 'method', 'does', 'not', 'exist')")
+ self.assertBody(
+ "default:('this', 'method', 'does', 'not', 'exist')")
self.getPage("/extra/too/much")
self.assertBody("('too', 'much')")
@@ -206,7 +214,8 @@ class ObjectMappingTest(helper.CPWebCase):
# Test that default method must be exposed in order to match.
self.getPage("/dir1/dir2/dir3/dir4/index")
- self.assertBody("default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")
+ self.assertBody(
+ "default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")
# Test *vpath when default() is defined but not index()
# This also tests HTTPRedirect with default.
@@ -215,17 +224,20 @@ class ObjectMappingTest(helper.CPWebCase):
self.assertHeader('Location', '%s/contact' % self.base())
self.getPage("/defnoindex/")
self.assertStatus((302, 303))
- self.assertHeader('Location', '%s/defnoindex/contact' % self.base())
+ self.assertHeader('Location', '%s/defnoindex/contact' %
+ self.base())
self.getPage("/defnoindex/page")
self.assertStatus((302, 303))
- self.assertHeader('Location', '%s/defnoindex/contact' % self.base())
+ self.assertHeader('Location', '%s/defnoindex/contact' %
+ self.base())
self.getPage("/redirect")
self.assertStatus('302 Found')
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")
@@ -268,7 +280,8 @@ class ObjectMappingTest(helper.CPWebCase):
self.assertStatus("404 Not Found")
# Make sure /foobar maps to Root.foobar and not to the app
- # mounted at /foo. See https://bitbucket.org/cherrypy/cherrypy/issue/573
+ # mounted at /foo. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/573
self.getPage("/foobar")
self.assertBody("bar")
@@ -375,6 +388,7 @@ class ObjectMappingTest(helper.CPWebCase):
def testTreeMounting(self):
class Root(object):
+
def hello(self):
return "Hello world!"
hello.exposed = True
@@ -402,3 +416,16 @@ class ObjectMappingTest(helper.CPWebCase):
# However, this does not apply to tree.mount
self.assertRaises(TypeError, cherrypy.tree.mount, a, None)
+ def testKeywords(self):
+ if sys.version_info < (3,):
+ return self.skip("skipped (Python 3 only)")
+ exec("""class Root(object):
+ @cherrypy.expose
+ def hello(self, *, name='world'):
+ return 'Hello %s!' % name
+cherrypy.tree.mount(Application(Root(), '/keywords'))""")
+
+ self.getPage('/keywords/hello')
+ self.assertStatus(200)
+ self.getPage('/keywords/hello/extra')
+ self.assertStatus(404)
diff --git a/cherrypy/test/test_proxy.py b/cherrypy/test/test_proxy.py
index a620115a..821a4e52 100644
--- a/cherrypy/test/test_proxy.py
+++ b/cherrypy/test/test_proxy.py
@@ -12,7 +12,7 @@ class ProxyTest(helper.CPWebCase):
cherrypy.config.update({
'tools.proxy.on': True,
'tools.proxy.base': 'www.mydomain.test',
- })
+ })
# Set up application
@@ -20,7 +20,8 @@ class ProxyTest(helper.CPWebCase):
def __init__(self, sn):
# Calculate a URL outside of any requests.
- self.thisnewpage = cherrypy.url("/this/new/page", script_name=sn)
+ self.thisnewpage = cherrypy.url(
+ "/this/new/page", script_name=sn)
def pageurl(self):
return self.thisnewpage
@@ -66,27 +67,33 @@ class ProxyTest(helper.CPWebCase):
(self.scheme, self.prefix()))
# Test X-Forwarded-Host (Apache 1.3.33+ and Apache 2)
- self.getPage("/", headers=[('X-Forwarded-Host', 'http://www.example.test')])
+ self.getPage(
+ "/", headers=[('X-Forwarded-Host', 'http://www.example.test')])
self.assertHeader('Location', "http://www.example.test/dummy")
self.getPage("/", headers=[('X-Forwarded-Host', 'www.example.test')])
- self.assertHeader('Location', "%s://www.example.test/dummy" % self.scheme)
+ self.assertHeader('Location', "%s://www.example.test/dummy" %
+ self.scheme)
# Test multiple X-Forwarded-Host headers
self.getPage("/", headers=[
('X-Forwarded-Host', 'http://www.example.test, www.cherrypy.test'),
- ])
+ ])
self.assertHeader('Location', "http://www.example.test/dummy")
# Test X-Forwarded-For (Apache2)
self.getPage("/remoteip",
headers=[('X-Forwarded-For', '192.168.0.20')])
self.assertBody("192.168.0.20")
+ #Fix bug #1268
self.getPage("/remoteip",
- headers=[('X-Forwarded-For', '67.15.36.43, 192.168.0.20')])
- self.assertBody("192.168.0.20")
+ headers=[
+ ('X-Forwarded-For', '67.15.36.43, 192.168.0.20')
+ ])
+ self.assertBody("67.15.36.43")
# Test X-Host (lighttpd; see https://trac.lighttpd.net/trac/ticket/418)
self.getPage("/xhost", headers=[('X-Host', 'www.example.test')])
- self.assertHeader('Location', "%s://www.example.test/blah" % self.scheme)
+ self.assertHeader('Location', "%s://www.example.test/blah" %
+ self.scheme)
# Test X-Forwarded-Proto (lighttpd)
self.getPage("/base", headers=[('X-Forwarded-Proto', 'https')])
@@ -100,8 +107,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"
@@ -122,8 +130,8 @@ class ProxyTest(helper.CPWebCase):
self.getPage(sn + "/pageurl")
self.assertBody(expected)
- # Test trailing slash (see https://bitbucket.org/cherrypy/cherrypy/issue/562).
+ # Test trailing slash (see
+ # https://bitbucket.org/cherrypy/cherrypy/issue/562).
self.getPage("/xhost/", headers=[('X-Host', 'www.example.test')])
self.assertHeader('Location', "%s://www.example.test/xhost"
% self.scheme)
-
diff --git a/cherrypy/test/test_refleaks.py b/cherrypy/test/test_refleaks.py
index 00126e1a..a87b8df7 100644
--- a/cherrypy/test/test_refleaks.py
+++ b/cherrypy/test/test_refleaks.py
@@ -17,6 +17,7 @@ class ReferenceTests(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self, *args, **kwargs):
cherrypy.request.thing = data
return "Hello world!"
@@ -56,4 +57,3 @@ class ReferenceTests(helper.CPWebCase):
t.join()
self.assertEqual(len(success), ITERATIONS)
-
diff --git a/cherrypy/test/test_request_obj.py b/cherrypy/test/test_request_obj.py
index 3f1fc03c..d9989e97 100644
--- a/cherrypy/test/test_request_obj.py
+++ b/cherrypy/test/test_request_obj.py
@@ -18,6 +18,7 @@ defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
from cherrypy.test import helper
+
class RequestObjectTests(helper.CPWebCase):
def setup_server():
@@ -33,10 +34,10 @@ 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)
@@ -60,12 +61,12 @@ class RequestObjectTests(helper.CPWebCase):
return "Coordinates: %s, %s" % (x, y)
def default(self, *args, **kwargs):
- return "args: %s kwargs: %s" % (args, kwargs)
+ return "args: %s kwargs: %s" % (args, sorted(kwargs.items()))
default._cp_config = {'request.query_string_encoding': 'latin1'}
-
class ParamErrorsCallable(object):
exposed = True
+
def __call__(self):
return "data"
@@ -110,12 +111,12 @@ class RequestObjectTests(helper.CPWebCase):
raise_type_error.exposed = True
def raise_type_error_with_default_param(self, x, y=None):
- return '%d' % 'a' # throw an exception
+ return '%d' % 'a' # throw an exception
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):
@@ -126,14 +127,17 @@ class RequestObjectTests(helper.CPWebCase):
raise cherrypy.HTTPError("410 Gone fishin'")
def custom(self, err='404'):
- raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!")
- custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"),
- 'error_page.401': callable_error_page,
- }
+ raise cherrypy.HTTPError(
+ int(err), "No, <b>really</b>, not found!")
+ custom._cp_config = {
+ 'error_page.404': os.path.join(localDir, "static/index.html"),
+ 'error_page.401': callable_error_page,
+ }
def custom_default(self):
- return 1 + 'a' # raise an unexpected error
- custom_default._cp_config = {'error_page.default': callable_error_page}
+ return 1 + 'a' # raise an unexpected error
+ custom_default._cp_config = {
+ 'error_page.default': callable_error_page}
def noexist(self):
raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
@@ -155,14 +159,16 @@ class RequestObjectTests(helper.CPWebCase):
def cause_err_in_finalize(self):
# Since status must start with an int, this should error.
cherrypy.response.status = "ZOO OK"
- cause_err_in_finalize._cp_config = {'request.show_tracebacks': False}
+ cause_err_in_finalize._cp_config = {
+ '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}
-
class Expect(Test):
def expectation_failed(self):
@@ -179,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
@@ -204,14 +211,12 @@ class RequestObjectTests(helper.CPWebCase):
cherrypy.response.headers['ETag'] = val
return val
-
class HeaderElements(Test):
def get_elements(self, headername):
e = cherrypy.request.headers.elements(headername)
return "\n".join([unicodestr(x) for x in e])
-
class Method(Test):
def index(self):
@@ -236,11 +241,13 @@ class RequestObjectTests(helper.CPWebCase):
return "success"
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()
"""
@@ -251,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
@@ -263,7 +271,6 @@ class RequestObjectTests(helper.CPWebCase):
root.divorce = Divorce()
-
class ThreadLocal(Test):
def index(self):
@@ -272,8 +279,10 @@ 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)
@@ -315,13 +324,14 @@ class RequestObjectTests(helper.CPWebCase):
self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville")
self.assertBody("args: %s kwargs: %s" %
(('\xd4 \xe3', 'cheese'),
- {'Gruy\xe8re': ntou('Bulgn\xe9ville')}))
+ [('Gruy\xe8re', ntou('Bulgn\xe9ville'))]))
# Make sure that encoded = and & get parsed correctly
- self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2")
+ self.getPage(
+ "/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2")
self.assertBody("args: %s kwargs: %s" %
(('code',),
- {'url': ntou('http://cherrypy.org/index?a=1&b=2')}))
+ [('url', ntou('http://cherrypy.org/index?a=1&b=2'))]))
# Test coordinates sent by <img ismap>
self.getPage("/params/ismap?223,114")
@@ -331,8 +341,8 @@ class RequestObjectTests(helper.CPWebCase):
self.getPage("/params/dictlike?a[1]=1&a[2]=2&b=foo&b[bar]=baz")
self.assertBody("args: %s kwargs: %s" %
(('dictlike',),
- {'a[1]': ntou('1'), 'b[bar]': ntou('baz'),
- 'b': ntou('foo'), 'a[2]': ntou('2')}))
+ [('a[1]', ntou('1')), ('a[2]', ntou('2')),
+ ('b', ntou('foo')), ('b[bar]', ntou('baz'))]))
def testParamErrors(self):
@@ -344,51 +354,66 @@ 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&param2=bar',
- '/paramerrors/one_positional_args_kwargs/foo?param2=bar&param3=baz',
- '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz',
- '/paramerrors/one_positional_kwargs?param1=foo&param2=bar&param3=baz',
- '/paramerrors/one_positional_kwargs/foo?param4=foo&param2=bar&param3=baz',
+ '/paramerrors/one_positional_args_kwargs?'
+ 'param1=foo&param2=bar',
+ '/paramerrors/one_positional_args_kwargs/foo?'
+ 'param2=bar&param3=baz',
+ '/paramerrors/one_positional_args_kwargs/foo/bar/baz?'
+ 'param2=bar&param3=baz',
+ '/paramerrors/one_positional_kwargs?'
+ 'param1=foo&param2=bar&param3=baz',
+ '/paramerrors/one_positional_kwargs/foo?'
+ 'param4=foo&param2=bar&param3=baz',
'/paramerrors/no_positional',
'/paramerrors/no_positional_args/foo',
'/paramerrors/no_positional_args/foo/bar/baz',
'/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar',
'/paramerrors/no_positional_args_kwargs/foo?param2=bar',
- '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz',
+ '/paramerrors/no_positional_args_kwargs/foo/bar/baz?'
+ 'param2=bar&param3=baz',
'/paramerrors/no_positional_kwargs?param1=foo&param2=bar',
'/paramerrors/callable_object',
- ):
+ ):
self.getPage(uri)
self.assertStatus(200)
# query string parameters are part of the URI, so if they are wrong
# for a particular handler, the status MUST be a 404.
error_msgs = [
- 'Missing parameters',
- 'Nothing matches the given URI',
- 'Multiple values for parameters',
- 'Unexpected query string parameters',
- 'Unexpected body parameters',
- ]
+ 'Missing parameters',
+ 'Nothing matches the given URI',
+ 'Multiple values for parameters',
+ 'Unexpected query string parameters',
+ 'Unexpected body parameters',
+ ]
for uri, msg in (
('/paramerrors/one_positional', error_msgs[0]),
('/paramerrors/one_positional?foo=foo', error_msgs[0]),
('/paramerrors/one_positional/foo/bar/baz', error_msgs[1]),
('/paramerrors/one_positional/foo?param1=foo', error_msgs[2]),
- ('/paramerrors/one_positional/foo?param1=foo&param2=foo', error_msgs[2]),
- ('/paramerrors/one_positional_args/foo?param1=foo&param2=foo', 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&param3=baz', error_msgs[2]),
- ('/paramerrors/one_positional_kwargs/foo?param1=foo&param2=bar&param3=baz', error_msgs[2]),
+ ('/paramerrors/one_positional/foo?param1=foo&param2=foo',
+ error_msgs[2]),
+ ('/paramerrors/one_positional_args/foo?param1=foo&param2=foo',
+ 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&param3=baz',
+ error_msgs[2]),
+ ('/paramerrors/one_positional_kwargs/foo?'
+ 'param1=foo&param2=bar&param3=baz',
+ error_msgs[2]),
('/paramerrors/no_positional/boo', error_msgs[1]),
('/paramerrors/no_positional?param1=foo', error_msgs[3]),
('/paramerrors/no_positional_args/boo?param1=foo', error_msgs[3]),
- ('/paramerrors/no_positional_kwargs/boo?param1=foo', error_msgs[1]),
+ ('/paramerrors/no_positional_kwargs/boo?param1=foo',
+ error_msgs[1]),
('/paramerrors/callable_object?param1=foo', error_msgs[3]),
('/paramerrors/callable_object/boo', error_msgs[1]),
- ):
+ ):
for show_mismatched_params in (True, False):
- cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
+ cherrypy.config.update(
+ {'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri)
self.assertStatus(404)
if show_mismatched_params:
@@ -398,18 +423,26 @@ class RequestObjectTests(helper.CPWebCase):
# if body parameters are wrong, a 400 must be returned.
for uri, body, msg in (
- ('/paramerrors/one_positional/foo', 'param1=foo', error_msgs[2]),
- ('/paramerrors/one_positional/foo', 'param1=foo&param2=foo', error_msgs[2]),
- ('/paramerrors/one_positional_args/foo', 'param1=foo&param2=foo', error_msgs[2]),
- ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo', error_msgs[4]),
- ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar&param3=baz', error_msgs[2]),
- ('/paramerrors/one_positional_kwargs/foo', 'param1=foo&param2=bar&param3=baz', error_msgs[2]),
+ ('/paramerrors/one_positional/foo',
+ 'param1=foo', error_msgs[2]),
+ ('/paramerrors/one_positional/foo',
+ 'param1=foo&param2=foo', error_msgs[2]),
+ ('/paramerrors/one_positional_args/foo',
+ 'param1=foo&param2=foo', error_msgs[2]),
+ ('/paramerrors/one_positional_args/foo/bar/baz',
+ 'param2=foo', error_msgs[4]),
+ ('/paramerrors/one_positional_args_kwargs/foo/bar/baz',
+ 'param1=bar&param3=baz', error_msgs[2]),
+ ('/paramerrors/one_positional_kwargs/foo',
+ 'param1=foo&param2=bar&param3=baz', error_msgs[2]),
('/paramerrors/no_positional', 'param1=foo', error_msgs[4]),
- ('/paramerrors/no_positional_args/boo', 'param1=foo', error_msgs[4]),
+ ('/paramerrors/no_positional_args/boo',
+ 'param1=foo', error_msgs[4]),
('/paramerrors/callable_object', 'param1=foo', error_msgs[4]),
- ):
+ ):
for show_mismatched_params in (True, False):
- cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
+ cherrypy.config.update(
+ {'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri, method='POST', body=body)
self.assertStatus(400)
if show_mismatched_params:
@@ -417,20 +450,27 @@ class RequestObjectTests(helper.CPWebCase):
else:
self.assertInBody("400 Bad")
-
# even if body parameters are wrong, if we get the uri wrong, then
# it's a 404
for uri, body, msg in (
- ('/paramerrors/one_positional?param2=foo', 'param1=foo', error_msgs[3]),
- ('/paramerrors/one_positional/foo/bar', 'param2=foo', error_msgs[1]),
- ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo', error_msgs[3]),
- ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar&param3=baz', error_msgs[1]),
- ('/paramerrors/no_positional?param1=foo', 'param2=foo', error_msgs[3]),
- ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo', error_msgs[3]),
- ('/paramerrors/callable_object?param2=bar', 'param1=foo', error_msgs[3]),
- ):
+ ('/paramerrors/one_positional?param2=foo',
+ 'param1=foo', error_msgs[3]),
+ ('/paramerrors/one_positional/foo/bar',
+ 'param2=foo', error_msgs[1]),
+ ('/paramerrors/one_positional_args/foo/bar?param2=foo',
+ 'param3=foo', error_msgs[3]),
+ ('/paramerrors/one_positional_kwargs/foo/bar',
+ 'param2=bar&param3=baz', error_msgs[1]),
+ ('/paramerrors/no_positional?param1=foo',
+ 'param2=foo', error_msgs[3]),
+ ('/paramerrors/no_positional_args/boo?param2=foo',
+ 'param1=foo', error_msgs[3]),
+ ('/paramerrors/callable_object?param2=bar',
+ 'param1=foo', error_msgs[3]),
+ ):
for show_mismatched_params in (True, False):
- cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
+ cherrypy.config.update(
+ {'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri, method='POST', body=body)
self.assertStatus(404)
if show_mismatched_params:
@@ -444,7 +484,7 @@ class RequestObjectTests(helper.CPWebCase):
'/paramerrors/raise_type_error',
'/paramerrors/raise_type_error_with_default_param?x=0',
'/paramerrors/raise_type_error_with_default_param?x=0&y=0',
- ):
+ ):
self.getPage(uri, method='GET')
self.assertStatus(500)
self.assertTrue('Client Error', self.body)
@@ -465,7 +505,7 @@ class RequestObjectTests(helper.CPWebCase):
self.assertErrorPage(500, pattern=valerr)
if (cherrypy.server.protocol_version == "HTTP/1.0" or
- getattr(cherrypy.server, "using_apache", False)):
+ getattr(cherrypy.server, "using_apache", False)):
self.getPage("/error/page_streamed")
# Because this error is raised after the response body has
# started, the status should not change to an error status.
@@ -496,20 +536,29 @@ class RequestObjectTests(helper.CPWebCase):
# Test custom error page for a specific error.
self.getPage("/error/custom?err=401")
self.assertStatus(401)
- self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!")
+ self.assertBody(
+ "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))
+ self.assertBody(
+ "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).
self.getPage("/error/noexist")
self.assertStatus(404)
+ if sys.version_info >= (3, 3):
+ exc_name = "FileNotFoundError"
+ else:
+ exc_name = "IOError"
msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />"
"In addition, the custom error page failed:\n<br />"
- "IOError: [Errno 2] No such file or directory: 'nonexistent.html'")
+ "%s: [Errno 2] "
+ "No such file or directory: 'nonexistent.html'") % (exc_name,)
self.assertInBody(msg)
if getattr(cherrypy.server, "using_apache", False):
@@ -535,7 +584,10 @@ class RequestObjectTests(helper.CPWebCase):
self.assertBody("audio/basic\n"
"audio/*;q=0.2")
- h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')]
+ h = [
+ ('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"
@@ -554,14 +606,16 @@ class RequestObjectTests(helper.CPWebCase):
# Test Accept-Charset
h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')]
- self.getPage("/headerelements/get_elements?headername=Accept-Charset", h)
+ self.getPage(
+ "/headerelements/get_elements?headername=Accept-Charset", h)
self.assertStatus("200 OK")
self.assertBody("iso-8859-5\n"
"unicode-1-1;q=0.8")
# Test Accept-Encoding
h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')]
- self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h)
+ self.getPage(
+ "/headerelements/get_elements?headername=Accept-Encoding", h)
self.assertStatus("200 OK")
self.assertBody("gzip;q=1.0\n"
"identity;q=0.5\n"
@@ -569,13 +623,15 @@ class RequestObjectTests(helper.CPWebCase):
# Test Accept-Language
h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')]
- self.getPage("/headerelements/get_elements?headername=Accept-Language", h)
+ self.getPage(
+ "/headerelements/get_elements?headername=Accept-Language", h)
self.assertStatus("200 OK")
self.assertBody("da\n"
"en-gb;q=0.8\n"
"en;q=0.7")
- # Test malformed header parsing. See https://bitbucket.org/cherrypy/cherrypy/issue/763.
+ # Test malformed header parsing. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/763.
self.getPage("/headerelements/get_elements?headername=Content-Type",
# Note the illegal trailing ";"
headers=[('Content-Type', 'text/html; charset=utf-8;')])
@@ -600,13 +656,15 @@ class RequestObjectTests(helper.CPWebCase):
def test_encoded_headers(self):
# First, make sure the innards work like expected.
- self.assertEqual(httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr"))
+ self.assertEqual(
+ httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr"))
if cherrypy.server.protocol_version == "HTTP/1.1":
# Test RFC-2047-encoded request and response header values
u = ntou('\u212bngstr\xf6m', 'escape')
c = ntou("=E2=84=ABngstr=C3=B6m")
- self.getPage("/headers/ifmatch", [('If-Match', ntou('=?utf-8?q?%s?=') % c)])
+ self.getPage("/headers/ifmatch",
+ [('If-Match', ntou('=?utf-8?q?%s?=') % c)])
# The body should be utf-8 encoded.
self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m"))
# But the Etag header should be RFC-2047 encoded (binary)
@@ -617,7 +675,8 @@ 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",
+ etag = self.assertHeader(
+ "ETag",
'=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
'4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
'4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
@@ -655,7 +714,7 @@ class RequestObjectTests(helper.CPWebCase):
# Request a PUT method with a form-urlencoded body
self.getPage("/method/parameterized", method="PUT",
- body="data=on+top+of+other+things")
+ body="data=on+top+of+other+things")
self.assertBody("on top of other things")
# Request a PUT method with a file body
@@ -698,7 +757,8 @@ class RequestObjectTests(helper.CPWebCase):
'</prop></propfind>')
h = [('Content-Type', 'text/xml'),
('Content-Length', str(len(b)))]
- self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b)
+ self.getPage("/method/request_body", headers=h,
+ method="PROPFIND", body=b)
self.assertStatus(200)
self.assertBody(b)
@@ -734,4 +794,3 @@ class RequestObjectTests(helper.CPWebCase):
self.getPage("/threadlocal/")
results.append(self.body)
self.assertEqual(results, [ntob("None")] * 20)
-
diff --git a/cherrypy/test/test_routes.py b/cherrypy/test/test_routes.py
index d6664880..41f744f9 100644
--- a/cherrypy/test/test_routes.py
+++ b/cherrypy/test/test_routes.py
@@ -6,6 +6,7 @@ import cherrypy
from cherrypy.test import helper
import nose
+
class RoutesDispatchTest(helper.CPWebCase):
def setup_server():
@@ -16,6 +17,7 @@ class RoutesDispatchTest(helper.CPWebCase):
raise nose.SkipTest('Install routes to test RoutesDispatcher code')
class Dummy:
+
def index(self):
return "I said good day!"
@@ -27,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']
@@ -37,8 +43,9 @@ class RoutesDispatchTest(helper.CPWebCase):
d = cherrypy.dispatch.RoutesDispatcher()
d.connect(action='index', name='hounslow', route='/hounslow',
controller=City('Hounslow'))
- d.connect(name='surbiton', route='/surbiton', controller=City('Surbiton'),
- action='index', conditions=dict(method=['GET']))
+ d.connect(
+ name='surbiton', route='/surbiton', controller=City('Surbiton'),
+ action='index', conditions=dict(method=['GET']))
d.mapper.connect('/surbiton', controller='surbiton',
action='update', conditions=dict(method=['POST']))
d.connect('main', ':action', controller=Dummy())
@@ -66,4 +73,3 @@ class RoutesDispatchTest(helper.CPWebCase):
self.assertStatus("200 OK")
self.assertHeader("Content-Language", "en-GB")
self.assertBody("Welcome to Surbiton, pop. 1327")
-
diff --git a/cherrypy/test/test_session.py b/cherrypy/test/test_session.py
index 4a9145da..4bea7f6f 100755
--- a/cherrypy/test/test_session.py
+++ b/cherrypy/test/test_session.py
@@ -51,7 +51,8 @@ def setup_server():
testStr.exposed = True
def setsessiontype(self, newtype):
- self.__class__._cp_config.update({'tools.sessions.storage_type': newtype})
+ self.__class__._cp_config.update(
+ {'tools.sessions.storage_type': newtype})
if hasattr(cherrypy, "session"):
del cherrypy.session
cls = getattr(sessions, newtype.title() + 'Session')
@@ -195,12 +196,14 @@ class SessionTest(helper.CPWebCase):
self.assertBody("done")
self.getPage('/delete', cookieset1)
self.assertBody("done")
- f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
+ f = lambda: [
+ x for x in os.listdir(localDir) if x.startswith('session-')]
self.assertEqual(f(), [])
# Wait for the cleanup thread to delete remaining session files
self.getPage('/')
- f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
+ f = lambda: [
+ x for x in os.listdir(localDir) if x.startswith('session-')]
self.assertNotEqual(f(), [])
time.sleep(2)
self.assertEqual(f(), [])
@@ -303,9 +306,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')
@@ -315,7 +319,8 @@ class SessionTest(helper.CPWebCase):
self.getPage('/clear')
self.getPage('/session_cookie')
# grab the cookie ID
- cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
+ cookie_parts = dict([p.strip().split('=')
+ for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
id1 = cookie_parts['temp']
@@ -323,7 +328,8 @@ class SessionTest(helper.CPWebCase):
# Send another request in the same "browser session".
self.getPage('/session_cookie', self.cookies)
- cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
+ cookie_parts = dict([p.strip().split('=')
+ for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
self.assertBody(id1)
@@ -332,13 +338,15 @@ class SessionTest(helper.CPWebCase):
# Simulate a browser close by just not sending the cookies
self.getPage('/session_cookie')
# grab the cookie ID
- cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
+ cookie_parts = dict([p.strip().split('=')
+ for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
# Assert a new id has been generated...
id2 = cookie_parts['temp']
self.assertNotEqual(id1, id2)
- self.assertEqual(set(sessions.RamSession.cache.keys()), set([id1, id2]))
+ self.assertEqual(set(sessions.RamSession.cache.keys()),
+ set([id1, id2]))
# Wait for the session.timeout on both sessions
time.sleep(2.5)
@@ -467,7 +475,8 @@ else:
def test_5_Error_paths(self):
self.getPage('/unknown/page')
- self.assertErrorPage(404, "The path '/unknown/page' was not found.")
+ self.assertErrorPage(
+ 404, "The path '/unknown/page' was not found.")
# Note: this path is *not* the same as above. The above
# takes a normal route through the session code; this one
diff --git a/cherrypy/test/test_sessionauthenticate.py b/cherrypy/test/test_sessionauthenticate.py
index d6cc3f43..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
@@ -35,7 +36,6 @@ class SessionAuthenticateTest(helper.CPWebCase):
cherrypy.tree.mount(Test())
setup_server = staticmethod(setup_server)
-
def testSessionAuthenticate(self):
# request a page and check for login form
self.getPage('/')
@@ -59,4 +59,3 @@ class SessionAuthenticateTest(helper.CPWebCase):
# verify we are logged out
self.getPage('/', self.cookies)
self.assertInBody('<form method="post" action="do_login">')
-
diff --git a/cherrypy/test/test_states.py b/cherrypy/test/test_states.py
index ee86a52d..d86e5cf5 100644
--- a/cherrypy/test/test_states.py
+++ b/cherrypy/test/test_states.py
@@ -1,13 +1,14 @@
-from cherrypy._cpcompat import BadStatusLine, ntob
import os
+import signal
+import socket
import sys
import time
-import signal
import unittest
-import socket
+import warnings
import cherrypy
import cherrypy.process.servers
+from cherrypy._cpcompat import BadStatusLine, ntob
from cherrypy.test import helper
engine = cherrypy.engine
@@ -48,8 +49,10 @@ class Dependency:
db_connection = Dependency(engine)
+
def setup_server():
class Root:
+
def index(self):
return "Hello World"
index.exposed = True
@@ -80,12 +83,13 @@ def setup_server():
cherrypy.config.update({
'environment': 'test_suite',
'engine.deadlock_poll_freq': 0.1,
- })
+ })
db_connection.subscribe()
# ------------ Enough helpers. Time for real live test cases. ------------ #
+
class ServerStateTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
@@ -245,8 +249,13 @@ class ServerStateTests(helper.CPWebCase):
engine.exit()
def test_4_Autoreload(self):
+ # If test_3 has not been executed, the server won't be stopped,
+ # so we'll have to do it.
+ if engine.state != engine.states.EXITING:
+ engine.exit()
+
# Start the demo script in a new process
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'))
p.write_conf(extra='test_case_name: "test_4_Autoreload"')
p.start(imports='cherrypy.test._test_states_demo')
try:
@@ -275,12 +284,17 @@ class ServerStateTests(helper.CPWebCase):
p.join()
def test_5_Start_Error(self):
+ # If test_3 has not been executed, the server won't be stopped,
+ # so we'll have to do it.
+ if engine.state != engine.states.EXITING:
+ engine.exit()
+
# If a process errors during start, it should stop the engine
# and exit with a non-zero exit code.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'),
wait=True)
p.write_conf(
- extra="""starterror: True
+ extra="""starterror: True
test_case_name: "test_5_Start_Error"
"""
)
@@ -290,6 +304,7 @@ test_case_name: "test_5_Start_Error"
class PluginTests(helper.CPWebCase):
+
def test_daemonize(self):
if os.name not in ['posix']:
return self.skip("skipped (not on posix) ")
@@ -298,12 +313,12 @@ class PluginTests(helper.CPWebCase):
# Spawn the process and wait, when this returns, the original process
# is finished. If it daemonized properly, we should still be able
# to access pages.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'),
wait=True, daemonize=True,
socket_host='127.0.0.1',
socket_port=8081)
p.write_conf(
- extra='test_case_name: "test_daemonize"')
+ extra='test_case_name: "test_daemonize"')
p.start(imports='cherrypy.test._test_states_demo')
try:
# Just get the pid of the daemonization process.
@@ -323,6 +338,7 @@ class PluginTests(helper.CPWebCase):
class SignalHandlingTests(helper.CPWebCase):
+
def test_SIGHUP_tty(self):
# When not daemonized, SIGHUP should shut down the server.
try:
@@ -331,9 +347,9 @@ class SignalHandlingTests(helper.CPWebCase):
return self.skip("skipped (no SIGHUP) ")
# Spawn the process.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'))
p.write_conf(
- extra='test_case_name: "test_SIGHUP_tty"')
+ extra='test_case_name: "test_SIGHUP_tty"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGHUP
os.kill(p.get_pid(), SIGHUP)
@@ -353,10 +369,10 @@ class SignalHandlingTests(helper.CPWebCase):
# Spawn the process and wait, when this returns, the original process
# is finished. If it daemonized properly, we should still be able
# to access pages.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'),
wait=True, daemonize=True)
p.write_conf(
- extra='test_case_name: "test_SIGHUP_daemonized"')
+ extra='test_case_name: "test_SIGHUP_daemonized"')
p.start(imports='cherrypy.test._test_states_demo')
pid = p.get_pid()
@@ -386,9 +402,9 @@ class SignalHandlingTests(helper.CPWebCase):
self._require_signal_and_kill('SIGTERM')
# Spawn a normal, undaemonized process.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'))
p.write_conf(
- extra='test_case_name: "test_SIGTERM"')
+ extra='test_case_name: "test_SIGTERM"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGTERM
os.kill(p.get_pid(), signal.SIGTERM)
@@ -397,10 +413,10 @@ class SignalHandlingTests(helper.CPWebCase):
if os.name in ['posix']:
# Spawn a daemonized process and test again.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'),
wait=True, daemonize=True)
p.write_conf(
- extra='test_case_name: "test_SIGTERM_2"')
+ extra='test_case_name: "test_SIGTERM_2"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGTERM
os.kill(p.get_pid(), signal.SIGTERM)
@@ -418,7 +434,7 @@ class SignalHandlingTests(helper.CPWebCase):
self.skip("SIGTERM not available")
# Spawn a normal, undaemonized process.
- p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
+ p = helper.CPProcess(ssl=(self.scheme.lower() == 'https'))
p.write_conf(
extra="""unsubsig: True
test_case_name: "test_signal_handler_unsubscribe"
@@ -434,7 +450,9 @@ test_case_name: "test_signal_handler_unsubscribe"
if not ntob("I am an old SIGTERM handler.") in target_line:
self.fail("Old SIGTERM handler did not run.\n%r" % target_line)
+
class WaitTests(unittest.TestCase):
+
def test_wait_for_occupied_port_INADDR_ANY(self):
"""
Wait on INADDR_ANY should not raise IOError
@@ -470,11 +488,17 @@ class WaitTests(unittest.TestCase):
def do_waiting():
# Wait on the free port that's unbound
- servers.wait_for_occupied_port('0.0.0.0', free_port)
+ with warnings.catch_warnings(record=True) as w:
+ servers.wait_for_occupied_port('0.0.0.0', free_port)
+ self.assertEqual(len(w), 1)
+ self.assertTrue(isinstance(w[0], warnings.WarningMessage))
+ self.assertTrue(
+ 'Unable to verify that the server is bound on ' in str(w[0]))
+
# The wait should still raise an IO error if INADDR_ANY was
# not supplied.
self.assertRaises(IOError, servers.wait_for_occupied_port,
- '127.0.0.1', free_port)
+ '127.0.0.1', free_port)
with_shorter_timeouts(do_waiting)
diff --git a/cherrypy/test/test_static.py b/cherrypy/test/test_static.py
index 8d6fd20c..0526844f 100644
--- a/cherrypy/test/test_static.py
+++ b/cherrypy/test/test_static.py
@@ -1,11 +1,17 @@
+import os
+import sys
+
from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob
from cherrypy._cpcompat import BytesIO
-import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
has_space_filepath = os.path.join(curdir, 'static', 'has space.html')
bigfile_filepath = os.path.join(curdir, "static", "bigfile.log")
-BIGFILE_SIZE = 1024 * 1024
+
+# The file size needs to be big enough such that half the size of it
+# won't be socket-buffered (or server-buffered) all in one go. See
+# test_file_stream.
+BIGFILE_SIZE = 1024 * 1024 * 4
import cherrypy
from cherrypy.lib import static
@@ -17,7 +23,8 @@ class StaticTest(helper.CPWebCase):
def setup_server():
if not os.path.exists(has_space_filepath):
open(has_space_filepath, 'wb').write(ntob('Hello, world\r\n'))
- if not os.path.exists(bigfile_filepath):
+ if not os.path.exists(bigfile_filepath) or \
+ os.path.getsize(bigfile_filepath) != BIGFILE_SIZE:
open(bigfile_filepath, 'wb').write(ntob("x" * BIGFILE_SIZE))
class Root:
@@ -55,7 +62,6 @@ class StaticTest(helper.CPWebCase):
return "This is a DYNAMIC page"
dynamic.exposed = True
-
root = Root()
root.static = Static()
@@ -79,7 +85,13 @@ class StaticTest(helper.CPWebCase):
'tools.staticdir.on': True,
'request.show_tracebacks': True,
},
+ '/404test': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.root': curdir,
+ 'tools.staticdir.dir': 'static',
+ 'error_page.404': error_page_404,
}
+ }
rootApp = cherrypy.Application(root)
rootApp.merge(rootconf)
@@ -89,8 +101,8 @@ class StaticTest(helper.CPWebCase):
'tools.staticdir.on': True,
'tools.staticdir.root': curdir,
'tools.staticdir.dir': 'static',
- },
- }
+ },
+ }
testApp = cherrypy.Application(Static())
testApp.merge(test_app_conf)
@@ -98,7 +110,6 @@ class StaticTest(helper.CPWebCase):
cherrypy.tree.graft(vhost)
setup_server = staticmethod(setup_server)
-
def teardown_server():
for f in (has_space_filepath, bigfile_filepath):
if os.path.exists(f):
@@ -108,7 +119,6 @@ class StaticTest(helper.CPWebCase):
pass
teardown_server = staticmethod(teardown_server)
-
def testStatic(self):
self.getPage("/static/index.html")
self.assertStatus('200 OK')
@@ -157,15 +167,20 @@ class StaticTest(helper.CPWebCase):
self.getPage("/docroot")
self.assertStatus(301)
self.assertHeader('Location', '%s/docroot/' % self.base())
- self.assertMatchesBody("This resource .* <a href='%s/docroot/'>"
+ self.assertMatchesBody("This resource .* <a href=(['\"])%s/docroot/\\1>"
"%s/docroot/</a>." % (self.base(), self.base()))
def test_config_errors(self):
# Check that we get an error if no .file or .dir
self.getPage("/error/thing.html")
self.assertErrorPage(500)
- self.assertMatchesBody(ntob("TypeError: staticdir\(\) takes at least 2 "
- "(positional )?arguments \(0 given\)"))
+ if sys.version_info >= (3, 3):
+ errmsg = ntob("TypeError: staticdir\(\) missing 2 "
+ "required positional arguments")
+ else:
+ errmsg = ntob("TypeError: staticdir\(\) takes at least 2 "
+ "(positional )?arguments \(0 given\)")
+ self.assertMatchesBody(errmsg)
def test_security(self):
# Test up-level security
@@ -246,22 +261,38 @@ class StaticTest(helper.CPWebCase):
else:
tell_position = int(b)
- expected = len(body)
+ read_so_far = len(body)
+
+ # It is difficult for us to force the server to only read
+ # the bytes that we ask for - there are going to be buffers
+ # inbetween.
+ #
+ # CherryPy will attempt to write as much data as it can to
+ # the socket, and we don't have a way to determine what that
+ # size will be. So we make the following assumption - by
+ # the time we have read in the entire file on the server,
+ # we will have at least received half of it. If this is not
+ # the case, then this is an indicator that either:
+ # - machines that are running this test are using buffer
+ # sizes greater than half of BIGFILE_SIZE; or
+ # - streaming is broken.
+ #
+ # At the time of writing, we seem to have encountered
+ # buffer sizes bigger than 512K, so we've increased
+ # BIGFILE_SIZE to 4MB.
if tell_position >= BIGFILE_SIZE:
- # 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):
+ if read_so_far < (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)
- elif tell_position < 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)" % read_so_far)
+ elif tell_position < read_so_far:
self.fail(
"The file should have advanced to position %r, but has "
"only advanced to position %r. It may not be streamed "
- "as intended, or at the wrong chunk size (65536)" %
- (expected, tell_position))
+ "as intended, or at the wrong chunk size (64k)" %
+ (read_so_far, tell_position))
if body != ntob("x" * BIGFILE_SIZE):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
@@ -296,3 +327,13 @@ class StaticTest(helper.CPWebCase):
if self.body != ntob("x" * BIGFILE_SIZE):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
(BIGFILE_SIZE, self.body[:50], len(body)))
+
+ def test_error_page_with_serve_file(self):
+ self.getPage("/404test/yunyeen")
+ self.assertStatus(404)
+ self.assertInBody("I couldn't find that thing")
+
+def error_page_404(status, message, traceback, version):
+ import os.path
+ return static.serve_file(os.path.join(curdir, 'static', '404.html'),
+ content_type='text/html')
diff --git a/cherrypy/test/test_tools.py b/cherrypy/test/test_tools.py
index 21ada1f7..4fb1a555 100644
--- a/cherrypy/test/test_tools.py
+++ b/cherrypy/test/test_tools.py
@@ -23,6 +23,7 @@ from cherrypy.test import helper
class ToolTests(helper.CPWebCase):
+
def setup_server():
# Put check_access in a custom toolbox with its own namespace
@@ -31,7 +32,8 @@ class ToolTests(helper.CPWebCase):
def check_access(default=False):
if not getattr(cherrypy.request, "userid", default):
raise cherrypy.HTTPError(401)
- myauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
+ myauthtools.check_access = cherrypy.Tool(
+ 'before_request_body', check_access)
def numerify():
def number_it(body):
@@ -42,6 +44,7 @@ class ToolTests(helper.CPWebCase):
cherrypy.response.body = number_it(cherrypy.response.body)
class NumTool(cherrypy.Tool):
+
def _setup(self):
def makemap():
m = self._merged_args().get("map", {})
@@ -49,7 +52,8 @@ class ToolTests(helper.CPWebCase):
cherrypy.request.hooks.attach('on_start_resource', makemap)
def critical():
- cherrypy.request.error_response = cherrypy.HTTPError(502).set_response
+ cherrypy.request.error_response = cherrypy.HTTPError(
+ 502).set_response
critical.failsafe = True
cherrypy.request.hooks.attach('on_start_resource', critical)
@@ -93,6 +97,7 @@ class ToolTests(helper.CPWebCase):
# Assert that we can use a callable object instead of a function.
class Rotator(object):
+
def __call__(self, scale):
r = cherrypy.response
r.collapse_body()
@@ -103,25 +108,30 @@ class ToolTests(helper.CPWebCase):
cherrypy.tools.rotator = cherrypy.Tool('before_finalize', Rotator())
def stream_handler(next_handler, *args, **kwargs):
+ assert cherrypy.request.config.get('tools.streamer.arg') == 'arg value'
cherrypy.response.output = o = BytesIO()
try:
response = next_handler(*args, **kwargs)
- # Ignore the response and return our accumulated output instead.
+ # Ignore the response and return our accumulated output
+ # instead.
return o.getvalue()
finally:
o.close()
- cherrypy.tools.streamer = cherrypy._cptools.HandlerWrapperTool(stream_handler)
+ cherrypy.tools.streamer = cherrypy._cptools.HandlerWrapperTool(
+ stream_handler)
class Root:
+
def index(self):
return "Howdy earth!"
index.exposed = True
def tarfile(self):
+ assert cherrypy.request.config.get('tools.streamer.arg') == 'arg value'
cherrypy.response.output.write(ntob('I am '))
cherrypy.response.output.write(ntob('a tarfile'))
tarfile.exposed = True
- tarfile._cp_config = {'tools.streamer.on': True}
+ tarfile._cp_config = {'tools.streamer.on': True, 'tools.streamer.arg': 'arg value'}
def euro(self):
hooks = list(cherrypy.request.hooks['before_finalize'])
@@ -154,8 +164,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)
@@ -186,7 +197,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!"
@@ -201,7 +213,6 @@ class ToolTests(helper.CPWebCase):
yield str(x)
stream._cp_config = {'response.stream': True}
-
conf = {
# METHOD THREE:
# Declare Tools in detached config
@@ -263,7 +274,7 @@ class ToolTests(helper.CPWebCase):
# If body is "razdrez", then on_end_request is being called too early.
if (cherrypy.server.protocol_version == "HTTP/1.0" or
- getattr(cherrypy.server, "using_apache", False)):
+ getattr(cherrypy.server, "using_apache", False)):
self.getPage("/demo/errinstream?id=5")
# Because this error is raised after the response body has
# started, the status should not change to an error status.
@@ -329,17 +340,21 @@ class ToolTests(helper.CPWebCase):
# but our 'critical' hook should run and set the error to 502.
self.getPage("/demo/err_in_onstart")
self.assertErrorPage(502)
- self.assertInBody("AttributeError: 'str' object has no attribute 'items'")
+ self.assertInBody(
+ "AttributeError: 'str' object has no attribute 'items'")
def testCombinedTools(self):
- expectedResult = (ntou("Hello,world") + europoundUnicode).encode('utf-8')
+ expectedResult = (ntou("Hello,world") +
+ europoundUnicode).encode('utf-8')
zbuf = BytesIO()
zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
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()
@@ -359,7 +374,8 @@ class ToolTests(helper.CPWebCase):
if py3k:
self.assertInBody(bytes([(x + 3) % 256 for x in zbuf.getvalue()]))
else:
- self.assertInBody(''.join([chr((ord(x) + 3) % 256) for x in zbuf.getvalue()]))
+ self.assertInBody(''.join([chr((ord(x) + 3) % 256)
+ for x in zbuf.getvalue()]))
def testBareHooks(self):
content = "bit of a pain in me gulliver"
@@ -397,7 +413,9 @@ class ToolTests(helper.CPWebCase):
else:
raise AssertionError("Tool.on did not error as it should have.")
+
class SessionAuthTest(unittest.TestCase):
+
def test_login_screen_returns_bytes(self):
"""
login_screen must return bytes even if unicode parameters are passed.
@@ -406,5 +424,5 @@ class SessionAuthTest(unittest.TestCase):
"""
sa = cherrypy.lib.cptools.SessionAuth()
res = sa.login_screen(None, username=unicodestr('nobody'),
- password=unicodestr('anypass'))
- self.assertIsInstance(res, bytestr)
+ password=unicodestr('anypass'))
+ self.assertTrue(isinstance(res, bytestr))
diff --git a/cherrypy/test/test_tutorials.py b/cherrypy/test/test_tutorials.py
index b92ad18c..ad89a9ea 100644
--- a/cherrypy/test/test_tutorials.py
+++ b/cherrypy/test/test_tutorials.py
@@ -45,7 +45,6 @@ class TutorialTest(helper.CPWebCase):
cherrypy.tree.mount(root)
setup_server = classmethod(setup_server)
-
def test01HelloWorld(self):
self.getPage("/load_tut_module/tut01_helloworld")
self.getPage("/")
@@ -53,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):
@@ -83,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>'''
@@ -124,22 +123,24 @@ 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")
self.getPage('/')
self.assertBody('<html><body><h2>Generators rule!</h2>'
- '<h3>List of users:</h3>'
- 'Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/>'
- '</body></html>')
+ '<h3>List of users:</h3>'
+ 'Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/>'
+ '</body></html>')
def test09Files(self):
self.getPage("/load_tut_module/tut09_files")
@@ -148,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>
@@ -201,4 +202,3 @@ class TutorialTest(helper.CPWebCase):
self.getPage("/messageArg")
self.assertStatus(500)
self.assertInBody("If you construct an HTTPError with a 'message'")
-
diff --git a/cherrypy/test/test_virtualhost.py b/cherrypy/test/test_virtualhost.py
index b2b940f5..7c3d8c71 100644
--- a/cherrypy/test/test_virtualhost.py
+++ b/cherrypy/test/test_virtualhost.py
@@ -9,6 +9,7 @@ class VirtualHostTest(helper.CPWebCase):
def setup_server():
class Root:
+
def index(self):
return "Hello, world"
index.exposed = True
@@ -22,6 +23,7 @@ class VirtualHostTest(helper.CPWebCase):
method.exposed = True
class VHost:
+
def __init__(self, sitename):
self.sitename = sitename
@@ -38,7 +40,8 @@ class VirtualHostTest(helper.CPWebCase):
url.exposed = True
# Test static as a handler (section must NOT include vhost prefix)
- static = cherrypy.tools.staticdir.handler(section='/static', dir=curdir)
+ static = cherrypy.tools.staticdir.handler(
+ section='/static', dir=curdir)
root = Root()
root.mydom2 = VHost("Domain 2")
@@ -48,14 +51,17 @@ 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)
def testVirtualHost(self):
diff --git a/cherrypy/test/test_wsgi_ns.py b/cherrypy/test/test_wsgi_ns.py
index 9bcb8dea..0df2365b 100644
--- a/cherrypy/test/test_wsgi_ns.py
+++ b/cherrypy/test/test_wsgi_ns.py
@@ -18,6 +18,7 @@ class WSGI_Namespace_Test(helper.CPWebCase):
def next(self):
return self.iter.next()
+
def __next__(self):
return next(self.iter)
@@ -25,7 +26,6 @@ class WSGI_Namespace_Test(helper.CPWebCase):
if hasattr(self.appresults, "close"):
self.appresults.close()
-
class ChangeCase(object):
def __init__(self, app, to=None):
@@ -34,9 +34,12 @@ class WSGI_Namespace_Test(helper.CPWebCase):
def __call__(self, environ, start_response):
res = self.app(environ, start_response)
+
class CaseResults(WSGIResponse):
+
def next(this):
return getattr(this.iter.next(), self.to)()
+
def __next__(this):
return getattr(next(this.iter), self.to)()
return CaseResults(res)
@@ -49,12 +52,15 @@ class WSGI_Namespace_Test(helper.CPWebCase):
def __call__(self, environ, start_response):
res = self.app(environ, start_response)
+
class ReplaceResults(WSGIResponse):
+
def next(this):
line = this.iter.next()
for k, v in self.map.iteritems():
line = line.replace(k, v)
return line
+
def __next__(this):
line = next(this.iter)
for k, v in self.map.items():
@@ -68,7 +74,6 @@ class WSGI_Namespace_Test(helper.CPWebCase):
return "HellO WoRlD!"
index.exposed = True
-
root_conf = {'wsgi.pipeline': [('replace', Replacer)],
'wsgi.replace.map': {ntob('L'): ntob('X'),
ntob('l'): ntob('r')},
@@ -80,7 +85,6 @@ class WSGI_Namespace_Test(helper.CPWebCase):
cherrypy.tree.mount(app, config={'/': root_conf})
setup_server = staticmethod(setup_server)
-
def test_pipeline(self):
if not cherrypy.server.httpserver:
return self.skip()
@@ -88,4 +92,3 @@ class WSGI_Namespace_Test(helper.CPWebCase):
self.getPage("/")
# If body is "HEXXO WORXD!", the middleware was applied out of order.
self.assertBody("HERRO WORRD!")
-
diff --git a/cherrypy/test/test_wsgi_vhost.py b/cherrypy/test/test_wsgi_vhost.py
index 1c559bef..a36a936c 100644
--- a/cherrypy/test/test_wsgi_vhost.py
+++ b/cherrypy/test/test_wsgi_vhost.py
@@ -15,7 +15,6 @@ class WSGI_VirtualHost_Test(helper.CPWebCase):
return "Welcome to the %s website!" % self.name
index.exposed = True
-
default = cherrypy.Application(None)
domains = {}
@@ -31,6 +30,6 @@ class WSGI_VirtualHost_Test(helper.CPWebCase):
return self.skip("skipped (not using WSGI)... ")
for year in range(1997, 2008):
- self.getPage("/", headers=[('Host', 'www.classof%s.example' % year)])
+ self.getPage(
+ "/", headers=[('Host', 'www.classof%s.example' % year)])
self.assertBody("Welcome to the Class of %s website!" % year)
-
diff --git a/cherrypy/test/test_wsgiapps.py b/cherrypy/test/test_wsgiapps.py
index a8bff630..ac65c1eb 100644
--- a/cherrypy/test/test_wsgiapps.py
+++ b/cherrypy/test/test_wsgiapps.py
@@ -1,3 +1,5 @@
+import sys
+
from cherrypy._cpcompat import ntob
from cherrypy.test import helper
@@ -19,15 +21,16 @@ class WSGIGraftTests(helper.CPWebCase):
keys = list(environ.keys())
keys.sort()
for k in keys:
- output.append('%s: %s\n' % (k,environ[k]))
+ output.append('%s: %s\n' % (k, environ[k]))
return [ntob(x, 'utf-8') for x in output]
def test_empty_string_app(environ, start_response):
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):
@@ -38,16 +41,17 @@ class WSGIGraftTests(helper.CPWebCase):
def __iter__(self):
return self
- def next(self):
- return self.iter.next()
- def __next__(self):
- return next(self.iter)
+ if sys.version_info >= (3, 0):
+ def __next__(self):
+ return next(self.iter)
+ else:
+ def next(self):
+ return self.iter.next()
def close(self):
if hasattr(self.appresults, "close"):
self.appresults.close()
-
class ReversingMiddleware(object):
def __init__(self, app):
@@ -55,23 +59,28 @@ class WSGIGraftTests(helper.CPWebCase):
def __call__(self, environ, start_response):
results = app(environ, start_response)
+
class Reverser(WSGIResponse):
- def next(this):
- line = list(this.iter.next())
- line.reverse()
- return "".join(line)
- def __next__(this):
- line = list(next(this.iter))
- line.reverse()
- return bytes(line)
+
+ if sys.version_info >= (3, 0):
+ def __next__(this):
+ line = list(next(this.iter))
+ line.reverse()
+ return bytes(line)
+ else:
+ def next(this):
+ line = list(this.iter.next())
+ line.reverse()
+ return "".join(line)
+
return Reverser(results)
class Root:
+
def index(self):
return ntob("I'm a regular CherryPy page handler!")
index.exposed = True
-
cherrypy.tree.mount(Root())
cherrypy.tree.graft(test_app, '/hosted/app1')
@@ -115,4 +124,3 @@ This is a wsgi app running within CherryPy!'''
self.getPage("/hosted/app3")
self.assertHeader("Content-Type", "text/plain")
self.assertInBody('Hello world')
-
diff --git a/cherrypy/test/test_xmlrpc.py b/cherrypy/test/test_xmlrpc.py
index 90f28e9c..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
@@ -15,7 +17,9 @@ if py3k:
socket.ssl = True
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
@@ -50,11 +54,11 @@ def setup_server():
from cherrypy import _cptools
class Root:
+
def index(self):
return "I'm a standard index!"
index.exposed = True
-
class XmlRpc(_cptools.XMLRPCController):
def foo(self):
@@ -78,7 +82,7 @@ def setup_server():
return_dict.exposed = True
def return_composite(self):
- return dict(a=1,z=26), 'hi', ['welcome', 'friend']
+ return dict(a=1, z=26), 'hi', ['welcome', 'friend']
return_composite.exposed = True
def return_int(self):
@@ -110,13 +114,15 @@ def setup_server():
cherrypy.tree.mount(root, config={'/': {
'request.dispatch': cherrypy.dispatch.XMLRPCDispatcher(),
'tools.xmlrpc.allow_none': 0,
- }})
+ }})
from cherrypy.test import helper
+
class XmlRpcTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
+
def testXmlRpc(self):
scheme = self.scheme
@@ -134,7 +140,8 @@ class XmlRpcTest(helper.CPWebCase):
self.assertEqual(proxy.return_single_item_list(), [42])
self.assertNotEqual(proxy.return_single_item_list(), 'one bazillion')
self.assertEqual(proxy.return_string(), "here is a string")
- self.assertEqual(proxy.return_tuple(), list(('here', 'is', 1, 'tuple')))
+ self.assertEqual(proxy.return_tuple(),
+ list(('here', 'is', 1, 'tuple')))
self.assertEqual(proxy.return_dict(), {'a': 1, 'c': 3, 'b': 2})
self.assertEqual(proxy.return_composite(),
[{'a': 1, 'z': 26}, 'hi', ['welcome', 'friend']])
@@ -163,7 +170,8 @@ class XmlRpcTest(helper.CPWebCase):
except Exception:
x = sys.exc_info()[1]
self.assertEqual(x.__class__, Fault)
- self.assertEqual(x.faultString, 'method "non_method" is not supported')
+ self.assertEqual(x.faultString,
+ 'method "non_method" is not supported')
else:
self.fail("Expected xmlrpclib.Fault")
@@ -176,4 +184,3 @@ class XmlRpcTest(helper.CPWebCase):
self.assertEqual(x.faultString, ("custom Fault response"))
else:
self.fail("Expected xmlrpclib.Fault")
-
diff --git a/cherrypy/test/webtest.py b/cherrypy/test/webtest.py
index 32ea6413..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):
@@ -56,6 +57,7 @@ class TerseTestResult(_TextTestResult):
class TerseTestRunner(TextTestRunner):
+
"""A test runner class that displays results in textual form."""
def _makeResult(self):
@@ -73,7 +75,8 @@ class TerseTestRunner(TextTestRunner):
if failed:
self.stream.write("failures=%d" % failed)
if errored:
- if failed: self.stream.write(", ")
+ if failed:
+ self.stream.write(", ")
self.stream.write("errors=%d" % errored)
self.stream.writeln(")")
return result
@@ -109,7 +112,7 @@ class ReloadingTestLoader(TestLoader):
parts = unused_parts
break
except ImportError:
- unused_parts.insert(0,parts_copy[-1])
+ unused_parts.insert(0, parts_copy[-1])
del parts_copy[-1]
if not parts_copy:
raise
@@ -118,13 +121,13 @@ class ReloadingTestLoader(TestLoader):
for part in parts:
obj = getattr(obj, part)
- if type(obj) == types.ModuleType:
+ if isinstance(obj, types.ModuleType):
return self.loadTestsFromModule(obj)
elif (((py3k and isinstance(obj, type))
or isinstance(obj, (type, types.ClassType)))
and issubclass(obj, TestCase)):
return self.loadTestsFromTestCase(obj)
- elif type(obj) == types.UnboundMethodType:
+ elif isinstance(obj, types.UnboundMethodType):
if py3k:
return obj.__self__.__class__(obj.__name__)
else:
@@ -134,7 +137,7 @@ class ReloadingTestLoader(TestLoader):
if not isinstance(test, TestCase) and \
not isinstance(test, TestSuite):
raise ValueError("calling %s returned %s, "
- "not a test" % (obj,test))
+ "not a test" % (obj, test))
return test
else:
raise ValueError("do not know how to make test from: %s" % obj)
@@ -149,11 +152,14 @@ try:
else:
# On Windows, msvcrt.getch reads a single char without output.
import msvcrt
+
def getchar():
return msvcrt.getch()
except ImportError:
# Unix getchr
- import tty, termios
+ import tty
+ import termios
+
def getchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
@@ -219,6 +225,7 @@ class WebCase(TestCase):
def _get_persistent(self):
return hasattr(self.HTTP_CONN, "__class__")
+
def _set_persistent(self, on):
self.set_persistent(on)
persistent = property(_get_persistent, _set_persistent)
@@ -230,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):
@@ -265,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:
@@ -388,7 +399,8 @@ class WebCase(TestCase):
value = value.encode(self.encoding)
if value != self.body:
if msg is None:
- msg = 'expected body:\n%r\n\nactual body:\n%r' % (value, self.body)
+ msg = 'expected body:\n%r\n\nactual body:\n%r' % (
+ value, self.body)
self._handlewebError(msg)
def assertInBody(self, value, msg=None):
@@ -421,6 +433,7 @@ class WebCase(TestCase):
methods_with_bodies = ("POST", "PUT")
+
def cleanHeaders(headers, method, body, host, port):
"""Return request headers, with required headers added (if missing)."""
if headers is None:
@@ -447,7 +460,8 @@ def cleanHeaders(headers, method, body, host, port):
found = True
break
if not found:
- headers.append(("Content-Type", "application/x-www-form-urlencoded"))
+ headers.append(
+ ("Content-Type", "application/x-www-form-urlencoded"))
headers.append(("Content-Length", str(len(body or ""))))
return headers
@@ -503,7 +517,8 @@ def openURL(url, headers=None, method="GET", body=None,
return
self.__class__.putheader(self, header, value)
import new
- conn.putheader = new.instancemethod(putheader, conn, conn.__class__)
+ conn.putheader = new.instancemethod(
+ putheader, conn, conn.__class__)
conn.putrequest(method.upper(), url, skip_host=True)
elif not py3k:
conn.putrequest(method.upper(), url, skip_host=True,
@@ -511,20 +526,27 @@ def openURL(url, headers=None, method="GET", body=None,
else:
import http.client
# 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)
@@ -563,6 +585,7 @@ ignored_exceptions = []
# for example, when handling requests via multiple threads.
ignore_all = False
+
class ServerError(Exception):
on = False
diff --git a/cherrypy/tutorial/bonus-sqlobject.py b/cherrypy/tutorial/bonus-sqlobject.py
deleted file mode 100644
index c43feb45..00000000
--- a/cherrypy/tutorial/bonus-sqlobject.py
+++ /dev/null
@@ -1,168 +0,0 @@
-'''
-Bonus Tutorial: Using SQLObject
-
-This is a silly little contacts manager application intended to
-demonstrate how to use SQLObject from within a CherryPy2 project. It
-also shows how to use inline Cheetah templates.
-
-SQLObject is an Object/Relational Mapper that allows you to access
-data stored in an RDBMS in a pythonic fashion. You create data objects
-as Python classes and let SQLObject take care of all the nasty details.
-
-This code depends on the latest development version (0.6+) of SQLObject.
-You can get it from the SQLObject Subversion server. You can find all
-necessary information at <http://www.sqlobject.org>. This code will NOT
-work with the 0.5.x version advertised on their website!
-
-This code also depends on a recent version of Cheetah. You can find
-Cheetah at <http://www.cheetahtemplate.org>.
-
-After starting this application for the first time, you will need to
-access the /reset URI in order to create the database table and some
-sample data. Accessing /reset again will drop and re-create the table,
-so you may want to be careful. :-)
-
-This application isn't supposed to be fool-proof, it's not even supposed
-to be very GOOD. Play around with it some, browse the source code, smile.
-
-:)
-
--- Hendrik Mans <hendrik@mans.de>
-'''
-
-import cherrypy
-from Cheetah.Template import Template
-from sqlobject import *
-
-# configure your database connection here
-__connection__ = 'mysql://root:@localhost/test'
-
-# this is our (only) data class.
-class Contact(SQLObject):
- lastName = StringCol(length = 50, notNone = True)
- firstName = StringCol(length = 50, notNone = True)
- phone = StringCol(length = 30, notNone = True, default = '')
- email = StringCol(length = 30, notNone = True, default = '')
- url = StringCol(length = 100, notNone = True, default = '')
-
-
-class ContactManager:
- def index(self):
- # Let's display a list of all stored contacts.
- contacts = Contact.select()
-
- template = Template('''
- <h2>All Contacts</h2>
-
- #for $contact in $contacts
- <a href="mailto:$contact.email">$contact.lastName, $contact.firstName</a>
- [<a href="./edit?id=$contact.id">Edit</a>]
- [<a href="./delete?id=$contact.id">Delete</a>]
- <br/>
- #end for
-
- <p>[<a href="./edit">Add new contact</a>]</p>
- ''', [locals(), globals()])
-
- return template.respond()
-
- index.exposed = True
-
-
- def edit(self, id = 0):
- # we really want id as an integer. Since GET/POST parameters
- # are always passed as strings, let's convert it.
- id = int(id)
-
- if id > 0:
- # if an id is specified, we're editing an existing contact.
- contact = Contact.get(id)
- title = "Edit Contact"
- else:
- # if no id is specified, we're entering a new contact.
- contact = None
- title = "New Contact"
-
-
- # In the following template code, please note that we use
- # Cheetah's $getVar() construct for the form values. We have
- # to do this because contact may be set to None (see above).
- template = Template('''
- <h2>$title</h2>
-
- <form action="./store" method="POST">
- <input type="hidden" name="id" value="$id" />
- Last Name: <input name="lastName" value="$getVar('contact.lastName', '')" /><br/>
- First Name: <input name="firstName" value="$getVar('contact.firstName', '')" /><br/>
- Phone: <input name="phone" value="$getVar('contact.phone', '')" /><br/>
- Email: <input name="email" value="$getVar('contact.email', '')" /><br/>
- URL: <input name="url" value="$getVar('contact.url', '')" /><br/>
- <input type="submit" value="Store" />
- </form>
- ''', [locals(), globals()])
-
- return template.respond()
-
- edit.exposed = True
-
-
- def delete(self, id):
- # Delete the specified contact
- contact = Contact.get(int(id))
- contact.destroySelf()
- return 'Deleted. <a href="./">Return to Index</a>'
-
- delete.exposed = True
-
-
- def store(self, lastName, firstName, phone, email, url, id = None):
- if id and int(id) > 0:
- # If an id was specified, update an existing contact.
- contact = Contact.get(int(id))
-
- # We could set one field after another, but that would
- # cause multiple UPDATE clauses. So we'll just do it all
- # in a single pass through the set() method.
- contact.set(
- lastName = lastName,
- firstName = firstName,
- phone = phone,
- email = email,
- url = url)
- else:
- # Otherwise, add a new contact.
- contact = Contact(
- lastName = lastName,
- firstName = firstName,
- phone = phone,
- email = email,
- url = url)
-
- return 'Stored. <a href="./">Return to Index</a>'
-
- store.exposed = True
-
-
- def reset(self):
- # Drop existing table
- Contact.dropTable(True)
-
- # Create new table
- Contact.createTable()
-
- # Create some sample data
- Contact(
- firstName = 'Hendrik',
- lastName = 'Mans',
- email = 'hendrik@mans.de',
- phone = '++49 89 12345678',
- url = 'http://www.mornography.de')
-
- return "reset completed!"
-
- reset.exposed = True
-
-
-print("If you're running this application for the first time, please go to http://localhost:8080/reset once in order to create the database!")
-
-cherrypy.quickstart(ContactManager())
diff --git a/cherrypy/tutorial/tut01_helloworld.py b/cherrypy/tutorial/tut01_helloworld.py
index ef947601..2fa61afa 100644
--- a/cherrypy/tutorial/tut01_helloworld.py
+++ b/cherrypy/tutorial/tut01_helloworld.py
@@ -7,7 +7,9 @@ The most basic (working) CherryPy application possible.
# Import CherryPy global namespace
import cherrypy
+
class HelloWorld:
+
""" Sample request handler class. """
def index(self):
diff --git a/cherrypy/tutorial/tut02_expose_methods.py b/cherrypy/tutorial/tut02_expose_methods.py
index c17e9d29..64a10f26 100644
--- a/cherrypy/tutorial/tut02_expose_methods.py
+++ b/cherrypy/tutorial/tut02_expose_methods.py
@@ -7,17 +7,18 @@ handler.
import cherrypy
+
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/tut03_get_and_post.py b/cherrypy/tutorial/tut03_get_and_post.py
index 6163b42a..a487a909 100644
--- a/cherrypy/tutorial/tut03_get_and_post.py
+++ b/cherrypy/tutorial/tut03_get_and_post.py
@@ -19,7 +19,7 @@ class WelcomePage:
</form>'''
index.exposed = True
- def greetUser(self, name = None):
+ def greetUser(self, name=None):
# CherryPy passes all GET and POST variables as method parameters.
# It doesn't make a difference where the variables come from, how
# large their contents are, and so on.
diff --git a/cherrypy/tutorial/tut04_complex_site.py b/cherrypy/tutorial/tut04_complex_site.py
index 9ced38dd..e0660024 100644
--- a/cherrypy/tutorial/tut04_complex_site.py
+++ b/cherrypy/tutorial/tut04_complex_site.py
@@ -9,6 +9,7 @@ import cherrypy
class HomePage:
+
def index(self):
return '''
<p>Hi, this is the home page! Check out the other
@@ -22,6 +23,7 @@ class HomePage:
class JokePage:
+
def index(self):
return '''
<p>"In Python, how do you create a string of random
@@ -31,6 +33,7 @@ class JokePage:
class LinksPage:
+
def __init__(self):
# Request handler objects can create their own nested request
# handler objects. Simply create them inside their __init__
@@ -46,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
@@ -59,6 +66,7 @@ class LinksPage:
class ExtraLinksPage:
+
def index(self):
# Note the relative link back to the Links page!
return '''
@@ -66,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>'''
@@ -95,4 +103,3 @@ if __name__ == '__main__':
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(root, config=tutconf)
-
diff --git a/cherrypy/tutorial/tut05_derived_objects.py b/cherrypy/tutorial/tut05_derived_objects.py
index 2649bc6d..9f4f2c32 100644
--- a/cherrypy/tutorial/tut05_derived_objects.py
+++ b/cherrypy/tutorial/tut05_derived_objects.py
@@ -80,4 +80,3 @@ if __name__ == '__main__':
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HomePage(), config=tutconf)
-
diff --git a/cherrypy/tutorial/tut06_default_method.py b/cherrypy/tutorial/tut06_default_method.py
index 3a4bd8ef..f71c315a 100644
--- a/cherrypy/tutorial/tut06_default_method.py
+++ b/cherrypy/tutorial/tut06_default_method.py
@@ -61,4 +61,3 @@ if __name__ == '__main__':
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(UsersPage(), config=tutconf)
-
diff --git a/cherrypy/tutorial/tut07_sessions.py b/cherrypy/tutorial/tut07_sessions.py
index 26c3658b..62efa440 100644
--- a/cherrypy/tutorial/tut07_sessions.py
+++ b/cherrypy/tutorial/tut07_sessions.py
@@ -41,4 +41,3 @@ if __name__ == '__main__':
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HitCounter(), config=tutconf)
-
diff --git a/cherrypy/tutorial/tut08_generators_and_yield.py b/cherrypy/tutorial/tut08_generators_and_yield.py
index 2b33a9a7..40566e32 100644
--- a/cherrypy/tutorial/tut08_generators_and_yield.py
+++ b/cherrypy/tutorial/tut08_generators_and_yield.py
@@ -44,4 +44,3 @@ if __name__ == '__main__':
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(GeneratorDemo(), config=tutconf)
-
diff --git a/cherrypy/tutorial/tut10_http_errors.py b/cherrypy/tutorial/tut10_http_errors.py
index d8352e38..c0e9893b 100644
--- a/cherrypy/tutorial/tut10_http_errors.py
+++ b/cherrypy/tutorial/tut10_http_errors.py
@@ -18,7 +18,8 @@ import cherrypy
class HTTPErrorDemo(object):
# Set a custom response for 403 errors.
- _cp_config = {'error_page.403' : os.path.join(curpath, "custom_error.html")}
+ _cp_config = {'error_page.403':
+ os.path.join(curpath, "custom_error.html")}
def index(self):
# display some links that will result in errors
@@ -32,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>
@@ -57,7 +62,7 @@ class HTTPErrorDemo(object):
def error(self, code):
# raise an error based on the get query
- raise cherrypy.HTTPError(status = code)
+ raise cherrypy.HTTPError(status=code)
error.exposed = True
def messageArg(self):
diff --git a/cherrypy/wsgiserver/ssl_builtin.py b/cherrypy/wsgiserver/ssl_builtin.py
index 7148dfda..2c74ad84 100644
--- a/cherrypy/wsgiserver/ssl_builtin.py
+++ b/cherrypy/wsgiserver/ssl_builtin.py
@@ -25,6 +25,7 @@ from cherrypy import wsgiserver
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
+
"""A wrapper for integrating Python's builtin ssl module with CherryPy."""
certificate = None
@@ -48,8 +49,9 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
"""Wrap and return the given socket, plus WSGI environ entries."""
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)
+ server_side=True, certfile=self.certificate,
+ keyfile=self.private_key,
+ ssl_version=ssl.PROTOCOL_SSLv23)
except ssl.SSLError:
e = sys.exc_info()[1]
if e.errno == ssl.SSL_ERROR_EOF:
@@ -77,9 +79,9 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
"HTTPS": "on",
'SSL_PROTOCOL': cipher[1],
'SSL_CIPHER': cipher[0]
-## SSL_VERSION_INTERFACE string The mod_ssl program version
-## SSL_VERSION_LIBRARY string The OpenSSL program version
- }
+ # SSL_VERSION_INTERFACE string The mod_ssl program version
+ # SSL_VERSION_LIBRARY string The OpenSSL program version
+ }
return ssl_environ
if sys.version_info >= (3, 0):
@@ -88,4 +90,3 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
else:
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
return wsgiserver.CP_fileobject(sock, mode, bufsize)
-
diff --git a/cherrypy/wsgiserver/ssl_pyopenssl.py b/cherrypy/wsgiserver/ssl_pyopenssl.py
index 6cfebdc6..f8f2dafe 100644
--- a/cherrypy/wsgiserver/ssl_pyopenssl.py
+++ b/cherrypy/wsgiserver/ssl_pyopenssl.py
@@ -44,6 +44,7 @@ except ImportError:
class SSL_fileobject(wsgiserver.CP_fileobject):
+
"""SSL file object attached to a socket object."""
ssl_timeout = 3
@@ -109,6 +110,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
class SSLConnection:
+
"""A thread-safe wrapper for an SSL.Connection.
``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
@@ -144,6 +146,7 @@ class SSLConnection:
class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
+
"""A wrapper for integrating pyOpenSSL with CherryPy."""
context = None
@@ -198,11 +201,11 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
ssl_environ = {
"HTTPS": "on",
# pyOpenSSL doesn't provide access to any of these AFAICT
-## 'SSL_PROTOCOL': 'SSLv2',
-## SSL_CIPHER string The cipher specification name
-## SSL_VERSION_INTERFACE string The mod_ssl program version
-## SSL_VERSION_LIBRARY string The OpenSSL program version
- }
+ # 'SSL_PROTOCOL': 'SSLv2',
+ # SSL_CIPHER string The cipher specification name
+ # SSL_VERSION_INTERFACE string The mod_ssl program version
+ # SSL_VERSION_LIBRARY string The OpenSSL program version
+ }
if self.certificate:
# Server certificate attributes
@@ -211,9 +214,11 @@ 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()),
("S", cert.get_subject())]:
@@ -246,4 +251,3 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
return f
else:
return wsgiserver.CP_fileobject(sock, mode, bufsize)
-
diff --git a/cherrypy/wsgiserver/wsgiserver2.py b/cherrypy/wsgiserver/wsgiserver2.py
index f2d53757..b64bf8b4 100644
--- a/cherrypy/wsgiserver/wsgiserver2.py
+++ b/cherrypy/wsgiserver/wsgiserver2.py
@@ -97,11 +97,23 @@ except ImportError:
import StringIO
DEFAULT_BUFFER_SIZE = -1
-_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
+
+class FauxSocket(object):
+
+ """Faux socket with the minimal interface required by pypy"""
+
+ def _reuse(self):
+ pass
+
+_fileobject_uses_str_type = isinstance(
+ socket._fileobject(FauxSocket())._rbuf, basestring)
+del FauxSocket # this class is not longer required for anything.
import threading
import time
import traceback
+
+
def format_exc(limit=None):
"""Like print_exc() but return a string. Backport for Python 2.3."""
try:
@@ -119,16 +131,22 @@ if sys.version_info >= (3, 0):
bytestr = bytes
unicodestr = str
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:
bytestr = str
unicodestr = unicode
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.
@@ -149,6 +167,7 @@ quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
+
def plat_specific_errors(*errnames):
"""Return error numbers for all errors in errnames on this platform.
@@ -173,24 +192,27 @@ socket_errors_to_ignore = plat_specific_errors(
"ECONNABORTED", "WSAECONNABORTED",
"ENETRESET", "WSAENETRESET",
"EHOSTDOWN", "EHOSTUNREACH",
- )
+)
socket_errors_to_ignore.append("timed out")
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
+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']]
+ 'WWW-Authenticate']
+]
import logging
-if not hasattr(logging, 'statistics'): logging.statistics = {}
+if not hasattr(logging, 'statistics'):
+ logging.statistics = {}
def read_headers(rfile, hdict=None):
@@ -245,7 +267,9 @@ def read_headers(rfile, hdict=None):
class MaxSizeExceeded(Exception):
pass
+
class SizeCheckWrapper(object):
+
"""Wraps a file-like object, raising MaxSizeExceeded if too large."""
def __init__(self, rfile, maxlen):
@@ -315,6 +339,7 @@ class SizeCheckWrapper(object):
class KnownLengthRFile(object):
+
"""Wraps a file-like object, returning an empty string when exhausted."""
def __init__(self, rfile, content_length):
@@ -371,6 +396,7 @@ class KnownLengthRFile(object):
class ChunkedRFile(object):
+
"""Wraps a file-like object, returning an empty string when exhausted.
This class is intended to provide a conforming wsgi.input value for
@@ -420,8 +446,8 @@ class ChunkedRFile(object):
crlf = self.rfile.read(2)
if crlf != CRLF:
raise ValueError(
- "Bad chunked transfer coding (expected '\\r\\n', "
- "got " + repr(crlf) + ")")
+ "Bad chunked transfer coding (expected '\\r\\n', "
+ "got " + repr(crlf) + ")")
def read(self, size=None):
data = EMPTY
@@ -523,6 +549,7 @@ class ChunkedRFile(object):
class HTTPRequest(object):
+
"""An HTTP Request (and response).
A single HTTP connection may consist of multiple request/response pairs.
@@ -556,7 +583,7 @@ class HTTPRequest(object):
This value is set automatically inside send_headers."""
def __init__(self, server, conn):
- self.server= server
+ self.server = server
self.conn = conn
self.ready = False
@@ -582,7 +609,8 @@ class HTTPRequest(object):
try:
success = self.read_request_line()
except MaxSizeExceeded:
- self.simple_response("414 Request-URI Too Long",
+ self.simple_response(
+ "414 Request-URI Too Long",
"The Request-URI sent with the request exceeds the maximum "
"allowed bytes.")
return
@@ -593,7 +621,8 @@ class HTTPRequest(object):
try:
success = self.read_request_headers()
except MaxSizeExceeded:
- self.simple_response("413 Request Entity Too Large",
+ self.simple_response(
+ "413 Request Entity Too Large",
"The headers sent with the request exceed the maximum "
"allowed bytes.")
return
@@ -629,7 +658,8 @@ class HTTPRequest(object):
return False
if not request_line.endswith(CRLF):
- self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
+ self.simple_response(
+ "400 Bad Request", "HTTP requires CRLF terminators")
return False
try:
@@ -712,7 +742,8 @@ 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",
+ self.simple_response(
+ "413 Request Entity Too Large",
"The entity sent with the request exceeds the maximum "
"allowed bytes.")
return False
@@ -766,7 +797,8 @@ class HTTPRequest(object):
# but it seems like it would be a big slowdown for such a rare case.
if self.inheaders.get("Expect", "") == "100-continue":
# Don't use simple_response here, because it emits headers
- # we don't want. See https://bitbucket.org/cherrypy/cherrypy/issue/951
+ # we don't want. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/951
msg = self.server.protocol + " 100 Continue\r\n\r\n"
try:
self.conn.wfile.sendall(msg)
@@ -803,7 +835,8 @@ class HTTPRequest(object):
if i > 0 and QUESTION_MARK not in uri[:i]:
# An absoluteURI.
# If there's a scheme (and it must be http or https), then:
- # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
+ # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query
+ # ]]
scheme, remainder = uri[:i].lower(), uri[i + 3:]
authority, path = remainder.split(FORWARD_SLASH, 1)
path = FORWARD_SLASH + path
@@ -825,7 +858,8 @@ 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",
+ self.simple_response(
+ "413 Request Entity Too Large",
"The entity sent with the request exceeds the maximum "
"allowed bytes.")
return
@@ -900,7 +934,7 @@ class HTTPRequest(object):
pass
else:
if (self.response_protocol == 'HTTP/1.1'
- and self.method != 'HEAD'):
+ and self.method != 'HEAD'):
# Use the chunked transfer-coding
self.chunked_write = True
self.outheaders.append(("Transfer-Encoding", "chunked"))
@@ -949,16 +983,19 @@ class HTTPRequest(object):
class NoSSLError(Exception):
+
"""Exception raised when a client speaks HTTP to an HTTPS socket."""
pass
class FatalSSLAlert(Exception):
+
"""Exception raised when the SSL implementation signals a fatal alert."""
pass
class CP_fileobject(socket._fileobject):
+
"""Faux file object attached to a socket object."""
def __init__(self, *args, **kwargs):
@@ -995,23 +1032,26 @@ class CP_fileobject(socket._fileobject):
return data
except socket.error, e:
if (e.args[0] not in socket_errors_nonblocking
- and e.args[0] not in socket_error_eintr):
+ and e.args[0] not in socket_error_eintr):
raise
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:
# Read until EOF
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ # reset _rbuf. we consume it via buf.
+ self._rbuf = StringIO.StringIO()
while True:
data = self.recv(rbufsize)
if not data:
@@ -1022,14 +1062,16 @@ class CP_fileobject(socket._fileobject):
# Read until size bytes or EOF seen, whichever comes first
buf_len = buf.tell()
if buf_len >= size:
- # Already have size bytes in our buffer? Extract and return.
+ # Already have size bytes in our buffer? Extract and
+ # return.
buf.seek(0)
rv = buf.read(size)
self._rbuf = StringIO.StringIO()
self._rbuf.write(buf.read())
return rv
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ # reset _rbuf. we consume it via buf.
+ self._rbuf = StringIO.StringIO()
while True:
left = size - buf_len
# recv() will malloc the amount of memory given as its
@@ -1077,7 +1119,8 @@ class CP_fileobject(socket._fileobject):
# Speed up unbuffered case
buf.seek(0)
buffers = [buf.read()]
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ # reset _rbuf. we consume it via buf.
+ self._rbuf = StringIO.StringIO()
data = None
recv = self.recv
while data != "\n":
@@ -1088,7 +1131,8 @@ class CP_fileobject(socket._fileobject):
return "".join(buffers)
buf.seek(0, 2) # seek end
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ # reset _rbuf. we consume it via buf.
+ self._rbuf = StringIO.StringIO()
while True:
data = self.recv(self._rbufsize)
if not data:
@@ -1103,7 +1147,8 @@ class CP_fileobject(socket._fileobject):
buf.write(data)
return buf.getvalue()
else:
- # Read until size bytes or \n or EOF seen, whichever comes first
+ # Read until size bytes or \n or EOF seen, whichever comes
+ # first
buf.seek(0, 2) # seek end
buf_len = buf.tell()
if buf_len >= size:
@@ -1112,7 +1157,8 @@ class CP_fileobject(socket._fileobject):
self._rbuf = StringIO.StringIO()
self._rbuf.write(buf.read())
return rv
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ # reset _rbuf. we consume it via buf.
+ self._rbuf = StringIO.StringIO()
while True:
data = self.recv(self._rbufsize)
if not data:
@@ -1128,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:
@@ -1223,7 +1269,8 @@ class CP_fileobject(socket._fileobject):
break
return "".join(buffers)
else:
- # Read until size bytes or \n or EOF seen, whichever comes first
+ # Read until size bytes or \n or EOF seen, whichever comes
+ # first
nl = data.find('\n', 0, size)
if nl >= 0:
nl += 1
@@ -1259,6 +1306,7 @@ class CP_fileobject(socket._fileobject):
class HTTPConnection(object):
+
"""An HTTP connection (active socket).
server: the Server object which received this connection.
@@ -1276,8 +1324,8 @@ class HTTPConnection(object):
def __init__(self, server, sock, makefile=CP_fileobject):
self.server = server
self.socket = sock
- self.rfile = makefile(sock, "rb", self.rbufsize)
- self.wfile = makefile(sock, "wb", self.wbufsize)
+ self.rfile = makefile(sock._sock, "rb", self.rbufsize)
+ self.wfile = makefile(sock._sock, "wb", self.wbufsize)
self.requests_seen = 0
def communicate(self):
@@ -1309,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.
@@ -1341,8 +1392,10 @@ class HTTPConnection(object):
except NoSSLError:
if req and not req.sent_headers:
# Unwrap our wfile
- self.wfile = CP_fileobject(self.socket._sock, "wb", self.wbufsize)
- req.simple_response("400 Bad Request",
+ 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.")
self.linger = True
@@ -1363,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()
@@ -1382,9 +1436,13 @@ 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
+
def __radd__(self, other):
return other
trueyzero = TrueyZero()
@@ -1392,7 +1450,9 @@ trueyzero = TrueyZero()
_SHUTDOWNREQUEST = None
+
class WorkerThread(threading.Thread):
+
"""Thread which continuously polls a Queue for Connection objects.
Due to the timing issues of polling a Queue, a WorkerThread does not
@@ -1412,7 +1472,6 @@ class WorkerThread(threading.Thread):
"""A simple flag for the calling server to know when this thread
has begun polling the Queue."""
-
def __init__(self, server):
self.ready = False
self.server = server
@@ -1423,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)
@@ -1461,18 +1538,21 @@ class WorkerThread(threading.Thread):
class ThreadPool(object):
+
"""A Request Queue for an HTTPServer which pools threads.
ThreadPool objects must provide min, get(), put(obj), start()
and stop(timeout) attributes.
"""
- def __init__(self, server, min=10, max=-1):
+ def __init__(self, server, min=10, max=-1,
+ accepted_queue_size=-1, accepted_queue_timeout=10):
self.server = server
self.min = min
self.max = max
self._threads = []
- self._queue = queue.Queue()
+ self._queue = queue.Queue(maxsize=accepted_queue_size)
+ self._queue_put_timeout = accepted_queue_timeout
self.get = self._queue.get
def start(self):
@@ -1492,7 +1572,7 @@ class ThreadPool(object):
idle = property(_get_idle, doc=_get_idle.__doc__)
def put(self, obj):
- self._queue.put(obj)
+ self._queue.put(obj, block=True, timeout=self._queue_put_timeout)
if obj is _SHUTDOWNREQUEST:
return
@@ -1576,7 +1656,8 @@ class ThreadPool(object):
worker.join()
except (AssertionError,
# Ignore repeated Ctrl-C.
- # See https://bitbucket.org/cherrypy/cherrypy/issue/691.
+ # See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/691.
KeyboardInterrupt):
pass
@@ -1585,7 +1666,6 @@ class ThreadPool(object):
qsize = property(_get_qsize)
-
try:
import fcntl
except ImportError:
@@ -1617,12 +1697,14 @@ else:
class SSLAdapter(object):
+
"""Base class for SSL driver library adapters.
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):
@@ -1638,6 +1720,7 @@ class SSLAdapter(object):
class HTTPServer(object):
+
"""An HTTP server."""
_bind_addr = "127.0.0.1"
@@ -1650,7 +1733,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()."""
@@ -1662,15 +1746,18 @@ 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)."""
- version = "CherryPy/3.2.4"
+ version = "CherryPy/3.5.1"
"""A version string for the HTTPServer."""
software = None
@@ -1679,7 +1766,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."""
@@ -1723,14 +1811,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),
@@ -1738,7 +1827,7 @@ class HTTPServer(object):
[w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
for w in s['Worker Threads'].values()], 0),
'Worker Threads': {},
- }
+ }
logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
def runtime(self):
@@ -1753,6 +1842,7 @@ class HTTPServer(object):
def _get_bind_addr(self):
return self._bind_addr
+
def _set_bind_addr(self, value):
if isinstance(value, tuple) and value[0] in ('', None):
# Despite the socket module docs, using '' does not
@@ -1769,7 +1859,9 @@ 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,
+ 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
@@ -1794,14 +1886,14 @@ class HTTPServer(object):
# SSL backward compatibility
if (self.ssl_adapter is None and
- getattr(self, 'ssl_certificate', None) and
- getattr(self, 'ssl_private_key', None)):
+ getattr(self, 'ssl_certificate', None) and
+ getattr(self, 'ssl_private_key', None)):
warnings.warn(
- "SSL attributes are deprecated in CherryPy 3.2, and will "
- "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
- "instead.",
- DeprecationWarning
- )
+ "SSL attributes are deprecated in CherryPy 3.2, and will "
+ "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
+ "instead.",
+ DeprecationWarning
+ )
try:
from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
except ImportError:
@@ -1816,21 +1908,28 @@ class HTTPServer(object):
# AF_UNIX socket
# So we can reuse the socket...
- try: os.unlink(self.bind_addr)
- except: pass
+ try:
+ os.unlink(self.bind_addr)
+ except:
+ pass
# So everyone can access the socket...
- try: os.chmod(self.bind_addr, 511) # 0777
- except: pass
+ try:
+ os.chmod(self.bind_addr, 511) # 0777
+ except:
+ pass
- info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
+ info = [
+ (socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
else:
# AF_INET or AF_INET6 socket
- # Get the correct address family for our host (allows IPv6 addresses)
+ # Get the correct address family for our host (allows IPv6
+ # 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,
@@ -1901,11 +2000,13 @@ class HTTPServer(object):
self.socket = self.ssl_adapter.bind(self.socket)
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
- # activate dual-stack. See https://bitbucket.org/cherrypy/cherrypy/issue/871.
+ # activate dual-stack. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/871.
if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
- and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
+ and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
try:
- self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+ self.socket.setsockopt(
+ socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
except (AttributeError, socket.error):
# Apparently, the socket option is not available in
# this machine's TCP stack
@@ -1940,7 +2041,7 @@ class HTTPServer(object):
"Content-Type: text/plain\r\n\r\n",
msg]
- wfile = makefile(s, "wb", DEFAULT_BUFFER_SIZE)
+ wfile = makefile(s._sock, "wb", DEFAULT_BUFFER_SIZE)
try:
wfile.sendall("".join(buf))
except socket.error:
@@ -1960,7 +2061,7 @@ class HTTPServer(object):
if not isinstance(self.bind_addr, basestring):
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
- if addr is None: # sometimes this can happen
+ if addr is None: # sometimes this can happen
# figure out if AF_INET or AF_INET6.
if len(s.getsockname()) == 2:
# AF_INET
@@ -1973,7 +2074,12 @@ class HTTPServer(object):
conn.ssl_env = ssl_env
- self.requests.put(conn)
+ try:
+ self.requests.put(conn)
+ except queue.Full:
+ # Just drop the conn. TODO: write 503 back?
+ conn.close()
+ return
except socket.timeout:
# The only reason for the timeout in start() is so we can
# notice keyboard interrupts on Win32, which don't interrupt
@@ -1988,10 +2094,12 @@ class HTTPServer(object):
# is received during the accept() call; all docs say retry
# the call, and I *think* I'm reading it right that Python
# will then go ahead and poll for and handle the signal
- # elsewhere. See https://bitbucket.org/cherrypy/cherrypy/issue/707.
+ # elsewhere. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/707.
return
if x.args[0] in socket_errors_nonblocking:
- # Just try again. See https://bitbucket.org/cherrypy/cherrypy/issue/479.
+ # Just try again. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/479.
return
if x.args[0] in socket_errors_to_ignore:
# Our socket was closed.
@@ -2001,6 +2109,7 @@ class HTTPServer(object):
def _get_interrupt(self):
return self._interrupt
+
def _set_interrupt(self, interrupt):
self._interrupt = True
self.stop()
@@ -2026,7 +2135,8 @@ class HTTPServer(object):
x = sys.exc_info()[1]
if x.args[0] not in socket_errors_to_ignore:
# Changed to use error code and not message
- # See https://bitbucket.org/cherrypy/cherrypy/issue/860.
+ # See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/860.
raise
else:
# Note that we're explicitly NOT using AI_PASSIVE,
@@ -2039,8 +2149,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()
@@ -2055,7 +2166,9 @@ 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
@@ -2070,7 +2183,8 @@ class Gateway(object):
ssl_adapters = {
'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
- }
+}
+
def get_ssl_adapter_class(name='pyopenssl'):
"""Return an SSL adapter class for the given name."""
@@ -2097,18 +2211,22 @@ def get_ssl_adapter_class(name='pyopenssl'):
return adapter
-# -------------------------------- WSGI Stuff -------------------------------- #
+# ------------------------------- WSGI Stuff -------------------------------- #
class CherryPyWSGIServer(HTTPServer):
+
"""A subclass of HTTPServer which calls a WSGI application."""
wsgi_version = (1, 0)
"""The version of WSGI to produce."""
def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
- max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
- self.requests = ThreadPool(self, min=numthreads or 1, max=max)
+ max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5,
+ accepted_queue_size=-1, accepted_queue_timeout=10):
+ self.requests = ThreadPool(self, min=numthreads or 1, max=max,
+ accepted_queue_size=accepted_queue_size,
+ accepted_queue_timeout=accepted_queue_timeout)
self.wsgi_app = wsgi_app
self.gateway = wsgi_gateways[self.wsgi_version]
@@ -2124,12 +2242,14 @@ class CherryPyWSGIServer(HTTPServer):
def _get_numthreads(self):
return self.requests.min
+
def _set_numthreads(self, value):
self.requests.min = value
numthreads = property(_get_numthreads, _set_numthreads)
class WSGIGateway(Gateway):
+
"""A base class to interface HTTPServer with WSGI."""
def __init__(self, req):
@@ -2161,7 +2281,7 @@ class WSGIGateway(Gateway):
if hasattr(response, "close"):
response.close()
- def start_response(self, status, headers, exc_info = None):
+ def start_response(self, status, headers, exc_info=None):
"""WSGI callable to begin the HTTP response."""
# "The application may call start_response more than once,
# if and only if the exc_info argument is provided."
@@ -2182,9 +2302,11 @@ class WSGIGateway(Gateway):
self.req.status = status
for k, v in headers:
if not isinstance(k, str):
- raise TypeError("WSGI response header key %r is not of type str." % k)
+ raise TypeError(
+ "WSGI response header key %r is not of type str." % k)
if not isinstance(v, str):
- raise TypeError("WSGI response header value %r is not of type str." % v)
+ raise TypeError(
+ "WSGI response header value %r is not of type str." % v)
if k.lower() == 'content-length':
self.remaining_bytes_out = int(v)
self.req.outheaders.extend(headers)
@@ -2205,7 +2327,8 @@ 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",
+ self.req.simple_response(
+ "500 Internal Server Error",
"The requested resource returned more bytes than the "
"declared Content-Length.")
else:
@@ -2227,6 +2350,7 @@ class WSGIGateway(Gateway):
class WSGIGateway_10(WSGIGateway):
+
"""A Gateway class to interface HTTPServer with WSGI 1.0.x."""
def get_environ(self):
@@ -2255,7 +2379,7 @@ class WSGIGateway_10(WSGIGateway):
'wsgi.run_once': False,
'wsgi.url_scheme': req.scheme,
'wsgi.version': (1, 0),
- }
+ }
if isinstance(req.server.bind_addr, basestring):
# AF_UNIX. This isn't really allowed by WSGI, which doesn't
@@ -2283,17 +2407,19 @@ class WSGIGateway_10(WSGIGateway):
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):
"""Return a new environ dict targeting the given wsgi.version"""
req = self.req
env_10 = WSGIGateway_10.get_environ(self)
- env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
+ env = dict([(k.decode('ISO-8859-1'), v)
+ for k, v in env_10.iteritems()])
env[u'wsgi.version'] = ('u', 0)
# Request-URI
@@ -2318,7 +2444,9 @@ wsgi_gateways = {
('u', 0): WSGIGateway_u0,
}
+
class WSGIPathInfoDispatcher(object):
+
"""A WSGI dispatcher for dispatch based on the PATH_INFO.
apps: a dict or list of (path_prefix, app) pairs.
@@ -2331,7 +2459,7 @@ class WSGIPathInfoDispatcher(object):
pass
# Sort the apps by len(path), descending
- apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
+ apps.sort(cmp=lambda x, y: cmp(len(x[0]), len(y[0])))
apps.reverse()
# The path_prefix strings must start, but not end, with a slash.
@@ -2351,4 +2479,3 @@ class WSGIPathInfoDispatcher(object):
start_response('404 Not Found', [('Content-Type', 'text/plain'),
('Content-Length', '0')])
return ['']
-
diff --git a/cherrypy/wsgiserver/wsgiserver3.py b/cherrypy/wsgiserver/wsgiserver3.py
index c59410c3..415b51a0 100644
--- a/cherrypy/wsgiserver/wsgiserver3.py
+++ b/cherrypy/wsgiserver/wsgiserver3.py
@@ -91,7 +91,7 @@ if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27
-if sys.version_info < (3,1):
+if sys.version_info < (3, 1):
import io
else:
import _pyio as io
@@ -105,16 +105,22 @@ if sys.version_info >= (3, 0):
bytestr = bytes
unicodestr = str
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:
bytestr = str
unicodestr = unicode
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.
@@ -135,6 +141,7 @@ quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
+
def plat_specific_errors(*errnames):
"""Return error numbers for all errors in errnames on this platform.
@@ -159,24 +166,27 @@ socket_errors_to_ignore = plat_specific_errors(
"ECONNABORTED", "WSAECONNABORTED",
"ENETRESET", "WSAENETRESET",
"EHOSTDOWN", "EHOSTUNREACH",
- )
+)
socket_errors_to_ignore.append("timed out")
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
+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']]
+ 'WWW-Authenticate']
+]
import logging
-if not hasattr(logging, 'statistics'): logging.statistics = {}
+if not hasattr(logging, 'statistics'):
+ logging.statistics = {}
def read_headers(rfile, hdict=None):
@@ -231,7 +241,9 @@ def read_headers(rfile, hdict=None):
class MaxSizeExceeded(Exception):
pass
+
class SizeCheckWrapper(object):
+
"""Wraps a file-like object, raising MaxSizeExceeded if too large."""
def __init__(self, rfile, maxlen):
@@ -265,7 +277,7 @@ class SizeCheckWrapper(object):
self._check_length()
res.append(data)
# See https://bitbucket.org/cherrypy/cherrypy/issue/421
- if len(data) < 256 or data[-1:].decode() == LF:
+ if len(data) < 256 or data[-1:] == LF:
return EMPTY.join(res)
def readlines(self, sizehint=0):
@@ -301,6 +313,7 @@ class SizeCheckWrapper(object):
class KnownLengthRFile(object):
+
"""Wraps a file-like object, returning an empty string when exhausted."""
def __init__(self, rfile, content_length):
@@ -357,6 +370,7 @@ class KnownLengthRFile(object):
class ChunkedRFile(object):
+
"""Wraps a file-like object, returning an empty string when exhausted.
This class is intended to provide a conforming wsgi.input value for
@@ -406,8 +420,8 @@ class ChunkedRFile(object):
crlf = self.rfile.read(2)
if crlf != CRLF:
raise ValueError(
- "Bad chunked transfer coding (expected '\\r\\n', "
- "got " + repr(crlf) + ")")
+ "Bad chunked transfer coding (expected '\\r\\n', "
+ "got " + repr(crlf) + ")")
def read(self, size=None):
data = EMPTY
@@ -509,6 +523,7 @@ class ChunkedRFile(object):
class HTTPRequest(object):
+
"""An HTTP Request (and response).
A single HTTP connection may consist of multiple request/response pairs.
@@ -542,7 +557,7 @@ class HTTPRequest(object):
This value is set automatically inside send_headers."""
def __init__(self, server, conn):
- self.server= server
+ self.server = server
self.conn = conn
self.ready = False
@@ -568,7 +583,8 @@ class HTTPRequest(object):
try:
success = self.read_request_line()
except MaxSizeExceeded:
- self.simple_response("414 Request-URI Too Long",
+ self.simple_response(
+ "414 Request-URI Too Long",
"The Request-URI sent with the request exceeds the maximum "
"allowed bytes.")
return
@@ -579,7 +595,8 @@ class HTTPRequest(object):
try:
success = self.read_request_headers()
except MaxSizeExceeded:
- self.simple_response("413 Request Entity Too Large",
+ self.simple_response(
+ "413 Request Entity Too Large",
"The headers sent with the request exceed the maximum "
"allowed bytes.")
return
@@ -615,12 +632,14 @@ class HTTPRequest(object):
return False
if not request_line.endswith(CRLF):
- self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
+ self.simple_response(
+ "400 Bad Request", "HTTP requires CRLF terminators")
return False
try:
method, uri, req_protocol = request_line.strip().split(SPACE, 2)
- # The [x:y] slicing is necessary for byte strings to avoid getting ord's
+ # The [x:y] slicing is necessary for byte strings to avoid getting
+ # ord's
rp = int(req_protocol[5:6]), int(req_protocol[7:8])
except ValueError:
self.simple_response("400 Bad Request", "Malformed Request-Line")
@@ -675,7 +694,8 @@ class HTTPRequest(object):
# Notice that, in (b), the response will be "HTTP/1.1" even though
# the client only understands 1.0. RFC 2616 10.5.6 says we should
# only return 505 if the _major_ version is different.
- # The [x:y] slicing is necessary for byte strings to avoid getting ord's
+ # The [x:y] slicing is necessary for byte strings to avoid getting
+ # ord's
sp = int(self.server.protocol[5:6]), int(self.server.protocol[7:8])
if sp[0] != rp[0]:
@@ -699,7 +719,8 @@ 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",
+ self.simple_response(
+ "413 Request Entity Too Large",
"The entity sent with the request exceeds the maximum "
"allowed bytes.")
return False
@@ -753,8 +774,10 @@ class HTTPRequest(object):
# but it seems like it would be a big slowdown for such a rare case.
if self.inheaders.get(b"Expect", b"") == b"100-continue":
# Don't use simple_response here, because it emits headers
- # we don't want. See https://bitbucket.org/cherrypy/cherrypy/issue/951
- msg = self.server.protocol.encode('ascii') + b" 100 Continue\r\n\r\n"
+ # we don't want. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/951
+ msg = self.server.protocol.encode(
+ 'ascii') + b" 100 Continue\r\n\r\n"
try:
self.conn.wfile.write(msg)
except socket.error:
@@ -790,9 +813,10 @@ class HTTPRequest(object):
if sep and QUESTION_MARK not in scheme:
# An absoluteURI.
# If there's a scheme (and it must be http or https), then:
- # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
+ # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query
+ # ]]
authority, path_a, path_b = remainder.partition(FORWARD_SLASH)
- return scheme.lower(), authority, path_a+path_b
+ return scheme.lower(), authority, path_a + path_b
if uri.startswith(FORWARD_SLASH):
# An abs_path.
@@ -822,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)
@@ -897,7 +922,7 @@ class HTTPRequest(object):
pass
else:
if (self.response_protocol == 'HTTP/1.1'
- and self.method != b'HEAD'):
+ and self.method != b'HEAD'):
# Use the chunked transfer-coding
self.chunked_write = True
self.outheaders.append((b"Transfer-Encoding", b"chunked"))
@@ -933,14 +958,17 @@ 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(
(b"Server", self.server.server_name.encode('ISO-8859-1')))
- buf = [self.server.protocol.encode('ascii') + SPACE + self.status + CRLF]
+ buf = [self.server.protocol.encode(
+ 'ascii') + SPACE + self.status + CRLF]
for k, v in self.outheaders:
buf.append(k + COLON + SPACE + v + CRLF)
buf.append(CRLF)
@@ -948,16 +976,19 @@ class HTTPRequest(object):
class NoSSLError(Exception):
+
"""Exception raised when a client speaks HTTP to an HTTPS socket."""
pass
class FatalSSLAlert(Exception):
+
"""Exception raised when the SSL implementation signals a fatal alert."""
pass
class CP_BufferedWriter(io.BufferedWriter):
+
"""Faux file object attached to a socket object."""
def write(self, b):
@@ -988,7 +1019,9 @@ def CP_makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
else:
return CP_BufferedWriter(socket.SocketIO(sock, mode), bufsize)
+
class HTTPConnection(object):
+
"""An HTTP connection (active socket).
server: the Server object which received this connection.
@@ -1039,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.
@@ -1071,10 +1107,12 @@ class HTTPConnection(object):
except NoSSLError:
if req and not req.sent_headers:
# 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.")
+ 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.")
self.linger = True
except Exception:
e = sys.exc_info()[1]
@@ -1093,13 +1131,15 @@ 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.
-## self.socket._sock.close()
+ # 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:
# On the other hand, sometimes we want to hang around for a bit
@@ -1112,9 +1152,13 @@ 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
+
def __radd__(self, other):
return other
trueyzero = TrueyZero()
@@ -1122,7 +1166,9 @@ trueyzero = TrueyZero()
_SHUTDOWNREQUEST = None
+
class WorkerThread(threading.Thread):
+
"""Thread which continuously polls a Queue for Connection objects.
Due to the timing issues of polling a Queue, a WorkerThread does not
@@ -1142,7 +1188,6 @@ class WorkerThread(threading.Thread):
"""A simple flag for the calling server to know when this thread
has begun polling the Queue."""
-
def __init__(self, server):
self.ready = False
self.server = server
@@ -1153,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)
@@ -1191,18 +1254,21 @@ class WorkerThread(threading.Thread):
class ThreadPool(object):
+
"""A Request Queue for an HTTPServer which pools threads.
ThreadPool objects must provide min, get(), put(obj), start()
and stop(timeout) attributes.
"""
- def __init__(self, server, min=10, max=-1):
+ def __init__(self, server, min=10, max=-1,
+ accepted_queue_size=-1, accepted_queue_timeout=10):
self.server = server
self.min = min
self.max = max
self._threads = []
- self._queue = queue.Queue()
+ self._queue = queue.Queue(maxsize=accepted_queue_size)
+ self._queue_put_timeout = accepted_queue_timeout
self.get = self._queue.get
def start(self):
@@ -1222,7 +1288,7 @@ class ThreadPool(object):
idle = property(_get_idle, doc=_get_idle.__doc__)
def put(self, obj):
- self._queue.put(obj)
+ self._queue.put(obj, block=True, timeout=self._queue_put_timeout)
if obj is _SHUTDOWNREQUEST:
return
@@ -1301,7 +1367,8 @@ class ThreadPool(object):
worker.join()
except (AssertionError,
# Ignore repeated Ctrl-C.
- # See https://bitbucket.org/cherrypy/cherrypy/issue/691.
+ # See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/691.
KeyboardInterrupt):
pass
@@ -1310,7 +1377,6 @@ class ThreadPool(object):
qsize = property(_get_qsize)
-
try:
import fcntl
except ImportError:
@@ -1342,12 +1408,14 @@ else:
class SSLAdapter(object):
+
"""Base class for SSL driver library adapters.
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):
@@ -1363,6 +1431,7 @@ class SSLAdapter(object):
class HTTPServer(object):
+
"""An HTTP server."""
_bind_addr = "127.0.0.1"
@@ -1375,7 +1444,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()."""
@@ -1387,15 +1457,18 @@ 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)."""
- version = "CherryPy/3.2.4"
+ version = "CherryPy/3.5.1"
"""A version string for the HTTPServer."""
software = None
@@ -1404,7 +1477,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."""
@@ -1448,14 +1523,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),
@@ -1463,7 +1539,7 @@ class HTTPServer(object):
[w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
for w in s['Worker Threads'].values()], 0),
'Worker Threads': {},
- }
+ }
logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
def runtime(self):
@@ -1478,6 +1554,7 @@ class HTTPServer(object):
def _get_bind_addr(self):
return self._bind_addr
+
def _set_bind_addr(self, value):
if isinstance(value, tuple) and value[0] in ('', None):
# Despite the socket module docs, using '' does not
@@ -1494,7 +1571,9 @@ 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,
+ 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
@@ -1522,21 +1601,28 @@ class HTTPServer(object):
# AF_UNIX socket
# So we can reuse the socket...
- try: os.unlink(self.bind_addr)
- except: pass
+ try:
+ os.unlink(self.bind_addr)
+ except:
+ pass
# So everyone can access the socket...
- try: os.chmod(self.bind_addr, 511) # 0777
- except: pass
+ try:
+ os.chmod(self.bind_addr, 511) # 0777
+ except:
+ pass
- info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
+ info = [
+ (socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
else:
# AF_INET or AF_INET6 socket
- # Get the correct address family for our host (allows IPv6 addresses)
+ # Get the correct address family for our host (allows IPv6
+ # addresses)
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,
@@ -1606,11 +1692,13 @@ class HTTPServer(object):
self.socket = self.ssl_adapter.bind(self.socket)
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
- # activate dual-stack. See https://bitbucket.org/cherrypy/cherrypy/issue/871.
+ # activate dual-stack. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/871.
if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
- and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
+ and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
try:
- self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+ self.socket.setsockopt(
+ socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
except (AttributeError, socket.error):
# Apparently, the socket option is not available in
# this machine's TCP stack
@@ -1665,7 +1753,7 @@ class HTTPServer(object):
if not isinstance(self.bind_addr, basestring):
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
- if addr is None: # sometimes this can happen
+ if addr is None: # sometimes this can happen
# figure out if AF_INET or AF_INET6.
if len(s.getsockname()) == 2:
# AF_INET
@@ -1678,7 +1766,12 @@ class HTTPServer(object):
conn.ssl_env = ssl_env
- self.requests.put(conn)
+ try:
+ self.requests.put(conn)
+ except queue.Full:
+ # Just drop the conn. TODO: write 503 back?
+ conn.close()
+ return
except socket.timeout:
# The only reason for the timeout in start() is so we can
# notice keyboard interrupts on Win32, which don't interrupt
@@ -1693,10 +1786,12 @@ class HTTPServer(object):
# is received during the accept() call; all docs say retry
# the call, and I *think* I'm reading it right that Python
# will then go ahead and poll for and handle the signal
- # elsewhere. See https://bitbucket.org/cherrypy/cherrypy/issue/707.
+ # elsewhere. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/707.
return
if x.args[0] in socket_errors_nonblocking:
- # Just try again. See https://bitbucket.org/cherrypy/cherrypy/issue/479.
+ # Just try again. See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/479.
return
if x.args[0] in socket_errors_to_ignore:
# Our socket was closed.
@@ -1706,6 +1801,7 @@ class HTTPServer(object):
def _get_interrupt(self):
return self._interrupt
+
def _set_interrupt(self, interrupt):
self._interrupt = True
self.stop()
@@ -1731,7 +1827,8 @@ class HTTPServer(object):
x = sys.exc_info()[1]
if x.args[0] not in socket_errors_to_ignore:
# Changed to use error code and not message
- # See https://bitbucket.org/cherrypy/cherrypy/issue/860.
+ # See
+ # https://bitbucket.org/cherrypy/cherrypy/issue/860.
raise
else:
# Note that we're explicitly NOT using AI_PASSIVE,
@@ -1744,8 +1841,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()
@@ -1760,7 +1858,9 @@ 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
@@ -1774,7 +1874,8 @@ class Gateway(object):
# of such classes (in which case they will be lazily loaded).
ssl_adapters = {
'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
- }
+}
+
def get_ssl_adapter_class(name='builtin'):
"""Return an SSL adapter class for the given name."""
@@ -1801,18 +1902,22 @@ def get_ssl_adapter_class(name='builtin'):
return adapter
-# -------------------------------- WSGI Stuff -------------------------------- #
+# ------------------------------- WSGI Stuff -------------------------------- #
class CherryPyWSGIServer(HTTPServer):
+
"""A subclass of HTTPServer which calls a WSGI application."""
wsgi_version = (1, 0)
"""The version of WSGI to produce."""
def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
- max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
- self.requests = ThreadPool(self, min=numthreads or 1, max=max)
+ max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5,
+ accepted_queue_size=-1, accepted_queue_timeout=10):
+ self.requests = ThreadPool(self, min=numthreads or 1, max=max,
+ accepted_queue_size=accepted_queue_size,
+ accepted_queue_timeout=accepted_queue_timeout)
self.wsgi_app = wsgi_app
self.gateway = wsgi_gateways[self.wsgi_version]
@@ -1828,12 +1933,14 @@ class CherryPyWSGIServer(HTTPServer):
def _get_numthreads(self):
return self.requests.min
+
def _set_numthreads(self, value):
self.requests.min = value
numthreads = property(_get_numthreads, _set_numthreads)
class WSGIGateway(Gateway):
+
"""A base class to interface HTTPServer with WSGI."""
def __init__(self, req):
@@ -1865,7 +1972,7 @@ class WSGIGateway(Gateway):
if hasattr(response, "close"):
response.close()
- def start_response(self, status, headers, exc_info = None):
+ def start_response(self, status, headers, exc_info=None):
"""WSGI callable to begin the HTTP response."""
# "The application may call start_response more than once,
# if and only if the exc_info argument is provided."
@@ -1893,12 +2000,15 @@ class WSGIGateway(Gateway):
for k, v in headers:
if not isinstance(k, str):
- raise TypeError("WSGI response header key %r is not of type str." % k)
+ raise TypeError(
+ "WSGI response header key %r is not of type str." % k)
if not isinstance(v, str):
- raise TypeError("WSGI response header value %r is not of type str." % v)
+ raise TypeError(
+ "WSGI response header value %r is not of type str." % v)
if k.lower() == 'content-length':
self.remaining_bytes_out = int(v)
- self.req.outheaders.append((k.encode('ISO-8859-1'), v.encode('ISO-8859-1')))
+ self.req.outheaders.append(
+ (k.encode('ISO-8859-1'), v.encode('ISO-8859-1')))
return self.write
@@ -1917,8 +2027,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.
@@ -1938,6 +2049,7 @@ class WSGIGateway(Gateway):
class WSGIGateway_10(WSGIGateway):
+
"""A Gateway class to interface HTTPServer with WSGI 1.0.x."""
def get_environ(self):
@@ -1966,7 +2078,7 @@ class WSGIGateway_10(WSGIGateway):
'wsgi.run_once': False,
'wsgi.url_scheme': req.scheme.decode('ISO-8859-1'),
'wsgi.version': (1, 0),
- }
+ }
if isinstance(req.server.bind_addr, basestring):
# AF_UNIX. This isn't really allowed by WSGI, which doesn't
# address unix domain sockets. But it's better than nothing.
@@ -1994,10 +2106,11 @@ class WSGIGateway_10(WSGIGateway):
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):
@@ -2026,7 +2139,9 @@ wsgi_gateways = {
('u', 0): WSGIGateway_u0,
}
+
class WSGIPathInfoDispatcher(object):
+
"""A WSGI dispatcher for dispatch based on the PATH_INFO.
apps: a dict or list of (path_prefix, app) pairs.
@@ -2059,4 +2174,3 @@ class WSGIPathInfoDispatcher(object):
start_response('404 Not Found', [('Content-Type', 'text/plain'),
('Content-Length', '0')])
return ['']
-
diff --git a/sphinx/source/_static/bgsides.png b/docs/_static/bgsides.png
index 27c305fa..27c305fa 100644
--- a/sphinx/source/_static/bgsides.png
+++ b/docs/_static/bgsides.png
Binary files differ
diff --git a/sphinx/source/_static/cpdocmain.css b/docs/_static/cpdocmain.css
index 4414d472..4414d472 100644
--- a/sphinx/source/_static/cpdocmain.css
+++ b/docs/_static/cpdocmain.css
diff --git a/sphinx/source/progguide/cpreturn.gif b/docs/_static/images/cpreturn.gif
index 074ea3cd..074ea3cd 100644
--- a/sphinx/source/progguide/cpreturn.gif
+++ b/docs/_static/images/cpreturn.gif
Binary files differ
diff --git a/sphinx/source/progguide/cpyield.gif b/docs/_static/images/cpyield.gif
index e07c50aa..e07c50aa 100644
--- a/sphinx/source/progguide/cpyield.gif
+++ b/docs/_static/images/cpyield.gif
Binary files differ
diff --git a/docs/_static/images/sushibelt.JPG b/docs/_static/images/sushibelt.JPG
new file mode 100644
index 00000000..8f379b73
--- /dev/null
+++ b/docs/_static/images/sushibelt.JPG
Binary files differ
diff --git a/docs/advanced.rst b/docs/advanced.rst
new file mode 100644
index 00000000..44f54edb
--- /dev/null
+++ b/docs/advanced.rst
@@ -0,0 +1,664 @@
+.. _advanced:
+
+Advanced
+--------
+
+CherryPy has support for more advanced features that these sections
+will describe.
+
+.. contents::
+ :depth: 4
+
+.. _aliases:
+
+Set aliases to page handlers
+############################
+
+A fairly unknown, yet useful, feature provided by the :func:`cherrypy.expose`
+decorator is to support aliases.
+
+Let's use the template provided by :ref:`tutorial 03 <tut03>`:
+
+.. code-block:: python
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose(['generer', 'generar'])
+ def generate(self, length=8):
+ return ''.join(random.sample(string.hexdigits, int(length)))
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(StringGenerator())
+
+In this example, we create localized aliases for
+the page handler. This means the page handler will be
+accessible via:
+
+- /generate
+- /generer (French)
+- /generar (Spanish)
+
+Obviously, your aliases may be whatever suits your needs.
+
+.. note::
+
+ The alias may be a single string or a list of them.
+
+RESTful-style dispatching
+#########################
+
+The term `RESTful URL` is sometimes used to talk about friendly URLs
+that nicely map to the entities an application exposes.
+
+.. important::
+
+ We will not enter the debate around what is restful or not but we will
+ showcase two mechanisms to implement the usual idea in your
+ CherryPy application.
+
+Let's assume you wish to create an application that exposes
+music bands and their records. Your application will probably have
+the following URLs:
+
+- http://hostname/<bandname>/
+- http://hostname/<bandname>/albums/<recordname>/
+
+It's quite clear you would not create a page handler named after
+every possible band in the world. This means you will need a page handler
+that acts as a proxy for all of them.
+
+The default dispatcher cannot deal with that scenario on its own
+because it expects page handlers to be explicitely declared in your
+source code. Luckily, CherryPy provides ways to support those use cases.
+
+.. seealso::
+
+ This section extends from this `stackoverflow response <http://stackoverflow.com/a/15789415/1363905>`_.
+
+The special _cp_dispatch method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``_cp_dispatch`` is a special method you declare in any of your :term:`controller`
+to massage the remaining segments before CherryPy gets to process them.
+This offers you the capacity to remove, add or otherwise handle any segment
+you wish and, even, entirely change the remaining parts.
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Band(object):
+ def __init__(self):
+ self.albums = Album()
+
+ def _cp_dispatch(self, vpath):
+ if len(vpath) == 1:
+ cherrypy.request.params['name'] = vpath.pop()
+ return self
+
+ if len(vpath) == 3:
+ cherrypy.request.params['artist'] = vpath.pop(0) # /band name/
+ vpath.pop(0) # /albums/
+ cherrypy.request.params['title'] = vpath.pop(0) # /album title/
+ return self.albums
+
+ return vpath
+
+ @cherrypy.expose
+ def index(self, name):
+ return 'About %s...' % name
+
+ class Album(object):
+ @cherrypy.expose
+ def index(self, artist, title):
+ return 'About %s by %s...' % (title, artist)
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(Band())
+
+Notice how the controller defines `_cp_dispatch`, it takes
+a single argument, the URL path info broken into its segments.
+
+The method can inspect and manipulate the list of segments,
+removing any or adding new segments at any position. The new list of
+segments is then sent to the dispatcher which will use it
+to locate the appropriate resource.
+
+In the above example, you should be able to go to the following URLs:
+
+- http://localhost:8080/nirvana/
+- http://localhost:8080/nirvana/albums/nevermind/
+
+The ``/nirvana/`` segment is associated to the band and
+the ``/nevermind/`` segment relates to the album.
+
+To achieve this, our `_cp_dispatch` method works on the idea
+that the default dispatcher matches URLs against page handler
+signatures and their position in the tree of handlers.
+
+In this case, we take the dynamic segments in the URL (band and record names),
+we inject them into the request parameters and we remove them
+from the segment lists as if they had never been there in the first place.
+
+In other words, `_cp_dispatch` makes it as if we were
+working on the following URLs:
+
+- http://localhost:8080/?artist=nirvana
+- http://localhost:8080/albums/?artist=nirvana&title=nevermind
+
+
+The popargs decorator
+^^^^^^^^^^^^^^^^^^^^^
+:func:`cherrypy.popargs` is more straightforward as it gives a name to any segment
+that CherryPy wouldn't be able to interpret otherwise. This makes the
+matching of segments with page handler signatures easier and helps CherryPy
+understand the structure of your URL.
+
+.. code-block:: python
+
+ import cherrypy
+
+ @cherrypy.popargs('name')
+ class Band(object):
+ def __init__(self):
+ self.albums = Album()
+
+ @cherrypy.expose
+ def index(self, name):
+ return 'About %s...' % name
+
+ @cherrypy.popargs('title')
+ class Album(object):
+ @cherrypy.expose
+ def index(self, name, title):
+ return 'About %s by %s...' % (title, name)
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(Band())
+
+This works similarly to `_cp_dispatch` but, as said above, is more
+explicit and localized. It says:
+
+- take the first segment and store it into a parameter name `band`
+- take again the first segment (since we removed the previous first)
+ and store it into a parameter named `title`
+
+Note that the decorator accepts more than a single binding. For instance:
+
+.. code-block:: python
+
+ @cherrypy.popargs('title')
+ class Album(object):
+ def __init__(self):
+ self.tracks = Track()
+
+ @cherrypy.popargs('num', 'track')
+ class Track(object):
+ @cherrypy.expose
+ def index(self, name, title, num, track):
+ ...
+
+This would handle the following URL:
+
+- http://localhost:8080/nirvana/albums/nevermind/track/06/polly
+
+Notice finally how the whole stack of segments is passed to each
+page handler so that you have the full context.
+
+Streaming the response body
+###########################
+
+CherryPy handles HTTP requests, packing and unpacking the low-level details,
+then passing control to your application's :term:`page handler`, which produce
+the body of the response. CherryPy allows you to return body content in a
+variety of types: a string, a list of strings, a file. CherryPy also allows you
+to *yield* content, rather than *return* content. When you use "yield", you also
+have the option of streaming the output.
+
+**In general, it is safer and easier to not stream output.** Therefore,
+streaming output is off by default. Streaming output and also using sessions
+requires a good understanding of :py:mod:`how session locks work
+<cherrypy.lib.sessions>`.
+
+The "normal" CherryPy response process
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When you provide content from your page handler, CherryPy manages the
+conversation between the HTTP server and your code like this:
+
+.. image:: _static/images/cpreturn.gif
+
+Notice that the HTTP server gathers all output first and then writes everything
+to the client at once: status, headers, and body. This works well for static or
+simple pages, since the entire response can be changed at any time, either in
+your application code, or by the CherryPy framework.
+
+How "streaming output" works with CherryPy
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When you set the config entry "response.stream" to True (and use "yield"),
+CherryPy manages the conversation between the HTTP server and your code like this:
+
+.. image:: _static/images/cpyield.gif
+
+When you stream, your application doesn't immediately pass raw body content
+back to CherryPy or to the HTTP server. Instead, it passes back a generator.
+At that point, CherryPy finalizes the status and headers, **before** the
+generator has been consumed, or has produced any output. This is necessary to
+allow the HTTP server to send the headers and pieces of the body as they become
+available.
+
+Once CherryPy has set the status and headers, it sends them to the HTTP server,
+which then writes them out to the client. From that point on, the CherryPy
+framework mostly steps out of the way, and the HTTP server essentially requests
+content directly from your application code (your page handler method).
+
+Therefore, when streaming, if an error occurs within your page handler,
+CherryPy will not catch it--the HTTP server will catch it. Because the headers
+(and potentially some of the body) have already been written to the client,
+the server *cannot* know a safe means of handling the error, and will therefore
+simply close the connection (the current, builtin servers actually write out a
+short error message in the body, but this may be changed, and is not guaranteed
+behavior for all HTTP servers you might use with CherryPy).
+
+In addition, you cannot manually modify the status or headers within your page
+handler if that handler method is a streaming generator, because the method will
+not be iterated over until after the headers have been written to the client.
+**This includes raising exceptions like HTTPError, NotFound, InternalRedirect
+and HTTPRedirect.** To use a streaming generator while modifying headers, you
+would have to return a generator that is separate from (or embedded in) your
+page handler. For example:
+
+.. code-block:: python
+
+ class Root:
+ @cherrypy.expose
+ def thing(self):
+ cherrypy.response.headers['Content-Type'] = 'text/plain'
+ if not authorized():
+ raise cherrypy.NotFound()
+ def content():
+ yield "Hello, "
+ yield "world"
+ return content()
+ thing._cp_config = {'response.stream': True}
+
+Streaming generators are sexy, but they play havoc with HTTP. CherryPy allows
+you to stream output for specific situations: pages which take many minutes to
+produce, or pages which need a portion of their content immediately output to
+the client. Because of the issues outlined above, **it is usually better to
+flatten (buffer) content rather than stream content**. Do otherwise only when
+the benefits of streaming outweigh the risks.
+
+Response timeouts
+#################
+
+CherryPy responses include 3 attributes related to time:
+
+ * ``response.time``: the :func:`time.time` at which the response began
+ * ``response.timeout``: the number of seconds to allow responses to run
+ * ``response.timed_out``: a boolean indicating whether the response has
+ timed out (default False).
+
+The request processing logic inspects the value of ``response.timed_out`` at
+various stages; if it is ever True, then :class:`TimeoutError` is raised.
+You are free to do the same within your own code.
+
+Rather than calculate the difference by hand, you can call
+``response.check_timeout`` to set ``timed_out`` for you.
+
+.. note::
+
+ The default response timeout is 300 seconds.
+
+.. _timeoutmonitor:
+
+Timeout Monitor
+^^^^^^^^^^^^^^^
+
+In addition, CherryPy includes a ``cherrypy.engine.timeout_monitor`` which
+monitors all active requests in a separate thread; periodically, it calls
+``check_timeout`` on them all. It is subscribed by default. To turn it off:
+
+.. code-block:: ini
+
+ [global]
+ engine.timeout_monitor.on: False
+
+or:
+
+.. code-block:: python
+
+ cherrypy.engine.timeout_monitor.unsubscribe()
+
+You can also change the interval (in seconds) at which the timeout monitor runs:
+
+.. code-block:: ini
+
+ [global]
+ engine.timeout_monitor.frequency: 60 * 60
+
+The default is once per minute. The above example changes that to once per hour.
+
+Deal with signals
+#################
+
+This :ref:`engine plugin <busplugins>` is instantiated automatically as
+`cherrypy.engine.signal_handler`.
+However, it is only *subscribed* automatically by :func:`cherrypy.quickstart`.
+So if you want signal handling and you're calling:
+
+.. code-block:: python
+
+ tree.mount()
+ engine.start()
+ engine.block()
+
+on your own, be sure to add before you start the engine:
+
+.. code-block:: python
+
+ engine.signals.subscribe()
+
+.. index:: Windows, Ctrl-C, shutdown
+.. _windows-console:
+
+Windows Console Events
+^^^^^^^^^^^^^^^^^^^^^^
+
+Microsoft Windows uses console events to communicate some signals, like Ctrl-C.
+When deploying CherryPy on Windows platforms, you should obtain the
+`Python for Windows Extensions <http://sourceforge.net/projects/pywin32/>`_;
+once you have them installed, CherryPy will handle Ctrl-C and other
+console events (CTRL_C_EVENT, CTRL_LOGOFF_EVENT, CTRL_BREAK_EVENT,
+CTRL_SHUTDOWN_EVENT, and CTRL_CLOSE_EVENT) automatically, shutting down the
+bus in preparation for process exit.
+
+
+Securing your server
+####################
+
+.. note::
+
+ This section is not meant as a complete guide to securing
+ a web application or ecosystem. Please review the various
+ guides provided at `OWASP <https://www.owasp.org/index.php/Main_Page>`_.
+
+
+There are several settings that can be enabled to make CherryPy pages more secure. These include:
+
+ Transmitting data:
+
+ #. Use Secure Cookies
+
+ Rendering pages:
+
+ #. Set HttpOnly cookies
+ #. Set XFrame options
+ #. Enable XSS Protection
+ #. Set the Content Security Policy
+
+An easy way to accomplish this is to set headers with a tool
+and wrap your entire CherryPy application with it:
+
+.. code-block:: python
+
+ import cherrypy
+
+ def secureheaders():
+ headers = cherrypy.response.headers
+ headers['X-Frame-Options'] = 'DENY'
+ headers['X-XSS-Protection'] = '1; mode=block'
+ headers['Content-Security-Policy'] = "default-src='self'"
+
+ # set the priority according to your needs if you are hooking something
+ # else on the 'before_finalize' hook point.
+ cherrypy.tools.secureheaders = cherrypy.Tool('before_finalize', secureheaders, priority=60)
+
+.. note::
+
+ Read more about `those headers <https://www.owasp.org/index.php/List_of_useful_HTTP_headers>`_.
+
+Then, in the :ref:`configuration file <config>` (or any other place that you want to enable the tool):
+
+.. code-block:: ini
+
+ [/]
+ tools.secureheaders.on = True
+
+
+If you use :ref:`sessions <basicsession>` you can also enable these settings:
+
+.. code-block:: ini
+
+ [/]
+ tools.sessions.on = True
+ # increase security on sessions
+ tools.sessions.secure = True
+ tools.sessions.httponly = True
+
+
+If you use SSL you can also enable Strict Transport Security:
+
+.. code-block:: python
+
+ # add this to secureheaders():
+ # only add Strict-Transport headers if we're actually using SSL; see the ietf spec
+ # "An HSTS Host MUST NOT include the STS header field in HTTP responses
+ # conveyed over non-secure transport"
+ # http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-7.2
+ if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None):
+ headers['Strict-Transport-Security'] = 'max-age=31536000' # one year
+
+Next, you should probably use :ref:`SSL <ssl>`.
+
+Multiple HTTP servers support
+#############################
+
+CherryPy starts its own HTTP server whenever you start the
+engine. In some cases, you may wish to host your application
+on more than a single port. This is easily achieved:
+
+.. code-block:: python
+
+ from cherrypy._cpserver import Server
+ server = Server()
+ server.socket_port = 8090
+ server.subscribe()
+
+You can create as many :class:`server <cherrypy._cpserver.Server>`
+server instances as you need, once :ref:`subscribed <busplugins>`,
+they will follow the CherryPy engine's life-cycle.
+
+WSGI support
+############
+
+CherryPy supports the WSGI interface defined in :pep:`333`
+as well as its updates in :pep:`3333`. It means the following:
+
+- You can host a foreign WSGI application with the CherryPy server
+- A CherryPy application can be hosted by another WSGI server
+
+Make your CherryPy application a WSGI application
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A WSGI application can be obtained from your application as follows:
+
+.. code-block:: python
+
+ import cherrypy
+ wsgiapp = cherrypy.Application(StringGenerator(), '/', config=myconf)
+
+Simply use the `wsgiapp` instance in any WSGI-aware server.
+
+.. _hostwsgiapp:
+
+Host a foreign WSGI application in CherryPy
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Assuming you have a WSGI-aware application, you can host it
+in your CherryPy server using the :meth:`cherrypy.tree.graft <cherrypy._cptree.Tree.graft>`
+facility.
+
+.. code-block:: python
+
+ def raw_wsgi_app(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ return ['Hello world!']
+
+ cherrypy.tree.graft(raw_wsgi_app, '/')
+
+.. important::
+
+ You cannot use tools with a foreign WSGI application.
+ However, you can still benefit from the
+ :ref:`CherryPy bus <buspattern>`.
+
+
+No need for the WSGI interface?
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The default CherryPy HTTP server supports the WSGI interfaces
+defined in :pep:`333` and :pep:`3333`. However, if your application
+is a pure CherryPy application, you can switch to a HTTP
+server that by-passes the WSGI layer altogether. It will provide
+a slight performance increase.
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ if __name__ == '__main__':
+ from cherrypy._cpnative_server import CPHTTPServer
+ cherrypy.server.httpserver = CPHTTPServer(cherrypy.server)
+
+ cherrypy.quickstart(Root(), '/')
+
+.. important::
+
+ Using the native server, you will not be able to
+ graft a WSGI application as shown in the previous section.
+ Doing so will result in a server error at runtime.
+
+WebSocket support
+#################
+
+`WebSocket <http://tools.ietf.org/html/rfc6455>`_
+is a recent application protocol that came to life
+from the HTML5 working-group in response to the needs for
+bi-directional communication. Various hacks had been proposed
+such as Comet, polling, etc.
+
+WebSocket is a socket that starts its life from a HTTP upgrade request.
+Once the upgrade is performed, the underlying socket is
+kept opened but not used in a HTTP context any longer.
+Instead, both connected endpoints may use the socket
+to push data to the other end.
+
+CherryPy itself does not support WebSocket, but the feature
+is provided by an external library called
+`ws4py <https://github.com/Lawouach/WebSocket-for-Python>`_.
+
+Database support
+################
+
+CherryPy does not bundle any database access but its architecture
+makes it easy to integrate common database interfaces such as
+the DB-API specified in :pep:`249`. Alternatively, you can also
+use an `ORM <en.wikipedia.org/wiki/Object-relational_mapping>`_
+such as `SQLAlchemy <http://sqlalchemy.readthedocs.org>`_
+or `SQLObject <https://pypi.python.org/pypi/SQLObject/>`_.
+
+You will find `here <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/web/database/sql_alchemy/>`_
+a recipe on how integrating SQLAlchemy using a mix of
+:ref:`plugins <busplugins>` and :ref:`tools <tools>`.
+
+HTML Templating support
+#######################
+
+CherryPy does not provide any HTML template but its architecture
+makes it easy to integrate one. Popular ones are `Mako <www.makotemplates.org>`_
+or `Jinja2 <jinja.pocoo.org/docs/>`_.
+
+You will find `here <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/web/templating/>`_
+a recipe on how to integrate them using a mix
+:ref:`plugins <busplugins>` and :ref:`tools <tools>`.
+
+Testing your application
+########################
+
+Web applications, like any other kind of code, must be tested. CherryPy provides
+a :class:`helper class <cherrypy.test.helper.CPWebCase>` to ease writing
+functional tests.
+
+Here is a simple example for a basic echo application:
+
+.. code-block:: python
+
+ import cherrypy
+ from cherrypy.test import helper
+
+ class SimpleCPTest(helper.CPWebCase):
+ def setup_server():
+ class Root(object):
+ @cherrypy.expose
+ def echo(self, message):
+ return message
+
+ cherrypy.tree.mount(Root())
+ setup_server = staticmethod(setup_server)
+
+ def test_message_should_be_returned_as_is(self):
+ self.getPage("/echo?message=Hello%20world")
+ self.assertStatus('200 OK')
+ self.assertHeader('Content-Type', 'text/html;charset=utf-8')
+ self.assertBody('Hello world')
+
+ def test_non_utf8_message_will_fail(self):
+ """
+ CherryPy defaults to decode the query-string
+ using UTF-8, trying to send a query-string with
+ a different encoding will raise a 404 since
+ it considers it's a different URL.
+ """
+ self.getPage("/echo?message=A+bient%F4t",
+ headers=[
+ ('Accept-Charset', 'ISO-8859-1,utf-8'),
+ ('Content-Type', 'text/html;charset=ISO-8859-1')
+ ]
+ )
+ self.assertStatus('404 Not Found')
+
+As you can see the, test inherits from that helper class. You should
+setup your application and mount it as per-usual. Then, define your various
+tests and call the helper :meth:`~cherrypy.test.helper.CPWebCase.getPage`
+method to perform a request. Simply use the various specialized
+assert* methods to validate your workflow and data.
+
+You can then run the test using `py.test <http://pytest.org/latest/>`_ as follows:
+
+.. code-block:: bash
+
+ $ py.test -s test_echo_app.py
+
+The ``-s`` is necessary because the CherryPy class also wraps stdin and stdout.
+
+.. note::
+
+ Although they are written using the typical pattern the
+ :mod:`unittest` module supports, they are not bare unit tests.
+ Indeed, a whole CherryPy stack is started for you and runs your application.
+ If you want to really unit test your CherryPy application, meaning without
+ having to start a server, you may want to have a look at
+ this `recipe <https://bitbucket.org/Lawouach/cherrypy-recipes/src/tip/testing/unit/serverless/>`_.
diff --git a/docs/basics.rst b/docs/basics.rst
new file mode 100644
index 00000000..8129fc45
--- /dev/null
+++ b/docs/basics.rst
@@ -0,0 +1,732 @@
+.. _basics:
+
+Basics
+------
+
+The following sections will drive you through the basics of
+a CherryPy application, introducing some essential concepts.
+
+.. contents::
+ :depth: 4
+
+The one-minute application example
+##################################
+
+The most basic application you can write with CherryPy
+involves almost all its core concepts.
+
+.. code-block:: python
+ :linenos:
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(Root(), '/')
+
+
+First and foremost, for most tasks, you will never need more than
+a single import statement as demonstrated in line 1.
+
+Before discussing the meat, let's jump to line 9 which shows,
+how to host your application with the CherryPy application server
+and serve it with its builtin HTTP server at the `'/'` path.
+All in one single line. Not bad.
+
+Let's now step back to the actual application. Even though CherryPy
+does not mandate it, most of the time your applications
+will be written as Python classes. Methods of those classes will
+be called by CherryPy to respond to client requests. However,
+CherryPy needs to be aware that a method can be used that way, we
+say the method needs to be :term:`exposed`. This is precisely
+what the :func:`cherrypy.expose()` decorator does in line 4.
+
+Save the snippet in a file named `myapp.py` and run your first
+CherryPy application:
+
+.. code-block:: bash
+
+ $ python myapp.py
+
+Then point your browser at http://127.0.0.1:8080. Tada!
+
+
+.. note::
+
+ CherryPy is a small framework that focuses on one single task:
+ take a HTTP request and locate the most appropriate
+ Python function or method that match the request's URL.
+ Unlike other well-known frameworks, CherryPy does not
+ provide a built-in support for database access, HTML
+ templating or any other middleware nifty features.
+
+ In a nutshell, once CherryPy has found and called an
+ :term:`exposed` method, it is up to you, as a developer, to
+ provide the tools to implement your application's logic.
+
+ CherryPy takes the opinion that you, the developer, know best.
+
+.. warning::
+
+ The previous example demonstrated the simplicty of the
+ CherryPy interface but, your application will likely
+ contain a few other bits and pieces: static service,
+ more complex structure, database access, etc.
+ This will be developed in the tutorial section.
+
+
+CherryPy is a minimal framework but not a bare one, it comes
+with a few basic tools to cover common usages that you would
+expect.
+
+Hosting one or more applications
+################################
+
+A web application needs an HTTP server to be accessed to. CherryPy
+provides its own, production ready, HTTP server. There are two
+ways to host an application with it. The simple one and the almost-as-simple one.
+
+Single application
+^^^^^^^^^^^^^^^^^^
+
+The most straightforward way is to use :func:`cherrypy.quickstart`
+function. It takes at least one argument, the instance of the
+application to host. Two other settings are optionals. First, the
+base path at which the application will be accessible from. Second,
+a config dictionary or file to configure your application.
+
+.. code-block:: python
+
+ cherrypy.quickstart(Blog())
+ cherrypy.quickstart(Blog(), '/blog')
+ cherrypy.quickstart(Blog(), '/blog', {'/': {'tools.gzip.on': True}})
+
+The first one means that your application will be available at
+http://hostname:port/ whereas the other two will make your blog
+application available at http://hostname:port/blog. In addition,
+the last one provides specific settings for the application.
+
+.. note::
+
+ Notice in the third case how the settings are still
+ relative to the application, not where it is made available at,
+ hence the `{'/': ... }` rather than a `{'/blog': ... }`
+
+
+Multiple applications
+^^^^^^^^^^^^^^^^^^^^^
+
+The :func:`cherrypy.quickstart` approach is fine for a single application,
+but lacks the capacity to host several applications with the server.
+To achieve this, one must use the :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>`
+function as follows:
+
+.. code-block:: python
+
+ cherrypy.tree.mount(Blog(), '/blog', blog_conf)
+ cherrypy.tree.mount(Forum(), '/forum', forum_conf)
+
+ cherrypy.engine.start()
+ cherrypy.engine.block()
+
+Essentially, :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>`
+takes the same parameters as :func:`cherrypy.quickstart`: an :term:`application`,
+a hosting path segment and a configuration. The last two lines
+are simply starting application server.
+
+.. important::
+
+ :func:`cherrypy.quickstart` and :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>`
+ are not exclusive. For instance, the previous lines can be written as:
+
+ .. code-block:: python
+
+ cherrypy.tree.mount(Blog(), '/blog', blog_conf)
+ cherrypy.quickstart(Forum(), '/forum', forum_conf)
+
+.. note::
+
+ You can also :ref:`host foreign WSGI application <hostwsgiapp>`.
+
+
+Logging
+#######
+
+Logging is an important task in any application. CherryPy will
+log all incoming requests as well as protocol errors.
+
+To do so, CherryPy manages two loggers:
+
+- an access one that logs every incoming requests
+- an application/error log that traces errors or other application-level messages
+
+Your application may leverage that second logger by calling
+:func:`cherrypy.log() <cherrypy._cplogging.LogManager.error>`.
+
+.. code-block:: python
+
+ cherrypy.log("hello there")
+
+You can also log an exception:
+
+.. code-block:: python
+
+ try:
+ ...
+ except:
+ cherrypy.log("kaboom!", traceback=True)
+
+Both logs are writing to files identified by the following keys
+in your configuration:
+
+- ``log.access_file`` for incoming requests using the
+ `common log format <http://en.wikipedia.org/wiki/Common_Log_Format>`_
+- ``log.error_file`` for the other log
+
+.. seealso::
+
+ Refer to the :mod:`cherrypy._cplogging` module for more
+ details about CherryPy's logging architecture.
+
+Disable logging
+^^^^^^^^^^^^^^^
+
+You may be interested in disabling either logs.
+
+To disable file logging, simply set a en empty string to the
+``log.access_file`` or ``log.error_file`` keys in your
+:ref:`global configuration <globalsettings>`.
+
+To disable, console logging, set ``log.screen`` to `False`.
+
+
+Play along with your other loggers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Your application may obviously already use the :mod:`logging`
+module to trace application level messages. CherryPy will not
+interfere with them as long as your loggers are explicitely
+named. This would work nicely:
+
+.. code-block:: python
+
+ import logging
+ logger = logging.getLogger('myapp.mypackage')
+ logger.setLevel(logging.INFO)
+ stream = logging.StreamHandler()
+ stream.setLevel(logging.INFO)
+ logger.addHandler(stream)
+
+.. _config:
+
+Configuring
+###########
+
+CherryPy comes with a fine-grained configuration mechanism and
+settings can be set at various levels.
+
+.. seealso::
+
+ Once you have the reviewed the basics, please refer
+ to the :ref:`in-depth discussion <configindepth>`
+ around configuration.
+
+.. _globalsettings:
+
+Global server configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To configure the HTTP and application servers,
+use the :meth:`cherrypy.config.update() <cherrypy._cpconfig.Config.update>`
+method.
+
+.. code-block:: python
+
+ cherrypy.config.update({'server.socket_port': 9090})
+
+The :mod:`cherrypy.config <cherrypy._cpconfig>` object is a dictionary and the
+update method merges the passed dictionary into it.
+
+You can also pass a file instead (assuming a `server.conf`
+file):
+
+.. code-block:: ini
+
+ [global]
+ server.socket_port: 9090
+
+.. code-block:: python
+
+ cherrypy.config.update("server.conf")
+
+.. warning::
+
+ :meth:`cherrypy.config.update() <cherrypy._cpconfig.Config.update>`
+ is not meant to be used to configure the application.
+ It is a common mistake. It is used to configure the server and engine.
+
+.. _perappconf:
+
+Per-application configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To configure your application, pass in a dictionary or a file
+when you associate their application to the server.
+
+.. code-block:: python
+
+ cherrypy.quickstart(myapp, '/', {'/': {'tools.gzip.on': True}})
+
+or via a file (called `app.conf` for instance):
+
+.. code-block:: ini
+
+ [/]
+ tools.gzip.on: True
+
+.. code-block:: python
+
+ cherrypy.quickstart(myapp, '/', "app.conf")
+
+Although, you can define most of your configuration in a global
+fashion, it is sometimes convenient to define them
+where they are applied in the code.
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.gzip()
+ def index(self):
+ return "hello world!"
+
+A variant notation to the above:
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world!"
+ index._cp_config = {'tools.gzip.on': True}
+
+Both methods have the same effect so pick the one
+that suits your style best.
+
+Additional application settings
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can add settings that are not specific to a request URL
+and retrieve them from your page handler as follows:
+
+.. code-block:: ini
+
+ [/]
+ tools.gzip.on: True
+
+ [googleapi]
+ key = "..."
+ appid = "..."
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ google_appid = cherrypy.request.app.config['googleapi']['appid']
+ return "hello world!"
+
+ cherrypy.quickstart(Root(), '/', "app.conf")
+
+
+Cookies
+#######
+
+CherryPy uses the :mod:`Cookie` module from python and in particular the
+:class:`Cookie.SimpleCookie` object type to handle cookies.
+
+- To send a cookie to a browser, set ``cherrypy.response.cookie[key] = value``.
+- To retrieve a cookie sent by a browser, use ``cherrypy.request.cookie[key]``.
+- To delete a cookie (on the client side), you must *send* the cookie with its
+ expiration time set to `0`:
+
+.. code-block:: python
+
+ cherrypy.response.cookie[key] = value
+ cherrypy.response.cookie[key]['expires'] = 0
+
+It's important to understand that the request cookies are **not** automatically
+copied to the response cookies. Clients will send the same cookies on every
+request, and therefore ``cherrypy.request.cookie`` should be populated each
+time. But the server doesn't need to send the same cookies with every response;
+therefore, ``cherrypy.response.cookie`` will usually be empty. When you wish
+to “delete” (expire) a cookie, therefore, you must set
+``cherrypy.response.cookie[key] = value`` first, and then set its ``expires``
+attribute to 0.
+
+Extended example:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class MyCookieApp(object):
+ @cherrypy.expose
+ def set(self):
+ cookie = cherrypy.response.cookie
+ cookie['cookieName'] = 'cookieValue'
+ cookie['cookieName']['path'] = '/'
+ cookie['cookieName']['max-age'] = 3600
+ cookie['cookieName']['version'] = 1
+ return "<html><body>Hello, I just sent you a cookie</body></html>"
+
+ @cherrypy.expose
+ def read(self):
+ cookie = cherrypy.request.cookie
+ res = """<html><body>Hi, you sent me %s cookies.<br />
+ Here is a list of cookie names/values:<br />""" % len(cookie)
+ for name in cookie.keys():
+ res += "name: %s, value: %s<br>" % (name, cookie[name].value)
+ return res + "</body></html>"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(MyCookieApp(), '/cookie')
+
+
+.. _basicsession:
+
+Using sessions
+##############
+
+Sessions are one of the most common mechanism used by developers to
+identify users and synchronize their activity. By default, CherryPy
+does not activate sessions because it is not a mandatory feature
+to have, to enable it simply add the following settings in your
+configuration:
+
+.. code-block:: ini
+
+ [/]
+ tools.sessions.on: True
+
+.. code-block:: python
+
+ cherrypy.quickstart(myapp, '/', "app.conf")
+
+Sessions are, by default, stored in RAM so, if you restart your server
+all of your current sessions will be lost. You can store them in memcached
+or on the filesystem instead.
+
+Using sessions in your applications is done as follows:
+
+.. code-block:: python
+
+ import cherrypy
+
+ @cherrypy.expose
+ def index(self):
+ if 'count' not in cherrypy.session:
+ cherrypy.session['count'] = 0
+ cherrypy.session['count'] += 1
+
+In this snippet, everytime the the index page handler is called,
+the current user's session has its `'count'` key incremented by `1`.
+
+CherryPy knows which session to use by inspecting the cookie
+sent alongside the request. This cookie contains the session
+identifier used by CherryPy to load the user's session from
+the storage.
+
+.. seealso::
+
+ Refer to the :mod:`cherrypy.lib.sessions` module for more
+ details about the session interface and implementation.
+ Notably you will learn about sessions expiration.
+
+Filesystem backend
+^^^^^^^^^^^^^^^^^^
+
+Using a filesystem is a simple to not lose your sessions
+between reboots. Each session is saved in its own file within
+the given directory.
+
+.. code-block:: ini
+
+ [/]
+ tools.sessions.on: True
+ tools.sessions.storage_type = "file"
+ tools.sessions.storage_path = "/some/directorys"
+
+Memcached backend
+^^^^^^^^^^^^^^^^^
+
+`Memcached <http://memcached.org/>`_ is a popular key-store on top of your RAM,
+it is distributed and a good choice if you want to
+share sessions outside of the process running CherryPy.
+
+.. code-block:: ini
+
+ [/]
+ tools.sessions.on: True
+ tools.sessions.storage_type = "memcached"
+
+.. _staticontent:
+
+Static content serving
+######################
+
+CherryPy can serve your static content such as images, javascript and
+CSS resources, etc.
+
+.. note::
+
+ CherryPy uses the :mod:`mimetypes` module to determine the
+ best content-type to serve a particular resource. If the choice
+ is not valid, you can simply set more media-types as follows:
+
+ .. code-block:: python
+
+ import mimetypes
+ mimetypes.types_map['.csv'] = 'text/csv'
+
+
+Serving a single file
+^^^^^^^^^^^^^^^^^^^^^
+
+You can serve a single file as follows:
+
+.. code-block:: ini
+
+ [/style.css]
+ tools.staticfile.on = True
+ tools.staticfile.filename = "/home/site/style.css"
+
+CherryPy will automatically respond to URLs such as
+`http://hostname/style.css`.
+
+Serving a whole directory
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Serving a whole directory is similar to a single file:
+
+.. code-block:: ini
+
+ [/static]
+ tools.staticdir.on = True
+ tools.staticdir.dir = "/home/site/static"
+
+Assuming you have a file at `static/js/my.js`,
+CherryPy will automatically respond to URLs such as
+`http://hostname/static/js/my.js`.
+
+
+.. note::
+
+ CherryPy always requires the absolute path to the files or directories
+ it will serve. If you have several static sections to configure
+ but located in the same root directory, you can use the following
+ shortcut:
+
+
+ .. code-block:: ini
+
+ [/]
+ tools.staticdir.root = "/home/site"
+
+ [/static]
+ tools.staticdir.on = True
+ tools.staticdir.dir = "static"
+
+Allow files downloading
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Using ``"application/x-download"`` response content-type,
+you can tell a browser that a resource should be downloaded
+onto the user's machine rather than displayed.
+
+You could for instance write a page handler as follows:
+
+.. code-block:: python
+
+ from cherrypy.lib.static import serve_file
+
+ @cherrypy.expose
+ def download(self, filepath):
+ return serve_file(filepath, "application/x-download", "attachment")
+
+Assuming the filepath is a valid path on your machine, the
+response would be considered as a downloadable content by
+the browser.
+
+.. warning::
+
+ The above page handler is a security risk on its own since any file
+ of the server could be accessed (if the user running the
+ server had permissions on them).
+
+
+Dealing with JSON
+#################
+
+CherryPy has built-in support for JSON encoding and decoding
+of the request and/or response.
+
+Decoding request
+^^^^^^^^^^^^^^^^
+
+To automatically decode the content of a request using JSON:
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.json_in()
+ def index(self):
+ data = cherrypy.request.json
+
+The `json` attribute attached to the request contains
+the decoded content.
+
+Encoding response
+^^^^^^^^^^^^^^^^^
+
+To automatically encode the content of a response using JSON:
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def index(self):
+ return {'key': 'value'}
+
+CherryPy will encode any content returned by your page handler
+using JSON. Not all type of objects may natively be
+encoded.
+
+Authentication
+##############
+
+CherryPy provides support for two very simple authentication mechanisms,
+both described in :rfc:`2617`: Basic and Digest. They are most commonly
+known to trigger a browser's popup asking users their name
+and password.
+
+Basic
+^^^^^
+
+Basic authentication is the simplest form of authentication however
+it is not a secure one as the user's credentials are embedded into
+the request. We advise against using it unless you are running on
+SSL or within a closed network.
+
+.. code-block:: python
+
+ from cherrypy.lib import auth_basic
+
+ USERS = {'jon': 'secret'}
+
+ def validate_password(username, password):
+ if username in USERS and USERS[username] == password:
+ return True
+ return False
+
+ conf = {
+ '/protected/area': {
+ 'tools.auth_basic.on': True,
+ 'tools.auth_basic.realm': 'localhost',
+ 'tools.auth_basic.checkpassword': validate_password
+ }
+ }
+
+ cherrypy.quickstart(myapp, '/', conf)
+
+Simply put, you have to provide a function that will
+be called by CherryPy passing the username and password
+decoded from the request.
+
+The function can read its data from any source it has to: a file,
+a database, memory, etc.
+
+
+Digest
+^^^^^^
+
+Digest authentication differs by the fact the credentials
+are not carried on by the request so it's a little more secure
+than basic.
+
+CherryPy's digest support has a similar interface to the
+basic one explained above.
+
+.. code-block:: python
+
+ from cherrypy.lib import auth_digest
+
+ USERS = {'jon': 'secret'}
+
+ conf = {
+ '/protected/area': {
+ 'tools.auth_digest.on': True,
+ 'tools.auth_digest.realm': 'localhost',
+ 'tools.auth_digest.get_ha1': auth_digest.get_ha1_dict_plain(USERS),
+ 'tools.auth_digest.key': 'a565c27146791cfb'
+ }
+ }
+
+ cherrypy.quickstart(myapp, '/', conf)
+
+Favicon
+#######
+
+CherryPy serves its own sweet red cherrypy as the default
+`favicon <http://en.wikipedia.org/wiki/Favicon>`_ using the static file
+tool. You can serve your own favicon as follows:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class HelloWorld(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(HelloWorld(), '/',
+ {
+ '/favicon.ico':
+ {
+ 'tools.staticfile.on': True,
+ 'tools.staticfile.filename:' '/path/to/myfavicon.ico'
+ }
+ }
+ )
+
+Please refer to the :ref:`static serving <staticontent>` section
+for more details.
+
+You can also use a file to configure it:
+
+.. code-block:: ini
+
+ [/favicon.ico]
+ tools.staticfile.on: True
+ tools.staticfile.filename: "/path/to/myfavicon.ico"
+
+
+.. code-block:: python
+
+ import cherrypy
+
+ class HelloWorld(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(HelloWorld(), '/', app.conf)
diff --git a/sphinx/source/conf.py b/docs/conf.py
index 8452d262..2c1ceb63 100644
--- a/sphinx/source/conf.py
+++ b/docs/conf.py
@@ -3,7 +3,8 @@
# CherryPy documentation build configuration file, created by
# sphinx-quickstart on Sat Feb 20 09:18:03 2010.
#
-# This file is execfile()d with the current directory set to its containing dir.
+# This file is execfile()d with the current directory set to its containing
+# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
@@ -11,18 +12,26 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import sys
+import os
+
+try:
+ import sphinx_rtd_theme
+ WITH_RTD_THEME = True
+except ImportError:
+ WITH_RTD_THEME = False
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+# sys.path.append(os.path.abspath('.'))
-# -- General configuration -----------------------------------------------------
+# -- General configuration -----------------------------------------------
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.intersphinx']
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.todo', 'sphinx.ext.intersphinx']
intersphinx_mapping = {'http://docs.python.org/2/': None}
# Add any paths that contain templates here, relative to this directory.
@@ -39,15 +48,14 @@ master_doc = 'index'
# General information about the project.
project = u'CherryPy'
-copyright = u'2013, CherryPy Team'
+copyright = u'2014, CherryPy Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-import sys
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../'))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
import cherrypy
version = cherrypy.__version__
# The full version, including alpha/beta/rc tags.
@@ -70,7 +78,8 @@ release = cherrypy.__version__
# for source files.
exclude_trees = []
-# The reST default role (used for this markup: `text`) to use for all documents.
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
@@ -91,32 +100,37 @@ pygments_style = 'sphinx'
#modindex_common_prefix = []
-# -- Options for HTML output ---------------------------------------------------
+# -- Options for HTML output ---------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
-html_theme = 'default'
+if WITH_RTD_THEME:
+ html_theme = "sphinx_rtd_theme"
+else:
+ html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-html_theme_options = {
- "relbarbgcolor": "#880000",
- "relbartextcolor": "white",
- "relbarlinkcolor": "#FFEEEE",
- "sidebarbgcolor": "#880000",
- "sidebartextcolor": "white",
- "sidebarlinkcolor": "#FFEEEE",
- "headbgcolor": "#FFF8FB",
- "headtextcolor": "black",
- "headlinkcolor": "#660000",
- "footerbgcolor": "#880000",
- "footertextcolor": "white",
- "codebgcolor": "#FFEEEE",
-}
+# html_theme_options = {
+# "relbarbgcolor": "#880000",
+# "relbartextcolor": "white",
+# "relbarlinkcolor": "#FFEEEE",
+# "sidebarbgcolor": "#880000",
+# "sidebartextcolor": "white",
+# "sidebarlinkcolor": "#FFEEEE",
+# "headbgcolor": "#FFF8FB",
+# "headtextcolor": "black",
+# "headlinkcolor": "#660000",
+# "footerbgcolor": "#880000",
+# "footertextcolor": "white",
+# "codebgcolor": "#FFEEEE",
+# }
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
+if WITH_RTD_THEME:
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@@ -139,7 +153,7 @@ html_theme_options = {
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
-html_style = 'cpdocmain.css'
+# html_style = 'cpdocmain.css'
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
@@ -180,7 +194,7 @@ html_style = 'cpdocmain.css'
htmlhelp_basename = 'CherryPydoc'
-# -- Options for LaTeX output --------------------------------------------------
+# -- Options for LaTeX output --------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
@@ -189,10 +203,11 @@ htmlhelp_basename = 'CherryPydoc'
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
+# (source start file, target name, title, author,
+# documentclass [howto/manual]).
latex_documents = [
- ('index', 'CherryPy.tex', u'CherryPy Documentation',
- u'CherryPy Team', 'manual'),
+ ('index', 'CherryPy.tex', u'CherryPy Documentation',
+ u'CherryPy Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -211,3 +226,37 @@ latex_documents = [
# If false, no module index is generated.
#latex_use_modindex = True
+
+import os
+
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+if on_rtd:
+ # so that ReadTheDocs can generate the docs properly
+ # even the PDF version. Since ReadTheDocs runs on Linux,
+ # it can't import pywin32. See:
+ # http://read-the-docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
+ import sys
+
+ class Mock(object):
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def __call__(self, *args, **kwargs):
+ return Mock()
+
+ @classmethod
+ def __getattr__(cls, name):
+ if name in ('__file__', '__path__'):
+ return '/dev/null'
+ elif name[0] == name[0].upper():
+ mockType = type(name, (), {})
+ mockType.__module__ = __name__
+ return mockType
+ else:
+ return Mock()
+
+ MOCK_MODULES = ['win32api', 'win32con', 'win32event',
+ 'win32service', 'win32serviceutil']
+ for mod_name in MOCK_MODULES:
+ sys.modules[mod_name] = Mock()
diff --git a/sphinx/source/tutorial/config.rst b/docs/config.rst
index 7d231fe9..a4eb3acc 100644
--- a/sphinx/source/tutorial/config.rst
+++ b/docs/config.rst
@@ -1,8 +1,7 @@
-:tocdepth: 3
+.. _configindepth:
-*************
-Configuration
-*************
+Configure
+---------
Configuration in CherryPy is implemented via dictionaries. Keys are strings
which name the mapped value; values may be of any type.
@@ -12,8 +11,16 @@ directly on the engine, server, request, response, and log objects. So the
best way to know the full range of what's available in the config file is to
simply import those objects and see what ``help(obj)`` tells you.
+.. note::
+
+ If you are new to CherryPy, please refer first to the simpler
+ :ref:`basic config <config>` section first.
+
+.. contents::
+ :depth: 3
+
Architecture
-============
+############
The first thing you need to know about CherryPy 3's configuration is that it
separates *global* config from *application* config. If you're deploying
@@ -27,7 +34,7 @@ context, and configuration data may apply to any of those three scopes.
Let's look at each of those scopes in turn.
Global config
--------------
+^^^^^^^^^^^^^
Global config entries apply everywhere, and are stored in
:class:`cherrypy.config <cherrypy._cpconfig.Config>`. This flat dict only holds
@@ -38,7 +45,9 @@ Global config is stored in the
:class:`cherrypy.config <cherrypy._cpconfig.Config>` dict,
and you therefore update it by calling ``cherrypy.config.update(conf)``.
The ``conf`` argument can be either a filename, an open file, or a dict of
-config entries. Here's an example of passing a dict argument::
+config entries. Here's an example of passing a dict argument:
+
+.. code-block:: python
cherrypy.config.update({'server.socket_host': '64.72.221.48',
'server.socket_port': 80,
@@ -49,7 +58,7 @@ interface CherryPy will listen. The ``server.socket_port`` option declares
the TCP port on which to listen.
Application config
-------------------
+^^^^^^^^^^^^^^^^^^
Application entries apply to a single mounted application, and are stored on
each Application object itself as
@@ -63,13 +72,17 @@ although you may also use ``app.merge(conf)``.
The ``conf`` argument can be either a filename, an open file, or a dict of
config entries.
-Configuration file example::
+Configuration file example:
+
+.. code-block:: ini
[/]
tools.trailing_slash.on = False
request.dispatch: cherrypy.dispatch.MethodDispatcher()
-or, in python code::
+or, in python code:
+
+.. code-block:: python
config = {'/':
{
@@ -82,7 +95,9 @@ or, in python code::
CherryPy only uses sections that start with ``"/"`` (except
``[global]``, see below). That means you can place your own configuration
entries in a CherryPy config file by giving them a section name which does not
-start with ``"/"``. For example, you might include database entries like this::
+start with ``"/"``. For example, you might include database entries like this:
+
+.. code-block:: ini
[global]
server.socket_host: "0.0.0.0"
@@ -100,7 +115,7 @@ via ``cherrypy.request.app.config['Databases']``. For code that is outside the
request process, you'll have to pass a reference to your Application around.
Request config
---------------
+^^^^^^^^^^^^^^
Each Request object possesses a single
:attr:`request.config <cherrypy._cprequest.Request.config>` dict. Early in the
@@ -114,24 +129,26 @@ This dict contains only those config entries which apply to the given request.
this config attribute is recalculated for the new path.
Declaration
-===========
+###########
Configuration data may be supplied as a Python dictionary, as a filename,
or as an open file object.
Configuration files
--------------------
+^^^^^^^^^^^^^^^^^^^
When you supply a filename or file, CherryPy uses Python's builtin ConfigParser;
you declare Application config by writing each path as a section header,
-and each entry as a ``"key: value"`` (or ``"key = value"``) pair::
+and each entry as a ``"key: value"`` (or ``"key = value"``) pair:
+
+.. code-block:: ini
[/path/to/my/page]
response.stream: True
tools.trailing_slash.extra = False
Combined Configuration Files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are only deploying a single application, you can make a single config
file that contains both global and app entries. Just stick the global entries
@@ -143,7 +160,7 @@ config to both places for you. But as soon as you decide to add another
application to the same site, you need to separate the two config files/dicts.
Separate Configuration Files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're deploying more than one application in the same process, you need
(1) file for global config, plus (1) file for *each* Application.
@@ -155,7 +172,9 @@ and application config is usually passed in a call to
In general, you should set global config first, and then mount each
application with its own config. Among other benefits, this allows you to set
up global logging so that, if something goes wrong while trying to mount
-an application, you'll see the tracebacks. In other words, use this order::
+an application, you'll see the tracebacks. In other words, use this order:
+
+.. code-block:: python
# global config
cherrypy.config.update({'environment': 'production',
@@ -178,7 +197,7 @@ an application, you'll see the tracebacks. In other words, use this order::
cherrypy.engine.start()
Values in config files use Python syntax
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Config entries are always a key/value pair, like ``server.socket_port = 8080``.
The key is always a name, and the value is always a Python object. That is,
@@ -186,7 +205,9 @@ if the value you are setting is an ``int`` (or other number), it needs to look
like a Python ``int``; for example, ``8080``. If the value is a string, it
needs to be quoted, just like a Python string. Arbitrary objects can also be
created, just like in Python code (assuming they can be found/imported).
-Here's an extended example, showing you some of the different types::
+Here's an extended example, showing you some of the different types:
+
+.. code-block:: ini
[global]
log.error_file: "/home/fumanchu/myapp.log"
@@ -200,17 +221,21 @@ Here's an extended example, showing you some of the different types::
.. _cp_config:
_cp_config: attaching config to handlers
-----------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Config files have a severe limitation: values are always keyed by URL.
-For example::
+For example:
+
+.. code-block:: ini
[/path/to/page]
methods_with_bodies = ("POST", "PUT", "PROPPATCH")
It's obvious that the extra method is the norm for that path; in fact,
the code could be considered broken without it. In CherryPy, you can attach
-that bit of config directly on the page handler::
+that bit of config directly on the page handler:
+
+.. code-block:: python
def page(self):
return "Hello, world!"
@@ -225,7 +250,9 @@ config dictionary is placed in
:attr:`cherrypy.request.config <cherrypy._cprequest.Request.config>`.
This can be done at any point in the tree of objects; for example, we could have
-attached that config to a class which contains the page method::
+attached that config to a class which contains the page method:
+
+.. code-block:: python
class SetOPages:
@@ -251,7 +278,7 @@ This technique allows you to:
.. _namespaces:
Namespaces
-==========
+##########
Because config entries usually just set attributes on objects, they're almost
all of the form: ``object.attribute``. A few are of the form:
@@ -262,7 +289,9 @@ as possible to the actual object referenced by the namespace; for example,
the entry ``response.stream`` actually sets the ``stream`` attribute of
:class:`cherrypy.response <cherrypy._cprequest.Response>`! In this way,
you can easily determine the default value by firing up a python interpreter
-and typing::
+and typing:
+
+.. code-block:: python
>>> import cherrypy
>>> cherrypy.response.stream
@@ -275,7 +304,7 @@ don't work like normal attributes behind the scenes; however, they still use
dotted keys and are considered to "have a namespace".
Builtin namespaces
-------------------
+^^^^^^^^^^^^^^^^^^
Entries from each namespace may be allowed in the global, application root
(``"/"``) or per-path config, or a combination:
@@ -293,7 +322,8 @@ tools X X X
========== ====== ================== =========
engine
-^^^^^^
+~~~~~~
+
Entries in this namespace controls the 'application engine'. These can only be
declared in the global config. Any attribute of
:class:`cherrypy.engine<cherrypy.process.wspbus.Bus>` may be set
@@ -312,17 +342,19 @@ in config; however, there are a few extra entries available in config:
:func:`cherrypy.quickstart`.
hooks
-^^^^^
+~~~~~
Declares additional request-processing functions. Use this to append your own
:class:`Hook<cherrypy._cprequest.Hook>` functions to the request. For example,
-to add ``my_hook_func`` to the ``before_handler`` hookpoint::
+to add ``my_hook_func`` to the ``before_handler`` hookpoint:
+
+.. code-block:: ini
[/]
hooks.before_handler = myapp.my_hook_func
log
-^^^
+~~~
Configures logging. These can only be declared in the global config (for global
logging) or ``[/]`` config (for each application).
@@ -331,32 +363,33 @@ configurable attributes. Typically, the "access_file", "error_file", and
"screen" attributes are the most commonly configured.
request
-^^^^^^^
+~~~~~~~
Sets attributes on each Request. See the
:class:`Request<cherrypy._cprequest.Request>` class for a complete list.
response
-^^^^^^^^
+~~~~~~~~
Sets attributes on each Response. See the
:class:`Response<cherrypy._cprequest.Response>` class for a complete list.
server
-^^^^^^
+~~~~~~
+
Controls the default HTTP server via
:class:`cherrypy.server<cherrypy._cpserver.Server>` (see that class for a
complete list of configurable attributes). These can only be
declared in the global config.
tools
-^^^^^
+~~~~~
Enables and configures additional request-processing packages. See the
:doc:`/tutorial/tools` overview for more information.
wsgi
-^^^^
+~~~~
Adds WSGI middleware to an Application's "pipeline". These can only be
declared in the app's root config ("/").
@@ -370,7 +403,7 @@ declared in the app's root config ("/").
:class:`Response<cherrypy._cprequest.Response>` class.
checker
-^^^^^^^
+~~~~~~~
Controls the "checker", which looks for common errors in app state (including
config) when the engine starts. You can turn off individual checks by setting
@@ -379,7 +412,8 @@ complete list. Global config only.
Custom config namespaces
-------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^
+
You can define your own namespaces if you like, and they can do far more than
simply set attributes. The ``test/test_config`` module, for example, shows an
@@ -392,7 +426,9 @@ any config entries in its namespace. You add it to a namespaces registry
(a dict), where keys are namespace names and values are handler functions.
When a config entry for your namespace is encountered, the corresponding
handler function will be called, passing the config key and value; that is,
-``namespaces[namespace](k, v)``. For example, if you write::
+``namespaces[namespace](k, v)``. For example, if you write:
+
+.. code-block:: python
def db_namespace(k, v):
if k == 'connstring':
@@ -422,7 +458,7 @@ the handler. Context managers are defined in :pep:`343`.
.. _environments:
Environments
-============
+^^^^^^^^^^^^
The only key that does not exist in a namespace is the *"environment"* entry.
It only applies to the global config, and only when you use
@@ -435,7 +471,9 @@ entry *imports* other config entries from the following template stored in
:end-before: Sphinx end config.environments
If you find the set of existing environments (production, staging, etc) too
-limiting or just plain wrong, feel free to extend them or add new environments::
+limiting or just plain wrong, feel free to extend them or add new environments:
+
+.. code-block:: python
cherrypy._cpconfig.environments['staging']['log.screen'] = False
diff --git a/docs/contribute.rst b/docs/contribute.rst
new file mode 100644
index 00000000..94bdf2c0
--- /dev/null
+++ b/docs/contribute.rst
@@ -0,0 +1,6 @@
+
+Contribute
+----------
+
+
+To be done.
diff --git a/docs/deploy.rst b/docs/deploy.rst
new file mode 100644
index 00000000..b7bab85e
--- /dev/null
+++ b/docs/deploy.rst
@@ -0,0 +1,602 @@
+
+Deploy
+------
+
+CherryPy stands on its own, but as an application server, it is often
+located in shared or complex environments. For this reason,
+it is not uncommon to run CherryPy behind a reverse proxy
+or use other servers to host the application.
+
+.. note::
+
+ CherryPy's server has proven reliable and fast enough
+ for years now. If the volume of traffic you receive is
+ average, it will do well enough on its own. Nonetheless,
+ it is common to delegate the serving of static content
+ to more capable servers such as `nginx <http://nginx.org>`_ or
+ CDN.
+
+.. contents::
+ :depth: 3
+
+
+Run as a daemon
+###############
+
+CherryPy allows you to easily decouple the current process from the parent
+environment, using the traditional double-fork:
+
+.. code-block:: python
+
+ from cherrypy.process.plugins import Daemonizer
+ d = Daemonizer(cherrypy.engine)
+ d.subscribe()
+
+.. note::
+
+ This :ref:`engine plugin <busplugins>` is only available on
+ Unix and similar systems which provide `fork()`.
+
+If a startup error occurs in the forked children, the return code from the
+parent process will still be 0. Errors in the initial daemonizing process still
+return proper exit codes, but errors after the fork won't. Therefore, if you use
+this plugin to daemonize, don't use the return code as an accurate indicator of
+whether the process fully started. In fact, that return code only indicates if
+the process successfully finished the first fork.
+
+The plugin takes optional arguments to redirect standard streams: ``stdin``,
+``stdout``, and ``stderr``. By default, these are all redirected to
+:file:`/dev/null`, but you're free to send them to log files or elsewhere.
+
+.. warning::
+
+ You should be careful to not start any threads before this plugin runs.
+ The plugin will warn if you do so, because "...the effects of calling functions
+ that require certain resources between the call to fork() and the call to an
+ exec function are undefined". (`ref <http://www.opengroup.org/onlinepubs/000095399/functions/fork.html>`_).
+ It is for this reason that the Server plugin runs at priority 75 (it starts
+ worker threads), which is later than the default priority of 65 for the
+ Daemonizer.
+
+Run as a different user
+#######################
+
+Use this :ref:`engine plugin <busplugins>` to start your
+CherryPy site as root (for example, to listen on a privileged port like 80)
+and then reduce privileges to something more restricted.
+
+This priority of this plugin's "start" listener is slightly higher than the
+priority for `server.start` in order to facilitate the most common use:
+starting on a low port (which requires root) and then dropping to another user.
+
+.. code-block:: python
+
+ DropPrivileges(cherrypy.engine, uid=1000, gid=1000).subscribe()
+
+PID files
+#########
+
+The PIDFile :ref:`engine plugin <busplugins>` is pretty straightforward: it writes
+the process id to a file on start, and deletes the file on exit. You must
+provide a 'pidfile' argument, preferably an absolute path:
+
+.. code-block:: python
+
+ PIDFile(cherrypy.engine, '/var/run/myapp.pid').subscribe()
+
+
+Control via Supervisord
+#######################
+
+`Supervisord <http://supervisord.org>`_ is a powerful process control
+and management tool that can perform a lot of tasks around process monitoring.
+
+Below is a simple supervisor configuration for your CherryPy
+application.
+
+.. code-block:: ini
+
+ [unix_http_server]
+ file=/tmp/supervisor.sock
+
+ [supervisord]
+ logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
+ logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
+ logfile_backups=10 ; (num of main logfile rotation backups;default 10)
+ loglevel=info ; (log level;default info; others: debug,warn,trace)
+ pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
+ nodaemon=false ; (start in foreground if true;default false)
+ minfds=1024 ; (min. avail startup file descriptors;default 1024)
+ minprocs=200 ; (min. avail process descriptors;default 200)
+
+ [rpcinterface:supervisor]
+ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+ [supervisorctl]
+ serverurl=unix:///tmp/supervisor.sock
+
+ [program:myapp]
+ command=python server.py
+ environment=PYTHONPATH=.
+ directory=.
+
+This could control your server via the ``server.py`` module as
+the application entry point.
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+
+ cherrypy.config.update({'server.socket_port': 8090,
+ 'engine.autoreload_on': False,
+ 'log.access_file': './access.log',
+ 'log.error_file': './error.log'})
+ cherrypy.quickstart(Root())
+
+To take the configuration (assuming it was saved in a file
+called ``supervisor.conf``) into account:
+
+.. code-block:: bash
+
+ $ supervisord -c supervisord.conf
+ $ supervisorctl update
+
+Now, you can point your browser at http://localhost:8090/
+and it will display `Hello World!`.
+
+To stop supervisor, type:
+
+.. code-block:: bash
+
+ $ supervisorctl shutdown
+
+This will obviously shutdown your application.
+
+.. _ssl:
+
+SSL support
+###########
+
+.. note::
+
+ You may want to test your server for SSL using the services
+ from `Qualys, Inc. <https://www.ssllabs.com/ssltest/index.html>`_
+
+
+CherryPy can encrypt connections using SSL to create an https connection. This keeps your web traffic secure. Here's how.
+
+1. Generate a private key. We'll use openssl and follow the `OpenSSL Keys HOWTO <https://www.openssl.org/docs/HOWTO/keys.txt>`_.:
+
+.. code-block:: bash
+
+ $ openssl genrsa -out privkey.pem 2048
+
+You can create either a key that requires a password to use, or one without a password. Protecting your private key with a password is much more secure, but requires that you enter the password every time you use the key. For example, you may have to enter the password when you start or restart your CherryPy server. This may or may not be feasible, depending on your setup.
+
+If you want to require a password, add one of the ``-aes128``, ``-aes192`` or ``-aes256`` switches to the command above. You should not use any of the DES, 3DES, or SEED algoritms to protect your password, as they are insecure.
+
+SSL Labs recommends using 2048-bit RSA keys for security (see references section at the end).
+
+
+2. Generate a certificate. We'll use openssl and follow the `OpenSSL Certificates HOWTO <https://www.openssl.org/docs/HOWTO/certificates.txt>`_. Let's start off with a self-signed certificate for testing:
+
+.. code-block:: bash
+
+ $ openssl req -new -x509 -days 365 -key privkey.pem -out cert.pem
+
+openssl will then ask you a series of questions. You can enter whatever values are applicable, or leave most fields blank. The one field you *must* fill in is the 'Common Name': enter the hostname you will use to access your site. If you are just creating a certificate to test on your own machine and you access the server by typing 'localhost' into your browser, enter the Common Name 'localhost'.
+
+
+3. Decide whether you want to use python's built-in SSL library, or the pyOpenSSL library. CherryPy supports either.
+
+ a) *Built-in.* To use python's built-in SSL, add the following line to your CherryPy config:
+
+ .. code-block:: python
+
+ cherrypy.server.ssl_module = 'builtin'
+
+ b) *pyOpenSSL*. Because python did not have a built-in SSL library when CherryPy was first created, the default setting is to use pyOpenSSL. To use it you'll need to install it (we could recommend you install `cython <http://cython.org/>`_ first):
+
+ .. code-block:: bash
+
+ $ pip install cython, pyOpenSSL
+
+
+4. Add the following lines in your CherryPy config to point to your certificate files:
+
+.. code-block:: python
+
+ cherrypy.server.ssl_certificate = "cert.pem"
+ cherrypy.server.ssl_private_key = "privkey.pem"
+
+5. If you have a certificate chain at hand, you can also specify it:
+
+.. code-block:: python
+
+ cherrypy.server.ssl_certificate_chain = "certchain.perm"
+
+6. Start your CherryPy server normally. Note that if you are debugging locally and/or using a self-signed certificate, your browser may show you security warnings.
+
+WSGI servers
+############
+
+Embedding into another WSGI framework
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Though CherryPy comes with a very reliable and fast enough HTTP server,
+you may wish to integrate your CherryPy application within a
+different framework. To do so, we will benefit from the WSGI
+interface defined in :pep:`333` and :pep:`3333`.
+
+Note that you should follow some basic rules when embedding CherryPy
+in a third-party WSGI server:
+
+- If you rely on the `"main"` channel to be published on, as
+ it would happen within the CherryPy's mainloop, you should
+ find a way to publish to it within the other framework's mainloop.
+
+- Start the CherryPy's engine. This will publish to the `"start"` channel
+ of the bus.
+
+ .. code-block:: python
+
+ cherrypy.engine.start()
+
+- Stop the CherryPy's engine when you terminate. This will publish
+ to the `"stop"` channel of the bus.
+
+ .. code-block:: python
+
+ cherrypy.engine.stop()
+
+- Do not call ``cherrypy.engine.block()``.
+
+- Disable the built-in HTTP server since it will not be used.
+
+ .. code-block:: python
+
+ cherrypy.server.unsubscribe()
+
+- Disable autoreload. Usually other frameworks won't react well to it,
+ or sometimes, provide the same feature.
+
+ .. code-block:: python
+
+ cherrypy.config.update({'engine.autoreload.on': False})
+
+- Disable CherryPy signals handling. This may not be needed, it depends
+ on how the other framework handles them.
+
+ .. code-block:: python
+
+ cherrypy.engine.signals.subscribe()
+
+- Use the ``"embedded"`` environment configuration scheme.
+
+ .. code-block:: python
+
+ cherrypy.config.update({'environment': 'embedded'})
+
+ Essentially this will disable the following:
+
+ - Stdout logging
+ - Autoreloader
+ - Configuration checker
+ - Headers logging on error
+ - Tracebacks in error
+ - Mismatched params error during dispatching
+ - Signals (SIGHUP, SIGTERM)
+
+Tornado
+^^^^^^^
+
+You can use `tornado <http://www.tornadoweb.org/>`_ HTTP server as
+follow:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ if __name__ == '__main__':
+ import tornado
+ import tornado.httpserver
+ import tornado.wsgi
+
+ # our WSGI application
+ wsgiapp = cherrypy.tree.mount(Root())
+
+ # Disable the autoreload which won't play well
+ cherrypy.config.update({'engine.autoreload.on': False})
+
+ # let's not start the CherryPy HTTP server
+ cherrypy.server.unsubscribe()
+
+ # use CherryPy's signal handling
+ cherrypy.engine.signals.subscribe()
+
+ # Prevent CherryPy logs to be propagated
+ # to the Tornado logger
+ cherrypy.log.error_log.propagate = False
+
+ # Run the engine but don't block on it
+ cherrypy.engine.start()
+
+ # Run thr tornado stack
+ container = tornado.wsgi.WSGIContainer(wsgiapp)
+ http_server = tornado.httpserver.HTTPServer(container)
+ http_server.listen(8080)
+ # Publish to the CherryPy engine as if
+ # we were using its mainloop
+ tornado.ioloop.PeriodicCallback(lambda: cherrypy.engine.publish('main'), 100).start()
+ tornado.ioloop.IOLoop.instance().start()
+
+Twisted
+^^^^^^^
+
+You can use `Twisted <https://twistedmatrix.com/>`_ HTTP server as
+follow:
+
+.. code-block:: python
+
+ import cherrypy
+
+ from twisted.web.wsgi import WSGIResource
+ from twisted.internet import reactor
+ from twisted.internet import task
+
+ # Our CherryPy application
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world"
+
+ # Create our WSGI app from the CherryPy application
+ wsgiapp = cherrypy.tree.mount(Root())
+
+ # Configure the CherryPy's app server
+ # Disable the autoreload which won't play well
+ cherrypy.config.update({'engine.autoreload.on': False})
+
+ # We will be using Twisted HTTP server so let's
+ # disable the CherryPy's HTTP server entirely
+ cherrypy.server.unsubscribe()
+
+ # If you'd rather use CherryPy's signal handler
+ # Uncomment the next line. I don't know how well this
+ # will play with Twisted however
+ #cherrypy.engine.signals.subscribe()
+
+ # Publish periodically onto the 'main' channel as the bus mainloop would do
+ task.LoopingCall(lambda: cherrypy.engine.publish('main')).start(0.1)
+
+ # Tie our app to Twisted
+ reactor.addSystemEventTrigger('after', 'startup', cherrypy.engine.start)
+ reactor.addSystemEventTrigger('before', 'shutdown', cherrypy.engine.exit)
+ resource = WSGIResource(reactor, reactor.getThreadPool(), wsgiapp)
+
+Notice how we attach the bus methods to the Twisted's own lifecycle.
+
+Save that code into a module named `cptw.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ twistd -n web --port 8080 --wsgi cptw.wsgiapp
+
+
+uwsgi
+^^^^^
+
+You can use `uwsgi <http://projects.unbit.it/uwsgi/>`_ HTTP server as
+follow:
+
+.. code-block:: python
+
+ import cherrypy
+
+ # Our CherryPy application
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world"
+
+ cherrypy.config.update({'engine.autoreload.on': False})
+ cherrypy.server.unsubscribe()
+ cherrypy.engine.start()
+
+ wsgiapp = cherrypy.tree.mount(Root())
+
+Save this into a Python module called `mymod.py` and run
+it as follows:
+
+
+.. code-block:: bash
+
+ $ uwsgi --socket 127.0.0.1:8080 --protocol=http --wsgi-file mymod.py --callable wsgiapp
+
+
+Virtual Hosting
+###############
+
+CherryPy has support for virtual-hosting. It does so through
+a dispatchers that locate the appropriate resource based
+on the requested domain.
+
+Below is a simple example for it:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ def __init__(self):
+ self.app1 = App1()
+ self.app2 = App2()
+
+ class App1(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello world from app1"
+
+ class App2(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello world from app2"
+
+ if __name__ == '__main__':
+ hostmap = {
+ 'company.com:8080': '/app1',
+ 'home.net:8080': '/app2',
+ }
+
+ config = {
+ 'request.dispatch': cherrypy.dispatch.VirtualHost(**hostmap)
+ }
+
+ cherrypy.quickstart(Root(), '/', {'/': config})
+
+In this example, we declare two domains and their ports:
+
+- company.com:8080
+- home.net:8080
+
+Thanks to the :class:`cherrypy.dispatch.VirtualHost` dispatcher,
+we tell CherryPy which application to dispatch to when a request
+arrives. The dispatcher looks up the requested domain and
+call the according application.
+
+.. note::
+
+ To test this example, simply add the following rules to
+ your `hosts` file:
+
+ .. code-block:: text
+
+ 127.0.0.1 company.com
+ 127.0.0.1 home.net
+
+
+
+Reverse-proxying
+################
+
+Apache
+^^^^^^
+
+Nginx
+^^^^^
+
+nginx is a fast and modern HTTP server with a small footprint. It is
+a popular choice as a reverse proxy to application servers such as
+CherryPy.
+
+This section will not cover the whole range of features nginx provides.
+Instead, it will simply provide you with a basic configuration that can
+be a good starting point.
+
+
+.. code-block:: nginx
+ :linenos:
+
+ upstream apps {
+ server 127.0.0.1:8080;
+ server 127.0.0.1:8081;
+ }
+
+ gzip_http_version 1.0;
+ gzip_proxied any;
+ gzip_min_length 500;
+ gzip_disable "MSIE [1-6]\.";
+ gzip_types text/plain text/xml text/css
+ text/javascript
+ application/javascript;
+
+ server {
+ listen 80;
+ server_name www.example.com;
+
+ access_log /app/logs/www.example.com.log combined;
+ error_log /app/logs/www.example.com.log;
+
+ location ^~ /static/ {
+ root /app/static/;
+ }
+
+ location / {
+ proxy_pass http://apps;
+ proxy_redirect off;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Host $server_name;
+ }
+ }
+
+Edit this configuration to match your own paths. Then, save this configuration
+into a file under ``/etc/nginx/conf.d/`` (assuming Ubuntu).
+The filename is irrelevant. Then run the following commands:
+
+.. code-block:: bash
+
+ $ sudo service nginx stop
+ $ sudo service nginx start
+
+Hopefully, this will be enough to forward requests hitting
+the nginx frontend to your CherryPy application. The ``upstream``
+block defines the addresses of your CherryPy instances.
+
+It shows that you can load-balance between two application
+servers. Refer to the nginx documentation to understand
+how this achieved.
+
+.. code-block:: nginx
+
+ upstream apps {
+ server 127.0.0.1:8080;
+ server 127.0.0.1:8081;
+ }
+
+Later on, this block is used to define the reverse
+proxy section.
+
+Now, let's see our application:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world"
+
+ if __name__ == '__main__':
+ cherrypy.config.update({
+ 'server.socket_port': 8080,
+ 'tools.proxy.on': True,
+ 'tools.proxy.base': 'http://www.example.com'
+ })
+ cherrypy.quickstart(Root())
+
+If you run two instances of this code, one on each
+port defined in the nginx section, you will be able
+to reach both of them via the load-balancing done
+by nginx.
+
+Notice how we define the proxy tool. It is not mandatory and
+used only so that the CherryPy request knows about the true
+client's address. Otherwise, it would know only about the
+nginx's own address. This is most visible in the logs.
+
+The ``base`` attribute should match the ``server_name``
+section of the nginx configuration.
diff --git a/docs/extend.rst b/docs/extend.rst
new file mode 100644
index 00000000..ef8bf8b5
--- /dev/null
+++ b/docs/extend.rst
@@ -0,0 +1,691 @@
+.. _extend:
+
+Extend
+------
+
+CherryPy is truly an open framework, you can extend and plug
+new functions at will either server-side or on a per-requests basis.
+Either way, CherryPy is made to help you build your
+application and support your architecture via simple patterns.
+
+.. contents::
+ :depth: 4
+
+Server-wide functions
+#####################
+
+CherryPy can be considered both as a HTTP library
+as much as a web application framework. In that latter case,
+its architecture provides mechanisms to support operations
+accross the whole server instance. This offers a powerful
+canvas to perform persistent operations as server-wide
+functions live outside the request processing itself. They
+are available to the whole process as long as the bus lives.
+
+Typical use cases:
+
+- Keeping a pool of connection to an external server so that
+ your need not to re-open them on each request (database connections
+ for instance).
+- Background processing (say you need work to be done without
+ blocking the whole request itself).
+
+
+Publish/Subscribe pattern
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+CherryPy's backbone consists of a bus system implementing
+a simple `publish/subscribe messaging pattern <http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern>`_.
+Simply put, in CherryPy everything is controlled via that bus.
+One can easily picture the bus as a sushi restaurant's belt as in
+the picture below.
+
+.. image:: _static/images/sushibelt.JPG
+ :target: http://en.wikipedia.org/wiki/YO!_Sushi
+
+
+You can subscribe and publish to channels on a bus. A channel is
+bit like a unique identifier within the bus. When a message is
+published to a channel, the bus will dispatch the message to
+all subscribers for that channel.
+
+One interesting aspect of a pubsub pattern is that it promotes
+decoupling between a caller and the callee. A published message
+will eventually generate a response but the publisher does not
+know where that response came from.
+
+Thanks to that decoupling, a CherryPy application can easily
+access functionalities without having to hold a reference to
+the entity providing that functionality. Instead, the
+application simply publishes onto the bus and will receive
+the appropriate response, which is all that matter.
+
+.. _buspattern:
+
+Typical pattern
+~~~~~~~~~~~~~~~
+
+Let's take the following dummy application:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class ECommerce(object):
+ def __init__(self, db):
+ self.mydb = db
+
+ @cherrypy.expose
+ def save_kart(self, cart_data):
+ cart = Cart(cart_data)
+ self.mydb.save(cart)
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(ECommerce(), '/')
+
+The application has a reference to the database but
+this creates a fairly strong coupling between the
+database provider and the application.
+
+Another approach to work around the coupling is by
+using a pubsub workflow:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class ECommerce(object):
+ @cherrypy.expose
+ def save_kart(self, cart_data):
+ cart = Cart(cart_data)
+ cherrypy.engine.publish('db-save', cart)
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(ECommerce(), '/')
+
+In this example, we publish a `cart` instance to
+`db-save` channel. One or many subscribers can then
+react to that message and the application doesn't
+have to know about them.
+
+.. note::
+
+ This approach is not mandatory and it's up to you to
+ decide how to design your entities interaction.
+
+
+Implementation details
+~~~~~~~~~~~~~~~~~~~~~~
+
+CherryPy's bus implementation is simplistic as it registers
+functions to channels. Whenever a message is published to
+a channel, each registered function is applied with that
+message passas as a parameter.
+
+The whole behaviour happens synchronously and, in that sense,
+if a subscriber takes too long to process a message, the
+remaining subscribers will be delayed.
+
+CherryPy's bus is not an advanced pubsub messaging broker
+system such as provided by `zeromq <http://zeromq.org/>`_ or
+`RabbitMQ <https://www.rabbitmq.com/>`_.
+Use it with the understanding that it may have a cost.
+
+.. _cpengine:
+
+Engine as a pubsub bus
+~~~~~~~~~~~~~~~~~~~~~~
+
+As said earlier, CherryPy is built around a pubsub bus. All
+entities that the framework manages at runtime are working on
+top of a single bus instance, which is named the `engine`.
+
+The bus implementation therefore provides a set of common
+channels which describe the application's lifecycle:
+
+.. code-block:: text
+
+ O
+ |
+ V
+ STOPPING --> STOPPED --> EXITING -> X
+ A A |
+ | \___ |
+ | \ |
+ | V V
+ STARTED <-- STARTING
+
+The states' transitions trigger channels to be published
+to so that subscribers can react to them.
+
+One good example is the HTTP server which will tranisition
+from a `"STOPPED"` stated to a `"STARTED"` state whenever
+a message is published to the `start` channel.
+
+Built-in channels
+~~~~~~~~~~~~~~~~~
+
+In order to support its life-cycle, CherryPy defines a set
+of common channels that will be published to at various states:
+
+- **"start"**: When the bus is in the `"STARTING"` state
+- **"main"**: Periodically from the CherryPy's mainloop
+- **"stop"**: When the bus is in the `"STOPPING"` state
+- **"graceful"**: When the bus requests a reload of subscribers
+- **"exit"**: When the bus is in the `"EXITING"` state
+
+This channel will be published to by the `engine` automatically.
+Register therefore any subscribers that would need to react
+to the transition changes of the `engine`.
+
+In addition, a few other channels are also published to during
+the request processing.
+
+- `**"before_request"**: right before the request is processed by CherryPy
+- **"after_request"**: right after it has been processed
+
+Also, from the :class:`cherrypy.process.plugins.ThreadManager` plugin:
+
+- **"acquire_thread"**
+- **"start_thread"**
+- **"stop_thread"**
+- **"release_thread"**
+
+Bus API
+~~~~~~~
+
+In order to work with the bus, the implementation
+provides the following simple API:
+
+- :meth:`cherrypy.engine.publish(channel, *args) <cherrypy.process.wspbus.Bus.publish>`:
+ - The `channel` parameter is a string identifying the channel to
+ which the message should be sent to
+ - `*args` is the message and may contain any valid Python values or
+ objects.
+- :meth:`cherrypy.engine.subscribe(channel, callable) <cherrypy.process.wspbus.Bus.subscribe>`:
+ - The `channel` parameter is a string identifying the channel the
+ `callable` will be registered to.
+ - `callable` is a Python function or method which signature must
+ match what will be published.
+- :meth:`cherrypy.engine.unsubscribe(channel, callable) <cherrypy.process.wspbus.Bus.unsubscribe>`:
+ - The `channel` parameter is a string identifying the channel the
+ `callable` was registered to.
+ - `callable` is the Python function or method which was registered.
+
+.. _busplugins:
+
+Plugins
+^^^^^^^
+
+Plugins, simply put, are entities that play with the bus, either by
+publishing or subscribing to channels, usually both at the same time.
+
+.. important::
+
+ Plugins are extremely useful whenever you have functionalities:
+
+ - Available accross the whole application server
+ - Associated to the application's life-cycle
+ - You want to avoid being strongly coupled to the application
+
+Create a plugin
+~~~~~~~~~~~~~~~
+
+A typical plugin looks like this:
+
+.. code-block:: python
+
+ import cherrypy
+ from cherrypy.process import wspbus, plugins
+
+ class DatabasePlugin(plugins.SimplePlugin):
+ def __init__(self, bus, db_klass):
+ plugins.SimplePlugin.__init__(self, bus)
+ self.db = db_klass()
+
+ def start(self):
+ self.bus.log('Starting up DB access')
+ self.bus.subscribe("db-save", self.save_it)
+
+ def stop(self):
+ self.bus.log('Stopping down DB access')
+ self.bus.unsubscribe("db-save", self.save_it)
+
+ def save_it(self, entity):
+ self.db.save(entity)
+
+The :class:`cherrypy.process.plugins.SimplePlugin` is a helper
+class provided by CherryPy that will automatically subscribe
+your `start` and `stop` methods to the related channels.
+
+When the `start` and `stop` channels are published on, those
+methods are called accordingly.
+
+Notice then how our plugin subscribes to the `db-save`
+channel so that the bus can dispatch messages to the plugin.
+
+Enable a plugin
+~~~~~~~~~~~~~~~
+
+To enable the plugin, it has to be registered to the the
+bus as follows:
+
+.. code-block:: python
+
+ DatabasePlugin(cherrypy.engine, SQLiteDB).subscribe()
+
+The `SQLiteDB` here is a fake class that is used as our
+database provider.
+
+Disable a plugin
+~~~~~~~~~~~~~~~~
+
+You can also unregister a plugin as follows:
+
+.. code-block:: python
+
+ someplugin.unsubscribe()
+
+This is often used when you want to prevent the default
+HTTP server from being started by CherryPy, for instance
+if you run on top of a different HTTP server (WSGI capable):
+
+.. code-block:: python
+
+ cherrypy.server.unsubscribe()
+
+Let's see an example using this default application:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(Root())
+
+For instance, this is what you would see when running
+this application:
+
+.. code-block:: python
+
+ [27/Apr/2014:13:04:07] ENGINE Listening for SIGHUP.
+ [27/Apr/2014:13:04:07] ENGINE Listening for SIGTERM.
+ [27/Apr/2014:13:04:07] ENGINE Listening for SIGUSR1.
+ [27/Apr/2014:13:04:07] ENGINE Bus STARTING
+ [27/Apr/2014:13:04:07] ENGINE Started monitor thread 'Autoreloader'.
+ [27/Apr/2014:13:04:07] ENGINE Started monitor thread '_TimeoutMonitor'.
+ [27/Apr/2014:13:04:08] ENGINE Serving on http://127.0.0.1:8080
+ [27/Apr/2014:13:04:08] ENGINE Bus STARTED
+
+Now let's unsubscribe the HTTP server:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def index(self):
+ return "hello world"
+
+ if __name__ == '__main__':
+ cherrypy.server.unsubscribe()
+ cherrypy.quickstart(Root())
+
+This is what we get:
+
+.. code-block:: python
+
+ [27/Apr/2014:13:08:06] ENGINE Listening for SIGHUP.
+ [27/Apr/2014:13:08:06] ENGINE Listening for SIGTERM.
+ [27/Apr/2014:13:08:06] ENGINE Listening for SIGUSR1.
+ [27/Apr/2014:13:08:06] ENGINE Bus STARTING
+ [27/Apr/2014:13:08:06] ENGINE Started monitor thread 'Autoreloader'.
+ [27/Apr/2014:13:08:06] ENGINE Started monitor thread '_TimeoutMonitor'.
+ [27/Apr/2014:13:08:06] ENGINE Bus STARTED
+
+As you can see, the server is not started. The missing:
+
+.. code-block:: python
+
+ [27/Apr/2014:13:04:08] ENGINE Serving on http://127.0.0.1:8080
+
+Per-request functions
+#####################
+
+One of the most common task in a web application development
+is to tailor the request's processing to the runtime context.
+
+Within CherryPy, this is performed via what are called `tools`.
+If you are familiar with Django or WSGI middlewares,
+CherryPy tools are similar in spirit.
+They add functions that are applied during the
+request/response processing.
+
+.. _hookpoint:
+
+Hook point
+^^^^^^^^^^
+
+A hook point is a point during the request/response processing.
+
+Here is a quick rundown of the "hook points" that you can hang your tools on:
+
+ * **"on_start_resource"** - The earliest hook; the Request-Line and request headers
+ have been processed and a dispatcher has set request.handler and request.config.
+ * **"before_request_body"** - Tools that are hooked up here run right before the
+ request body would be processed.
+ * **"before_handler"** - Right before the request.handler (the :term:`exposed` callable
+ that was found by the dispatcher) is called.
+ * **"before_finalize"** - This hook is called right after the page handler has been
+ processed and before CherryPy formats the final response object. It helps
+ you for example to check for what could have been returned by your page
+ handler and change some headers if needed.
+ * **"on_end_resource"** - Processing is complete - the response is ready to be
+ returned. This doesn't always mean that the request.handler (the exposed
+ page handler) has executed! It may be a generator. If your tool absolutely
+ needs to run after the page handler has produced the response body, you
+ need to either use on_end_request instead, or wrap the response.body in a
+ generator which applies your tool as the response body is being generated.
+ * **"before_error_response"** - Called right before an error response
+ (status code, body) is set.
+ * **"after_error_response"** - Called right after the error response
+ (status code, body) is set and just before the error response is finalized.
+ * **"on_end_request"** - The request/response conversation is over, all data has
+ been written to the client, nothing more to see here, move along.
+
+.. _tools:
+
+Tools
+^^^^^
+
+A tool is a simple callable object (function, method, object
+implementing a `__call__` method) that is attached to a
+:ref:`hook point <hookpoint>`.
+
+Below is a simple tool that is attached to the `before_finalize`
+hook point, hence after the page handler was called:
+
+.. code-block:: python
+
+ def log_it():
+ print(cherrypy.request.remote.ip)
+
+ cherrypy.tools.logit = cherrypy.Tool('before_finalize', log_it)
+
+Using that tool is as simple as follows:
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.logit()
+ def index(self):
+ return "hello world"
+
+Obviously the tool may be declared the
+:ref:`other usual ways <perappconf>`.
+
+.. note::
+
+ The name of the tool, technically the attribute set to `cherrypy.tools`,
+ does not have to match the name of the callable. However, it is
+ that name that will be used in the configuration to refer to that
+ tool.
+
+Stateful tools
+~~~~~~~~~~~~~~
+
+The tools mechanism is really flexible and enables
+rich per-request functionalities.
+
+Straight tools as shown in the previous section are
+usually good enough. However, if your workflow
+requires some sort of state during the request processing,
+you will probably want a class-based approach:
+
+.. code-block:: python
+
+ import time
+
+ import cherrypy
+
+ class TimingTool(cherrypy.Tool):
+ def __init__(self):
+ cherrypy.Tool.__init__(self, 'before_handler',
+ self.start_timer,
+ priority=95)
+
+ def _setup(self):
+ cherrypy.Tool._setup(self)
+ cherrypy.request.hooks.attach('before_finalize',
+ self.end_timer,
+ priority=5)
+
+ def start_timer(self):
+ cherrypy.request._time = time.time()
+
+ def end_timer(self):
+ duration = time.time() - cherrypy.request._time
+ cherrypy.log("Page handler took %.4f" % duration)
+
+ cherrypy.tools.timeit = TimingTool()
+
+This tool computes the time taken by the page handler
+for a given request. It stores the time at which the handler
+is about to get called and logs the time difference
+right after the handler returned its result.
+
+The import bits is that the :class:`cherrypy.Tool <cherrypy._cptools.Tool>` constructor
+allows you to register to a hook point but, to attach the
+same tool to a different hook point, you must use the
+:meth:`cherrypy.request.hooks.attach <cherrypy._cprequest.HookMap.attach>` method.
+The :meth:`cherrypy.Tool._setup <cherrypy._cptools.Tool._setup>`
+method is automatically called by CherryPy when the tool
+is applied to the request.
+
+Next, let's see how to use our tool:
+
+.. code-block:: python
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.timeit()
+ def index(self):
+ return "hello world"
+
+Tools ordering
+~~~~~~~~~~~~~~
+
+Since you can register many tools at the same hookpoint,
+you may wonder in which order they will be applied.
+
+CherryPy offers a deterministic, yet so simple, mechanism
+to do so. Simply set the **priority** attribute to a value
+from 1 to 100, lower values providing greater priority.
+
+If you set the same priority for several tools, they will
+be called in the order you declare them in your configuration.
+
+Toolboxes
+~~~~~~~~~
+
+All of the builtin CherryPy tools are collected into a Toolbox called
+:attr:`cherrypy.tools`. It responds to config entries in the ``"tools"``
+:ref:`namespace<namespaces>`. You can add your own Tools to this Toolbox
+as described above.
+
+You can also make your own Toolboxes if you need more modularity. For example,
+you might create multiple Tools for working with JSON, or you might publish
+a set of Tools covering authentication and authorization from which everyone
+could benefit (hint, hint). Creating a new Toolbox is as simple as:
+
+.. code-block:: python
+
+ import cherrypy
+
+ # Create a new Toolbox.
+ newauthtools = cherrypy._cptools.Toolbox("newauth")
+
+ # Add a Tool to our new Toolbox.
+ def check_access(default=False):
+ if not getattr(cherrypy.request, "userid", default):
+ raise cherrypy.HTTPError(401)
+ newauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
+
+Then, in your application, use it just like you would use ``cherrypy.tools``,
+with the additional step of registering your toolbox with your app.
+Note that doing so automatically registers the ``"newauth"`` config namespace;
+you can see the config entries in action below:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class Root(object):
+ @cherrypy.expose
+ def default(self):
+ return "Hello"
+
+ conf = {
+ '/demo': {
+ 'newauth.check_access.on': True,
+ 'newauth.check_access.default': True,
+ }
+ }
+
+ app = cherrypy.tree.mount(Root(), config=conf)
+
+Request parameters manipulation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+HTTP uses strings to carry data between two endpoints.
+However your application may make better use of richer
+object types. As it wouldn't be really readable, nor
+a good idea regarding maintenance, to let each page handler
+deserialize data, it's a common pattern to delegate
+this functions to tools.
+
+For instance, let's assume you have a user id in the query-string
+and some user data stored into a database. You could
+retrieve the data, create an object and pass it on to the
+page handler instead of the user id.
+
+
+.. code-block:: python
+
+ import cherrypy
+
+ class UserManager(cherrypy.Tool):
+ def __init__(self):
+ cherrypy.Tool.__init__(self, 'before_handler',
+ self.load, priority=10)
+
+ def load(self):
+ req = cherrypy.request
+
+ # let's assume we have a db session
+ # attached to the request somehow
+ db = req.db
+
+ # retrieve the user id and remove it
+ # from the request parameters
+ user_id = req.params.pop('user_id')
+ req.params['user'] = db.get(int(user_id))
+
+ cherrypy.tools.user = UserManager()
+
+
+ class Root(object):
+ @cherrypy.expose
+ @cherrypy.tools.user()
+ def index(self, user):
+ return "hello %s" % user.name
+
+In other words, CherryPy give you the power to:
+
+- inject data, that wasn't part of the initial request, into the page handler
+- remove data as well
+- convert data into a different, more useful, object to remove that burden
+ from the page handler itself
+
+.. _dispatchers:
+
+Tailored dispatchers
+####################
+
+Dispatching is the art of locating the appropriate page handler
+for a given request. Usually, dispatching is based on the
+request's URL, the query-string and, sometimes, the request's method
+(GET, POST, etc.).
+
+Based on this, CherryPy comes with various dispatchers already.
+
+In some cases however, you will need a little more. Here is an example
+of dispatcher that will always ensure the incoming URL leads
+to a lower-case page handler.
+
+.. code-block:: python
+
+ import random
+ import string
+
+ import cherrypy
+ from cherrypy._cpdispatch import Dispatcher
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def generate(self, length=8):
+ return ''.join(random.sample(string.hexdigits, int(length)))
+
+ class ForceLowerDispatcher(Dispatcher):
+ def __call__(self, path_info):
+ return Dispatcher.__call__(self, path_info.lower())
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'request.dispatch': ForceLowerDispatcher(),
+ }
+ }
+ cherrypy.quickstart(StringGenerator(), '/', conf)
+
+Once you run this snipper, go to:
+
+- http://localhost:8080/generate?length=8
+- http://localhost:8080/GENerAte?length=8
+
+In both cases, you will be led to the `generate` page
+handler. Without our home-made dispatcher, the second
+one would fail and return a 404 error (:rfc:`2616#sec10.4.5`).
+
+Tool or dispatcher?
+^^^^^^^^^^^^^^^^^^^
+
+In the previous example, why not simply use a tool? Well, the sooner
+a tool can be called is always after the page handler has been found.
+In our example, it would be already too late as the default dispatcher
+would have not even found a match for `/GENerAte`.
+
+A dispatcher exists mostly to determine the best page
+handler to serve the requested resource.
+
+On ther other hand, tools are there to adapt the request's processing
+to the runtime context of the application and the request's content.
+
+Usually, you will have to write a dispatcher only if you
+have a very specific use case to locate the most adequate
+page handler. Otherwise, the default ones will likely suffice.
+
+Request body processors
+#######################
+
+Since its 3.2 release, CherryPy provides a really elegant
+and powerful mechanism to deal with a request's body based
+on its mimetype. Refer to the :mod:`cherrypy._cpreqbody` module
+to understand how to implement your own processors.
diff --git a/docs/glossary.rst b/docs/glossary.rst
new file mode 100644
index 00000000..cfeb0966
--- /dev/null
+++ b/docs/glossary.rst
@@ -0,0 +1,34 @@
+
+Glossary
+--------
+
+.. glossary::
+
+ application
+ A CherryPy application is simply a class instance containing
+ at least one page handler.
+
+ controller
+ Loose name commonly given to a class owning at least one exposed method
+
+ exposed
+ A Python function or method which has an attribute called `exposed`
+ set to `True`. This attribute can be set directly or via the
+ :func:`cherrypy.expose()` decorator.
+
+ .. code-block:: python
+
+ @cherrypy.expose
+ def method(...):
+ ...
+
+ is equivalent to:
+
+ .. code-block:: python
+
+ def method(...):
+ ...
+ method.exposed = True
+
+ page handler
+ Name commonly given to an exposed method
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..0ccc48aa
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,51 @@
+
+CherryPy - A Minimalist Python Web Framework
+============================================
+
+.. toctree::
+ :hidden:
+
+ intro.rst
+ install.rst
+ tutorials.rst
+ basics.rst
+ advanced.rst
+ config.rst
+ extend.rst
+ deploy.rst
+ contribute.rst
+ glossary.rst
+
+`CherryPy <http://www.cherrypy.org>`_ is a pythonic, object-oriented web framework.
+
+CherryPy allows developers to build web applications in much the
+same way they would build any other object-oriented Python program.
+This results in smaller source code developed in less time.
+
+CherryPy is now more than ten years old and it is has proven to
+be fast and reliable. It is being used in production by many
+sites, from the simplest to the most demanding.
+
+A CherryPy application typically looks like this:
+
+.. code-block:: python
+
+ import cherrypy
+
+ class HelloWorld(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello World!"
+
+ cherrypy.quickstart(HelloWorld())
+
+In order to make the most of CherryPy, you should start
+with the :ref:`tutorials <tutorials>` that will lead you through the most common
+aspects of the framework. Once done, you will probably want to
+browse through the :ref:`basics <basics>` and :ref:`advanced <advanced>`
+sections that will demonstrate how to implement certain operations.
+Finally, you will want to carefully read the configuration and
+:ref:`extend <extend>` sections that go in-depth regarding the
+powerful features provided by the framework.
+
+Above all, have fun with your application!
diff --git a/docs/install.rst b/docs/install.rst
new file mode 100644
index 00000000..68dd9692
--- /dev/null
+++ b/docs/install.rst
@@ -0,0 +1,156 @@
+
+Installation
+------------
+
+CherryPy is a pure Python library. This has various consequences:
+
+ - It can run anywhere Python runs
+ - It does not require a C compiler
+ - It can run on various implementations of the Python language: `CPython <http://python.org/>`_,
+ `IronPython <http://ironpython.net/>`_, `Jython <http://www.jython.org/>`_ and `PyPy <http://pypy.org/>`_
+
+.. contents::
+ :depth: 4
+
+Requirements
+############
+
+CherryPy does not have any mandatory requirements. However certain features it comes with
+will require you install certain packages.
+
+- `routes <http://routes.readthedocs.org/en/latest/>`_ for declarative URL mapping dispatcher
+- `psycopg2 <http://pythonhosted.org//psycopg2/>`_ for PostgreSQL backend session
+- `pywin32 <http://sourceforge.net/projects/pywin32/>`_ for Windows services
+- `python-memcached <https://github.com/linsomniac/python-memcached>`_ for memcached backend session
+- `simplejson <https://github.com/simplejson/simplejson>`_ for a better JSON support
+- `pyOpenSSL <https://github.com/pyca/pyopenssl>`_ if your Python environment does not have the builtin :mod:`ssl` module
+
+Supported python version
+########################
+
+CherryPy supports Python 2.3 through to 3.4.
+
+
+Installing
+##########
+
+CherryPy can be easily installed via common Python package managers such as setuptools or pip.
+
+.. code-block:: bash
+
+ $ easy_install cherrypy
+
+
+.. code-block:: bash
+
+ $ pip install cherrypy
+
+You may also get the latest CherryPy version by grabbing the source code from BitBucket:
+
+.. code-block:: bash
+
+ $ hg clone https://bitbucket.org/cherrypy/cherrypy
+ $ cd cherrypy
+ $ python setup.py install
+
+Test your installation
+^^^^^^^^^^^^^^^^^^^^^^
+
+CherryPy comes with a set of simple tutorials that can be executed
+once you have deployed the package.
+
+.. code-block:: bash
+
+ $ python -m cherrypy.tutorial.tut01_helloworld
+
+Point your browser at http://127.0.0.1:8080 and enjoy the magic.
+
+Once started the above command shows the following logs:
+
+.. code-block:: bash
+
+ [15/Feb/2014:21:51:22] ENGINE Listening for SIGHUP.
+ [15/Feb/2014:21:51:22] ENGINE Listening for SIGTERM.
+ [15/Feb/2014:21:51:22] ENGINE Listening for SIGUSR1.
+ [15/Feb/2014:21:51:22] ENGINE Bus STARTING
+ [15/Feb/2014:21:51:22] ENGINE Started monitor thread 'Autoreloader'.
+ [15/Feb/2014:21:51:22] ENGINE Started monitor thread '_TimeoutMonitor'.
+ [15/Feb/2014:21:51:22] ENGINE Serving on http://127.0.0.1:8080
+ [15/Feb/2014:21:51:23] ENGINE Bus STARTED
+
+We will explain what all those lines mean later on, but suffice
+to know that once you see the last two lines, your server
+is listening and ready to receive requests.
+
+Run it
+######
+
+During development, the easiest path is to run your application as
+follow:
+
+.. code-block:: bash
+
+ $ python myapp.py
+
+As long as `myapp.py` defines a `"__main__"` section, it will
+run just fine.
+
+cherryd
+^^^^^^^
+
+Another way to run the application is through the ``cherryd`` script
+which is installed along side CherryPy.
+
+.. note::
+
+ This utility command will not concern you if you embed your
+ application with another framework.
+
+Command-Line Options
+~~~~~~~~~~~~~~~~~~~~
+
+.. program:: cherryd
+
+.. cmdoption:: -c, --config
+
+ Specify config file(s)
+
+.. cmdoption:: -d
+
+ Run the server as a daemon
+
+.. cmdoption:: -e, --environment
+
+ Apply the given config environment (defaults to None)
+
+
+.. index:: FastCGI
+
+.. cmdoption:: -f
+
+ Start a :ref:`FastCGI <fastcgi>` server instead of the default HTTP server
+
+
+.. index:: SCGI
+
+.. cmdoption:: -s
+
+ Start a SCGI server instead of the default HTTP server
+
+
+.. cmdoption:: -i, --import
+
+ Specify modules to import
+
+
+.. index:: PID file
+
+.. cmdoption:: -p, --pidfile
+
+ Store the process id in the given file (defaults to None)
+
+
+.. cmdoption:: -P, --Path
+
+ Add the given paths to sys.path
+
diff --git a/docs/intro.rst b/docs/intro.rst
new file mode 100644
index 00000000..72af26d3
--- /dev/null
+++ b/docs/intro.rst
@@ -0,0 +1,150 @@
+Foreword
+--------
+
+Why CherryPy?
+#############
+
+CherryPy is among the oldest web framework available for Python, yet many people aren't aware of its existence.
+One of the reason for this is that CherryPy is not a complete stack with built-in support for a multi-tier architecture.
+It doesn't provide frontend utilities nor will it tell you how to speak with your storage. Instead, CherryPy's take
+is to let the developer make those decisions. This is a contrasting position compared to other well-known frameworks.
+
+CherryPy has a clean interface and does its best to stay out of your way whilst providing
+a reliable scaffolding for you to build from.
+
+Typical use-cases for CherryPy go from regular web application with user frontends
+(think blogging, CMS, portals, ecommerce) to web-services only.
+
+Here are some reasons you would want to choose CherryPy:
+
+1. Simplicity
+
+ Developing with CherryPy is a simple task. “Hello, world” is only a few lines long, and does not require the developer to learn the entire (albeit very manageable) framework all at once. The framework is very pythonic; that is, it follows Python’s conventions very nicely (code is sparse and clean).
+
+ Contrast this with J2EE and Python’s most popular and visible web frameworks: Django, Zope, Pylons, and Turbogears. In all of them, the learning curve is massive. In these frameworks, “Hello, world” requires the programmer to set up a large scaffold which spans multiple files and to type a lot of boilerplate code. CherryPy succeeds because it does not include the bloat of other frameworks, allowing the programmer to write their web application quickly while still maintaining a high level of organization and scalability.
+
+ CherryPy is also very modular. The core is fast and clean, and extension features are easy to write and plug in using code or the elegant config system. The primary components (server, engine, request, response, etc.) are all extendable (even replaceable) and well-managed.
+
+ In short, CherryPy empowers the developer to work with the framework, not against or around it.
+
+2. Power
+
+ CherryPy leverages all of the power of Python. Python is a dynamic language which allows for rapid development of applications. Python also has an extensive built-in API which simplifies web app development. Even more extensive, however, are the third-party libraries available for Python. These range from object-relational mappers to form libraries, to an automatic Python optimizer, a Windows exe generator, imaging libraries, email support, HTML templating engines, etc. CherryPy applications are just like regular Python applications. CherryPy does not stand in your way if you want to use these brilliant tools.
+
+ CherryPy also provides :ref:`tools <tools>` and :ref:`plugins <busplugins>`, which are powerful extension points needed to develop world-class web applications.
+
+3. Maturity
+
+ Maturity is extremely important when developing a real-world application. Unlike many other web frameworks, CherryPy has had many final, stable releases. It is fully bugtested, optimized, and proven reliable for real-world use. The API will not suddenly change and break backwards compatibility, so your applications are assured to continue working even through subsequent updates in the current version series.
+
+ CherryPy is also a “3.0” project: the first edition of CherryPy set the tone, the second edition made it work, and the third edition makes it beautiful. Each version built on lessons learned from the previous, bringing the developer a superior tool for the job.
+
+4. Community
+
+ CherryPy has an devoted community that develops deployed CherryPy applications and are willing and ready to assist you on the CherryPy mailing list or IRC (#cherrypy on OFTC). The developers also frequent the list and often answer questions and implement features requested by the end-users.
+
+5. Deployability
+
+ Unlike many other Python web frameworks, there are cost-effective ways to deploy your CherryPy application.
+
+ Out of the box, CherryPy includes its own production-ready HTTP server to host your application. CherryPy can also be deployed on any WSGI-compliant gateway (a technology for interfacing numerous types of web servers): mod_wsgi, FastCGI, SCGI, IIS, uwsgi, tornado, etc. Reverse proxying is also a common and easy way to set it up.
+
+ In addition, CherryPy is pure-python and is compatible with Python 2.3. This means that CherryPy will run on all major platforms that Python will run on (Windows, MacOSX, Linux, BSD, etc).
+
+ `webfaction.com <https://www.webfaction.com>`_, run by the inventor of CherryPy, is a commercial web host that offers CherryPy hosting packages (in addition to several others).
+
+6. It’s free!
+
+ All of CherryPy is licensed under the open-source BSD license, which means CherryPy can be used commercially for ZERO cost.
+
+7. Where to go from here?
+
+ Check out the :ref:`tutorials <tutorials>` to start enjoying the fun!
+
+.. _successstories:
+
+Success Stories
+###############
+
+You are interested in CherryPy but you would like to hear more from people
+using it, or simply check out products or application running it.
+
+If you would like to have your CherryPy powered website or product listed here,
+contact us via our `mailing list <http://groups.google.com/group/cherrypy-users>`_
+or IRC (#cherrypy on `OFTC <http://www.oftc.net/oftc/>`_).
+
+
+Websites running atop CherryPy
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`Hulu Deejay and Hulu Sod <http://tech.hulu.com/blog/2013/03/13/python-and-hulu>`_ - Hulu uses
+CherryPy for some projects.
+"The service needs to be very high performance.
+Python, together with CherryPy,
+`gunicorn <http://gunicorn.org>`_, and gevent more than provides for this."
+
+`Netflix <http://techblog.netflix.com/2013/03/python-at-netflix.html>`_ - Netflix uses CherryPy as a building block in their infrastructure: "Restful APIs to
+large applications with requests, providing web interfaces with CherryPy and Bottle,
+and crunching data with scipy."
+
+`Urbanility <http://urbanility.com>`_ - French website for local neighbourhood assets in Rennes, France.
+
+`MROP Supply <https://www.mropsupply.com>`_ - Webshop for industrial equipment,
+developed using CherryPy 3.2.2 utilizing Python 3.2,
+with libs: `Jinja2-2.6 <http://jinja.pocoo.org/docs>`_, davispuh-MySQL-for-Python-3-3403794,
+pyenchant-1.6.5 (for search spelling).
+"I'm coming over from .net development and found Python and CherryPy to
+be surprisingly minimalistic. No unnecessary overhead - build everything you
+need without the extra fluff. I'm a fan!"
+
+`CherryMusic <http://www.fomori.org/cherrymusic>`_ - A music streaming server written in python:
+Stream your own music collection to all your devices! CherryMusic is open source.
+
+`YouGov Global <http://www.yougov.com>`_ - International market research firm, conducts
+millions of surveys on CherryPy yearly.
+
+`Aculab Cloud <http://cloud.aculab.com>`_ - Voice and fax applications on the cloud.
+A simple telephony API for Python, C#, C++, VB, etc...
+The website and all front-end and back-end web services are built with CherryPy,
+fronted by nginx (just handling the ssh and reverse-proxy), and running on AWS in two regions.
+
+`Learnit Training <http://www.learnit.nl>`_ - Dutch website for an IT, Management and
+Communication training company. Built on CherryPy 3.2.0 and Python 2.7.3, with
+`oursql <http://pythonhosted.org/oursql>`_ and
+`DBUtils <http://www.webwareforpython.org/DBUtils>`_ libraries, amongst others.
+
+`Linstic <http://linstic.com>`_ - Sticky Notes in your browser (with linking).
+
+`Almad's Homepage <http://www.almad.net>`_ - Simple homepage with blog.
+
+`Fight.Watch <http://fight.watch>`_ - Twitch.tv web portal for fighting games.
+Built on CherryPy 3.3.0 and Python 2.7.3 with Jinja 2.7.2 and SQLAlchemy 0.9.4.
+
+Products based on CherryPy
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`SABnzbd <http://sabnzbd.org>`_ - Open Source Binary Newsreader written in Python.
+
+`Headphones <https://github.com/rembo10/headphones>`_ - Third-party add-on for SABnzbd.
+
+`SickBeard <http://sickbeard.com>`_ - "Sick Beard is a PVR for newsgroup users (with limited torrent support). It watches for new episodes of your favorite shows and when they are posted it downloads them, sorts and renames them, and optionally generates metadata for them."
+
+`TurboGears <http://www.turbogears.org>`_ - The rapid web development megaframework. Turbogears 1.x used Cherrypy. "CherryPy is the underlying application server for TurboGears. It is responsible for taking the requests from the user’s browser, parses them and turns them into calls into the Python code of the web application. Its role is similar to application servers used in other programming languages".
+
+`Indigo <http://www.perceptiveautomation.com/indigo/index.html>`_ - "An intelligent home control
+server that integrates home control hardware modules to provide control of your home. Indigo's built-in
+Web server and client/server architecture give you control and access to your home remotely from
+other Macs, PCs, internet tablets, PDAs, and mobile phones."
+
+`SlikiWiki <http://www.sf.net/projects/slikiwiki>`_ - Wiki built on CherryPy and featuring WikiWords, automatic backlinking, site map generation, full text search, locking for concurrent edits, RSS feed embedding, per page access control lists, and page formatting using PyTextile markup."
+
+`read4me <http://sourceforge.net/projects/read4me>`_ - read4me is a Python feed-reading web service.
+
+`Firebird QA tools <http://www.firebirdsql.org/en/quality-assurance>`_ - Firebird QA tools are based on CherryPy.
+
+`salt-api <https://github.com/saltstack/salt-api>`_ - A REST API for Salt, the infrastructure orchestration tool.
+
+Products inspired by CherryPy
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`OOWeb <http://ooweb.sourceforge.net/>`_ - "OOWeb is a lightweight, embedded HTTP server for Java applications that maps objects to URL directories, methods to pages and form/querystring arguments as method parameters. OOWeb was originally inspired by CherryPy."
diff --git a/docs/pkg/cherrypy.lib.rst b/docs/pkg/cherrypy.lib.rst
new file mode 100644
index 00000000..b43db26a
--- /dev/null
+++ b/docs/pkg/cherrypy.lib.rst
@@ -0,0 +1,166 @@
+cherrypy.lib package
+====================
+
+Submodules
+----------
+
+cherrypy.lib.auth module
+------------------------
+
+.. automodule:: cherrypy.lib.auth
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.auth_basic module
+------------------------------
+
+.. automodule:: cherrypy.lib.auth_basic
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.auth_digest module
+-------------------------------
+
+.. automodule:: cherrypy.lib.auth_digest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.caching module
+---------------------------
+
+.. automodule:: cherrypy.lib.caching
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.covercp module
+---------------------------
+
+.. automodule:: cherrypy.lib.covercp
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.cpstats module
+---------------------------
+
+.. automodule:: cherrypy.lib.cpstats
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.cptools module
+---------------------------
+
+.. automodule:: cherrypy.lib.cptools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.encoding module
+----------------------------
+
+.. automodule:: cherrypy.lib.encoding
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.gctools module
+---------------------------
+
+.. automodule:: cherrypy.lib.gctools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.http module
+------------------------
+
+.. automodule:: cherrypy.lib.http
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.httpauth module
+----------------------------
+
+.. automodule:: cherrypy.lib.httpauth
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.httputil module
+----------------------------
+
+.. automodule:: cherrypy.lib.httputil
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.jsontools module
+-----------------------------
+
+.. automodule:: cherrypy.lib.jsontools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.lockfile module
+----------------------------
+
+.. automodule:: cherrypy.lib.lockfile
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.profiler module
+----------------------------
+
+.. automodule:: cherrypy.lib.profiler
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.reprconf module
+----------------------------
+
+.. automodule:: cherrypy.lib.reprconf
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.sessions module
+----------------------------
+
+.. automodule:: cherrypy.lib.sessions
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.static module
+--------------------------
+
+.. automodule:: cherrypy.lib.static
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.lib.xmlrpcutil module
+------------------------------
+
+.. automodule:: cherrypy.lib.xmlrpcutil
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.lib
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.process.rst b/docs/pkg/cherrypy.process.rst
new file mode 100644
index 00000000..86fa5436
--- /dev/null
+++ b/docs/pkg/cherrypy.process.rst
@@ -0,0 +1,46 @@
+cherrypy.process package
+========================
+
+Submodules
+----------
+
+cherrypy.process.plugins module
+-------------------------------
+
+.. automodule:: cherrypy.process.plugins
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.process.servers module
+-------------------------------
+
+.. automodule:: cherrypy.process.servers
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.process.win32 module
+-----------------------------
+
+.. automodule:: cherrypy.process.win32
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.process.wspbus module
+------------------------------
+
+.. automodule:: cherrypy.process.wspbus
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.process
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.rst b/docs/pkg/cherrypy.rst
new file mode 100644
index 00000000..e9287331
--- /dev/null
+++ b/docs/pkg/cherrypy.rst
@@ -0,0 +1,162 @@
+cherrypy package
+================
+
+Subpackages
+-----------
+
+.. toctree::
+
+ cherrypy.lib
+ cherrypy.process
+ cherrypy.scaffold
+ cherrypy.test
+ cherrypy.tutorial
+ cherrypy.wsgiserver
+
+Submodules
+----------
+
+cherrypy._cpchecker module
+--------------------------
+
+.. automodule:: cherrypy._cpchecker
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpcompat module
+-------------------------
+
+.. automodule:: cherrypy._cpcompat
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpcompat_subprocess module
+------------------------------------
+
+.. automodule:: cherrypy._cpcompat_subprocess
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpconfig module
+-------------------------
+
+.. automodule:: cherrypy._cpconfig
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpdispatch module
+---------------------------
+
+.. automodule:: cherrypy._cpdispatch
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cperror module
+------------------------
+
+.. automodule:: cherrypy._cperror
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cplogging module
+--------------------------
+
+.. automodule:: cherrypy._cplogging
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpmodpy module
+------------------------
+
+.. automodule:: cherrypy._cpmodpy
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpnative_server module
+--------------------------------
+
+.. automodule:: cherrypy._cpnative_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpreqbody module
+--------------------------
+
+.. automodule:: cherrypy._cpreqbody
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cprequest module
+--------------------------
+
+.. automodule:: cherrypy._cprequest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpserver module
+-------------------------
+
+.. automodule:: cherrypy._cpserver
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpthreadinglocal module
+---------------------------------
+
+.. automodule:: cherrypy._cpthreadinglocal
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cptools module
+------------------------
+
+.. automodule:: cherrypy._cptools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cptree module
+-----------------------
+
+.. automodule:: cherrypy._cptree
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpwsgi module
+-----------------------
+
+.. automodule:: cherrypy._cpwsgi
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy._cpwsgi_server module
+------------------------------
+
+.. automodule:: cherrypy._cpwsgi_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.scaffold.rst b/docs/pkg/cherrypy.scaffold.rst
new file mode 100644
index 00000000..c13adbf0
--- /dev/null
+++ b/docs/pkg/cherrypy.scaffold.rst
@@ -0,0 +1,10 @@
+cherrypy.scaffold package
+=========================
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.scaffold
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.test.rst b/docs/pkg/cherrypy.test.rst
new file mode 100644
index 00000000..88f1d68a
--- /dev/null
+++ b/docs/pkg/cherrypy.test.rst
@@ -0,0 +1,390 @@
+cherrypy.test package
+=====================
+
+Submodules
+----------
+
+cherrypy.test._test_decorators module
+-------------------------------------
+
+.. automodule:: cherrypy.test._test_decorators
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test._test_states_demo module
+--------------------------------------
+
+.. automodule:: cherrypy.test._test_states_demo
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.benchmark module
+------------------------------
+
+.. automodule:: cherrypy.test.benchmark
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.checkerdemo module
+--------------------------------
+
+.. automodule:: cherrypy.test.checkerdemo
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.helper module
+---------------------------
+
+.. automodule:: cherrypy.test.helper
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.logtest module
+----------------------------
+
+.. automodule:: cherrypy.test.logtest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.modfastcgi module
+-------------------------------
+
+.. automodule:: cherrypy.test.modfastcgi
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.modfcgid module
+-----------------------------
+
+.. automodule:: cherrypy.test.modfcgid
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.modpy module
+--------------------------
+
+.. automodule:: cherrypy.test.modpy
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.modwsgi module
+----------------------------
+
+.. automodule:: cherrypy.test.modwsgi
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.sessiondemo module
+--------------------------------
+
+.. automodule:: cherrypy.test.sessiondemo
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_auth_basic module
+------------------------------------
+
+.. automodule:: cherrypy.test.test_auth_basic
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_auth_digest module
+-------------------------------------
+
+.. automodule:: cherrypy.test.test_auth_digest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_bus module
+-----------------------------
+
+.. automodule:: cherrypy.test.test_bus
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_caching module
+---------------------------------
+
+.. automodule:: cherrypy.test.test_caching
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_compat module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_compat
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_config module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_config_server module
+---------------------------------------
+
+.. automodule:: cherrypy.test.test_config_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_conn module
+------------------------------
+
+.. automodule:: cherrypy.test.test_conn
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_core module
+------------------------------
+
+.. automodule:: cherrypy.test.test_core
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_dynamicobjectmapping module
+----------------------------------------------
+
+.. automodule:: cherrypy.test.test_dynamicobjectmapping
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_encoding module
+----------------------------------
+
+.. automodule:: cherrypy.test.test_encoding
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_etags module
+-------------------------------
+
+.. automodule:: cherrypy.test.test_etags
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_http module
+------------------------------
+
+.. automodule:: cherrypy.test.test_http
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_httpauth module
+----------------------------------
+
+.. automodule:: cherrypy.test.test_httpauth
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_httplib module
+---------------------------------
+
+.. automodule:: cherrypy.test.test_httplib
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_json module
+------------------------------
+
+.. automodule:: cherrypy.test.test_json
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_logging module
+---------------------------------
+
+.. automodule:: cherrypy.test.test_logging
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_mime module
+------------------------------
+
+.. automodule:: cherrypy.test.test_mime
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_misc_tools module
+------------------------------------
+
+.. automodule:: cherrypy.test.test_misc_tools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_objectmapping module
+---------------------------------------
+
+.. automodule:: cherrypy.test.test_objectmapping
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_proxy module
+-------------------------------
+
+.. automodule:: cherrypy.test.test_proxy
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_refleaks module
+----------------------------------
+
+.. automodule:: cherrypy.test.test_refleaks
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_request_obj module
+-------------------------------------
+
+.. automodule:: cherrypy.test.test_request_obj
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_routes module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_routes
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_session module
+---------------------------------
+
+.. automodule:: cherrypy.test.test_session
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_sessionauthenticate module
+---------------------------------------------
+
+.. automodule:: cherrypy.test.test_sessionauthenticate
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_states module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_states
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_static module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_static
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_tools module
+-------------------------------
+
+.. automodule:: cherrypy.test.test_tools
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_tutorials module
+-----------------------------------
+
+.. automodule:: cherrypy.test.test_tutorials
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_virtualhost module
+-------------------------------------
+
+.. automodule:: cherrypy.test.test_virtualhost
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_wsgi_ns module
+---------------------------------
+
+.. automodule:: cherrypy.test.test_wsgi_ns
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_wsgi_vhost module
+------------------------------------
+
+.. automodule:: cherrypy.test.test_wsgi_vhost
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_wsgiapps module
+----------------------------------
+
+.. automodule:: cherrypy.test.test_wsgiapps
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.test_xmlrpc module
+--------------------------------
+
+.. automodule:: cherrypy.test.test_xmlrpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.test.webtest module
+----------------------------
+
+.. automodule:: cherrypy.test.webtest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.test
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.tutorial.rst b/docs/pkg/cherrypy.tutorial.rst
new file mode 100644
index 00000000..3676e0ba
--- /dev/null
+++ b/docs/pkg/cherrypy.tutorial.rst
@@ -0,0 +1,94 @@
+cherrypy.tutorial package
+=========================
+
+Submodules
+----------
+
+cherrypy.tutorial.tut01_helloworld module
+-----------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut01_helloworld
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut02_expose_methods module
+---------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut02_expose_methods
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut03_get_and_post module
+-------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut03_get_and_post
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut04_complex_site module
+-------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut04_complex_site
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut05_derived_objects module
+----------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut05_derived_objects
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut06_default_method module
+---------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut06_default_method
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut07_sessions module
+---------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut07_sessions
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut08_generators_and_yield module
+---------------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut08_generators_and_yield
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut09_files module
+------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut09_files
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.tutorial.tut10_http_errors module
+------------------------------------------
+
+.. automodule:: cherrypy.tutorial.tut10_http_errors
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.tutorial
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/cherrypy.wsgiserver.rst b/docs/pkg/cherrypy.wsgiserver.rst
new file mode 100644
index 00000000..9e91f631
--- /dev/null
+++ b/docs/pkg/cherrypy.wsgiserver.rst
@@ -0,0 +1,46 @@
+cherrypy.wsgiserver package
+===========================
+
+Submodules
+----------
+
+cherrypy.wsgiserver.ssl_builtin module
+--------------------------------------
+
+.. automodule:: cherrypy.wsgiserver.ssl_builtin
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.wsgiserver.ssl_pyopenssl module
+----------------------------------------
+
+.. automodule:: cherrypy.wsgiserver.ssl_pyopenssl
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.wsgiserver.wsgiserver2 module
+--------------------------------------
+
+.. automodule:: cherrypy.wsgiserver.wsgiserver2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+cherrypy.wsgiserver.wsgiserver3 module
+--------------------------------------
+
+.. automodule:: cherrypy.wsgiserver.wsgiserver3
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: cherrypy.wsgiserver
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/pkg/modules.rst b/docs/pkg/modules.rst
new file mode 100644
index 00000000..fe45a992
--- /dev/null
+++ b/docs/pkg/modules.rst
@@ -0,0 +1,7 @@
+cherrypy
+========
+
+.. toctree::
+ :maxdepth: 4
+
+ cherrypy
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
new file mode 100644
index 00000000..d063ebcf
--- /dev/null
+++ b/docs/tutorials.rst
@@ -0,0 +1,933 @@
+.. _tutorials:
+
+Tutorials
+---------
+
+
+This tutorial will walk you through basic but complete CherryPy applications
+that will show you common concepts as well as slightly more adavanced ones.
+
+.. contents::
+ :depth: 4
+
+Tutorial 1: A basic web application
+###################################
+
+The following example demonstrates the most basic application
+you could write with CherryPy. It starts a server and hosts
+an application that will be served at request reaching
+http://127.0.0.1:8080/
+
+.. code-block:: python
+ :linenos:
+
+ import cherrypy
+
+ class HelloWorld(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello world!"
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(HelloWorld())
+
+Store this code snippet into a file named `tut01.py` and
+execute it as follows:
+
+.. code-block:: bash
+
+ $ python tut01.py
+
+This will display something along the following:
+
+.. code-block:: text
+ :linenos:
+
+ [24/Feb/2014:21:01:46] ENGINE Listening for SIGHUP.
+ [24/Feb/2014:21:01:46] ENGINE Listening for SIGTERM.
+ [24/Feb/2014:21:01:46] ENGINE Listening for SIGUSR1.
+ [24/Feb/2014:21:01:46] ENGINE Bus STARTING
+ CherryPy Checker:
+ The Application mounted at '' has an empty config.
+
+ [24/Feb/2014:21:01:46] ENGINE Started monitor thread 'Autoreloader'.
+ [24/Feb/2014:21:01:46] ENGINE Started monitor thread '_TimeoutMonitor'.
+ [24/Feb/2014:21:01:46] ENGINE Serving on http://127.0.0.1:8080
+ [24/Feb/2014:21:01:46] ENGINE Bus STARTED
+
+This tells you several things. The first three lines indicate
+the server will handle :mod:`signal` for you. The next line tells you
+the current state of the server, as that
+point it is in `STARTING` stage. Then, you are notified your
+application has no specific configuration set to it.
+Next, the server starts a couple of internal utilities that
+we will explain later. Finally, the server indicates it is now
+ready to accept incoming communications as it listens on
+the address `127.0.0.1:8080`. In other words, at that stage your
+application is ready to be used.
+
+Before moving on, let's discuss the message
+regarding the lack of configuration. By default, CherryPy has
+a feature which will review the syntax correctness of settings
+you could provide to configure the application. When none are
+provided, a warning message is thus displayed in the logs. That
+log is harmless and will not prevent CherryPy from working. You
+can refer to :ref:`the documentation above <perappconf>` to
+understand how to set the configuration.
+
+Tutorial 2: Different URLs lead to different functions
+######################################################
+
+Your applications will obviously handle more than a single URL.
+Let's imagine you have an application that generates a random
+string each time it is called:
+
+.. code-block:: python
+ :linenos:
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello world!"
+
+ @cherrypy.expose
+ def generate(self):
+ return ''.join(random.sample(string.hexdigits, 8))
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(StringGenerator())
+
+Save this into a file named `tut02.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut02.py
+
+Go now to http://localhost:8080/generate and your browser
+will display a random string.
+
+Let's take a minute to decompose what's happening here. This is the
+URL that you have typed into your browser: http://localhost:8080/generate
+
+This URL contains various parts:
+
+- `http://` which roughly indicates it's a URL using the HTTP protocol (see :rfc:`2616`).
+- `localhost:8080` is the server's address. It's made of a hostname and a port.
+- `/generate` which is the path segment of the URL. This is what CherryPy uses to
+ locate an :term:`exposed` function or method to respond.
+
+Here CherryPy uses the `index()` method to handle `/` and the
+`generate()` method to handle `/generate`
+
+.. _tut03:
+
+Tutorial 3: My URLs have parameters
+###################################
+
+In the previous tutorial, we have seen how to create an application
+that could generate a random string. Let's not assume you wish
+to indicate the length of that string dynamically.
+
+.. code-block:: python
+ :linenos:
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return "Hello world!"
+
+ @cherrypy.expose
+ def generate(self, length=8):
+ return ''.join(random.sample(string.hexdigits, int(length)))
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(StringGenerator())
+
+Save this into a file named `tut03.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut03.py
+
+Go now to http://localhost:8080/generate?length=16 and your browser
+will display a generated string of length 16. Notice how
+we benefit from Python's default arguments' values to support
+URLs such as http://localhost:8080/password still.
+
+In a URL such as this one, the section after `?` is called a
+query-string. Traditionally, the query-string is used to
+contextualize the URL by passing a set of (key, value) pairs. The
+format for those pairs is `key=value`. Each pair being
+separated by a `&` character.
+
+Notice how we have to convert the given `length` value to
+and integer. Indeed, values are sent out from the client
+to our server as strings.
+
+Much like CherryPy maps URL path segments to exposed functions,
+query-string keys are mapped to those exposed function parameters.
+
+.. _tut04:
+
+Tutorial 4: Submit this form
+############################
+
+CherryPy is a web framework upon which you build web applications.
+The most traditionnal shape taken by applications is through
+an HTML user-interface speaking to your CherryPy server.
+
+Let's see how to handle HTML forms via the following
+example.
+
+.. code-block:: python
+ :linenos:
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return """<html>
+ <head></head>
+ <body>
+ <form method="get" action="generate">
+ <input type="text" value="8" name="length" />
+ <button type="submit">Give it now!</button>
+ </form>
+ </body>
+ </html>"""
+
+ @cherrypy.expose
+ def generate(self, length=8):
+ return ''.join(random.sample(string.hexdigits, int(length)))
+
+ if __name__ == '__main__':
+ cherrypy.quickstart(StringGenerator())
+
+Save this into a file named `tut04.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut04.py
+
+Go now to http://localhost:8080/ and your browser and this will
+display a simple input field to indicate the length of the string
+you want to generate.
+
+Notice that in this example, the form uses the `GET` method and
+when you pressed the `Give it now!` button, the form is sent using the
+same URL as in the :ref:`previous <tut03>` tutorial. HTML forms also support the
+`POST` method, in that case the query-string is not appended to the
+URL but it sent as the body of the client's request to the server.
+However, this would not change your application's exposed method because
+CherryPy handles both the same way and uses the exposed's handler
+parameters to deal with the query-string (key, value) pairs.
+
+.. _tut05:
+
+Tutorial 5: Track my end-user's activity
+########################################
+
+It's not uncommon that an application needs to follow the
+user's activity for a while. The usual mechanism is to use
+a `session identifier <http://en.wikipedia.org/wiki/Session_(computer_science)#HTTP_session_token>`_
+that is carried during the conversation between the user and
+your application.
+
+.. code-block:: python
+ :linenos:
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return """<html>
+ <head></head>
+ <body>
+ <form method="get" action="generate">
+ <input type="text" value="8" name="length" />
+ <button type="submit">Give it now!</button>
+ </form>
+ </body>
+ </html>"""
+
+ @cherrypy.expose
+ def generate(self, length=8):
+ some_string = ''.join(random.sample(string.hexdigits, int(length)))
+ cherrypy.session['mystring'] = some_string
+ return some_string
+
+ @cherrypy.expose
+ def display(self):
+ return cherrypy.session['mystring']
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'tools.sessions.on': True
+ }
+ }
+ cherrypy.quickstart(StringGenerator(), '/', conf)
+
+Save this into a file named `tut05.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut05.py
+
+In this example, we generate the string as in the
+:ref:`previous <tut04>` tutorial but also store it in the current
+session. If you go to http://localhostt:8080/, generate a
+random string, then go to http://localhostt:8080/display, you
+will see the string you just generated.
+
+The lines 30-34 show you how to enable the session support
+in your CherryPy application. By default, CherryPy will save
+sessions in the process's memory. It supports more persistent
+:ref:`backends <basicsession>` as well.
+
+Tutorial 6: What about my javascripts, CSS and images?
+######################################################
+
+Web application are usually also made of static content such
+as javascript, CSS files or images. CherryPy provides support
+to serve static content to end-users.
+
+Let's assume, you want to associate a stylesheet with your
+application to display a blue background color (why not?).
+
+First, save the following stylesheet into a file named `style.css`
+and stored into a local directory `public/css`.
+
+.. code-block:: css
+ :linenos:
+
+ body {
+ background-color: blue;
+ }
+
+Now let's update the HTML code so that we link to the stylesheet
+using the http://localhost:8080/static/css/style.css URL.
+
+.. code-block:: python
+ :linenos:
+
+ import os, os.path
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return """<html>
+ <head>
+ <link href="/static/css/style.css" rel="stylesheet">
+ </head>
+ <body>
+ <form method="get" action="generate">
+ <input type="text" value="8" name="length" />
+ <button type="submit">Give it now!</button>
+ </form>
+ </body>
+ </html>"""
+
+ @cherrypy.expose
+ def generate(self, length=8):
+ some_string = ''.join(random.sample(string.hexdigits, int(length)))
+ cherrypy.session['mystring'] = some_string
+ return some_string
+
+ @cherrypy.expose
+ def display(self):
+ return cherrypy.session['mystring']
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'tools.sessions.on': True,
+ 'tools.staticdir.root': os.path.abspath(os.getcwd())
+ },
+ '/static': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': './public'
+ }
+ }
+ cherrypy.quickstart(StringGenerator(), '/', conf)
+
+Save this into a file named `tut06.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut06.py
+
+Going to http://localhost:8080/, you should be greeted by a flashy blue color.
+
+CherryPy provides support to serve a single file or a complete
+directory structure. Most of the time, this is what you'll end
+up doing so this is what the code above demonstrates. First, we
+indicate the `root` directory of all of our static content. This
+must be an absolute path for security reason. CherryPy will
+complain if you provide only non-absolute paths when looking for a
+match to your URLs.
+
+Then we indicate that all URLs which path segment starts with `/static`
+will be served as static content. We map that URL to the `public`
+directory, a direct child of the `root` directory. The entire
+sub-tree of the `public` directory will be served as static content.
+CherryPy will map URLs to path within that directory. This is why
+`/static/css/style.css` is found in `public/css/style.css`.
+
+Tutorial 7: Give us a REST
+##########################
+
+It's not unusual nowadays that web applications expose some sort
+of datamodel or computation functions. Without going into
+its details, one strategy is to follow the `REST principles
+edicted by Roy T. Fielding
+<http://www.ibm.com/developerworks/library/ws-restful/index.html>`_.
+
+Roughly speaking, it assumes that you can identify a resource
+and that you can address that resource through that identifier.
+
+"What for?" you may ask. Well, mostly, these principles are there
+to ensure that you decouple, as best as you can, the entities
+your application expose from the way they are manipulated or
+consumed. To embrace this point of view, developers will
+usually design a web API that expose pairs of `(URL, HTTP method, data, constraints)`.
+
+.. note::
+
+ You will often hear REST and web API together. The former is
+ one strategy to provide the latter. This tutorial will not go
+ deeper in that whole web API concept as it's a much more
+ engaging subject, but you ought to read more about it online.
+
+
+Lets go through a small example of a very basic web API
+mildly following REST principles.
+
+.. code-block:: python
+ :linenos:
+
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGeneratorWebService(object):
+ exposed = True
+
+ @cherrypy.tools.accept(media='text/plain')
+ def GET(self):
+ return cherrypy.session['mystring']
+
+ def POST(self, length=8):
+ some_string = ''.join(random.sample(string.hexdigits, int(length)))
+ cherrypy.session['mystring'] = some_string
+ return some_string
+
+ def PUT(self, another_string):
+ cherrypy.session['mystring'] = another_string
+
+ def DELETE(self):
+ cherrypy.session.pop('mystring', None)
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
+ 'tools.sessions.on': True,
+ 'tools.response_headers.on': True,
+ 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
+ }
+ }
+ cherrypy.quickstart(StringGeneratorWebService(), '/', conf)
+
+
+Save this into a file named `tut07.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut07.py
+
+Before we see it in action, let's explain a few things. Until now,
+CherryPy was creating a tree of exposed methods that were used to
+math URLs. In the case of our web API, we want to stress the role
+played by the actual requests' HTTP methods. So we created
+methods that are named after them and they are all exposed at once
+through the `exposed = True` attribute of the class itself.
+
+However, we must then switch from the default mechanism of matching
+URLs to method for one that is aware of the whole HTTP method
+shenanigan. This is what goes on line 27 where we create
+a :class:`~cherrypy.dispatch.MethodDispatcher` instance.
+
+Then we force the responses `content-type` to be `text/plain` and
+we finally ensure that `GET` requests will only be responded to clients
+that accept that `content-type` by having a `Accept: text/plain`
+header set in their request. However, we do this only for that
+HTTP method as it wouldn't have much meaning on the oher methods.
+
+
+For the purpose of this tutorial, we will be using a Python client
+rather than your browser as we wouldn't be able to actually try
+our web API otherwiser.
+
+Please install `requests <http://www.python-requests.org/en/latest/>`_
+through the following command:
+
+.. code-block:: bash
+
+ $ pip install requests
+
+Then fire up a Python terminal and try the following commands:
+
+.. code-block:: pycon
+ :linenos:
+
+ >>> import requests
+ >>> s = requests.Session()
+ >>> r = s.get('http://127.0.0.1:8080/')
+ >>> r.status_code
+ 500
+ >>> r = s.post('http://127.0.0.1:8080/')
+ >>> r.status_code, r.text
+ (200, u'04A92138')
+ >>> r = s.get('http://127.0.0.1:8080/')
+ >>> r.status_code, r.text
+ (200, u'04A92138')
+ >>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'})
+ >>> r.status_code
+ 406
+ >>> r = s.put('http://127.0.0.1:8080/', params={'another_string': 'hello'})
+ >>> r = s.get('http://127.0.0.1:8080/')
+ >>> r.status_code, r.text
+ (200, u'hello')
+ >>> r = s.delete('http://127.0.0.1:8080/')
+ >>> r = s.get('http://127.0.0.1:8080/')
+ >>> r.status_code
+ 500
+
+The first and last `500` responses steam from the fact that, in
+the first case, we haven't yet generated a string through `POST` and,
+on the latter case, that it doesn't exist after we've deleted it.
+
+Lines 12-14 show you how the application reacted when our client requested
+the generated string as a JSON format. Since we configured the
+web API to only support plain text, it returns the appropriate
+`HTTP error code http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7`
+
+
+.. note::
+
+ We use the `Session <http://www.python-requests.org/en/latest/user/advanced/#session-objects>`_
+ interface of `requests` so that it takes care of carrying the
+ session id stored in the request cookie in each subsequent
+ request. That is handy.
+
+.. _tut08:
+
+
+Tutorial 8: Make it smoother with Ajax
+######################################
+
+In the recent years, web applications have moved away from the
+simple pattern of "HTML forms + refresh the whole page". This
+traditional scheme still works very well but users have become used
+to web applications that don't refresh the entire page.
+Broadly speaking, web applications carry code performed
+client-side that can speak with the backend without having to
+refresh the whole page.
+
+This tutorial will involve a little more code this time around. First,
+let's see our CSS stylesheet located in `public/css/style.css`.
+
+.. code-block:: css
+ :linenos:
+
+ body {
+ background-color: blue;
+ }
+
+ #the-string {
+ display: none;
+ }
+
+We're adding a simple rule about the element that will display
+the generated string. By default, let's not show it up.
+Save the following HTML code into a file named `index.html`.
+
+.. code-block:: html
+ :linenos:
+
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <link href="/static/css/style.css" rel="stylesheet">
+ <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function() {
+
+ $("#generate-string").click(function(e) {
+ $.post("/generator", {"length": $("input[name='length']").val()})
+ .done(function(string) {
+ $("#the-string").show();
+ $("#the-string input").val(string);
+ });
+ e.preventDefault();
+ });
+
+ $("#replace-string").click(function(e) {
+ $.ajax({
+ type: "PUT",
+ url: "/generator",
+ data: {"another_string": $("#the-string").val()}
+ })
+ .done(function() {
+ alert("Replaced!");
+ });
+ e.preventDefault();
+ });
+
+ $("#delete-string").click(function(e) {
+ $.ajax({
+ type: "DELETE",
+ url: "/generator"
+ })
+ .done(function() {
+ $("#the-string").hide();
+ });
+ e.preventDefault();
+ });
+
+ });
+ </script>
+ </head>
+ <body>
+ <input type="text" value="8" name="length" />
+ <button id="generate-string">Give it now!</button>
+ <div id="the-string">
+ <input type="text" />
+ <button id="replace-string">Replace</button>
+ <button id="delete-string">Delete it</button>
+ </div>
+ </body>
+ </html>
+
+We'll be using the `jQuery framework <http://jquery.com/>`_
+out of simplicity but feel free to replace it with your
+favourite tool. The page is composed of simple HTML elements
+to get user input and display the generated string. It also
+contains client-side code to talk to the backend API that
+actually performs the hard work.
+
+Finally, here's the application's code that serves the
+HTML page above and responds to requests to generate strings.
+Both are hosted by the same application server.
+
+.. code-block:: python
+ :linenos:
+
+ import os, os.path
+ import random
+ import string
+
+ import cherrypy
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return file('index.html')
+
+ class StringGeneratorWebService(object):
+ exposed = True
+
+ @cherrypy.tools.accept(media='text/plain')
+ def GET(self):
+ return cherrypy.session['mystring']
+
+ def POST(self, length=8):
+ some_string = ''.join(random.sample(string.hexdigits, int(length)))
+ cherrypy.session['mystring'] = some_string
+ return some_string
+
+ def PUT(self, another_string):
+ cherrypy.session['mystring'] = another_string
+
+ def DELETE(self):
+ cherrypy.session.pop('mystring', None)
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'tools.sessions.on': True,
+ 'tools.staticdir.root': os.path.abspath(os.getcwd())
+ },
+ '/generator': {
+ 'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
+ 'tools.response_headers.on': True,
+ 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
+ },
+ '/static': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': './public'
+ }
+ }
+ webapp = StringGenerator()
+ webapp.generator = StringGeneratorWebService()
+ cherrypy.quickstart(webapp, '/', conf)
+
+
+Save this into a file named `tut08.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut08.py
+
+Go to http://127.0.0.1:8080/ and play with the input and buttons
+to generate, replace or delete the strings. Notice how the page
+isn't refreshed, simply part of its content.
+
+Notice as well how your frontend converses with the backend using
+a straightfoward, yet clean, web service API. That same API
+could easily be used by non-HTML clients.
+
+
+Tutorial 9: Data is all my life
+###############################
+
+Until now, all the generated strings were saved in the
+session, which by default is stored in the process memory. Though,
+you can persist sessions on disk or in a distributed memory store,
+this is not the right way of keeping your data on the long run.
+Sessions are there to identify your user and carry as little
+amount of data as necessary for the operation carried by the user.
+
+To store, persist and query data your need a proper database server.
+There exist many to choose from with various paradigm support:
+
+- relational: PostgreSQL, SQLite, MariaDB, Firebird
+- column-oriented: HBase, Cassandra
+- key-store: redis, memcached
+- document oriented: Couchdb, MongoDB
+- graph-oriented: neo4j
+
+Let's focus on the relational ones since they are the most common
+and probably what you will want to learn first.
+
+For the sake of reducing the number of dependencies for these
+tutorials, we will go for the :mod:`sqlite` database which
+is directly supported by Python.
+
+Our application will replace the storage of the generated
+string from the session to a SQLite database. The application
+will have the same HTML code as :ref:`tutorial 08 <tut08>`.
+So let's simply focus on the application code itself:
+
+.. code-block:: python
+ :linenos:
+
+ import os, os.path
+ import random
+ import sqlite3
+ import string
+
+ import cherrypy
+
+ DB_STRING = "my.db"
+
+ class StringGenerator(object):
+ @cherrypy.expose
+ def index(self):
+ return file('index.html')
+
+ class StringGeneratorWebService(object):
+ exposed = True
+
+ @cherrypy.tools.accept(media='text/plain')
+ def GET(self):
+ with sqlite3.connect(DB_STRING) as c:
+ c.execute("SELECT value FROM user_string WHERE session_id=?",
+ [cherrypy.session.id])
+ return c.fetchone()
+
+ def POST(self, length=8):
+ some_string = ''.join(random.sample(string.hexdigits, int(length)))
+ with sqlite3.connect(DB_STRING) as c:
+ c.execute("INSERT INTO user_string VALUES (?, ?)",
+ [cherrypy.session.id, some_string])
+ return some_string
+
+ def PUT(self, another_string):
+ with sqlite3.connect(DB_STRING) as c:
+ c.execute("UPDATE user_string SET value=? WHERE session_id=?",
+ [another_string, cherrypy.session.id])
+
+ def DELETE(self):
+ with sqlite3.connect(DB_STRING) as c:
+ c.execute("DELETE FROM user_string WHERE session_id=?",
+ [cherrypy.session.id])
+
+ def setup_database():
+ """
+ Create the `user_string` table in the database
+ on server startup
+ """
+ with sqlite3.connect(DB_STRING) as con:
+ con.execute("CREATE TABLE user_string (session_id, value)")
+
+ def cleanup_database():
+ """
+ Destroy the `user_string` table from the database
+ on server shutdown.
+ """
+ with sqlite3.connect(DB_STRING) as con:
+ con.execute("DROP TABLE user_string")
+
+ if __name__ == '__main__':
+ conf = {
+ '/': {
+ 'tools.sessions.on': True,
+ 'tools.staticdir.root': os.path.abspath(os.getcwd())
+ },
+ '/generator': {
+ 'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
+ 'tools.response_headers.on': True,
+ 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
+ },
+ '/static': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': './public'
+ }
+ }
+
+ cherrypy.engine.subscribe('start', setup_database)
+ cherrypy.engine.subscribe('stop', cleanup_database)
+
+ webapp = StringGenerator()
+ webapp.generator = StringGeneratorWebService()
+ cherrypy.quickstart(webapp, '/', conf)
+
+
+Save this into a file named `tut09.py` and run it as follows:
+
+.. code-block:: bash
+
+ $ python tut09.py
+
+Let's first see how we create two functions that create
+and destroy the table within our database. These functions
+are registered to the CherryPy's server on lines 76-77,
+so that they are called when the server starts and stops.
+
+Next, notice how we replaced all the session code with calls
+to the database. We use the session id to identify the
+user's string within our database. Since the session will go
+away after a while, it's probably not the right approach.
+A better idea would be to associate the user's login or
+more resilient unique identifier. For the sake of our
+demo, this should do.
+
+.. note::
+
+ Unfortunately, sqlite in Python forbids us
+ to share a connection between threads. Since CherryPy is a
+ multi-threaded server, this would be an issue. This is the
+ reason why we open and close a connection to the database
+ on each call. This is clearly not really production friendly,
+ and it is probably advisable to either use a more capable
+ database engine or a higher level library, such as
+ `SQLAlchemy <http://sqlalchemy.readthedocs.org>`_, to better
+ support your application's needs.
+
+
+Tutorial 10: Organize my code
+#############################
+
+CherryPy comes with a powerful architecture
+that helps you organizing your code in a way that should make
+it easier to maintain and more flexible.
+
+Several mechanisms are at your disposal, this tutorial will focus
+on the three main ones:
+
+- :ref:`dispatchers <dispatchers>`
+- :ref:`tools <tools>`
+- :ref:`plugins <busplugins>`
+
+In order to understand them, let's imagine you are at a superstore:
+
+- You have several tills and people queuing for each of them (those are your requests)
+- You have various sections with food and other stuff (these are your data)
+- Finally you have the superstore people and their daily tasks
+ to make sure sections are always in order (this is your backend)
+
+In spite of being really simplistic, this is not far from how your
+application behaves. CherryPy helps your structure your application
+in a way that mirrors these high-level ideas.
+
+Dispatchers
+^^^^^^^^^^^
+
+Coming back to the superstore example, it is likely that you will
+want to perform operations based on the till:
+
+- Have a till for baskets with less than ten items
+- Have a till for disabled people
+- Have a till for pregnant women
+- Have a till where you can only using the store card
+
+To support these use-cases, CherryPy provides a mechanism called
+a :ref:`dispatcher <dispatchers>`. A dispatcher is executed early
+during the request processing in order to determine which piece of
+code of your application will handle the incoming request. Or, to
+continue on the store analogy, a dispatcher will decide which
+till to lead a customer to.
+
+Tools
+^^^^^
+
+Let's assume your store has decided to operate a discount spree but,
+only for a specific category of customers. CherryPy will deal
+with such use case via a mechanism called a :ref:`tool <tools>`.
+
+A tool is a piece of code that runs on a per-request
+basis in order to perform additional work. Usually a tool is a
+simple Python function that is executed at a given point during
+the process of the request by CherryPy.
+
+Plugins
+^^^^^^^
+
+As we have seen, the store has a crew of people dedicated to manage
+the stock and deal with any customers' expectation.
+
+In the CherryPy world, this translates into having functions
+that run outside of any request life-cycle. These functions should
+take care of background tasks, long lived connections (such as
+those to a database for instance), etc.
+
+:ref:`Plugins <busplugins>` are called that way because
+they work along with the CherryPy :ref:`engine <cpengine>`
+and extend it with your operations.
+
+
diff --git a/docs/util/convert-trac.py b/docs/util/convert-trac.py
new file mode 100644
index 00000000..ebb77d42
--- /dev/null
+++ b/docs/util/convert-trac.py
@@ -0,0 +1,134 @@
+#!python
+
+"""
+%prog <filename>
+
+A utility script for performing some commonly-encountered patterns in
+Trac Wiki format into reStructuredText (rst).
+
+filename is the name of the text file to be saved. If -U is not used,
+the file is converted in-place and filename is also the name of the
+source.
+"""
+
+from __future__ import print_function
+import sys
+import re
+import inspect
+import optparse
+import shutil
+import urllib2
+from StringIO import StringIO
+
+
+def get_options():
+ global options
+ parser = optparse.OptionParser(usage=inspect.cleandoc(__doc__))
+ parser.add_option('-U', '--url',
+ help="Trac URL from which to retrieve source")
+ options, args = parser.parse_args()
+ try:
+ options.filename = args.pop()
+ except IndexError:
+ parser.error("Filename required")
+
+# each of the replacement functions should have a docstring
+# which is a regular expression to be matched.
+
+
+def replace_external_link(matcher):
+ r"\[(?P<href>(?P<scheme>\w+)\://.+?) (?P<name>.+?)\]"
+ return '`{name} <{href}>`_'.format(**matcher.groupdict())
+
+
+def replace_wiki_link(matcher):
+ r"\[wiki\:(?P<ref>.+?) (?P<name>.+?)\]"
+ return '`{name} <TODO-fix wiki target {ref}>`_'.format(
+ **matcher.groupdict()
+ )
+
+# character array indexed by level for characters
+heading_characters = [None, '*', '=', '-', '^']
+
+
+def replace_headings(matcher):
+ r"^(?P<level>=+) (?P<name>.*) (?P=level)$"
+ level = len(matcher.groupdict()['level'])
+ char = heading_characters[level]
+ name = matcher.groupdict()['name']
+ lines = [name, char * len(name)]
+ if level == 1:
+ lines.insert(0, char * len(name))
+ return '\n'.join(lines)
+
+
+def indent(block):
+ add_indent = lambda s: ' ' + s
+ lines = StringIO(block)
+ i_lines = map(add_indent, lines)
+ return ''.join(i_lines)
+
+
+def replace_inline_code(matcher):
+ r"\{\{\{(?P<code>[^\n]*?)\}\}\}"
+ return '``{code}``'.format(**matcher.groupdict())
+
+
+def replace_code_block(matcher):
+ r"\{\{\{\n(?P<code>(.|\n)*?)^\}\}\}"
+ return '::\n\n' + indent(matcher.groupdict()['code'])
+
+
+def replace_page_outline(matcher):
+ r"\[\[PageOutline\]\]\n"
+ return ''
+
+
+def replace_bang_symbols(matcher):
+ r"!(?P<symbol>\w+)"
+ return matcher.groupdict()['symbol']
+
+# a number of the files end in
+"""{{{
+#!html
+<h2 class='compatibility'>Older versions</h2>
+}}}""" # and everything after is garbage, so just remove it.
+
+
+def remove_2x_compat_notes(matcher):
+ r"\{\{\{\n#!html\n<h2(.|\n)*"
+ return ''
+
+replacements = [remove_2x_compat_notes] + \
+ [func for name,
+ func in globals().items() if name.startswith('replace_')]
+
+
+def normalize_linebreaks(text):
+ return text.replace('\r\n', '\n')
+
+
+def convert_file():
+ filename = options.filename
+ if options.url:
+ text = urllib2.urlopen(options.url).read()
+ text = normalize_linebreaks(text)
+ else:
+ shutil.copy(filename, filename + '.bak')
+ text = open(filename).read()
+ # iterate over each of the replacements and execute it
+ new_text = text
+ for repl in replacements:
+ pattern = re.compile(inspect.getdoc(repl), re.MULTILINE)
+ new_text = pattern.sub(repl, new_text)
+
+ open(filename, 'w').write(new_text)
+ print("done")
+
+
+def handle_command_line():
+ get_options()
+ convert_file()
+
+if __name__ == '__main__':
+ handle_command_line()
diff --git a/sphinx/util/test-doc.py b/docs/util/test-doc.py
index 29423602..3d06da99 100644
--- a/sphinx/util/test-doc.py
+++ b/docs/util/test-doc.py
@@ -5,11 +5,12 @@ import docutils.utils
import docutils.parsers.rst
from StringIO import StringIO
+
def print_with_line_numbers(block):
- stream = StringIO(block)
- for number, line in enumerate(stream):
- number += 1
- print(number, line.rstrip())
+ stream = StringIO(block)
+ for number, line in enumerate(stream):
+ number += 1
+ print(number, line.rstrip())
target_class_spec = sys.argv[1]
import cherrypy
@@ -17,6 +18,6 @@ target_class = eval(target_class_spec)
source = inspect.getdoc(target_class)
print_with_line_numbers(source)
parser = docutils.parsers.rst.Parser()
-settings = None ## ?
+settings = None # ?
document = docutils.utils.new_document(source, settings)
parser.parse(source, document)
diff --git a/ez_setup.py b/ez_setup.py
deleted file mode 100644
index 28835dd1..00000000
--- a/ez_setup.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
- from ez_setup import use_setuptools
- use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-
-DEFAULT_VERSION = "0.5a13"
-DEFAULT_URL = "http://www.python.org/packages/source/s/setuptools/"
-
-import sys, os
-
-def use_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir
-):
- """Automatically find/download setuptools and make it available on sys.path
-
- `version` should be a valid setuptools version number that is available
- as an egg for download under the `download_base` URL (which should end with
- a '/'). `to_dir` is the directory where setuptools will be downloaded, if
- it is not already available.
-
- If an older version of setuptools is installed, this will print a message
- to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling
- script.
- """
- try:
- import setuptools
- if setuptools.__version__ == '0.0.1':
- sys.stderr.write(
- "You have an obsolete version of setuptools installed. Please\n"
- "remove it from your system entirely before rerunning this script."
- )
- sys.exit(2)
-
- except ImportError:
- egg = download_setuptools(version, download_base, to_dir)
- sys.path.insert(0, egg)
- import setuptools; setuptools.bootstrap_install_from = egg
-
- import pkg_resources
- try:
- pkg_resources.require("setuptools>="+version)
-
- except pkg_resources.VersionConflict:
- # XXX could we install in a subprocess here?
- sys.stderr.write(
- "The required version of setuptools (>=%s) is not available, and\n"
- "can't be installed while this script is running. Please install\n"
- " a more recent version first."
- ) % version
- sys.exit(2)
-
-def download_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir
-):
- """Download setuptools from a specified location and return its filename
-
- `version` should be a valid setuptools version number that is available
- as an egg for download under the `download_base` URL (which should end
- with a '/'). `to_dir` is the directory where the egg will be downloaded.
- """
- from urllib2 import urlopen
- import shutil
- egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
- url = download_base + egg_name + '.zip' # XXX
- saveto = os.path.join(to_dir, egg_name)
- src = dst = None
-
- if not os.path.exists(saveto): # Avoid repeated downloads
- try:
- from distutils import log
- log.warn("Downloading %s", url)
- src = urlopen(url)
- # Read/write all in one block, so we don't create a corrupt file
- # if the download is interrupted.
- data = src.read()
- dst = open(saveto,"wb")
- dst.write(data)
- finally:
- if src: src.close()
- if dst: dst.close()
-
- return os.path.realpath(saveto)
-
-def main(argv, version=DEFAULT_VERSION):
- """Install or upgrade setuptools and EasyInstall"""
-
- try:
- import setuptools
- except ImportError:
- import tempfile, shutil
- tmpdir = tempfile.mkdtemp(prefix="easy_install-")
- try:
- egg = download_setuptools(version, to_dir=tmpdir)
- sys.path.insert(0,egg)
- from setuptools.command.easy_install import main
- main(list(argv)+[egg])
- finally:
- shutil.rmtree(tmpdir)
- else:
- if setuptools.__version__ == '0.0.1':
- # tell the user to uninstall obsolete version
- use_setuptools(version)
-
- req = "setuptools>="+version
- import pkg_resources
- try:
- pkg_resources.require(req)
- except pkg_resources.VersionConflict:
- try:
- from setuptools.command.easy_install import main
- except ImportError:
- from easy_install import main
- main(list(argv)+[download_setuptools()])
- sys.exit(0) # try to force an exit
- else:
- if argv:
- from setuptools.command.easy_install import main
- main(argv)
- else:
- print("Setuptools version",version,"or greater has been installed.")
- print('(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)')
-if __name__=='__main__':
- main(sys.argv[1:])
diff --git a/docs/cherryd.1 b/man/cherryd.1
index cf27a360..cf27a360 100644
--- a/docs/cherryd.1
+++ b/man/cherryd.1
diff --git a/release.py b/release.py
index 46e77269..26bad57e 100644
--- a/release.py
+++ b/release.py
@@ -1,126 +1,62 @@
"""
-This script will walk a developer through the process of cutting a release.
+Use jaraco.packaging with this script to cut a release. After installing
+jaraco.packaging, invoke:
-Based on https://bitbucket.org/cherrypy/cherrypy/wiki/ReleaseProcess
-
-To cut a release, simply invoke this script at the changeset to be released.
+python -m jaraco.packaging.release
"""
from __future__ import print_function
-import subprocess
import sys
import os
-import platform
import shutil
-
-VERSION='3.2.4'
-
-if sys.version_info < (3,):
- input = raw_input
-
-def get_next_version():
- print("The last release on this branch was {version}".format(
- version=VERSION))
- return input("What version are you releasing? ")
-
-NEXT_VERSION = get_next_version()
-
-files_with_versions = ('release.py', 'setup.py', 'cherrypy/__init__.py',
- 'cherrypy/wsgiserver/wsgiserver2.py',
- 'cherrypy/wsgiserver/wsgiserver3.py',
+import importlib
+import textwrap
+
+files_with_versions = (
+ 'setup.py',
+ 'cherrypy/__init__.py',
+ 'cherrypy/wsgiserver/wsgiserver2.py',
+ 'cherrypy/wsgiserver/wsgiserver3.py',
)
-def check_status():
- """
- Make sure there aren't outstanding changesets that are unpushed, maybe
- run the tests or ask the user if the tests are passing.
- """
- print("You're about to release CherryPy {NEXT_VERSION}".format(
- **globals()))
- res = input('Have you run the tests with `nosetests -s ./` on '
- 'Windows, Linux, and Mac on at least Python 2.4, 2.5, 2.7, and 3.2? '
- .format(**globals()))
- if not res.lower().startswith('y'):
- print("Please do that")
- raise SystemExit(1)
-
-def bump_versions():
- """
- Bump the versions in each of the places where it appears and commit.
- """
- list(map(bump_version, files_with_versions))
- subprocess.check_call(['hg', 'ci', '-m',
- 'Bumped to {NEXT_VERSION} in preparation for next '
- 'release.'.format(**globals())])
+def check_wheel():
+ """
+ Ensure 'wheel' is installed (required for bdist_wheel).
+ """
+ try:
+ importlib.import_module('wheel')
+ except ImportError:
+ print("CherryPy requires 'wheel' be installed to produce wheels.",
+ file=sys.stderr)
+ raise SystemExit(5)
-def bump_version(filename):
- with open(filename, 'rb') as f:
- lines = [line.replace(VERSION, NEXT_VERSION) for line in f]
- with open(filename, 'wb') as f:
- f.writelines(lines)
+def before_upload():
+ check_wheel()
+ remove_files()
-def tag_release():
- """
- Tag the release.
- """
- subprocess.check_call(['hg', 'tag', NEXT_VERSION])
+test_info = textwrap.dedent("""
+ Run tests with `nosetests -s ./` on Windows, Linux, and Mac on at least
+ Python 2.4, 2.5, 2.7, and 3.2.
+ """).lstrip()
-dist_commands = [
- [sys.executable, 'setup.py', 'sdist', '--format=zip'],
- [sys.executable, 'setup.py', 'sdist', '--format=gztar'],
- [sys.executable, 'setup.py', 'bdist_wininst'],
-]
+dist_commands = 'sdist', 'bdist_wininst', 'bdist_wheel'
-def build():
- if os.path.isfile('MANIFEST'):
- os.remove('MANIFEST')
- if os.path.isdir('dist'):
- shutil.rmtree('dist')
- list(map(subprocess.check_call, dist_commands))
-def push():
- "The build went well, so let's push the SCM changesets"
- subprocess.check_call(['hg', 'push'])
+def remove_files():
+ if os.path.isfile('MANIFEST'):
+ os.remove('MANIFEST')
+ if os.path.isdir('dist'):
+ shutil.rmtree('dist')
-def publish():
- """
- Publish the dists on PyPI
- """
- try:
- upload_dist_commands = [cmd + ['register', 'upload']
- for cmd in dist_commands]
- list(map(subprocess.check_call, upload_dist_commands))
- except:
- print("Unable to upload the dist files. Ask in IRC for help access "
- "or assistance.")
- raise SystemExit(4)
- print('Distributions have been uploaded.')
- print('Please ask in IRC for others to help you test '
- 'CherryPy {NEXT_VERSION}.'
- .format(**globals()))
- print("Please confirm that the distro installs properly "
- "with `easy_install CherryPy=={NEXT_VERSION}`.".format(**globals()))
def announce():
- print("Please change the Wiki: Home page (news), CherryPyDownload")
- print("Please announce the release on newsgroups, mailing lists, "
- "and IRC /topic.")
-
-def main():
- assert sys.version_info >= (2, 6), ("Release script requires Python 2.6 "
- "or later.")
- assert platform.system() == 'Windows', ('You must release on Windows '
- '(to create Windows installers)')
- check_status()
- bump_versions()
- tag_release()
- build()
- push()
- publish()
- announce()
-
-if __name__ == '__main__':
- main()
+ print('Distributions have been uploaded.')
+ print('Please ask in IRC for others to help you test this release.')
+ print("Please confirm that the distro installs properly "
+ "with `easy_install CherryPy=={version}`.".format(**globals()))
+ print("Please change the Wiki: Home page (news), CherryPyDownload")
+ print("Please announce the release on newsgroups, mailing lists, "
+ "and IRC /topic.")
diff --git a/setup.cfg b/setup.cfg
index f12f0f55..cd62b8dd 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,11 +1,8 @@
[sdist]
-formats=gztar
+formats=gztar,zip
[nosetests]
-cover-erase=True
-with-coverage=True
where=cherrypy
-cover-package=cherrypy
logging-filter=cherrypy
verbosity=2
nocapture=True
diff --git a/setup.py b/setup.py
index 39b76add..c09768ca 100644
--- a/setup.py
+++ b/setup.py
@@ -16,17 +16,19 @@ from distutils.command.build_py import build_py
import sys
import re
+
class cherrypy_build_py(build_py):
- "Custom version of build_py that excludes Python-specific modules"
+ """Custom version of build_py that excludes Python-specific modules"""
+
def build_module(self, module, module_file, package):
python3 = sys.version_info >= (3,)
if python3:
exclude_pattern = re.compile('wsgiserver2|ssl_pyopenssl|'
- '_cpcompat_subprocess')
+ '_cpcompat_subprocess')
else:
exclude_pattern = re.compile('wsgiserver3')
if exclude_pattern.match(module):
- return # skip it
+ return # skip it
return build_py.build_module(self, module, module_file, package)
@@ -34,15 +36,17 @@ class cherrypy_build_py(build_py):
# arguments for the setup command
###############################################################################
name = "CherryPy"
-version = "3.2.4"
+version = "3.5.1"
desc = "Object-Oriented HTTP framework"
long_desc = "CherryPy is a pythonic, object-oriented HTTP framework"
-classifiers=[
+classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: Freely Distributable",
"Operating System :: OS Independent",
+ "Framework :: CherryPy",
+ "License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.3",
@@ -51,6 +55,11 @@ classifiers=[
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: Implementation",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: Jython",
+ "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
@@ -59,18 +68,18 @@ classifiers=[
"Topic :: Internet :: WWW/HTTP :: WSGI :: Server",
"Topic :: Software Development :: Libraries :: Application Frameworks",
]
-author="CherryPy Team"
-author_email="team@cherrypy.org"
-url="http://www.cherrypy.org"
-cp_license="BSD"
-packages=[
+author = "CherryPy Team"
+author_email = "team@cherrypy.org"
+url = "http://www.cherrypy.org"
+cp_license = "BSD"
+packages = [
"cherrypy", "cherrypy.lib",
"cherrypy.tutorial", "cherrypy.test",
"cherrypy.process",
"cherrypy.scaffold",
"cherrypy.wsgiserver",
]
-data_files=[
+data_files = [
('cherrypy', ['cherrypy/cherryd',
'cherrypy/favicon.ico',
'cherrypy/LICENSE.txt',
@@ -79,13 +88,13 @@ data_files=[
('cherrypy/scaffold', ['cherrypy/scaffold/example.conf',
'cherrypy/scaffold/site.conf',
]),
- ('cherrypy/scaffold/static', ['cherrypy/scaffold/static/made_with_cherrypy_small.png',
- ]),
+ ('cherrypy/scaffold/static', [
+ 'cherrypy/scaffold/static/made_with_cherrypy_small.png']),
('cherrypy/test', ['cherrypy/test/style.css',
'cherrypy/test/test.pem',
]),
('cherrypy/test/static', ['cherrypy/test/static/index.html',
- 'cherrypy/test/static/dirback.jpg',]),
+ 'cherrypy/test/static/dirback.jpg', ]),
('cherrypy/tutorial',
[
'cherrypy/tutorial/tutorial.conf',
@@ -93,12 +102,12 @@ data_files=[
'cherrypy/tutorial/pdf_file.pdf',
'cherrypy/tutorial/custom_error.html',
]
- ),
+ ),
]
scripts = ["cherrypy/cherryd"]
cmd_class = dict(
- build_py = cherrypy_build_py,
+ build_py=cherrypy_build_py,
)
if sys.version_info >= (3, 0):
@@ -112,10 +121,27 @@ else:
# wininst may install data_files in Python/x.y instead of the cherrypy package.
# Django's solution is at http://code.djangoproject.com/changeset/8313
-# See also http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
+# See also
+# http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
if 'bdist_wininst' in sys.argv or '--format=wininst' in sys.argv:
data_files = [(r'\PURELIB\%s' % path, files) for path, files in data_files]
+setup_params = dict(
+ name=name,
+ version=version,
+ description=desc,
+ long_description=long_desc,
+ classifiers=classifiers,
+ author=author,
+ author_email=author_email,
+ url=url,
+ license=cp_license,
+ packages=packages,
+ data_files=data_files,
+ scripts=scripts,
+ cmdclass=cmd_class,
+)
+
def main():
if sys.version < required_python_version:
s = "I'm sorry, but %s %s requires Python %s or later."
@@ -126,21 +152,7 @@ def main():
for scheme in list(INSTALL_SCHEMES.values()):
scheme['data'] = scheme['purelib']
- setup(
- name=name,
- version=version,
- description=desc,
- long_description=long_desc,
- classifiers=classifiers,
- author=author,
- author_email=author_email,
- url=url,
- license=cp_license,
- packages=packages,
- data_files=data_files,
- scripts=scripts,
- cmdclass=cmd_class,
- )
+ setup(**setup_params)
if __name__ == "__main__":
diff --git a/sphinx/Makefile b/sphinx/Makefile
deleted file mode 100644
index a0653b16..00000000
--- a/sphinx/Makefile
+++ /dev/null
@@ -1,89 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
-
-.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CherryPy.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CherryPy.qhc"
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
- "run these through (pdf)latex."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/sphinx/make.bat b/sphinx/make.bat
deleted file mode 100644
index 8a496f9e..00000000
--- a/sphinx/make.bat
+++ /dev/null
@@ -1,113 +0,0 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-set SPHINXBUILD=sphinx-build
-set BUILDDIR=build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^<target^>` where ^<target^> is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\CherryPy.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\CherryPy.ghc
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
diff --git a/sphinx/source/appendix/cherrypyspeed.rst b/sphinx/source/appendix/cherrypyspeed.rst
deleted file mode 100644
index f0199c8d..00000000
--- a/sphinx/source/appendix/cherrypyspeed.rst
+++ /dev/null
@@ -1,251 +0,0 @@
-.. _cherrypyspeed:
-
-**********************
-How fast is CherryPy ?
-**********************
-
-Introduction
-============
-
-When people ask this question, they usually mean "how fast will my CherryPy-based application be ?".
-
-In 99% of the cases, the answer is "this depends on your actual application code, not on CherryPy itself".
-
-The reason is that, for 99% of the real-world dynamic applications, most of the time spent to return a page will be spent in your actual application code, and the time actually spent in the CherryPy code will be negligible.
-
-For instance, a typical page that requires a few database calls to be built might take in total 200ms to be served. Out of these 200ms, about 2ms will be spent by CherryPy itself, and 198ms will be spend in your actual database calls and page rendering...
-
-So you can see that, if you want to optimize anything, you should really optimize your actual application code before you try to optimize CherryPy.
-
-Raw speed of the CherryPy HTTP server
-=====================================
-
-Despite the real-life most common scenario explained in the introduction, some people still want to know the raw speed of the CherryPy HTTP server.
-So I sat down and did some benchmarking...
-
-About the benchmark
--------------------
-
-This benchmarking only makes sense on very small documents, otherwise we're no longer measuring the raw speed of the HTTP server, but also the speed of the application ...
-
-This benchmarking was performed on a laptop in the following environment:
- * Processor: Pentium M 1.6 Ghz
- * RAM: 1GB
- * Windows XP 2
- * Load testing tool: ab from Apache2
- * CherryPy version: SVN snapshot on 2005/01/13
-
-Note that "ab" was running on the same machine as the CherryPy server, so CherryPy is probably a bit faster than what we're getting.
-
-Test 1: Dynamic content / single threaded server
-------------------------------------------------
-
-I used the following basic CherryPy app::
-
- #!python
- from cherrypy import cpg
- class Root:
- def index(self):
- return "OK"
- index.exposed = True
-
- cpg.root = Root()
-
- cpg.server.start(configMap = {'socketPort': 10000})
-
-Here are the "ab" results::
-
- $ ./ab.exe -n 1000 http://localhost:10000/
- This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
- Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
-
- Benchmarking localhost (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Finished 1000 requests
-
-
- Server Software: CherryPy/2.0.0b
- Server Hostname: localhost
- Server Port: 10000
-
- Document Path: /
- Document Length: 2 bytes
-
- Concurrency Level: 1
- Time taken for tests: 1.789044 seconds
- Complete requests: 1000
- Failed requests: 0
- Write errors: 0
- Total transferred: 127000 bytes
- HTML transferred: 2000 bytes
- Requests per second: 558.96 [#/sec] (mean)
- Time per request: 1.789 [ms] (mean)
- Time per request: 1.789 [ms] (mean, across all concurrent requests)
- Transfer rate: 69.31 [Kbytes/sec] received
-
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 0 1.9 0 15
- Processing: 0 1 4.2 0 15
- Waiting: 0 0 0.8 0 15
- Total: 0 1 4.5 0 15
-
- Percentage of the requests served within a certain time (ms)
- 50% 0
- 66% 0
- 75% 0
- 80% 0
- 90% 15
- 95% 15
- 98% 15
- 99% 15
- 100% 15 (longest request)
-
-As you can see, CherryPy averaged 558 requests/second, which is pretty good ...
-
-Test 2: Dynamic content / multi threaded server
------------------------------------------------
-
-I used the same code as test 1, but started CherryPy in thread-pool mode, with 10 threads.
-I also told "ab" to simulate 10 concurrent users ...
-Here are the "ab" results::
-
- $ ./ab.exe -c 10 -n 1000 http://localhost:10000/
- This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
- Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
-
- Benchmarking localhost (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Finished 1000 requests
-
-
- Server Software: CherryPy/2.0.0b
- Server Hostname: localhost
- Server Port: 10000
-
- Document Path: /
- Document Length: 2 bytes
-
- Concurrency Level: 10
- Time taken for tests: 2.327670 seconds
- Complete requests: 1000
- Failed requests: 0
- Write errors: 0
- Total transferred: 127000 bytes
- HTML transferred: 2000 bytes
- Requests per second: 429.61 [#/sec] (mean)
- Time per request: 23.277 [ms] (mean)
- Time per request: 2.328 [ms] (mean, across all concurrent requests)
- Transfer rate: 53.27 [Kbytes/sec] received
-
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 0 2.3 0 15
- Processing: 15 21 8.9 15 47
- Waiting: 0 16 6.2 15 47
- Total: 15 22 9.0 15 47
-
- Percentage of the requests served within a certain time (ms)
- 50% 15
- 66% 31
- 75% 31
- 80% 31
- 90% 31
- 95% 31
- 98% 47
- 99% 47
- 100% 47 (longest request)
-
-As you can see, CherryPy averaged 429 requests/second, which is a bit less than test 1 (there is a small thread-switching overhead), but is still pretty good ...
-
-Test 3: Static content / single threaded server
------------------------------------------------
-
-This time, I used CherryPy to serve a static file from disc.
-The file was a simple text containing "OK".
-Here was the config file for CherryPy::
-
- [server]
- socketPort = 10000
-
- [staticContent]
- static.html = static.html
-
-
-Here are the "ab" results::
-
- $ ./ab.exe -n 1000 http://localhost:10000/static.html
- This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
- Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
-
- Benchmarking localhost (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Finished 1000 requests
-
-
- Server Software: CherryPy/2.0.0b
- Server Hostname: localhost
- Server Port: 10000
-
- Document Path: /static.html
- Document Length: 4 bytes
-
- Concurrency Level: 1
- Time taken for tests: 1.979130 seconds
- Complete requests: 1000
- Failed requests: 0
- Write errors: 0
- Total transferred: 175000 bytes
- HTML transferred: 4000 bytes
- Requests per second: 505.27 [#/sec] (mean)
- Time per request: 1.979 [ms] (mean)
- Time per request: 1.979 [ms] (mean, across all concurrent requests)
- Transfer rate: 85.90 [Kbytes/sec] received
-
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 0 2.2 0 15
- Processing: 0 1 4.3 0 15
- Waiting: 0 0 0.5 0 15
- Total: 0 1 4.8 0 15
-
- Percentage of the requests served within a certain time (ms)
- 50% 0
- 66% 0
- 75% 0
- 80% 0
- 90% 15
- 95% 15
- 98% 15
- 99% 15
- 100% 15 (longest request)
-
-
-As you can see, CherryPy averaged 505 requests/second. Again it is a little bit less than a dynamic page, but it is still pretty good ...
diff --git a/sphinx/source/appendix/faq.rst b/sphinx/source/appendix/faq.rst
deleted file mode 100644
index 8abdb30e..00000000
--- a/sphinx/source/appendix/faq.rst
+++ /dev/null
@@ -1,207 +0,0 @@
-.. _faq:
-
-**************************
-Frequently Asked Questions
-**************************
-
-General
-=======
-
-:Q: How fast is CherryPy ?
-
-:A: Have a look at :doc:`/appendix/cherrypyspeed`.
-
-:Q: When will it be added to the standard python library?
-
-:A: Probably never. The standard python library is not the place to distribute
- an application server.
-
-:Q: Who uses CherryPy?
-
-:A: See :ref:`SuccessStories`.
-
-
-Server Features and Configuration
-=================================
-
-:Q: How do I serve multiple domains on one host?
-
-:A: You can use the :class:`cherrypy._cpdispatch.VirtualHost` dispatcher.
-
-:Q: Does CherryPy support https?
-
-:A: CherryPy has built-in SSL support as of 3.0.0beta. See the `ssl_*`
- properties of :mod:`cherrypy._cpserver`.
-
- Earlier versions do not have built-in SSL support, but Tim Evans has
- written a module called `SslCherry <http://tools.cherrypy.org/wiki/SSLWithM2Crypto>`_
- that uses M2Crypto for https support. It's not quite ready for production
- use, but it looks promising.
-
-:Q: Does CherryPy prevent cross-site scripting?
-
-:A: See `Malicious HTML Tags Embedded in Client Web Requests <http://www.cert.org/advisories/CA-2000-02.html>`_
- and `Understanding Malicious Content Mitigation for Web Developers <http://www.cert.org/tech_tips/malicious_code_mitigation.html>`_
- at `CERT <http://www.cert.org/>`_ for an overview of Cross-Site Scripting
- (XSS) issues.
-
- While it is ultimately up to the developer to remove potential XSS
- vulnerabilities from their apps and sites, there are several Cherrypy
- settings that can help. They are discussed in the
- :doc:`programmer's guide </progguide/security>`.
-
-:Q: Why does CherryPy take CPU/RAM even though it's not yet receiving requests?
-
-:A: CherryPy runs some tasks in the background by default, and some when you
- turn on certain tools. To strip CherryPy down to nothing, you might have to:
-
- * Turn off the :ref:`timeoutmonitor`
- via ``cherrypy.engine.timeout_monitor.unsubscribe()``.
- * Turn off the :class:`Autoreloader <cherrypy.process.plugins.Autoreloader>`
- via ``cherrypy.engine.autoreload.unsubscribe()``.
- * Examine the number of worker threads that WSGIServer uses.
- See :attr:`cherrypy._cpserver.Server.thread_pool`.
-
-:Q: CherryPy serves my HTML but not my CSS, Javascript, or images. Why does
- CherryPy wait to serve one resource before serving the next? Can it not
- handle more than one connection at a time?
-
-:A: CherryPy certainly can handle multiple connections. It's usually your
- browser that is the culprit. Firefox, for example, will only open two
- connections at a time to the same host (and if one of those is for the
- ``favicon.ico``, then you're down to one). Try increasing the number of
- concurrent connections your browser makes, or test your site with a tool
- that isn't a browser, like ``siege``, Apache's ``ab``, or even ``curl``.
-
-Development Questions
-=====================
-
-:Q: I can browse pages from my local machine, but not from other machines. What gives?
-
-:A: Set the config entry `server.socket_host` to either your server name/IP,
- or to '0.0.0.0' to listen on all interfaces.
- See :mod:`cherrypy._cpserver` for more details.
-
-:Q: How do I serve URL's with dots in them, like "/path/to/report.xml"?
-
-:A: Two ways: 1) Convert the dots to underscores for your page handler names,
- e.g. ``def report_xml(self)``
- (see :ref:`defaultdispatcher`) or 2) use a :ref:`default method<defaultmethods>`.
-
-:Q: How do I upload BIG files? (Or what is the best thing to do if I have many
- concurrent users uploading files?)
-
-:A: Please see :doc:`/progguide/files/uploading` for examples.
-
-:Q: Can I perform HTTP based authentication (.htaccess)?
-
-:A: There are two tools implementing :rfc:`2617`: :doc:`/refman/lib/auth_digest`
- and :doc:`/refman/lib/auth_basic`.
-
-:Q: What templating systems does CherryPy support?
-
-:A: All of them! One of the core idea of CherryPy is to be templating
- language independent. It is important to us to let developers keep
- their habits and preferred tools. Hence CherryPy does not favor any
- templating language. But for some ideas, see
- :doc:`/progguide/choosingtemplate` and the
- `Tools wiki <http://tools.cherrypy.org/wiki/>`_.
-
-:Q: My default handler throws an exception complaining about the number of
- arguments. How to handle this?
-
-:A: Suppose you have the following handler class setup: ::
-
- class Root:
- def project(self, id):
- data = db.query("project", id)
- return "Details for project %d: %r" % (id, data)
-
- and you want to provide project information based on urls of the form ::
-
- /project/123
-
- Here, 123 is a project id to search in a database. The above project()
- method will do the trick, but, when someone adds more arguments than the
- method expects, e.g. ::
-
- /project/123/456/789?x=blah
-
- those extra elements are passed on to the project() method as parameters, which
- is not able to handle the extra arguments and results in an exception being thrown.
-
- You can catch this by appending ``*args``, ``**kwargs`` to the default()
- method's parameter list. This way, the values 456 and 789 in the example
- will be placed in the 'args' list and the 'kwargs' dictionary will contain
- the string 'blah' for the key 'x'. In the following example, we just
- ignore any extra params: ::
-
- class Root:
- def project(self, id, *args, **kwargs):
- data = db.query("project", id)
- return "Details for project %d: %r" % (id, data)
-
-:Q: How do I publish objects with reserved Python names?
-
-:A: Example: ::
-
- class SomeClass(object):
- def __init__(self):
- setattr(self, 'print', self._print)
- setattr(self, 'class', self._class)
-
- def _print(self):
- ...
- _print.exposed = True
-
- def _class(self):
- ...
- _class.exposed = True
-
- Object attributes can have reserved names as long as you dynamically
- bind them so the Python parser doesn't choke on them.
-
-:Q: How does CherryPy compare to projects like mod_python, Twisted, and Django?
-
-:A: mod_python requires you to be running `Apache <http://httpd.apache.org/>`_.
- See http://www.modpython.org for more info. Since CherryPy 2.1, you can
- use mod_python as an interface to bridge CherryPy and Apache.
-
- Twisted is, well, twisted. You really have to spend the time to understand
- how the twisted framework works. It is deep and very powerful, but has a
- steep learning curve. CherryPy is, arguably, simpler to understand, due to
- its more traditional approach. Part of this comes from it not trying to do
- all the things that twisted does (SMTP, IRC, NNTP, etc etc). See
- http://twistedmatrix.com for more info.
-
- For a 3rd party discussion, refer to the
- `PyWebOff blog <http://pyre.third-bit.com/pyweb/index.html>`_ which concluded:
-
- "In no time at all, I was finished the library program. It took me
- significantly less time than it did with either of Quixote or Webware,
- and I'm very happy with the code that was produced. CherryPy needs more
- documenting, but otherwise it gets two enthusiastic thumbs up."
-
-:Q: When you run cherrypy and two dudes browse your website at the same time,
- does cherrypy create two instances of your root object? How does that work?
- I don't get it.
-
-:A: No, just one instance. It's no different than having two threads in any
- other Python application call the same method at the same time: each
- thread has its own set of local variables so they don't stomp each other.
-
-:Q: How do I get CherryPy to work if I don't have root?
-
-:A: Just append it to the path. Put the following at the top of the files
- you need CherryPy for: ::
-
- import sys
- sys.path.append("your local dir path")
-
-:Q: Can I change my root class, refresh my web page and see what is changed
- without restarting the CherryPy server?
-
-:A: See :class:`cherrypy.process.plugins.Autoreloader`. Note that this solution
- works properly only if the changes you make are syntactically correct.
- Re-compilation errors will exit the entire application.
-
diff --git a/sphinx/source/appendix/index.rst b/sphinx/source/appendix/index.rst
deleted file mode 100644
index 0f79c15b..00000000
--- a/sphinx/source/appendix/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-********
-Appendix
-********
-
-.. toctree::
- :maxdepth: 2
- :glob:
-
- *
-
-
diff --git a/sphinx/source/appendix/success.rst b/sphinx/source/appendix/success.rst
deleted file mode 100644
index b05587da..00000000
--- a/sphinx/source/appendix/success.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-.. _successstories:
-
-***************
-Success Stories
-***************
-
-You are interested in CherryPy but you would like to hear more from people
-using it, or simply check out products or application running it.
-
-If you would like to have your CherryPy powered website or product listed here,
-contact us via our `mailing list <http://groups.google.com/group/cherrypy-users>`_
-or IRC (#cherrypy on `OFTC <http://www.oftc.net/oftc/>`_).
-
-
-Websites running atop CherryPy
-==============================
-
-`Hulu Deejay and Hulu Sod <http://tech.hulu.com/blog/2013/03/13/python-and-hulu>`_ - Hulu uses
-CherryPy for some projects.
-"The service needs to be very high performance.
-Python, together with CherryPy,
-`gunicorn <http://gunicorn.org>`_, and gevent more than provides for this."
-
-`Netflix <http://techblog.netflix.com/2013/03/python-at-netflix.html>`_ - Netflix uses CherryPy as a building block in their infrastructure: "Restful APIs to
-large applications with requests, providing web interfaces with CherryPy and Bottle,
-and crunching data with scipy."
-
-`Urbanility <http://urbanility.com>`_ - French website for local neighbourhood assets in Rennes, France.
-
-`MROP Supply <https://www.mropsupply.com>`_ - Webshop for industrial equipment,
-developed using CherryPy 3.2.2 utilizing Python 3.2,
-with libs: `Jinja2-2.6 <http://jinja.pocoo.org/docs>`_, davispuh-MySQL-for-Python-3-3403794,
-pyenchant-1.6.5 (for search spelling).
-"I'm coming over from .net development and found Python and CherryPy to
-be surprisingly minimalistic. No unnecessary overhead - build everything you
-need without the extra fluff. I'm a fan!"
-
-`CherryMusic <http://www.fomori.org/cherrymusic>`_ - A music streaming server written in python:
-Stream your own music collection to all your devices! CherryMusic is open source.
-
-`YouGov Global <http://www.yougov.com>`_ - International market research firm, conducts
-millions of surveys on CherryPy yearly.
-
-`Aculab Cloud <http://cloud.aculab.com>`_ - Voice and fax applications on the cloud.
-A simple telephony API for Python, C#, C++, VB, etc...
-The website and all front-end and back-end web services are built with CherryPy,
-fronted by nginx (just handling the ssh and reverse-proxy), and running on AWS in two regions.
-
-`Learnit Training <http://www.learnit.nl>`_ - Dutch website for an IT, Management and
-Communication training company. Built on CherryPy 3.2.0 and Python 2.7.3, with
-`oursql <http://pythonhosted.org/oursql>`_ and
-`DBUtils <http://www.webwareforpython.org/DBUtils>`_ libraries, amongst others.
-
-`Linstic <http://linstic.com>`_ - Sticky Notes in your browser (with linking).
-
-`Almad's Homepage <http://www.almad.net>`_ - Simple homepage with blog.
-
-
-Products based on CherryPy
-==========================
-
-`SABnzbd <http://sabnzbd.org>`_ - Open Source Binary Newsreader written in Python.
-
-`Headphones <https://github.com/rembo10/headphones>`_ - Third-party add-on for SABnzbd.
-
-`SickBeard <http://sickbeard.com>`_ - "Sick Beard is a PVR for newsgroup users (with limited torrent support). It watches for new episodes of your favorite shows and when they are posted it downloads them, sorts and renames them, and optionally generates metadata for them."
-
-`TurboGears <http://www.turbogears.org>`_ - The rapid web development megaframework. Turbogears 1.x used Cherrypy. "CherryPy is the underlying application server for TurboGears. It is responsible for taking the requests from the user’s browser, parses them and turns them into calls into the Python code of the web application. Its role is similar to application servers used in other programming languages".
-
-`Indigo <http://www.perceptiveautomation.com/indigo/index.html>`_ - "An intelligent home control
-server that integrates home control hardware modules to provide control of your home. Indigo's built-in
-Web server and client/server architecture give you control and access to your home remotely from
-other Macs, PCs, internet tablets, PDAs, and mobile phones."
-
-`SlikiWiki <http://www.sf.net/projects/slikiwiki>`_ - Wiki built on CherryPy and featuring WikiWords, automatic backlinking, site map generation, full text search, locking for concurrent edits, RSS feed embedding, per page access control lists, and page formatting using PyTextile markup."
-
-`read4me <http://sourceforge.net/projects/read4me>`_ - read4me is a Python feed-reading web service.
-
-`Firebird QA tools <http://www.firebirdsql.org/en/quality-assurance>`_ - Firebird QA tools are based on CherryPy.
-
-`salt-api <https://github.com/saltstack/salt-api>`_ - A REST API for Salt, the infrastructure orchestration tool.
-
-Products inspired by CherryPy
-=============================
-
-`OOWeb <http://ooweb.sourceforge.net/>`_ - "OOWeb is a lightweight, embedded HTTP server for Java applications that maps objects to URL directories, methods to pages and form/querystring arguments as method parameters. OOWeb was originally inspired by CherryPy."
-
diff --git a/sphinx/source/deployguide/apache.rst b/sphinx/source/deployguide/apache.rst
deleted file mode 100644
index 9d9aa783..00000000
--- a/sphinx/source/deployguide/apache.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-********************************
-Deploying CherryPy behind Apache
-********************************
-
diff --git a/sphinx/source/deployguide/cherryd.rst b/sphinx/source/deployguide/cherryd.rst
deleted file mode 100644
index bca18396..00000000
--- a/sphinx/source/deployguide/cherryd.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-*******
-cherryd
-*******
-
-The ``cherryd`` script is used to start CherryPy servers, whether the builtin
-WSGI server, :ref:`FastCGI <fastcgi>`, or SCGI. Sites using mod_python don't
-need to use ``cherryd``; Apache will spawn the CherryPy process in that case.
-
-Command-Line Options
-====================
-
-.. program:: cherryd
-
-.. cmdoption:: -c, --config
-
- Specify config file(s)
-
-.. cmdoption:: -d
-
- Run the server as a daemon
-
-.. cmdoption:: -e, --environment
-
- Apply the given config environment (defaults to None)
-
-
-.. index:: FastCGI
-
-.. cmdoption:: -f
-
- Start a :ref:`FastCGI <fastcgi>` server instead of the default HTTP server
-
-
-.. index:: SCGI
-
-.. cmdoption:: -s
-
- Start a SCGI server instead of the default HTTP server
-
-
-.. cmdoption:: -i, --import
-
- Specify modules to import
-
-
-.. index:: PID file
-
-.. cmdoption:: -p, --pidfile
-
- Store the process id in the given file (defaults to None)
-
-
-.. cmdoption:: -P, --Path
-
- Add the given paths to sys.path
-
diff --git a/sphinx/source/deployguide/index.rst b/sphinx/source/deployguide/index.rst
deleted file mode 100644
index c8eff7bb..00000000
--- a/sphinx/source/deployguide/index.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-****************
-Deployment Guide
-****************
-
-CherryPy the application framework is quite flexible and can be deployed in a
-wide variety of ways. CherryPy the server is a production-ready, performant
-server that can be used to deploy any WSGI or CGI application.
-
-Applications
-============
-
-An easy way to deploy a CherryPy application is using the standard
-``quickstart()`` server. The ``cherryd`` script mentioned below wraps this same
-server. It is a production-ready, performant server that can be quickly
-configured for development or production use (or even somewhere in-between) by
-setting an :ref:`environment <environments>` in the application config.
-
-A CherryPy application can also be deployed using any WSGI-capable server. The
-return from :py:class:`cherrypy.tree.mount <cherrypy._cptree.Tree>` is a
-standard WSGI application.
-
-Servers
-=======
-
-CherryPy ships with a fast, production-ready server that can be used to serve
-applications independently of the CherryPy application framework.
-
-.. toctree::
- :maxdepth: 2
-
- apache
- /refman/process/servers
- /refman/wsgiserver/init
-
-
-Environment
-===========
-
-.. toctree::
- :maxdepth: 2
-
- cherryd
- /refman/process/plugins/daemonizer
- /refman/process/plugins/dropprivileges
- /refman/process/plugins/pidfile
- /refman/process/plugins/signalhandler
-
-
diff --git a/sphinx/source/index.rst b/sphinx/source/index.rst
deleted file mode 100644
index 1467aa98..00000000
--- a/sphinx/source/index.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-.. toctree::
- :hidden:
-
- intro/index
- tutorial/index
- progguide/index
- deployguide/index
- refman/index
- appendix/index
-
-
-********************************
-CherryPy |release| Documentation
-********************************
-
-Sections
-========
-
-:doc:`Introduction <intro/index>`
----------------------------------
-*The who, what, and why of CherryPy*
-
-:doc:`Tutorial <tutorial/index>`
----------------------------------
-*The basics of CherryPy*
-
-:doc:`Programmer's Guide <progguide/index>`
--------------------------------------------
-*How to perform common tasks*
-
-:doc:`Deployment Guide <deployguide/index>`
--------------------------------------------
-*Getting your application running and on the web*
-
-:doc:`Reference Manual <refman/index>`
---------------------------------------
-*Module, class, and function reference*
-
-:doc:`Appendix <appendix/index>`
---------------------------------
-:doc:`FAQ </appendix/faq>`, :doc:`Performance benchmarking </appendix/cherrypyspeed>`, and :doc:`Success Stories </appendix/success>`
-
-Other
------
-
-:ref:`genindex`
-
-:ref:`modindex`
-
-:ref:`search`
-
diff --git a/sphinx/source/intro/index.rst b/sphinx/source/intro/index.rst
deleted file mode 100644
index cf5a5951..00000000
--- a/sphinx/source/intro/index.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-************
-Introduction
-************
-
-What is CherryPy?
-=================
-
-CherryPy is a pythonic, object-oriented HTTP framework.
-
-CherryPy allows developers to build web applications in much the same way they
-would build any other object-oriented Python program. This results in smaller
-source code developed in less time.
-
-CherryPy does its best to stay out of the way between the programmer and the
-problem. It works out of the box; default behavior is sensible enough to allow
-use without extensive setup or customization. However, its :doc:`configuration </tutorial/config>`
-and :doc:`plugin </progguide/extending/customplugins>` systems are more than enough to easily build and deploy complex
-sites.
-
-You are free to use any kind of templating, data access etc. technology
-you want. CherryPy also has built-in tools for
-:doc:`sessions </refman/lib/sessions>`,
-:doc:`static files </progguide/files/static>`,
-:doc:`cookies </progguide/cookies>`,
-:doc:`file uploads </progguide/files/uploading>`,
-:doc:`caching </refman/lib/caching>`, :doc:`encoding </refman/lib/encoding>`,
-:doc:`authorization </refman/lib/auth_basic>`,
-:doc:`compression </refman/lib/encoding>`,
-and :doc:`many more </tutorial/tools>`.
-
-The production-ready, HTTP/1.1-compliant web server allows you to deploy
-web applications anywhere Python is installed. It also supports any other
-WSGI-enabled webserver or adapter, including Apache, IIS, lighttpd, mod_python,
-FastCGI, SCGI, and mod_wsgi, even multiple ones. And it's *fast*; typically,
-CherryPy itself takes only 1-2ms per request! It is being used in production
-by many sites, from the simplest to the most demanding.
-
-CherryPy applications run on Windows, Linux, Mac OS X and any other platform
-supporting Python 2.3 or higher.
-
-CherryPy is now more than eight years old and it is has proven very fast and
-stable. It is well tested, and includes tools for testing, profiling, and
-coverage of your own applications.
-
-Oh, and most importantly: CherryPy is fun to work with :-)
-Here's how easy it is to write "Hello World" in CherryPy::
-
- import cherrypy
-
- class HelloWorld(object):
- def index(self):
- return "Hello World!"
- index.exposed = True
-
- cherrypy.quickstart(HelloWorld())
-
-
-What CherryPy is NOT?
-=====================
-
-As an HTTP framework, CherryPy does all that is necessary to allow Python code
-to be executed when some resource (URL) is requested by the user. However:
-
- * CherryPy is not a templating language, such as PHP. CherryPy can work with
- several Python templating packages (see :doc:`/progguide/choosingtemplate`),
- but does not ship one by default.
- * CherryPy does not fill out HTML forms for you. You're free to use formencode
- or any other solution, or none at all if you're not using HTML ;)
- * CherryPy is not a database or ORM. Rather than dictate or bless a persistence
- layer to you, CherryPy allows you to choose your own.
-
-
-Contents
-========
-
-.. toctree::
- :maxdepth: 3
- :glob:
-
- whycherrypy
- install
- license
-
-
diff --git a/sphinx/source/intro/install.rst b/sphinx/source/intro/install.rst
deleted file mode 100644
index 51f59cbb..00000000
--- a/sphinx/source/intro/install.rst
+++ /dev/null
@@ -1,109 +0,0 @@
-************
-Installation
-************
-
-:ref:`prerequisites`
-
-:ref:`stableversions`
-
-:ref:`developmentversions`
-
-.. _prerequisites:
-
-Prerequisites
-=============
-
-All you need is a working version of Python-2.3 or later on your computer.
-If you are running Max OS X or some Linux distribution (e.g. Ubuntu, Debian, Fedora)
-you most likely already have python on you system, for a detailed instruction
-on how to install python follow the instruction on the
-`python wiki <http://wiki.python.org/moin/BeginnersGuide/Download>`_.
-
-.. _stableversions:
-
-Download Stable Versions
-========================
-
-Using `pip` or `easy_install`
------------------------------
-
-Using pip::
-
- $ pip install CherryPy
-
-or with easy_install::
-
- $ easy_install CherryPy
-
-It is recommended to use `pip` instead of `easy_install`.
-If you want to download and install CherryPy for yourself proceed to the
-next instructions depending on your platform.
-
-Unix/Mac
---------
-
-You may download the most current version from `PyPI <https://pypi.python.org/pypi/CherryPy/3.2.3>`_
-
-For other releases, browse our
-`download index <http://download.cherrypy.org/cherrypy>`_.
-
-* Unzip/untar the files
-* Enter the directory created by the file extraction.
-* Type "python setup.py install" to install the CherryPy module
-
-
-Windows
--------
-
-You may download the most current version from `PyPI <https://pypi.python.org/pypi/CherryPy/3.2.3>`_.
-
-For other releases, browse our `download index <http://download.cherrypy.org/cherrypy>`_.
-
-* Select the file ending in ".exe".
-* Run the downloaded file.
-
-
-Next Steps
-==========
-
-To run your first sample website:
-
- 1. In a command terminal or console go to cherrypy/tutorial/
- 2. Type::
-
- $ python tut01_helloworld.py
-
- and you'll have a running website on port 8080.
-
- 3. Open your favorite browser and point it to http://localhost:8080 to see your first CherryPy-served page :-)
-
-Now, you should try running some of the other tutorials found in the tutorial
-directory and look at their source code to understand how to develop a website
-with CherryPy.
-
-.. _developmentversions:
-
-Development versions
-====================
-
-CherryPy's source code is managed using `Mercurial <http://mercurial.selenic.com/>`_,
-a source code control system written in python.
-
-You can access our Mercurial repository using your favorite
-Mercurial client at `bitbucket <https://bitbucket.org/cherrypy/cherrypy>`_.
-
-For Windows users, we recommend the wonderful Mercurial
-client `TortoiseHg <http://tortoisehg.org/>`_. Users of
-other operating systems are advised to use multi-platform
-command line tools provided by the
-`core Mercurial distribution <http://mercurial.selenic.com/downloads/>`_.
-
-* To submit a patch: fork the repository and submit your pull request.
- For further information please contact us via email or IRC
- (see `getting involved <http://bitbucket.org/cherrypy/cherrypy/wiki/CherryPyInvolved>`_).
-
-Standalone WSGI server
-----------------------
-
-The WSGI server that comes bundled with CherryPy is available as a standalone
-module. Feel free to use it for all of your WSGI serving needs.
diff --git a/sphinx/source/intro/license.rst b/sphinx/source/intro/license.rst
deleted file mode 100644
index e4805eb6..00000000
--- a/sphinx/source/intro/license.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-**********************
-CherryPy License (BSD)
-**********************
-
-::
-
- Copyright (c) 2002-2010, CherryPy Team (team@cherrypy.org)
- All rights reserved.
-
- 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 the CherryPy Team nor the names of its 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
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
diff --git a/sphinx/source/intro/whycherrypy.rst b/sphinx/source/intro/whycherrypy.rst
deleted file mode 100644
index ffbcd0c7..00000000
--- a/sphinx/source/intro/whycherrypy.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-********************
-Why choose CherryPy?
-********************
-
-Let's face it: there are **dozens** of different Python web frameworks. Why would
-you want to choose CherryPy for your next dynamic Web project?
-
-1. Simplicity
--------------
-
-Developing with CherryPy is a simple task. "Hello, world" is only a few lines
-long, and does not require the developer to learn the entire (albeit very
-manageable) framework all at once. The framework is very pythonic; that is,
-it follows Python's conventions very nicely (code is sparse and clean).
-
-Contrast this with J2EE and Python's most popular and visible web frameworks:
-Django, Zope, Pylons, and Turbogears. In all of them, the learning curve is
-massive. In these frameworks, "Hello, world" requires the programmer to set
-up a large scaffold which spans multiple files and to type a lot of boilerplate
-code. CherryPy succeeds because it does not include the bloat of other
-frameworks, allowing the programmer to write their web application quickly
-while still maintaining a high level of organization and scalability.
-
-CherryPy is also very modular. The core is fast and clean, and extension
-features are easy to write and plug in using code or the elegant config
-system. The primary components (server, engine, request, response, etc.)
-are all extendable (even replaceable) and well-managed.
-
-In short, CherryPy empowers the developer to work with the framework,
-not against or around it.
-
-2. Power
---------
-
-CherryPy leverages all of the power of Python. Python is a dynamic language
-which allows for rapid development of applications. Python also has an
-extensive built-in API which simplifies web app development. Even more
-extensive, however, are the third-party libraries available for Python. These
-range from object-relational mappers to form libraries, to an automatic Python
-optimizer, a Windows exe generator, imaging libraries, email support, HTML
-templating engines, etc. CherryPy applications are just like regular Python
-applications. CherryPy does not stand in your way if you want to use these
-brilliant tools.
-
-CherryPy also provides :doc:`Tools </tutorial/tools>` and
-:doc:`Plugins </progguide/extending/customplugins>`, which are powerful
-extension points needed to develop world-class web applications.
-
-3. Maturity
------------
-
-Maturity is extremely important when developing a real-world application.
-Unlike many other web frameworks, CherryPy has had many final, stable releases.
-It is fully bugtested, optimized, and proven reliable for real-world use.
-The API will not suddenly change and break backwards compatibility, so your
-applications are assured to continue working even through subsequent updates
-in the current version series.
-
-CherryPy is also a "3.0" project: the first edition of CherryPy set the tone,
-the second edition made it work, and the third edition makes it beautiful.
-Each version built on lessons learned from the previous, bringing the developer
-a superior tool for the job.
-
-4. Community
-------------
-
-CherryPy has an active community that develops deployed CherryPy applications
-and are willing and ready to assist you on the
-`CherryPy mailing list <http://groups.google.com/group/cherrypy-users>`_ or
-IRC (#cherrypy on `OFTC <http://www.oftc.net/oftc/>`_).
-The developers also frequent the list and often answer questions and implement
-features requested by the end-users.
-
-5. Deployability
-----------------
-
-Unlike many other Python web frameworks, there are cost-effective ways to
-deploy your CherryPy application.
-
-Out of the box, CherryPy includes its own production-ready HTTP server
-to host your application. If the application needs to be deployed on Apache,
-there is copious documentation discussing how to connect the two. CherryPy can
-also be deployed on any WSGI-compliant gateway (a technology for interfacing
-numerous types of web servers): mod_wsgi, FastCGI, SCGI, IIS, etc.
-
-In addition, CherryPy is pure-python and is compatible with Python 2.3. This
-means that CherryPy will run on all major platforms that Python will run on
-(Windows, MacOSX, Linux, BSD, etc).
-
-`Webfaction.com <http://www.webfaction.com>`_, run by the inventor of CherryPy,
-is a commercial web host that offers CherryPy hosting packages (in addition to
-several others).
-
-6. It's free!
--------------
-
-:doc:`All of CherryPy is licensed </intro/license>` under the open-source BSD license, which means
-**CherryPy can be used commercially for ZERO cost**.
-
-7. Where to go from here?
--------------------------
-
-Check out the :doc:`/tutorial/index` and :doc:`/progguide/index` for
-more complete documentation.
-
diff --git a/sphinx/source/progguide/REST.rst b/sphinx/source/progguide/REST.rst
deleted file mode 100644
index 13cecadf..00000000
--- a/sphinx/source/progguide/REST.rst
+++ /dev/null
@@ -1,255 +0,0 @@
-*****************************************
-Creating RESTful applications in CherryPy
-*****************************************
-
-Introduction
-============
-
-REST (Representational State Transfer) is an architectural style that
-is well-suited to implementation in CherryPy. Both REST and CherryPy
-heavily leverage the HTTP protocol but otherwise carry minimal
-requisite overhead. This chapter briefly discusses the purpose of
-REST and an example implementation in CherryPy.
-
-REST in a nutshell
-==================
-
-The REST architectural style describes how domain models and their state
-are referenced and transferred. The primary goal of REST is promote
-certain advantageous qualities of a distributed system, namely high
-visibility, scalability, extensibility.
-
-Terminology
------------
-
- - "resources" are concepual objects represented by the system - any
- information that can be named is a resource.
- - "state" is data held by or associated with resources
- - "representations" are information in state with a specific encoding
- - "methods" are invoked to transfer or mutate state.
- - an "identifier" is a URL or URI which uniquely and usually globally
- references a resource.
-
-More information on REST can be found in abundance in Wikipedia and
-other readily-available resources.
-
-Implementing REST in CherryPy
-=============================
-
-From the canonical `Roy Fielding dissertation <http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5>`_ :
-
- REST is defined by four interface constraints: identification of resources;
- manipulation of resources through representations; self-descriptive messages;
- and, hypermedia as the engine of application state
-
-This section covers each of these four contstraints and demonstrates how each
-is applied in a CherryPy implementation.
-
-Identification of Resources
----------------------------
-
-As an HTTP service provider, resources represented in CherryPy are
-referenced by HTTP URIs (Uniform Resource Identifiers). A URI consists
-of four parts: scheme, hierarchical identifier, query, and fragment.
-For HTTP, the scheme is always ``http`` or ``https``. The hierarchical
-identifier consists of an authority (typically host/port) and a path
-(similar to a file system path, but not necessarily representing an
-actual path).
-
-A single CherryPy instance is typically bound to a single
-server/port pair, such that the scheme://authority portion of the URI
-references the server. This aspect is configured through the
-``server.socket_host`` and ``server.socket_port`` options or via another
-hosting server.
-
-Within the CherryPy server, the remainder of the hierarchical
-identifier--the path--is mapped to Python objects
-via the Dispatch mechanism. This behavior is highly
-customizable and documented in :doc:`/tutorial/dispatching`.
-
-Using the default dispatcher and page handlers, the path of the URI
-maps to a hierarchy of Python identifiers in the CherryPy app. For
-example, the URI path ``/container/objects/pencil`` will result in a
-call to ``app.root.container.objects.pencil()`` where ``app`` is the
-CherryPy app.
-
-Manipulation of Resources Through Representations
--------------------------------------------------
-
-REST defines the use of the HTTP protocol and HTTP methods to implement
-the standard REST methods.
-
- - GET retrieves the state of a specific resource,
- - PUT creates or replaces the state of a specific resource,
- - POST passes information to a resource to use at it sees fit,
- - DELETE removes resources.
-
-The default dispatcher in CherryPy stores the HTTP method name at
-:attr:`cherrypy.request.method<cherrypy._cprequest.Request.method>`.
-
-Because HTTP defines these invocation methods, the most direct
-way to implement REST using CherryPy is to utilize the
-:class:`MethodDispatcher<cherrypy._cpdispatch.MethodDispatcher>`
-instead of the default dispatcher. To enable
-the method dispatcher, add the
-following to your configuration for the root URI ("/")::
-
- '/': {
- 'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
- }
-
-Now, the REST methods will map directly to the same method names on
-your resources. That is, a GET method on a CherryPy class implements
-the HTTP GET on the resource represented by that class.
-
-For example::
-
- class Resource(object):
-
- exposed = True
-
- def GET(self):
- return """Some representation of self"""
-
- def PUT(self):
- self.content = initialize_from_representation(cherrypy.request.body.read())
-
-The concrete implementation of GET and PUT has been omitted, but the
-basic concepts remain: GET returns some meaningful representation of
-the resource and PUT stores an instance of an object represented by the
-content in the body of the request.
-
-Self-Descriptive Messages
--------------------------
-
-REST enables powerful clients and intermediaries by requiring messages to be
-self-descriptive; that is, everything you need to know about a message is
-carried within the message itself, either in headers or within the definition
-of the message's declared media type.
-
-CherryPy gives you easy access to the headers. It's as simple as
-:attr:`cherrypy.request.headers<cherrypy._cprequest.Request.headers>` and
-:attr:`cherrypy.response.headers<cherrypy._cprequest.Response.headers>`!
-Each is a normal Python dictionary which you can read and write as you like.
-They also have additional functions to help you parse complex values according
-to the HTTP spec.
-
-CherryPy also allows you to set whatever response Content-Type you prefer,
-just like any other response header. You have complete control. When reading
-request entities, you can register :ref:`custombodyprocessors` for different
-media types.
-
-Hypermedia as the Engine of Application State
----------------------------------------------
-
-REST is designed as a stateless protocol--all application state is
-maintained with the application at the client. Thus, concepts such as a
-"session" need not be maintained by the server. CherryPy does not enable
-sessions by default, so it is well-suited to the RESTful style.
-
-In order for the client to maintain meaningful state, however, the REST
-server implementer must provide meaningful URIs which supply semantic
-links between resources.
-
-For example, a CherryPy application might have a resource index, which
-a client might retrieve to inspect the application for other resources::
-
- class ResourceIndex(object):
- def GET(self):
- items = [item.get_href() for item in self.get_all_items()]
- return ', '.join(items)
-
-This very simple example demonstrates how to create an index of
-comma-separated hypertext references. This example assumes the client
-can effectively interpret comma-separated references. In practice,
-another representation such as HTML or JSON might be used.
-
-A Quick Example
-===============
-
-For example, consider the following contrived REST+HTML specification.
-
-1. Resources store arbitrary key/value pairs with unique keys
- (represented as a Python dict).
-
-2. A GET request returns colon-separated key/value pairs in ``<div>``
- elements.
-
-3. A PUT request accepts colon-separated key/value pairs in ``<div>``
- elements.
-
-4. An index resource provides an HTML anchor tag (hypertext link) to objects
- which it indexes (where the keys represent the names and the values
- represent the link).
-
-A REST+HTML implementation was chosen for this example as HTML defines
-relative links, which keeps the example simple yet functional.
-
-Complete Example
-----------------
-
-Brining the above code samples together and adding some basic
-configuration results in the following program, which can be run
-directly::
-
- import cherrypy
-
- class Resource(object):
-
- def __init__(self, content):
- self.content = content
-
- exposed = True
-
- def GET(self):
- return self.to_html()
-
- def PUT(self):
- self.content = self.from_html(cherrypy.request.body.read())
-
- def to_html(self):
- html_item = lambda (name,value): '<div>{name}:{value}</div>'.format(\*\*vars())
- items = map(html_item, self.content.items())
- items = ''.join(items)
- return '<html>{items}</html>'.format(**vars())
-
- @staticmethod
- def from_html(data):
- pattern = re.compile(r'\<div\>(?P<name>.*?)\:(?P<value>.*?)\</div\>')
- items = [match.groups() for match in pattern.finditer(data)]
- return dict(items)
-
- class ResourceIndex(Resource):
- def to_html(self):
- html_item = lambda (name,value): '<div><a href="{value}">{name}</a></div>'.format(\*\*vars())
- items = map(html_item, self.content.items())
- items = ''.join(items)
- return '<html>{items}</html>'.format(**vars())
-
- class Root(object):
- pass
-
- root = Root()
-
- root.sidewinder = Resource({'color': 'red', 'weight': 176, 'type': 'stable'})
- root.teebird = Resource({'color': 'green', 'weight': 173, 'type': 'overstable'})
- root.blowfly = Resource({'color': 'purple', 'weight': 169, 'type': 'putter'})
- root.resource_index = ResourceIndex({'sidewinder': 'sidewinder', 'teebird': 'teebird', 'blowfly': 'blowfly'})
-
- conf = {
- 'global': {
- 'server.socket_host': '0.0.0.0',
- 'server.socket_port': 8000,
- },
- '/': {
- 'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
- }
- }
-
- cherrypy.quickstart(root, '/', conf)
-
-Conclusion
-==========
-
-CherryPy provides a straightforward interface for readily creating
-RESTful interfaces.
diff --git a/sphinx/source/progguide/choosingtemplate.rst b/sphinx/source/progguide/choosingtemplate.rst
deleted file mode 100644
index 8cfd5631..00000000
--- a/sphinx/source/progguide/choosingtemplate.rst
+++ /dev/null
@@ -1,151 +0,0 @@
-******************************
-Choosing a templating language
-******************************
-
-CherryPy is an open-ended HTTP framework that integrates with a wide variety of
-templating systems. So the first point we want to make is that you should do
-your own study *with your own data* to find out which one is best for you.
-
-That said, we recommend you start with one of these three:
-
-`Mako <http://www.makotemplates.org/>`_
-=======================================
-
-Mako is a template library written in Python. It provides a familiar, non-XML
-syntax which compiles into Python modules for maximum performance. Mako's syntax
-and API borrows from the best ideas of many others, including Django templates,
-Cheetah, Myghty, and Genshi. Conceptually, Mako is an embedded Python (i.e.
-Python Server Page) language, which refines the familiar ideas of componentized
-layout and inheritance to produce one of the most straightforward and flexible
-models available, while also maintaining close ties to Python calling and
-scoping semantics.
-
-Mako snippet::
-
- <table>
- % for row in rows:
- ${makerow(row)}
- % endfor
- </table>
-
-
-CherryPy integration example::
-
- import cherrypy
- from mako.template import Template
- from mako.lookup import TemplateLookup
- lookup = TemplateLookup(directories=['html'])
-
- class Root:
- @cherrypy.expose
- def index(self):
- tmpl = lookup.get_template("index.html")
- return tmpl.render(salutation="Hello", target="World")
-
-
-`Jinja2 <http://jinja.pocoo.org/2/>`_
-=====================================
-
-Jinja2 is a library for Python 2.4 and onwards that is designed to be flexible,
-fast and secure. If you have any exposure to other text-based template languages,
-such as Smarty or Django, you should feel right at home with Jinja2. It’s both
-designer and developer friendly by sticking to Python’s principles and adding
-functionality useful for templating environments.
-
-The key-features are...
-
- * ... configurable syntax. If you are generating LaTeX or other formats with
- Jinja2 you can change the delimiters to something that integrates better
- into the LaTeX markup.
- * ... fast. While performance is not the primarily target of Jinja2 it’s
- surprisingly fast. The overhead compared to regular Python code was reduced
- to the very minimum.
- * ... easy to debug. Jinja2 integrates directly into the python traceback
- system which allows you to debug Jinja2 templates with regular python
- debugging helpers.
- * ... secure. It’s possible to evaluate untrusted template code if the optional
- sandbox is enabled. This allows Jinja2 to be used as templating language for
- applications where users may modify the template design.
-
-Jinja2 snippet::
-
- <ul id="navigation">
- {% for item in navigation %}
- <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
- {% endfor %}
- </ul>
-
-
-CherryPy integration example::
-
- import cherrypy
- from jinja2 import Environment, FileSystemLoader
- env = Environment(loader=FileSystemLoader('templates'))
-
- class Root:
- @cherrypy.expose
- def index(self):
- tmpl = env.get_template('index.html')
- return tmpl.render(salutation='Hello', target='World')
-
-
-`Genshi <http://genshi.edgewall.org>`_
-=======================================
-
-Genshi is a Python library that provides an integrated set of components for
-parsing, generating, and processing HTML, XML or other textual content for
-output generation on the web.
-
-The main feature is a template language that is smart about markup: unlike
-conventional template languages that only deal with bytes and (if you're lucky)
-characters, Genshi knows the difference between tags, attributes, and actual
-text nodes, and uses that knowledge to your advantage.
-
-Plain XHTML templates make Genshi easy to use even for web designers who don't
-know Python. Do you know XHTML? Then you're 75% of the way there! It's
-considered by many to be the successor to Kid.
-
-See the `Genshi tutorial <http://tools.cherrypy.org/wiki/Genshi>`_.
-
-Because it parses HTML/XML, it can be slower than other solutions.
-See `Genshi performance <http://genshi.edgewall.org/wiki/GenshiPerformance>`_
-for more information.
-
-Genshi snippet::
-
- <ol py:if="links">
- <li py:for="link in links">
- <a href="${link.url}">${link.title}</a>
- posted by ${link.username} at ${link.time.strftime('%x %X')}
- </li>
- </ol>
-
-CherryPy integration example::
-
- import cherrypy
- from genshi.template import TemplateLoader
- loader = TemplateLoader('/path/to/templates', auto_reload=True)
-
- class Root:
- @cherrypy.expose
- def index(self):
- tmpl = loader.load('index.html')
- page = tmpl.generate(salutation='Hello', target='World')
- return page.render('html', doctype='html')
-
-
-Others
-======
-
- * Cheetah
- * ClearSilver
- * Kid
- * HTMLTemplate
- * Nevow
- * PSP
- * PyMeld
- * py.xml
- * XSLT
- * Xyaptu
- * ZPT
-
diff --git a/sphinx/source/progguide/cookies.rst b/sphinx/source/progguide/cookies.rst
deleted file mode 100644
index afb218ab..00000000
--- a/sphinx/source/progguide/cookies.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-********************************
-How to use cookies with CherryPy
-********************************
-
-CherryPy uses the :mod:`Cookie` module from python and in particular the
-:class:`Cookie.SimpleCookie` object type to handle cookies.
-
-* To send a cookie to a browser, set ``cherrypy.response.cookie[key] = value``.
-* To retrieve a cookie sent by a browser, use ``cherrypy.request.cookie[key]``.
-* To delete a cookie (on the client side), you must *send* the cookie with its
- expiration time set to 0::
-
-
- cherrypy.response.cookie[key] = value
- cherrypy.response.cookie[key]['expires'] = 0
-
-
-It's important to understand that the request cookies are **not** automatically
-copied to the response cookies. Clients will send the same cookies on every
-request, and therefore ``cherrypy.request.cookie`` should be populated each
-time. But the server doesn't need to send the same cookies with every response;
-therefore, **``cherrypy.response.cookie`` will usually be empty**. When you wish
-to "delete" (expire) a cookie, therefore, you must set
-``cherrypy.response.cookie[key] = value`` first, and then set its ``expires``
-attribute to 0.
-
-Extended example::
-
- import cherrypy
-
- class Root:
- def setCookie(self):
- cookie = cherrypy.response.cookie
- cookie['cookieName'] = 'cookieValue'
- cookie['cookieName']['path'] = '/'
- cookie['cookieName']['max-age'] = 3600
- cookie['cookieName']['version'] = 1
- return "<html><body>Hello, I just sent you a cookie</body></html>"
- setCookie.exposed = True
-
- def readCookie(self):
- cookie = cherrypy.request.cookie
- res = """<html><body>Hi, you sent me %s cookies.<br />
- Here is a list of cookie names/values:<br />""" % len(cookie)
- for name in cookie.keys():
- res += "name: %s, value: %s<br>" % (name, cookie[name].value)
- return res + "</body></html>"
- readCookie.exposed = True
-
- cherrypy.quickstart(Root())
-
diff --git a/sphinx/source/progguide/customheaders.rst b/sphinx/source/progguide/customheaders.rst
deleted file mode 100644
index 1b1a6f80..00000000
--- a/sphinx/source/progguide/customheaders.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-
-Custom Response Headers
-***********************
-
-Although the ``cherrypy.response.headers`` is usually adequate for
-supplying headers in a CherryPy response, it is sometimes desirable for
-the server application to customize the order of the resultant headers
-or provide multiple headers with duplicate keys. This section describes
-how one can accomplish these tasks within the CherryPy framework.
-
-Process
-=======
-
-The CherryPy Response object maintains a dictionary of headers until the
-finalize phase, after which the headers in the dictionary are converted
-into a list of (name, value) tuples. See
-``cherrypy._cprequest.Response`` for details.
-
-Therefore, since a dictionary discards order and duplicate keys,
-customizing the order or duplicity of keys must occur after the finalize
-phase.
-
-This end can be effected using a tool bound to the ``on_end_resource``
-hook.
-
-Multiple Headers
-================
-
-The following example illustrates the creation of a multiheaders tool to
-deliver multiple headers with the same key in the response.
-
-::
-
- #python
- import cherrypy
-
- def multi_headers():
- cherrypy.response.header_list.extend(
- cherrypy.response.headers.encode_header_items(
- cherrypy.response.multiheaders))
-
- cherrypy.tools.multiheaders = cherrypy.Tool('on_end_resource', multi_headers)
-
- class Root(object):
- @cherrypy.expose
- @cherrypy.tools.multiheaders()
- def index(self):
- cherrypy.response.multiheaders = [('foo', '1'), ('foo', '2')]
- return "Hello"
-
- cherrypy.quickstart(Root())
-
-Header Order
-============
-
-The following example illustrates the creation of a firstheaders tool to
-deliver headers in a specified order (at the beginning) in the response.
-
-::
-
- #python
- import cherrypy
-
- def first_headers():
- cherrypy.response.header_list[:0] = cherrypy.response.first_headers
-
- cherrypy.tools.firstheaders = cherrypy.Tool('on_end_resource', first_headers)
-
- class Root(object):
- @cherrypy.expose
- @cherrypy.tools.firstheaders()
- def index(self):
- cherrypy.response.first_headers = [('foo', '1'), ('foo', '2')]
- return "Hello"
-
- cherrypy.quickstart(Root())
diff --git a/sphinx/source/progguide/extending/customplugins.rst b/sphinx/source/progguide/extending/customplugins.rst
deleted file mode 100644
index 609f5c59..00000000
--- a/sphinx/source/progguide/extending/customplugins.rst
+++ /dev/null
@@ -1,131 +0,0 @@
-**************
-Custom Plugins
-**************
-
-CherryPy allows you to extend startup, shutdown, and other behavior outside the
-request process via *Listeners* and *Plugins*. The
-:class:`cherrypy.engine<cherrypy.process.wspbus.Bus>` object controls
-these behaviors; to extend them, you subscribe listeners to the engine.
-These allow you to run functions at a particular point in the
-*site* process; for the *request* process, see :doc:`customtools` instead.
-
-Listeners
-=========
-
-The engine is a publish-subscribe service; event handlers publish to various
-*channels*, like "start", "stop", "exit", "graceful", or "log", and both
-CherryPy and you can subscribe *listeners* for those messages::
-
- engine.subscribe(channel, callback[, priority])
-
-channel
--------
-
-The channel is an event name:
-
- * start: the Engine is starting for the first time, or has been stopped and is
- now restarting; listeners here should start up sockets, files, or other
- services and not return until they are ready to be used by clients or
- other parts of the site.
- * stop: the Engine is stopping; plugins should cleanly stop what they are
- doing and not return until they have finished cleaning up. This is called
- by :func:`cherrypy.engine.stop<cherrypy.process.wspbus.Bus.stop>`, and
- plugins should make every effort to stop and clean up in a fashion that
- permits them to be restarted via a "start" listener.
- * graceful: advises all listeners to reload, e.g. by closing any open files
- and reopening them.
- * exit: this is called by
- :func:`cherrypy.engine.exit<cherrypy.process.wspbus.Bus.exit>`,
- and advises plugins to prepare for process termination. Note that
- :func:`cherrypy.engine.exit<cherrypy.process.wspbus.Bus.exit>` first calls
- :func:`cherrypy.engine.stop<cherrypy.process.wspbus.Bus.stop>`, so Plugins
- may expect to stop first, then exit in a separate step.
- * log(msg, level): in general, :class:`cherrypy.log<cherrypy._cplogging.LogManager>`
- listens on this channel. Plugins, however, should make every effort to
- publish to this channel verbosely to aid process event debugging. See the
- builtin Plugins for good examples.
- * main: New in 3.2. All Engine tasks run in threads other than the main thread;
- the main thread usually calls
- :func:`cherrypy.engine.block<cherrypy.process.wspbus.Bus.block>` to wait
- for KeyboardInterrupt and other signals. While blocked, it loops
- (every 1/10th of a second, by default), and publishes a message on the
- "main" channel each time. Listeners subscribed to this channel, therefore,
- are called at every interval.
-
-callback
---------
-
-The functionality you wish to run; this can be any function, class, or other
-callable. Each channel defines the arguments; currently, however, only the "log"
-channel defines any ('msg', the string message to log, and 'level', an int
-following the levels defined in the stdlib's :mod:`logging <logging>` module).
-
-priority
---------
-
-The optional priority (0 - 100) allows multiple listeners to run in the correct
-order. Lower numbers run first. The default is 50.
-
-If you omit the priority argument to engine.subscribe (or pass ``None``),
-you can instead set it as an attribute on the callback function::
-
- def setup_db():
- ....
- setup_db.priority = 90
- engine.subscribe('start', setup_db)
-
-
-Plugins
-=======
-
-You can manually subscribe bus listeners, but you probably shouldn't.
-*Plugins* allow your function to be subscribed and configured both
-via the CherryPy config system and via the Plugin itself. Plugins also allow
-you to write a single class that listens on multiple channels.
-
-Most of the built-in plugins have their own ``subscribe`` method,
-so that instead of writing ``engine.subscribe``, you write:
-``p = Plugin(engine).subscribe()``. If you want to turn off a plugin,
-call ``p.unsubscribe()``. The plugin already knows the correct channel,
-callback, and priority.
-
-You can run arbitrary code at any of the events by creating a
-SimplePlugin object, with one method for each *channel* you wish to handle::
-
- class ScratchDB(plugins.SimplePlugin):
-
- def start(self):
- self.fname = 'myapp_%d.db' % os.getpid()
- self.db = sqlite.connect(database=self.fname)
- start.priority = 80
-
- def stop(self):
- self.db.close()
- os.remove(self.fname)
- cherrypy.engine.scratchdb = ScratchDB(cherrypy.engine)
-
-...then, once you've authored your Plugin, turn it on by calling its
-``subscribe`` method::
-
- cherrypy.engine.scratchdb.subscribe()
-
-...or, in CherryPy 3.2 and above, in site config::
-
- [global]
- engine.scratchdb.on = True
-
-
-Priorities of the built-in "start" listeners:
-
-====================================================================== ================
- Listener Priority
-====================================================================== ================
- default 50
- :doc:`Daemonizer </refman/process/plugins/daemonizer>` 65
- :doc:`Timeout Monitor </progguide/responsetimeouts>` 70
- :class:`Autoreloader <cherrypy.process.plugins.Autoreloader>` 70
- :doc:`PID File </refman/process/plugins/pidfile>` 70
- :doc:`HTTP Servers </refman/process/servers>` 75
- :doc:`Drop Privileges </refman/process/plugins/dropprivileges>` 77
-====================================================================== ================
-
diff --git a/sphinx/source/progguide/extending/customtools.rst b/sphinx/source/progguide/extending/customtools.rst
deleted file mode 100644
index b4d22336..00000000
--- a/sphinx/source/progguide/extending/customtools.rst
+++ /dev/null
@@ -1,282 +0,0 @@
-************
-Custom Tools
-************
-
-CherryPy is an extremely capable platform for web application and framework
-development. One of the strengths of CherryPy is its modular design; CherryPy
-separates key-but-not-core functionality out into "tools". This provides two
-benefits: a slimmer, faster core system and a supported means of tying
-additional functionality into the framework.
-
-Tools can be enabled for any point of your CherryPy application: a certain
-path, a certain class, or even individual methods using the
-:ref:`_cp_config <cp_config>` dictionary. Tools can also be used as decorators
-which provide syntactic sugar for configuring a tool for a specific callable.
-See :doc:`/tutorial/tools` for more information on how to use Tools.
-This document will show you how to make your own.
-
-Your First Custom Tool
-======================
-
-Let's look at a very simple authorization tool::
-
- import cherrypy
-
- def protect(users):
- if cherrypy.request.login not in users:
- raise cherrypy.HTTPError("401 Unauthorized")
-
- cherrypy.tools.protect = Tool('on_start_resource', protect)
-
-We can now enable it in the standard ways: a config file or dict passed to an
-application, a :ref:`_cp_config<cp_config>` dict on a particular class or
-callable or via use of the tool as a decorator. Here's how to turn it on in
-a config file::
-
- [/path/to/protected/resource]
- tools.protect.on = True
- tools.protect.users = ['me', 'myself', 'I']
-
-Now let's look at the example tool a bit more closely.
-Working from the bottom up, the :class:`cherrypy.Tool<cherrypy._cptools.Tool>`
-constructor takes 2 required and 2 optional arguments.
-
-point
------
-
-First, we need to declare the point in the CherryPy request/response
-handling process where we want our tool to be triggered. Different request
-attributes are obtained and set at different points in the request process.
-In this example, we'll run at the first *hook point*, called "on_start_resource".
-
-.. _hooks:
-
-Hooks
-^^^^^
-
-Tools package up *hooks*. When we created a Tool instance above, the Tool
-class registered our `protect` function to run at the 'on_start_resource'
-*hookpoint*. You can write code that runs at hookpoints without using a Tool
-to help you, but you probably shouldn't. The Tool system allows your function
-to be turned on and configured both via the CherryPy config system and via the
-Tool itself as a decorator. You can also write a Tool that runs code at multiple
-hook points.
-
-Here is a quick rundown of the "hook points" that you can hang your tools on:
-
- * on_start_resource - The earliest hook; the Request-Line and request headers
- have been processed and a dispatcher has set request.handler and request.config.
- * before_request_body - Tools that are hooked up here run right before the
- request body would be processed.
- * before_handler - Right before the request.handler (the "exposed" callable
- that was found by the dispatcher) is called.
- * before_finalize - This hook is called right after the page handler has been
- processed and before CherryPy formats the final response object. It helps
- you for example to check for what could have been returned by your page
- handler and change some headers if needed.
- * on_end_resource - Processing is complete - the response is ready to be
- returned. This doesn't always mean that the request.handler (the exposed
- page handler) has executed! It may be a generator. If your tool absolutely
- needs to run after the page handler has produced the response body, you
- need to either use on_end_request instead, or wrap the response.body in a
- generator which applies your tool as the response body is being generated
- (what a mouthful--see
- `caching tee.output <http://www.cherrypy.org/browser/trunk/cherrypy/lib/caching.py>`_
- for an example).
- * before_error_response - Called right before an error response
- (status code, body) is set.
- * after_error_response - Called right after the error response
- (status code, body) is set and just before the error response is finalized.
- * on_end_request - The request/response conversation is over, all data has
- been written to the client, nothing more to see here, move along.
-
-
-callable
---------
-
-Second, we need to provide the function that will be called back at that
-hook point. Here, we provide our ``protect`` callable. The Tool
-class will find all config entries related to our tool and pass them as
-keyword arguments to our callback. Thus, if::
-
- 'tools.protect.on' = True
- 'tools.protect.users' = ['me', 'myself', 'I']
-
-is set in the config, the users list will get passed to the Tool's callable.
-[The 'on' config entry is special; it's never passed as a keyword argument.]
-
-The tool can also be invoked as a decorator like this::
-
- @cherrypy.expose
- @cherrypy.tools.protect(users=['me', 'myself', 'I'])
- def resource(self):
- return "Hello, %s!" % cherrypy.request.login
-
-name
-----
-
-This argument is optional as long as you set the Tool onto a Toolbox. That is::
-
-
- def foo():
- cherrypy.request.foo = True
- cherrypy.tools.TOOLNAME = cherrypy.Tool('on_start_resource', foo)
-
-The above will set the 'name' arg for you (to 'TOOLNAME'). The only time you
-would need to provide this argument is if you're bypassing the toolbox in some way.
-
-priority
---------
-
-This specifies a priority order (from 0 - 100) that determines the order in
-which callbacks in the same hook point are called. The lower the priority
-number, the sooner it will run (that is, we call .sort(priority) on the list).
-The default priority for a tool is set to 50 and most built-in tools use that
-default value.
-
-Custom Toolboxes
-================
-
-All of the builtin CherryPy tools are collected into a Toolbox called
-:attr:`cherrypy.tools`. It responds to config entries in the "tools"
-:ref:`namespace<namespaces>`. You can add your own Tools to this Toolbox
-as described above.
-
-You can also make your own Toolboxes if you need more modularity. For example,
-you might create multiple Tools for working with JSON, or you might publish
-a set of Tools covering authentication and authorization from which everyone
-could benefit (hint, hint). Creating a new Toolbox is as simple as::
-
- # cpstuff/newauth.py
- import cherrypy
-
- # Create a new Toolbox.
- newauthtools = cherrypy._cptools.Toolbox("newauth")
-
- # Add a Tool to our new Toolbox.
- def check_access(default=False):
- if not getattr(cherrypy.request, "userid", default):
- raise cherrypy.HTTPError(401)
- newauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
-
-Then, in your application, use it just like you would use ``cherrypy.tools``,
-with the additional step of registering your toolbox with your app.
-Note that doing so automatically registers the "newauth" config namespace;
-you can see the config entries in action below::
-
- import cherrypy
- from cpstuff import newauth
-
-
- class Root(object):
- def default(self):
- return "Hello"
- default.exposed = True
-
- conf = {'/demo': {
- 'newauth.check_access.on': True,
- 'newauth.check_access.default': True,
- }}
-
- app = cherrypy.tree.mount(Root(), config=conf)
- if hasattr(app, 'toolboxes'):
- # CherryPy 3.1+
- app.toolboxes['newauth'] = newauth.newauthtools
-
-Just the Beginning
-==================
-
-Hopefully that information is enough to get you up and running and create some
-simple but useful CherryPy tools. Much more than what you have seen in this
-tutorial is possible. Also, remember to take advantage of the fact that CherryPy
-is open source! Check out :doc:`/progguide/builtintools` and the
-:doc:`libraries</refman/lib/index>` that they are built upon.
-
-In closing, here is a slightly more complicated tool that acts as a
-"traffic meter" and triggers a callback if a certain traffic threshold is
-exceeded within a certain time frame. It should probably launch its own
-watchdog thread that actually checks the log and triggers the alerts rather
-than waiting on a request to do so, but I wanted to
-keep it simple for the purpose of example::
-
- # traffictool.py
- import time
-
- import cherrypy
-
-
- class TrafficAlert(cherrypy.Tool):
-
- def __init__(self, listclass=list):
- """Initialize the TrafficAlert Tool with the given listclass."""
-
- # A ring buffer subclass of list would probably be a more robust
- # choice than a standard Python list.
-
- self._point = "on_start_resource"
- self._name = None
- self._priority = 50
- # set the args of self.callable as attributes on self
- self._setargs()
- # a log for storing our per-path traffic data
- self._log = {}
- # a history of the last alert for a given path
- self._history = {}
- self.__doc__ = self.callable.__doc__
- self._struct = listclass
-
- def log_hit(self, path):
- """Log the time of a hit to a unique sublog for the path."""
- log = self._log.setdefault(path, self._struct())
- log.append(time.time())
-
- def last_alert(self, path):
- """Returns the time of the last alert for path."""
- return self._history.get(path, 0)
-
- def check_alert(self, path, window, threshhold, delay, callback=None):
- # set the bar
- now = time.time()
- bar = now - window
- hits = [t for t in self._log[path] if t > bar]
- num_hits = len(hits)
- if num_hits > threshhold:
- if self.last_alert(path) + delay < now:
- self._history[path] = now
- if callback:
- callback(path, window, threshhold, num_hits)
- else:
- msg = '%s - %s hits within the last %s seconds.'
- msg = msg % (path, num_hits, window)
- cherrypy.log.error(msg, 'TRAFFIC')
-
- def callable(self, window=60, threshhold=100, delay=30, callback=None):
- """Alert when traffic thresholds are exceeded.
-
- window: the time frame within which the threshhold applies
- threshhold: the number of hits within the window that will trigger
- an alert
- delay: the delay between alerts
- callback: a callback that accepts(path, window, threshhold, num_hits)
- """
-
- path = cherrypy.request.path_info
- self.log_hit(path)
- self.check_alert(path, window, threshhold, delay, callback)
-
-
- cherrypy.tools.traffic_alert = TrafficAlert()
-
- if __name__ == '__main__':
- class Root(object):
- @cherrypy.expose
- def index(self):
- return "Hi!!"
-
- @cherrypy.expose
- @cherrypy.tools.traffic_alert(threshhold=5)
- def popular(self):
- return "A popular page."
-
- cherrypy.quickstart(Root())
-
diff --git a/sphinx/source/progguide/extending/index.rst b/sphinx/source/progguide/extending/index.rst
deleted file mode 100644
index dd5fac54..00000000
--- a/sphinx/source/progguide/extending/index.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-******************
-Extending CherryPy
-******************
-
-If you need to perform some work that doesn't fit in a page handler, there are
-two ways to do it depending on the scope of the task. If your code needs to run
-on each request, or for only some URL's in your application, use a Tool. If your
-code needs to run elsewhere, such as process start/stop/restart/exit, or thread
-start/stop, use an Engine Plugin.
-
-.. toctree::
-
- customtools
- customplugins
-
diff --git a/sphinx/source/progguide/files/downloading.rst b/sphinx/source/progguide/files/downloading.rst
deleted file mode 100644
index d431134a..00000000
--- a/sphinx/source/progguide/files/downloading.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-*****************
-Downloading files
-*****************
-
-CherryPy allows you to serve a file from your page handler. Here is a simple recipe to handle downloads::
-
-
- #!python
- import glob
- import os.path
-
- import cherrypy
- from cherrypy.lib.static import serve_file
-
-
- class Root:
- def index(self, directory="."):
- html = """<html><body><h2>Here are the files in the selected directory:</h2>
- <a href="index?directory=%s">Up</a><br />
- """ % os.path.dirname(os.path.abspath(directory))
-
- for filename in glob.glob(directory + '/*'):
- absPath = os.path.abspath(filename)
- if os.path.isdir(absPath):
- html += '<a href="/index?directory=' + absPath + '">' + os.path.basename(filename) + "</a> <br />"
- else:
- html += '<a href="/download/?filepath=' + absPath + '">' + os.path.basename(filename) + "</a> <br />"
-
- html += """</body></html>"""
- return html
- index.exposed = True
-
- class Download:
-
- def index(self, filepath):
- return serve_file(filepath, "application/x-download", "attachment")
- index.exposed = True
-
- if __name__ == '__main__':
- root = Root()
- root.download = Download()
- cherrypy.quickstart(root)
-
-
-Note that `CherryPy <http://www.cherrypy.org/wiki/CherryPy>`_ is not the fastest for doing such things. If you think you'll have many and big downloads, put CP `BehindApache <http://www.cherrypy.org/wiki/BehindApache>`_ and let Apache serve those files.
-
-
-
-
-
diff --git a/sphinx/source/progguide/files/favicon.rst b/sphinx/source/progguide/files/favicon.rst
deleted file mode 100644
index b5ffa986..00000000
--- a/sphinx/source/progguide/files/favicon.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-*************************
-Serving the favorite icon
-*************************
-
-By default, CherryPy 3 adds a "favicon_ico" handler method to any root object
-which is mounted at "/". This is a staticfile handler, which grabs the
-favicon.ico shipped in the CherryPy distribution.
-
-To configure CherryPy to look in another file location, you can, in your server
-configuration, do the following::
-
- [/favicon.ico]
- tools.staticfile.on = True
- tools.staticfile.filename = "/path/to/favicon.ico"
-
-If you want a favicon, but don't want CherryPy to serve it, you can point to an
-HTTP URL via a link element in the HTML head. See http://www.w3.org/2005/10/howto-favicon
-and http://en.wikipedia.org/wiki/Favicon for instructions.
diff --git a/sphinx/source/progguide/files/index.rst b/sphinx/source/progguide/files/index.rst
deleted file mode 100644
index d6e39ab0..00000000
--- a/sphinx/source/progguide/files/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-*************
-File Handling
-*************
-
-.. toctree::
- :maxdepth: 2
- :glob:
-
- *
-
-
diff --git a/sphinx/source/progguide/files/static.rst b/sphinx/source/progguide/files/static.rst
deleted file mode 100644
index f5c447a6..00000000
--- a/sphinx/source/progguide/files/static.rst
+++ /dev/null
@@ -1,298 +0,0 @@
-Serving Static Content
-**********************
-
-Static content is now handled by ``tools.staticfile`` and ``tools.staticdir`` that can easily be enabled and configured in your config file. For instance, if you wanted to serve ``/style.css`` from ``/home/site/style.css`` and ``/static/*`` from ``/home/site/static/*``, you can use the following configuration:
-
-::
-
- [/]
- tools.staticdir.root = "/home/site"
-
- [/style.css]
- tools.staticfile.on = True
- tools.staticfile.filename = "/home/site/style.css"
-
- [/static]
- tools.staticdir.on = True
- tools.staticdir.dir = "static"
-
-
-Parameters
-==========
-
- * on: True or False (default). Enable or disable the filter.
- * match: a :mod:`regular expression <re>` of files to match.
- * filename: path to the target file.
- * dir: path to the target directory.
- * root: absolute path to a "root"; joined with .dir or .filename if they are relative paths.
-
-Usage
-=====
-
-Serving files through the ``staticfile`` tool
----------------------------------------------
-
-Directory structure
-::
-
- cpapp \
- __init__.py
- data \
- scripts \
- dummy.js
- css \
- style.css
-
-
-Here is our `cpapp/__init__.py`:
-::
-
- #!python
- import os.path
- current_dir = os.path.dirname(os.path.abspath(__file__))
-
- import cherrypy
-
-
- class Root:
- @cherrypy.expose
- def index(self):
- return """<html>
- <head>
- <title>CherryPy static example</title>
- <link rel="stylesheet" type="text/css" href="css/style.css" type="text/css"></link>
- <script type="application/javascript" src="js/some.js"></script>
- </head>
- <body>
- <p>Static example</p>
- </body>
- </html>"""
-
-
-...and a `prod.conf` configuration file:
-
-::
-
- [global]
- environment: 'production'
- log.error_file: 'site.log'
- log.screen: True
-
- tree.cpapp: cherrypy.Application(cpapp.Root())
-
- [/css/style.css]
- tools.staticfile.on: True
- tools.staticfile.filename: cpapp.current_dir + '/data/css/style.css'
-
- [/js/some.js]
- tools.staticfile.on: True
- tools.staticfile.filename: cpapp.current_dir + '/data/scripts/dummy.js'
-
-
-Note how we use the absolute path to point at the static files. Note also that when using the ``staticfile`` tool, the logical URI path and the physical file do not need to be the same. Parts of their components can differ as in the case of the Javascript resource.
-
-You can run the above with:
-
-::
-
- $ cherryd -i cpapp -c prod.conf
-
-
-Serving files through the ``staticdir`` tool
---------------------------------------------
-
-Keeping the same directory structure as above, we could have written our config file as follows:
-
-::
-
- [/]
- tools.staticdir.root: cpapp.current_dir + 'data'
-
- [/css]
- tools.staticdir.on: True
- tools.staticdir.dir: 'css'
-
- [/js]
- tools.staticdir.on: True
- tools.staticdir.dir: 'scripts'
-
-
-However in this case the ``GET /js/some.js`` request will fail with a ``404 Not Found`` response because when using the ``staticdir`` tool the last segment of the URI must match exactly the path of the physical file underneath the directory defined by ``tools.staticdir.dir``.
-
-In our example we must either rename the physical file or change the HTML code accordingly.
-
-staticdir.index
-^^^^^^^^^^^^^^^
-
-If `tools.staticdir.index` is provided, it should be the (relative) name of a file to serve for directory requests. For example, if the `staticdir.dir` argument is '/home/me', the Request-URI is 'myapp', and the `.index` arg is 'index.html', the file '/home/me/myapp/index.html' will be served.
-
-Specify the content-type of static resource
--------------------------------------------
-
-Both the ``staticfile`` and ``staticdir`` tool allow you to specify the mime type of resources by their extension.
-Although internally CherryPy will most of the time guess the correct mime type (using the Python :mod:`mimetypes` module),
-there may be cases when you need to provide the content type values. You can do this via configuration arguments
-``tools.staticdir.content_types`` and ``tools.staticfile.content_types``, as in the following example.
-
-::
-
- #!python
- import os.path
- import cherrypy
-
- class Root:
- @cherrypy.expose
- def index(self):
- return """<html>
- <head>
- <title>CherryPy static tutorial</title>
- </head>
- <html>
- <body>
- <a href="feed/notes.rss">RSS 2.0</a>
- <br />
- <a href="feed/notes.atom">Atom 1.0</a>
- </body>
- </html>"""
-
- if __name__ == '__main__':
- current_dir = os.path.dirname(os.path.abspath(__file__))
- # Set up site-wide config first so we get a log if errors occur.
- cherrypy.config.update({'environment': 'production',
- 'log.error_file': 'site.log',
- 'log.screen': True})
-
- conf = {'/feed': {'tools.staticdir.on': True,
- 'tools.staticdir.dir': os.path.join(current_dir, 'feeds'),
- 'tools.staticdir.content_types': {'rss': 'application/xml',
- 'atom': 'application/atom+xml'}}}
- cherrypy.quickstart(Root(), '/', config=conf)
-
-
-The value of ``tools.staticdir.content_types`` and ``tools.staticfile.content_types``
-is a dictionary whose keys are filename extensions, and values are the corresponding
-media-type strings (for the ``Content-Type`` header). Note that the key must NOT include any leading '.'.
-
-Serve static content from a page handler bypassing the static tools
--------------------------------------------------------------------
-
-It may happen that you would need the static tools power but from a page handler itself so that you can add more processing. You can do so by calling the ``serve_file`` function.
-
-::
-
- #!python
- import os.path
- import cherrypy
- from cherrypy.lib.static import serve_file
-
- class Root:
- @cherrypy.expose
- def feed(self, name):
- accepts = cherrypy.request.headers.elements('Accept')
-
- for accept in accepts:
- if accept.value == 'application/atom+xml':
- return serve_file(os.path.join(current_dir, 'feeds', '%s.atom' % name),
- content_type='application/atom+xml')
-
- return serve_file(os.path.join(current_dir, 'feeds', '%s.rss' % name),
- content_type='application/xml')
-
- if __name__ == '__main__':
- current_dir = os.path.dirname(os.path.abspath(__file__))
- # Set up site-wide config first so we get a log if errors occur.
- cherrypy.config.update({'environment': 'production',
- 'log.error_file': 'site.log',
- 'log.screen': True})
- cherrypy.quickstart(Root(), '/')
-
-
-In this example we rely on the Accept header of the HTTP request to tell us which content type is supported by the client. If it can process the Atom content type then we serve the Atom resource, otherwise we serve the RSS one.
-
-In any case by using the serve_file function we benefit from the CherryPy internal processing of the request in regards of HTTP headers such as If-Modified-Since. In fact the static tools use the serve_file function.
-
-Troubleshooting staticdir
-=========================
-
-When using staticdir, "root" and "dir" are concatenated using ``os.path.join``. So if you're having problems, try ``os.path.join(root, dir)`` in an interactive interpreter and make sure you at least get a valid, absolute path. Remember, you don't have to use "root" at all if you don't want to; just make "dir" an absolute path. If root + dir is not absolute, an error will be raised asking you to make it absolute. CherryPy doesn't make any assumptions about where your project files are, nor can it trust the current working directory, since that may change or not be under your control depending on your deployment environment.
-
-Once root and dir are joined, the final file is found by ``os.path.join``'ing a ''branch''. The branch is pulled from the current request's URL like this:
-
-::
-
- http://www2.mydomain.org/vhost /path/to/my/approot /path/to/section / path/to/actual/file.jpg
- | | | | | | | |
- +----------- base -----------+ +-- script_name --+ +-- section ---+ +------ branch -------+
-
-
-The 'base' is the value of the 'Host' request header (unless changed by tools.proxy). The 'script_name' is where you mounted your app root. The 'section' is what part of the remaining URL to ''ignore''; that is, none of its path atoms need to map to filesystem folders. It should exactly match the section header in your application config file where you defined 'tools.staticdir.dir'. In this example, your application config file should have:
-
-::
-
- [/]
- tools.staticdir.root = '/home/me/testproj'
-
- [/path/to/section]
- tools.staticdir.dir = 'images/jpegs'
-
-
-Note that the section must start with a slash, but not end with one. And in order for ``os.path.join`` to work on root + dir, our 'images' value neither starts nor ends with a slash. Also note that the values of "root" and "dir" need not have ''anything'' to do with any part of the URL; they are OS path components only. Only the section header needs to match a portion of the URL.
-
-Now we're finally ready to slice off the part of the URL that is our ''branch'' and add it to root + dir. So our final example will try to open the following file:
-
-::
-
- root + dir + branch
- >>> os.path.join('/home/me/testproj', 'images/jpegs', 'path/to/actual/file.jpg')
- '/home/me/testproj/images/jpegs/path/to/actual/file.jpg'
-
-
-Forming URLs
-============
-
-Creating links to static content is the inverse of the above. If you want to serve the file:
-
-::
-
- /home/me/testproj/images/jpegs/path/to/actual/file.jpg
-
-
-...you have a choice about where to split up the full path into root, dir, and branch. Remember, the 'root' value only exists to save typing; you could use absolute paths for all "dir" values. So if you're serving multiple static directories, find the common root to them all and use that for your "root" value. For example, instead of this:
-
-::
-
- [/images]
- tools.staticdir.dir = "/usr/home/me/app/static/images"
-
- [/styles]
- tools.staticdir.dir = "/usr/home/me/app/static/css"
-
- [/scripts]
- tools.staticdir.dir = "/usr/home/me/app/static/js"
-
-
-...write:
-
-::
-
- [/]
- tools.staticdir.root = "/usr/home/me/app/static"
-
- [/images]
- tools.staticdir.dir = "images"
-
- [/styles]
- tools.staticdir.dir = "css"
-
- [/scripts]
- tools.staticdir.dir = "js"
-
-
-Regardless of where you split "root" from "dir", the remainder of the OS path will be the "branch". Assuming the config above, our example branch would then be "jpegs/path/to/actual/file.jpg". Add the branch to the section name where you defined "dir", and use that for your URL. Even better, pass it to ``cherrypy.url()`` (which prepends base and script_name) and emit ''that''.
-
-::
-
- section + branch
- >>> cherrypy.url('/images' + '/' + 'jpegs/path/to/actual/file.jpg')
- http://www2.mydomain.org/vhost/path/to/my/approot/images/jpegs/path/to/actual/file.jpg
-
diff --git a/sphinx/source/progguide/files/uploading.rst b/sphinx/source/progguide/files/uploading.rst
deleted file mode 100644
index 526bc511..00000000
--- a/sphinx/source/progguide/files/uploading.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-***************
-Uploading Files
-***************
-
-When a client uploads a file to a CherryPy application, it's placed
-on disk immediately. CherryPy will pass it to your exposed method
-as an argument (see "myFile" below); that arg will have a "file"
-attribute, which is a handle to the temporary uploaded file.
-If you wish to permanently save the file, you need to read()
-from myFile.file and write() somewhere else.
-
-Note the use of 'enctype="multipart/form-data"' and 'input type="file"'
-in the HTML which the client uses to upload the file.
-
-
-Here is a simple example that shows how file uploads are handled by CherryPy::
-
- import os
- localDir = os.path.dirname(__file__)
- absDir = os.path.join(os.getcwd(), localDir)
-
- import cherrypy
-
- class FileDemo(object):
-
- def index(self):
- return """
- <html><body>
- <h2>Upload a file</h2>
- <form action="upload" method="post" enctype="multipart/form-data">
- filename: <input type="file" name="myFile" /><br />
- <input type="submit" />
- </form>
- <h2>Download a file</h2>
- <a href='download'>This one</a>
- </body></html>
- """
- index.exposed = True
-
- def upload(self, myFile):
- out = """<html>
- <body>
- myFile length: %s<br />
- myFile filename: %s<br />
- myFile mime-type: %s
- </body>
- </html>"""
-
- # Although this just counts the file length, it demonstrates
- # how to read large files in chunks instead of all at once.
- # CherryPy reads the uploaded file into a temporary file;
- # myFile.file.read reads from that.
- size = 0
- while True:
- data = myFile.file.read(8192)
- if not data:
- break
- size += len(data)
-
- return out % (size, myFile.filename, myFile.content_type)
- upload.exposed = True
-
-
-
- tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
-
- if __name__ == '__main__':
- # CherryPy always starts with app.root when trying to map request URIs
- # to objects, so we need to mount a request handler root. A request
- # to '/' will be mapped to HelloWorld().index().
- cherrypy.quickstart(FileDemo(), config=tutconf)
- else:
- # This branch is for the test suite; you can ignore it.
- cherrypy.tree.mount(FileDemo(), config=tutconf)
-
diff --git a/sphinx/source/progguide/index.rst b/sphinx/source/progguide/index.rst
deleted file mode 100644
index 9b54428b..00000000
--- a/sphinx/source/progguide/index.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-******************
-Programmer's Guide
-******************
-
-When you build a web application, you need more than just headers and bodies.
-Here are a number of discussions on how to add higher-level features to your
-CherryPy application, and how to leverage some of the power of HTTP.
-
-Features
-========
-
-.. toctree::
- :maxdepth: 2
-
- choosingtemplate
- Exceptions </refman/_cperror>
- files/index
- Logging </refman/_cplogging>
- responsetimeouts
- Sessions </refman/lib/sessions>
- security
- extending/index
-
-
-HTTP details
-============
-
-.. toctree::
- :maxdepth: 2
-
- cookies
- customheaders
- Request Bodies </refman/_cpreqbody>
- REST
- streaming
-
diff --git a/sphinx/source/progguide/responsetimeouts.rst b/sphinx/source/progguide/responsetimeouts.rst
deleted file mode 100644
index a024eb1b..00000000
--- a/sphinx/source/progguide/responsetimeouts.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-*****************
-Response Timeouts
-*****************
-
-CherryPy responses include 3 attributes related to time:
-
- * ``response.time``: the :func:`time.time` at which the response began
- * ``response.timeout``: the number of seconds to allow responses to run
- * ``response.timed_out``: a boolean indicating whether the response has
- timed out (default False).
-
-The request processing logic inspects the value of ``response.timed_out`` at
-various stages; if it is ever True, then :class:`TimeoutError` is raised.
-You are free to do the same within your own code.
-
-Rather than calculate the difference by hand, you can call
-``response.check_timeout`` to set ``timed_out`` for you.
-
-
-.. _timeoutmonitor:
-
-Timeout Monitor
-===============
-
-In addition, CherryPy includes a ``cherrypy.engine.timeout_monitor`` which
-monitors all active requests in a separate thread; periodically, it calls
-``check_timeout`` on them all. It is subscribed by default. To turn it off::
-
- [global]
- engine.timeout_monitor.on: False
-
-or::
-
- cherrypy.engine.timeout_monitor.unsubscribe()
-
-You can also change the interval (in seconds) at which the timeout monitor runs::
-
- [global]
- engine.timeout_monitor.frequency: 60 * 60
-
-The default is once per minute. The above example changes that to once per hour.
diff --git a/sphinx/source/progguide/security.rst b/sphinx/source/progguide/security.rst
deleted file mode 100644
index 63806119..00000000
--- a/sphinx/source/progguide/security.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-********************************
-Securing CherryPy
-********************************
-
-There are several settings that can be enabled to make CherryPy pages more secure. These include:
-
- Transmitting data:
-
- #. Use Secure Cookies
-
- Rendering pages:
-
- #. Set HttpOnly cookies
- #. Set XFrame options
- #. Enable XSS Protection
- #. Set the Content Security Policy
-
-An easy way to accomplish this is to set headers with a :doc:`Tool </tutorial/tools>` and wrap your entire CherryPy application with it::
-
- import cherrypy
-
- def secureheaders():
- headers = cherrypy.response.headers
- headers['X-Frame-Options'] = 'DENY'
- headers['X-XSS-Protection'] = '1; mode=block'
- headers['Content-Security-Policy'] = "default-src='self'"
-
- # set the priority according to your needs if you are hooking something
- # else on the 'before_finalize' hook point.
- cherrypy.tools.secureheaders = \
- cherrypy.Tool('before_finalize', secureheaders, priority=60)
-
-
-Then, in the :doc:`configuration file </tutorial/config>` (or any other place that you want to enable the tool)::
-
- [/]
- tools.secureheaders.on = True
-
-
-If you use :doc:`sessions </refman/lib/sessions>` you can also enable these settings::
-
- [/]
- tools.sessions.on = True
- # increase security on sessions
- tools.sessions.secure = True
- tools.sessions.httponly = True
-
-
-If you use SSL you can also enable Strict Transport Security::
-
- #add this to secureheaders():
- headers['Strict-Transport-Security'] = 'max-age=31536000' # one year
-
-
-
-Further Security Resources
-==========================
-
-For an introduction to webserver security see `Dan Callahan's presentation from PyCon CA 2013 <http://pyvideo.org/video/2315/quick-wins-for-better-website-security>`_.
diff --git a/sphinx/source/progguide/streaming.rst b/sphinx/source/progguide/streaming.rst
deleted file mode 100644
index 58b00fe7..00000000
--- a/sphinx/source/progguide/streaming.rst
+++ /dev/null
@@ -1,83 +0,0 @@
-***************************
-Streaming the response body
-***************************
-
-CherryPy handles HTTP requests, packing and unpacking the low-level details,
-then passing control to your application's :ref:`pagehandlers`, which produce
-the body of the response. CherryPy allows you to return body content in a
-variety of types: a string, a list of strings, a file. CherryPy also allows you
-to *yield* content, rather than *return* content. When you use "yield", you also
-have the option of streaming the output.
-
-**In general, it is safer and easier to not stream output.** Therefore,
-streaming output is off by default. Streaming output and also using sessions
-requires a good understanding of :py:mod:`how session locks work
-<cherrypy.lib.sessions>`.
-
-The "normal" CherryPy response process
-======================================
-
-When you provide content from your page handler, CherryPy manages the
-conversation between the HTTP server and your code like this:
-
-.. image:: cpreturn.gif
-
-Notice that the HTTP server gathers all output first and then writes everything
-to the client at once: status, headers, and body. This works well for static or
-simple pages, since the entire response can be changed at any time, either in
-your application code, or by the CherryPy framework.
-
-How "streaming output" works with CherryPy
-==========================================
-
-When you set the config entry "response.stream" to True (and use "yield"),
-CherryPy manages the conversation between the HTTP server and your code like this:
-
-.. image:: cpyield.gif
-
-When you stream, your application doesn't immediately pass raw body content
-back to CherryPy or to the HTTP server. Instead, it passes back a generator.
-At that point, CherryPy finalizes the status and headers, **before** the
-generator has been consumed, or has produced any output. This is necessary to
-allow the HTTP server to send the headers and pieces of the body as they become
-available.
-
-Once CherryPy has set the status and headers, it sends them to the HTTP server,
-which then writes them out to the client. From that point on, the CherryPy
-ramework mostly steps out of the way, and the HTTP server essentially requests
-content directly from your application code (your page handler method).
-
-Therefore, when streaming, if an error occurs within your page handler,
-CherryPy will not catch it--the HTTP server will catch it. Because the headers
-(and potentially some of the body) have already been written to the client,
-the server *cannot* know a safe means of handling the error, and will therefore
-simply close the connection (the current, builtin servers actually write out a
-short error message in the body, but this may be changed, and is not guaranteed
-behavior for all HTTP servers you might use with CherryPy).
-
-In addition, you cannot manually modify the status or headers within your page
-handler if that handler method is a streaming generator, because the method will
-not be iterated over until after the headers have been written to the client.
-**This includes raising exceptions like HTTPError, NotFound, InternalRedirect
-and HTTPRedirect.** To use a streaming generator while modifying headers, you
-would have to return a generator that is separate from (or embedded in) your
-page handler. For example::
-
- class Root:
- def thing(self):
- cherrypy.response.headers['Content-Type'] = 'text/plain'
- if not authorized():
- raise cherrypy.NotFound()
- def content():
- yield "Hello, "
- yield "world"
- return content()
- thing._cp_config = {'response.stream': True}
-
-Streaming generators are sexy, but they play havoc with HTTP. CherryPy allows
-you to stream output for specific situations: pages which take many minutes to
-produce, or pages which need a portion of their content immediately output to
-the client. Because of the issues outlined above, **it is usually better to
-flatten (buffer) content rather than stream content**. Do otherwise only when
-the benefits of streaming outweigh the risks.
-
diff --git a/sphinx/source/refman/_cpchecker.rst b/sphinx/source/refman/_cpchecker.rst
deleted file mode 100644
index 68b87ee1..00000000
--- a/sphinx/source/refman/_cpchecker.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-**************************
-:mod:`cherrypy._cpchecker`
-**************************
-
-.. automodule:: cherrypy._cpchecker
-
-Classes
-=======
-
-.. autoclass:: Checker
- :members:
-
diff --git a/sphinx/source/refman/_cpconfig.rst b/sphinx/source/refman/_cpconfig.rst
deleted file mode 100644
index b6c5ac4e..00000000
--- a/sphinx/source/refman/_cpconfig.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-*************************
-:mod:`cherrypy._cpconfig`
-*************************
-
-.. automodule:: cherrypy._cpconfig
-
-Classes
-=======
-
-.. autoclass:: Config
- :members:
- :inherited-members:
-
-Functions
-=========
-
-.. autofunction:: merge
-
diff --git a/sphinx/source/refman/_cpdispatch.rst b/sphinx/source/refman/_cpdispatch.rst
deleted file mode 100644
index 28c333a4..00000000
--- a/sphinx/source/refman/_cpdispatch.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-********************************************************
-:mod:`cherrypy._cpdispatch` -- Mapping URI's to handlers
-********************************************************
-
-.. automodule:: cherrypy._cpdispatch
-
-Classes
-=======
-
-.. autoclass:: PageHandler
- :members:
-
-.. autoclass:: LateParamPageHandler
- :members:
-
-.. autoclass:: Dispatcher
- :members:
-
-.. autoclass:: MethodDispatcher
- :members:
-
-.. autoclass:: RoutesDispatcher
- :members:
-
-.. autoclass:: XMLRPCDispatcher
- :members:
-
-.. autoclass:: VirtualHost
- :members:
-
-
diff --git a/sphinx/source/refman/_cperror.rst b/sphinx/source/refman/_cperror.rst
deleted file mode 100644
index 1e6042c2..00000000
--- a/sphinx/source/refman/_cperror.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-*************************
-:mod:`cherrypy._cperror`
-*************************
-
-.. automodule:: cherrypy._cperror
-
-Classes
-=======
-
-.. autoclass:: CherryPyException
- :members:
-
-.. autoclass:: TimeoutError
- :members:
-
-.. autoclass:: InternalRedirect
- :members:
-
-.. autoclass:: HTTPRedirect
- :members:
-
-.. autoclass:: HTTPError
- :members:
-
-.. autoclass:: NotFound
- :members:
-
-Functions
-=========
-
-.. autofunction:: format_exc
-
diff --git a/sphinx/source/refman/_cplogging.rst b/sphinx/source/refman/_cplogging.rst
deleted file mode 100644
index 3594c29e..00000000
--- a/sphinx/source/refman/_cplogging.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-**************************
-:mod:`cherrypy._cplogging`
-**************************
-
-.. automodule:: cherrypy._cplogging
-
-Classes
-=======
-
-.. autoclass:: LogManager
- :members:
-
-.. autoclass:: WSGIErrorHandler
- :members:
-
diff --git a/sphinx/source/refman/_cpreqbody.rst b/sphinx/source/refman/_cpreqbody.rst
deleted file mode 100644
index 58dca8f5..00000000
--- a/sphinx/source/refman/_cpreqbody.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-**************************
-:mod:`cherrypy._cpreqbody`
-**************************
-
-.. automodule:: cherrypy._cpreqbody
-
-Classes
-=======
-
-.. autoclass:: Entity
- :members:
-
-.. autoclass:: Part
- :show-inheritance:
- :members:
- :inherited-members:
-
-.. autoclass:: RequestBody
- :show-inheritance:
- :members:
- :inherited-members:
-
-.. autoclass:: SizedReader
- :members:
-
-
-Functions
-=========
-
-.. autofunction:: process_urlencoded
-
-.. autofunction:: process_multipart
-
-.. autofunction:: process_multipart_form_data
-
diff --git a/sphinx/source/refman/_cprequest.rst b/sphinx/source/refman/_cprequest.rst
deleted file mode 100644
index 475666ae..00000000
--- a/sphinx/source/refman/_cprequest.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-**************************
-:mod:`cherrypy._cprequest`
-**************************
-
-.. automodule:: cherrypy._cprequest
-
-Classes
-=======
-
-.. autoclass:: Request
- :members:
-
-.. autoclass:: Response
- :members:
-
-.. autoclass:: Hook
- :members:
-
-.. autoclass:: HookMap
- :members:
-
-
diff --git a/sphinx/source/refman/_cpserver.rst b/sphinx/source/refman/_cpserver.rst
deleted file mode 100644
index 8f2ea543..00000000
--- a/sphinx/source/refman/_cpserver.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-*************************
-:mod:`cherrypy._cpserver`
-*************************
-
-.. automodule:: cherrypy._cpserver
-
-Classes
-=======
-
-.. autoclass:: Server
- :members:
-
diff --git a/sphinx/source/refman/_cptools.rst b/sphinx/source/refman/_cptools.rst
deleted file mode 100644
index e3ddc48c..00000000
--- a/sphinx/source/refman/_cptools.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-************************
-:mod:`cherrypy._cptools`
-************************
-
-See :doc:`/tutorial/tools` for a conceptual overview of CherryPy Tools.
-
-.. automodule:: cherrypy._cptools
-
-Classes
-=======
-
-.. autoclass:: Tool
- :members:
-
-.. autoclass:: HandlerTool
- :members:
-
-.. autoclass:: HandlerWrapperTool
- :members:
-
-.. autoclass:: ErrorTool
- :members:
-
-.. autoclass:: SessionTool
- :members:
-
-.. autoclass:: XMLRPCController
- :members:
-
-.. autoclass:: SessionAuthTool
- :members:
-
-.. autoclass:: CachingTool
- :members:
-
-.. autoclass:: Toolbox
- :members:
-
-
diff --git a/sphinx/source/refman/_cptree.rst b/sphinx/source/refman/_cptree.rst
deleted file mode 100644
index 7df25387..00000000
--- a/sphinx/source/refman/_cptree.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-***********************
-:mod:`cherrypy._cptree`
-***********************
-
-.. automodule:: cherrypy._cptree
-
-Classes
-=======
-
-.. autoclass:: Application
- :members:
-
-.. autoclass:: Tree
- :members:
-
diff --git a/sphinx/source/refman/_cpwsgi.rst b/sphinx/source/refman/_cpwsgi.rst
deleted file mode 100644
index 44c52cc5..00000000
--- a/sphinx/source/refman/_cpwsgi.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-***********************
-:mod:`cherrypy._cpwsgi`
-***********************
-
-.. automodule:: cherrypy._cpwsgi
-
-Classes
-=======
-
-.. autoclass:: VirtualHost
- :members:
-
-.. autoclass:: InternalRedirector
- :members:
-
-.. autoclass:: ExceptionTrapper
- :members:
-
-.. autoclass:: AppResponse
- :members:
-
-.. autoclass:: CPWSGIApp
- :members:
-
diff --git a/sphinx/source/refman/cherrypy.rst b/sphinx/source/refman/cherrypy.rst
deleted file mode 100644
index a6ea0bb5..00000000
--- a/sphinx/source/refman/cherrypy.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-***************
-:mod:`cherrypy`
-***************
-
-.. automodule:: cherrypy
-
-
-Classes
-=======
-
-.. autoclass:: HTTPError
- :members:
-
-.. autoclass:: HTTPRedirect
- :members:
-
-.. autoclass:: InternalRedirect
- :members:
-
-.. autoclass:: NotFound
- :members:
-
-.. autoclass:: CherryPyException
- :members:
-
-.. autoclass:: TimeoutError
- :members:
-
-
-Functions
-=========
-
-.. autofunction:: expose
-
-.. autofunction:: log
-
-.. autofunction:: quickstart
-
-.. autofunction:: url
-
diff --git a/sphinx/source/refman/cperrors.gif b/sphinx/source/refman/cperrors.gif
deleted file mode 100644
index 4414539a..00000000
--- a/sphinx/source/refman/cperrors.gif
+++ /dev/null
Binary files differ
diff --git a/sphinx/source/refman/index.rst b/sphinx/source/refman/index.rst
deleted file mode 100644
index 993b9328..00000000
--- a/sphinx/source/refman/index.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-****************
-Reference Manual
-****************
-
-.. toctree::
- :glob:
-
- *
- lib/index
- process/index
- wsgiserver/index
-
diff --git a/sphinx/source/refman/lib/auth.rst b/sphinx/source/refman/lib/auth.rst
deleted file mode 100644
index 7008e268..00000000
--- a/sphinx/source/refman/lib/auth.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-************************
-:mod:`cherrypy.lib.auth`
-************************
-
-.. automodule:: cherrypy.lib.auth
-
-Functions
-=========
-
-.. autofunction:: check_auth
-
-.. autofunction:: basic_auth
-
-.. autofunction:: digest_auth
diff --git a/sphinx/source/refman/lib/auth_basic.rst b/sphinx/source/refman/lib/auth_basic.rst
deleted file mode 100644
index ce637fc5..00000000
--- a/sphinx/source/refman/lib/auth_basic.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-******************************
-:mod:`cherrypy.lib.auth_basic`
-******************************
-
-.. automodule:: cherrypy.lib.auth_basic
-
-Functions
-=========
-
-.. autofunction:: checkpassword_dict
-
-.. autofunction:: basic_auth
diff --git a/sphinx/source/refman/lib/auth_digest.rst b/sphinx/source/refman/lib/auth_digest.rst
deleted file mode 100644
index f45d0755..00000000
--- a/sphinx/source/refman/lib/auth_digest.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-*******************************
-:mod:`cherrypy.lib.auth_digest`
-*******************************
-
-.. automodule:: cherrypy.lib.auth_digest
-
-Classes
-=======
-
-.. autoclass:: HttpDigestAuthorization
- :members:
-
-Functions
-=========
-
-.. autofunction:: get_ha1_dict_plain
-
-.. autofunction:: get_ha1_dict
-
-.. autofunction:: get_ha1_file_htdigest
-
-.. autofunction:: synthesize_nonce
-
-.. autofunction:: www_authenticate
-
-.. autofunction:: digest_auth
diff --git a/sphinx/source/refman/lib/caching.rst b/sphinx/source/refman/lib/caching.rst
deleted file mode 100644
index d109a9b5..00000000
--- a/sphinx/source/refman/lib/caching.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-***************************
-:mod:`cherrypy.lib.caching`
-***************************
-
-.. automodule:: cherrypy.lib.caching
-
-Classes
-=======
-
-.. autoclass:: Cache
- :members:
-
-.. autoclass:: AntiStampedeCache
- :members:
-
-.. autoclass:: MemoryCache
- :members:
-
-Functions
-=========
-
-.. autofunction:: get
-
-.. autofunction:: tee_output
-
-.. autofunction:: expires
diff --git a/sphinx/source/refman/lib/covercp.rst b/sphinx/source/refman/lib/covercp.rst
deleted file mode 100644
index b6cf9101..00000000
--- a/sphinx/source/refman/lib/covercp.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-***************************
-:mod:`cherrypy.lib.covercp`
-***************************
-
-.. automodule:: cherrypy.lib.covercp
-
-Classes
-=======
-
-.. autoclass:: CoverStats
- :members:
-
-Functions
-=========
-
-.. autofunction:: get_tree
-
-.. autofunction:: serve
diff --git a/sphinx/source/refman/lib/cpstats.rst b/sphinx/source/refman/lib/cpstats.rst
deleted file mode 100644
index 697bd7d2..00000000
--- a/sphinx/source/refman/lib/cpstats.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-*****************************
-:mod:`cherrypy.lib.cpstats`
-*****************************
-
-.. automodule:: cherrypy.lib.cpstats
-
-Classes
-=======
-
-.. autoclass:: StatsTool
- :members:
-.. autoclass:: StatsPage
-
diff --git a/sphinx/source/refman/lib/cptools.rst b/sphinx/source/refman/lib/cptools.rst
deleted file mode 100644
index b139fb0f..00000000
--- a/sphinx/source/refman/lib/cptools.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-***************************
-:mod:`cherrypy.lib.cptools`
-***************************
-
-.. automodule:: cherrypy.lib.cptools
-
-Classes
-=======
-
-.. autoclass:: SessionAuth
- :members:
-
-.. autoclass:: MonitoredHeaderMap
- :members:
-
-Functions
-=========
-
-.. autofunction:: validate_etags
-
-.. autofunction:: validate_since
-
-.. autofunction:: proxy
-
-.. autofunction:: ignore_headers
-
-.. autofunction:: response_headers
-
-.. autofunction:: referer
-
-.. autofunction:: session_auth
-
-.. autofunction:: log_traceback
-
-.. autofunction:: log_request_headers
-
-.. autofunction:: log_hooks
-
-.. autofunction:: redirect
-
-.. autofunction:: trailing_slash
-
-.. autofunction:: flatten
-
-.. autofunction:: accept
-
-.. autofunction:: allow
-
-.. autofunction:: autovary
diff --git a/sphinx/source/refman/lib/encoding.rst b/sphinx/source/refman/lib/encoding.rst
deleted file mode 100644
index 0171e565..00000000
--- a/sphinx/source/refman/lib/encoding.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-****************************
-:mod:`cherrypy.lib.encoding`
-****************************
-
-.. automodule:: cherrypy.lib.encoding
-
-Classes
-=======
-
-.. autoclass:: ResponseEncoder
- :members:
-
-Functions
-=========
-
-.. autofunction:: decode
-
-.. autofunction:: compress
-
-.. autofunction:: decompress
-
-.. autofunction:: gzip
diff --git a/sphinx/source/refman/lib/httpauth.rst b/sphinx/source/refman/lib/httpauth.rst
deleted file mode 100644
index c0e871c2..00000000
--- a/sphinx/source/refman/lib/httpauth.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-****************************
-:mod:`cherrypy.lib.httpauth`
-****************************
-
-.. automodule:: cherrypy.lib.httpauth
-
-Functions
-=========
-
-.. autofunction:: calculateNonce
-
-.. autofunction:: digestAuth
-
-.. autofunction:: basicAuth
-
-.. autofunction:: doAuth
-
-.. autofunction:: parseAuthorization
-
-.. autofunction:: md5SessionKey
-
-.. autofunction:: checkResponse
-
diff --git a/sphinx/source/refman/lib/httputil.rst b/sphinx/source/refman/lib/httputil.rst
deleted file mode 100644
index 646e55a8..00000000
--- a/sphinx/source/refman/lib/httputil.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-****************************
-:mod:`cherrypy.lib.httputil`
-****************************
-
-.. automodule:: cherrypy.lib.httputil
-
-Classes
-=======
-
-.. autoclass:: HeaderElement
- :members:
-
-.. autoclass:: AcceptElement
- :members:
-
-.. autoclass:: CaseInsensitiveDict
- :members:
-
-.. autoclass:: HeaderMap
- :members:
-
-.. autoclass:: Host
- :members:
-
-Functions
-=========
-
-.. autofunction:: urljoin
-
-.. autofunction:: protocol_from_http
-
-.. autofunction:: get_ranges
-
-.. autofunction:: header_elements
-
-.. autofunction:: decode_TEXT
-
-.. autofunction:: valid_status
-
-.. autofunction:: parse_query_string
-
diff --git a/sphinx/source/refman/lib/index.rst b/sphinx/source/refman/lib/index.rst
deleted file mode 100644
index 72c875a5..00000000
--- a/sphinx/source/refman/lib/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-************
-cherrypy.lib
-************
-
-.. toctree::
- :glob:
-
- *
-
-
diff --git a/sphinx/source/refman/lib/jsontools.rst b/sphinx/source/refman/lib/jsontools.rst
deleted file mode 100644
index 8ec01453..00000000
--- a/sphinx/source/refman/lib/jsontools.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-*****************************
-:mod:`cherrypy.lib.jsontools`
-*****************************
-
-.. automodule:: cherrypy.lib.jsontools
-
-Functions
-=========
-
-.. autofunction:: json_processor
-
-.. autofunction:: json_in
-
-.. autofunction:: json_handler
-
-.. autofunction:: json_out
diff --git a/sphinx/source/refman/lib/profiler.rst b/sphinx/source/refman/lib/profiler.rst
deleted file mode 100644
index a7480693..00000000
--- a/sphinx/source/refman/lib/profiler.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-****************************
-:mod:`cherrypy.lib.profiler`
-****************************
-
-.. automodule:: cherrypy.lib.profiler
-
-Classes
-=======
-
-.. autoclass:: Profiler
- :members:
-
-.. autoclass:: ProfileAggregator
- :members:
-
-.. autoclass:: make_app
- :members:
-
-Functions
-=========
-
-.. autofunction:: new_func_strip_path
-
-.. autofunction:: serve
diff --git a/sphinx/source/refman/lib/reprconf.rst b/sphinx/source/refman/lib/reprconf.rst
deleted file mode 100644
index 62dc013e..00000000
--- a/sphinx/source/refman/lib/reprconf.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-****************************
-:mod:`cherrypy.lib.reprconf`
-****************************
-
-.. automodule:: cherrypy.lib.reprconf
-
-Classes
-=======
-
-.. autoclass:: NamespaceSet
- :members:
-
-.. autoclass:: Config
- :members:
-
-.. autoclass:: Parser
- :members:
-
-Functions
-=========
-
-.. autofunction:: as_dict
-
-.. autofunction:: unrepr
-
-.. autofunction:: modules
-
-.. autofunction:: attributes
-
diff --git a/sphinx/source/refman/lib/sessions.rst b/sphinx/source/refman/lib/sessions.rst
deleted file mode 100644
index 822f7797..00000000
--- a/sphinx/source/refman/lib/sessions.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-****************************
-:mod:`cherrypy.lib.sessions`
-****************************
-
-.. automodule:: cherrypy.lib.sessions
-
-Classes
-=======
-
-.. autoclass:: Session
- :members:
-
-.. autoclass:: RamSession
- :members:
- :inherited-members:
-
-.. autoclass:: FileSession
- :members:
- :inherited-members:
-
-.. autoclass:: PostgresqlSession
- :members:
- :inherited-members:
-
-.. autoclass:: MemcachedSession
- :members:
- :inherited-members:
-
-Functions
-=========
-
-.. autofunction:: save
-
-.. autofunction:: close
-
-.. autofunction:: init
-
-.. autofunction:: set_response_cookie
-
-.. autofunction:: expire
-
diff --git a/sphinx/source/refman/lib/static.rst b/sphinx/source/refman/lib/static.rst
deleted file mode 100644
index 41b10291..00000000
--- a/sphinx/source/refman/lib/static.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-**************************
-:mod:`cherrypy.lib.static`
-**************************
-
-.. automodule:: cherrypy.lib.static
-
-Functions
-=========
-
-.. autofunction:: serve_file
-
-.. autofunction:: serve_fileobj
-
-.. autofunction:: serve_download
-
-.. autofunction:: staticdir
-
-.. autofunction:: staticfile
-
diff --git a/sphinx/source/refman/lib/xmlrpc.rst b/sphinx/source/refman/lib/xmlrpc.rst
deleted file mode 100644
index e6b75de7..00000000
--- a/sphinx/source/refman/lib/xmlrpc.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-******************************
-:mod:`cherrypy.lib.xmlrpcutil`
-******************************
-
-.. automodule:: cherrypy.lib.xmlrpcutil
-
-Functions
-=========
-
-.. autofunction:: process_body
-
-.. autofunction:: patched_path
-
-.. autofunction:: respond
-
-.. autofunction:: on_error
-
diff --git a/sphinx/source/refman/process/index.rst b/sphinx/source/refman/process/index.rst
deleted file mode 100644
index c0a93a78..00000000
--- a/sphinx/source/refman/process/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-****************
-cherrypy.process
-****************
-
-.. toctree::
- :glob:
-
- *
- plugins/index
-
diff --git a/sphinx/source/refman/process/plugins/daemonizer.rst b/sphinx/source/refman/process/plugins/daemonizer.rst
deleted file mode 100644
index a5296ce4..00000000
--- a/sphinx/source/refman/process/plugins/daemonizer.rst
+++ /dev/null
@@ -1,47 +0,0 @@
-***************
-Run as a daemon
-***************
-
-CherryPy allows you to easily decouple the current process from the parent
-environment, using the traditional double-fork::
-
- from cherrypy.process.plugins import Daemonizer
- d = Daemonizer(cherrypy.engine)
- d.subscribe()
-
-.. note::
-
- This :ref:`Engine Plugin<plugins>` is only available on
- Unix and similar systems which provide fork().
-
-If a startup error occurs in the forked children, the return code from the
-parent process will still be 0. Errors in the initial daemonizing process still
-return proper exit codes, but errors after the fork won't. Therefore, if you use
-this plugin to daemonize, don't use the return code as an accurate indicator of
-whether the process fully started. In fact, that return code only indicates if
-the process successfully finished the first fork.
-
-The plugin takes optional arguments to redirect standard streams: ``stdin``,
-``stdout``, and ``stderr``. By default, these are all redirected to
-:file:`/dev/null`, but you're free to send them to log files or elsewhere.
-
-.. warning::
-
- You should be careful to not start any threads before this plugin runs.
- The plugin will warn if you do so, because "...the effects of calling functions
- that require certain resources between the call to fork() and the call to an
- exec function are undefined". (`ref <http://www.opengroup.org/onlinepubs/000095399/functions/fork.html>`_).
- It is for this reason that the Server plugin runs at priority 75 (it starts
- worker threads), which is later than the default priority of 65 for the
- Daemonizer.
-
-
-.. currentmodule:: cherrypy.process.plugins
-
-Classes
-=======
-
-.. autoclass:: Daemonizer
- :members:
- :show-inheritance:
-
diff --git a/sphinx/source/refman/process/plugins/dropprivileges.rst b/sphinx/source/refman/process/plugins/dropprivileges.rst
deleted file mode 100644
index 5eab3337..00000000
--- a/sphinx/source/refman/process/plugins/dropprivileges.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-***************
-Drop privileges
-***************
-
-Use this :ref:`Engine Plugin<plugins>` to start your
-CherryPy site as root (for example, to listen on a privileged port like 80)
-and then reduce privileges to something more restricted.
-
-This priority of this plugin's "start" listener is slightly higher than the
-priority for ``server.start`` in order to facilitate the most common use:
-starting on a low port (which requires root) and then dropping to another user.
-
-Example::
-
- DropPrivileges(cherrypy.engine, uid=1000, gid=1000).subscribe()
-
-.. currentmodule:: cherrypy.process.plugins
-
-Classes
-=======
-
-.. autoclass:: DropPrivileges
- :members:
- :show-inheritance:
-
diff --git a/sphinx/source/refman/process/plugins/index.rst b/sphinx/source/refman/process/plugins/index.rst
deleted file mode 100644
index a2ebb161..00000000
--- a/sphinx/source/refman/process/plugins/index.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-*************************************************
-:mod:`cherrypy.process.plugins` -- Engine Plugins
-*************************************************
-
-.. automodule:: cherrypy.process.plugins
-
-Classes
-=======
-
-.. autoclass:: SimplePlugin
- :members:
-
-.. autoclass:: ThreadManager
- :members:
- :show-inheritance:
-
-Monitors
---------
-
-.. autoclass:: BackgroundTask
- :members:
-
-.. autoclass:: PerpetualTimer
- :members:
-
-.. autoclass:: Monitor
- :members:
- :show-inheritance:
-
-.. autoclass:: Autoreloader
- :members:
- :inherited-members:
- :show-inheritance:
-
-
-Other Plugins
--------------
-
-.. toctree::
- :maxdepth: 2
-
- signalhandler
- dropprivileges
- daemonizer
- pidfile
-
diff --git a/sphinx/source/refman/process/plugins/pidfile.rst b/sphinx/source/refman/process/plugins/pidfile.rst
deleted file mode 100644
index 3c02a0ce..00000000
--- a/sphinx/source/refman/process/plugins/pidfile.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-*********
-PID files
-*********
-
-The PIDFile :ref:`Engine Plugin<plugins>` is pretty straightforward: it writes
-the process id to a file on start, and deletes the file on exit. You must
-provide a 'pidfile' argument, preferably an absolute path::
-
- PIDFile(cherrypy.engine, '/var/run/myapp.pid').subscribe()
-
-.. currentmodule:: cherrypy.process.plugins
-
-Classes
-=======
-
-.. autoclass:: PIDFile
- :members:
- :show-inheritance:
-
-
diff --git a/sphinx/source/refman/process/plugins/signalhandler.rst b/sphinx/source/refman/process/plugins/signalhandler.rst
deleted file mode 100644
index 5e8ced32..00000000
--- a/sphinx/source/refman/process/plugins/signalhandler.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-****************
-Handling Signals
-****************
-
-SignalHandler
-=============
-
-This :ref:`Engine Plugin<plugins>` is instantiated automatically as
-``cherrypy.engine.signal_handler``.
-However, it is only *subscribed* automatically by ``cherrypy.quickstart()``.
-So if you want signal handling and you're calling::
-
- tree.mount(); engine.start(); engine.block()
-
-on your own, be sure to add::
-
- if hasattr(cherrypy.engine, 'signal_handler'):
- cherrypy.engine.signal_handler.subscribe()
-
-.. currentmodule:: cherrypy.process.plugins
-
-.. autoclass:: SignalHandler
- :members:
-
-
-.. index:: Windows, Ctrl-C, shutdown
-.. _windows-console:
-
-Windows Console Events
-======================
-
-Microsoft Windows uses console events to communicate some signals, like Ctrl-C.
-When deploying CherryPy on Windows platforms, you should obtain the
-`Python for Windows Extensions <http://sourceforge.net/projects/pywin32/>`_;
-once you have them installed, CherryPy will handle Ctrl-C and other
-console events (CTRL_C_EVENT, CTRL_LOGOFF_EVENT, CTRL_BREAK_EVENT,
-CTRL_SHUTDOWN_EVENT, and CTRL_CLOSE_EVENT) automatically, shutting down the
-bus in preparation for process exit.
-
diff --git a/sphinx/source/refman/process/servers.rst b/sphinx/source/refman/process/servers.rst
deleted file mode 100644
index 902ab12a..00000000
--- a/sphinx/source/refman/process/servers.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-**********************************************************
-:mod:`cherrypy.process.servers` -- HTTP Server interfaces
-**********************************************************
-
-.. automodule:: cherrypy.process.servers
-
-
-Classes
-=======
-
-.. autoclass:: ServerAdapter
- :members:
-
-.. autoclass:: FlupFCGIServer
- :members:
-
-.. autoclass:: FlupSCGIServer
- :members:
-
-
-Functions
-=========
-
-.. autofunction:: client_host
-
-.. autofunction:: check_port
-
-.. autofunction:: wait_for_free_port
-
-.. autofunction:: wait_for_occupied_port
-
diff --git a/sphinx/source/refman/process/win32.rst b/sphinx/source/refman/process/win32.rst
deleted file mode 100644
index 20a3b3e4..00000000
--- a/sphinx/source/refman/process/win32.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-**********************************************************
-:mod:`cherrypy.process.win32` -- Bus support for Windows
-**********************************************************
-
-.. automodule:: cherrypy.process.win32
-
-Classes
-=======
-
-.. autoclass:: ConsoleCtrlHandler
- :members:
-
-.. autoclass:: Win32Bus
- :members:
-
-.. autoclass:: PyWebService
- :members:
diff --git a/sphinx/source/refman/process/wspbus.rst b/sphinx/source/refman/process/wspbus.rst
deleted file mode 100644
index 95799a52..00000000
--- a/sphinx/source/refman/process/wspbus.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-**********************************************************
-:mod:`cherrypy.process.wspbus` -- The Web Site Process Bus
-**********************************************************
-
-.. automodule:: cherrypy.process.wspbus
-
-Classes
-=======
-
-.. autoclass:: ChannelFailures
- :members:
-
-.. autoclass:: Bus
- :members:
-
diff --git a/sphinx/source/refman/wsgiserver/index.rst b/sphinx/source/refman/wsgiserver/index.rst
deleted file mode 100644
index ded4f516..00000000
--- a/sphinx/source/refman/wsgiserver/index.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-*******************
-cherrypy.wsgiserver
-*******************
-
-.. toctree::
- :glob:
-
- *
-
diff --git a/sphinx/source/refman/wsgiserver/init.rst b/sphinx/source/refman/wsgiserver/init.rst
deleted file mode 100644
index 444e7f69..00000000
--- a/sphinx/source/refman/wsgiserver/init.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-*****************************************
-:mod:`cherrypy.wsgiserver` -- WSGI Server
-*****************************************
-
-.. automodule:: cherrypy.wsgiserver
-
-
-HTTP
-====
-
-.. autoclass:: HTTPRequest
- :members:
-
-.. autoclass:: HTTPConnection
- :members:
-
-.. autoclass:: HTTPServer
- :members:
-
-
-Request Entities
-================
-
-.. autoclass:: SizeCheckWrapper
- :members:
-
-.. autoclass:: KnownLengthRFile
- :members:
-
-.. autoclass:: ChunkedRFile
- :members:
-
-.. autoclass:: CP_fileobject
- :members:
-
-Exceptions
-==========
-
-.. autoclass:: MaxSizeExceeded
- :members:
-
-.. autoclass:: NoSSLError
- :members:
-
-.. autoclass:: FatalSSLAlert
- :members:
-
-
-Thread Pool
-===========
-
-.. autoclass:: WorkerThread
- :members:
-
-.. autoclass:: ThreadPool
- :members:
-
-SSL
-===
-
-.. autoclass:: SSLAdapter
- :members:
-
-WSGI
-====
-
-.. autoclass:: CherryPyWSGIServer
- :members:
-
-.. autoclass:: Gateway
- :members:
-
-.. autoclass:: WSGIGateway
- :members:
-
-.. autoclass:: WSGIGateway_10
- :members:
-
-.. autoclass:: WSGIGateway_u0
- :members:
-
-.. autoclass:: WSGIPathInfoDispatcher
- :members:
-
diff --git a/sphinx/source/refman/wsgiserver/ssl_builtin.rst b/sphinx/source/refman/wsgiserver/ssl_builtin.rst
deleted file mode 100644
index ab2aa7ab..00000000
--- a/sphinx/source/refman/wsgiserver/ssl_builtin.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-*****************************************************
-:mod:`cherrypy.wsgiserver.ssl_builtin` -- Builtin SSL
-*****************************************************
-
-.. automodule:: cherrypy.wsgiserver.ssl_builtin
-
-Classes
-=======
-
-.. autoclass:: BuiltinSSLAdapter
- :members:
-
diff --git a/sphinx/source/refman/wsgiserver/ssl_pyopenssl.rst b/sphinx/source/refman/wsgiserver/ssl_pyopenssl.rst
deleted file mode 100644
index f80a5580..00000000
--- a/sphinx/source/refman/wsgiserver/ssl_pyopenssl.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-*****************************************************
-:mod:`cherrypy.wsgiserver.ssl_pyopenssl` -- pyOpenSSL
-*****************************************************
-
-.. automodule:: cherrypy.wsgiserver.ssl_pyopenssl
-
-Classes
-=======
-
-.. autoclass:: SSL_fileobject
- :members:
-
-.. autoclass:: SSLConnection
- :members:
-
-.. autoclass:: pyOpenSSLAdapter
- :members:
-
diff --git a/sphinx/source/tutorial/REST.rst b/sphinx/source/tutorial/REST.rst
deleted file mode 100644
index 8a91a506..00000000
--- a/sphinx/source/tutorial/REST.rst
+++ /dev/null
@@ -1,308 +0,0 @@
-**********************
-Creating a RESTful API
-**********************
-
-`REST <http://en.wikipedia.org/wiki/Representational_state_transfer>`_ is an elegant architecture concept which is widely used nowadays.
-
-The point is quite simple: rely on HTTP methods and statuses and associate them with actual data manipulations.
-
-You can read more theory on the :doc:`../progguide/REST` page.
-
-Overview
-========
-
-In this tutorial, we will create a RESTful backend for a song collection management web app.
-
-A song is a *resource* with certain data (called *state*). Let's assume, every song has **title** and **artist**, and is identified by a unique **ID**.
-
-There are also *methods* to view and change the state of a resource. The basic set of methods is called `CRUD <http://en.wikipedia.org/wiki/Create,_read,_update_and_delete>`_—Create, Read, Update, and Delete.
-
-Let's assume that the frontend part is developed by someone else, and can interact with our backend part only with API requests. Our jobs is only to handle those requests, perform actions, and return the proper response.
-
-Therefore, we will not take care about templating or page rendering.
-
-We will also not use a database in this tutorial for the sake of concentrating solely on the RESTful API concept.
-
-.. note::
-
- REST principles assume that a response status must always be meaningful. HTTP 1.1 specification already has all necessary error codes, and a developer should properly map erroneous backend events with according HTTP error codes.
-
- Fortunately, CherryPy has done it for us. For instance, if our backend app receives a request with wrong parameters, CherryPy will raise a ``400 Bad Request`` response automatically.
-
-Download the :download:`complete example file <files/songs.py>`.
-
-Getting Started
-===============
-
-Create a file called ``songs.py`` with the following content::
-
- import cherrypy
-
- songs = {
- '1': {
- 'title': 'Lumberjack Song',
- 'artist': 'Canadian Guard Choir'
- },
-
- '2': {
- 'title': 'Always Look On the Bright Side of Life',
- 'artist': 'Eric Idle'
- },
-
- '3': {
- 'title': 'Spam Spam Spam',
- 'artist': 'Monty Python'
- }
- }
-
- class Songs:
-
- exposed = True
-
- if __name__ == '__main__':
-
- cherrypy.tree.mount(
- Songs(), '/api/songs',
- {'/':
- {'request.dispatch': cherrypy.dispatch.MethodDispatcher()}
- }
- )
-
- cherrypy.engine.start()
- cherrypy.engine.block()
-
-Let's go through this code line by line.
-
-Import CherryPy::
-
- import cherrypy
-
-Define the song "database", which is a simple Python dictionary::
-
- songs = {
- '1': {
- 'title': 'Lumberjack Song',
- 'artist': 'Canadian Guard Choir'
- },
-
- '2': {
- 'title': 'Always Look On the Bright Side of Life',
- 'artist': 'Eric Idle'
- },
-
- '3': {
- 'title': 'Spam Spam Spam',
- 'artist': 'Monty Python'
- }
- }
-
-Note that we are using *strings* as dict keys, not *integers*. This is done only to avoid extra type convertings when we will parse the request parameters (which are always strings.) Normally, the ID handling is performed by a database automatically, but since we do not use any, we have to deal with it manually.
-
-Create a class to represent the *songs* resource::
-
- class Songs:
-
-Expose all the (future) class methods at once::
-
- exposed = True
-
-Standard Python check on whether the file is used directly or as module::
-
- if __name__ == '__main__':
-
-Create an instance of the class (called a CherryPy application) and mount it to ``/api/songs``::
-
- cherrypy.tree.mount(
- Songs(), '/api/songs',
-
-This means that this app will handle requests coming to the URLs starting with ``/api/songs``.
-
-Now, here goes the interesting part.
-
-CherryPy has a very helpful tool for creating RESTful APIs—the **MethodDispatcher**.
-
-Learn it and love it.
-
-Briefly speaking, it is a special sort of dispatcher which automatically connects the HTTP requests to the according handlers based on the request method. All you have to do is just name the handlers to correspond to the HTTP method names.
-
-Long story short, just call the HTTP GET handler ``GET``, and the HTTP POST handle ``POST``.
-
-Activate this dispatcher for our app::
-
- {'/':
- {'request.dispatch': cherrypy.dispatch.MethodDispatcher()}
- }
- )
-
-Note that the ``/`` path in this config is relative to the application mount point (``/api/songs``), and will apply only to it.
-
-The last 2 lines do just the same as the ``quickstart`` method, only written a bit more explicitly—run the server::
-
- cherrypy.engine.start()
- cherrypy.engine.block()
-
-GET
-===
-
-Represents the Read method in CRUD.
-
-Add a new method to the ``Songs`` class in ``songs.py``, called ``GET``::
-
- def GET(self, id=None):
-
- if id == None:
- return('Here are all the songs we have: %s' % songs)
- elif id in songs:
- song = songs[id]
- return('Song with the ID %s is called %s, and the artist is %s' % (id, song['title'], song['artist']))
- else:
- return('No song with the ID %s :-(' % id)
-
-This method will return the whole song dictionary if the ID is not specified (``/api/songs``), a particular song data if the ID is specified and exists (``/api/songs/1`` ), and the message about a not existing song otherwise (``/api/songs/42``.)
-
-Try it out in your browser by going to ``127.0.0.1:8080/api/songs/``, ``127.0.0.1:8080/api/songs/1``, or ``127.0.0.1:8080/api/songs/42``.
-
-POST
-====
-
-Represents the Create method in CRUD.
-
-Add a new method to the ``Songs`` class, called ``POST``::
-
- def POST(self, title, artist):
-
- id = str(max([int(_) for _ in songs.keys()]) + 1)
-
- songs[id] = {
- 'title': title,
- 'artist': artist
- }
-
- return ('Create a new song with the ID: %s' % id)
-
-This method defines the next unique ID and adds an item to the ``songs`` dictionary.
-
-Note that we do not validate the input arguments. CherryPy does it for us. If any parameter is missing or and extra one is provided, the 400 Bad Request error will be raised automatically.
-
-.. note::
-
- Unlike GET request, POST, PUT, and DELETE requests cannot be sent via the browser URL promt.
-
- You will need to use some special software to do it.
-
- The recommendation here is to use `cURL <http://en.wikipedia.org/wiki/CURL>`_, which is available by default in most GNU/Linux distributions and is available for Windows and Mac.
-
- Basic cURL usage to send a request, applied in the examples below, is as follows:
-
- .. code-block:: bash
-
- curl -d <param1>=<value1> -d <param2>=<value2> -X <HTTPMethod> <URL>
-
- You can send GET requests with cURL too, but using a browser is easier.
-
-Send a POST HTTP request to ``127.0.0.1:8080/api/songs/`` with cURL:
-
-.. code-block:: bash
-
- curl -d title='Frozen' -d artist='Madonna' -X POST '127.0.0.1:8080/api/songs/'
-
-You will see the response:
-
- Create a new song with the ID: 4%
-
-Now, if you go to ``127.0.0.1:8080/api/songs/4`` in your browser you will se the following message:
-
- Song with the ID 4 is called Frozen, and the artist is Madonna
-
-So it actually works!
-
-PUT
-===
-
-Represents the Update method in CRUD.
-
-Add a new method to the ``Songs`` class, called ``PUT``::
-
- def PUT(self, id, title=None, artist=None):
- if id in songs:
- song = songs['id']
-
- song['title'] = title or song['title']
- song['artist'] = artist or song['artist']
-
- return('Song with the ID %s is now called %s, and the artist is now %s' % (id, song['title'], song['artist']))
- else:
- return('No song with the ID %s :-(' % id)
-
-This method checks whether the requested song exists and updates the fields that are provided. If some field is not specified, the corresponding value will not be updated.
-
-Try sending some PUT HTTP requests to ``127.0.0.1:8080/api/songs/3`` via cURL, and check the result by requesting ``127.0.0.1:8080/api/songs/4`` in your browser:
-
-* .. code-block:: bash
-
- curl -d title='Yesterday' -X PUT '127.0.0.1:8080/api/songs/3'
-
- The response:
-
- Song with the ID 3 is now called Yesterday, and the artist is now Monty Python%
-
- What you'll see in the browser:
-
- Song with the ID 3 is called Yesterday, and the artist is Monty Python
-
-* .. code-block:: bash
-
- curl -d artist='Beatles' -X PUT '127.0.0.1:8080/api/songs/3'
-
- The response:
-
- Song with the ID 3 is now called Yesterday, and the artist is now Beatles%
-
- What you'll see in the browser:
-
- Song with the ID 3 is called Yesterday, and the artist is Beatles
-
-DELETE
-======
-
-Represents the DELETE method in CRUD.
-
-Add a new method to the ``Songs`` class, called ``DELETE``::
-
- def DELETE(self, id):
- if id in songs:
- songs.pop(id)
-
- return('Song with the ID %s has been deleted.' % id)
- else:
- return('No song with the ID %s :-(' % id)
-
-This method, like the previous ones, check if the given ID point to an existing song and pops it out of the ``songs`` dictionary.
-
-Send a DELETE HTTP request to ``127.0.0.1:8080/api/songs/2`` via cURL:
-
-.. code-block:: bash
-
- curl -X DELETE '127.0.0.1:8080/api/songs/2'
-
-The response:
-
- Song with the ID 2 has been deleted.%
-
-And the browser output:
-
- No song with the ID 2 :-(
-
-Multiple Resources
-==================
-
-You can have any number of resources represented this way. Each resource is a CherryPy application, i.e. a class.
-
-For another resource, say, *users*, just create a class ``Users`` the same way you created ``Songs``, and mount it to ``/api/users`` with the same config.
-
-Conclusion and Further Steps
-============================
-
-This is pretty much it about the logic of REST API in CherryPy.
-
-You can now add actual database manipulations, parameter validation, and whatever your project may require.
diff --git a/sphinx/source/tutorial/basics.rst b/sphinx/source/tutorial/basics.rst
deleted file mode 100644
index 3fc9f6bf..00000000
--- a/sphinx/source/tutorial/basics.rst
+++ /dev/null
@@ -1,148 +0,0 @@
-*******************************
-Your first CherryPy application
-*******************************
-
-The standard 'Hello world!' application takes less than 10 lines of code
-when written using CherryPy::
-
- import cherrypy
-
-
- class HelloWorld(object):
- def index(self):
- return "Hello world!"
- index.exposed = True
-
- cherrypy.quickstart(HelloWorld())
-
-We assume that you already have :doc:`installed </intro/install>` CherryPy.
-Copy the file above and save it locally as ``hello.py``, then start the
-application at the command prompt::
-
- $ python hello.py
-
-Direct your favorite web browser to http://localhost:8080 and you should
-see ``Hello world!`` printed there.
-
-How does it work?
------------------
-
-Let's take a look at ``hello.py``:
-
- * The ``import cherrypy`` statement imports the main CherryPy module.
- This is all that is required to have CherryPy working. Feel free to
- "import cherrypy" in an interactive session and see what's available!
- ``help(cherrypy)`` is also quite useful.
- * We declare a class named ``HelloWorld``. An instance of this class is the
- object that will be published by CherryPy. It contains a single method,
- named ``index``, which will get called when the root URL for the site is
- requested (for example, ``http://localhost/``). This method returns the
- **contents** of the Web page; in this case, the ``'Hello World!'`` string.
- Note that you don't have to subclass any framework-provided classes; in fact,
- you don't even have to use classes at all! But let's start with them for now.
- * The ``index.exposed = True`` is a necessary step to tell CherryPy that the
- ``index()`` method will be **exposed**. Only exposed methods can be called
- to answer a request. This feature allows the user to select which methods
- of an object will be accessible via the Web; non-exposed methods can't be
- accessed. Another way to **expose** a method is to use the decorator
- :func:`cherrypy.expose`.
- * ``cherrypy.quickstart(HelloWorld())`` mounts an instance of the HelloWorld
- class, and starts the embedded webserver. It runs until explicitly
- interrupted, either with ``Ctrl-C`` or via a suitable signal (a simple
- ``kill`` on Unix will do it).
-
-When the application is executed, the CherryPy server is started with the
-default configuration. It will listen on ``localhost`` at port ``8080``. These
-defaults can be overridden by using a :doc:`configuration file </tutorial/config>` or dictionary
-(more on this later).
-
-Finally, the web server receives the request for the URL
-``http://localhost:8080``. It searches for the best method to handle the
-request, starting from the ``HelloWorld`` instance. In this particular case,
-the root of the site is automatically mapped to the ``index()`` method (similar
-to the ``index.html`` that is the standard page for conventional Web servers).
-The HelloWorld class defines an ``index()`` method and exposes it. CherryPy
-calls ``HelloWorld().index()``, and the result of the call is sent back to
-the browser as the contents of the index page for the website. All the
-dispatching and HTTP-processing work is
-done automatically; the application programmer only needs to provide the
-desired content as the return value of the ``index`` method.
-
-CherryPy structure
-------------------
-
-Most of the features of CherryPy are available through the :mod:`cherrypy`
-module. It contains several members:
-
- * :class:`cherrypy.engine <cherrypy.process.wspbus.Bus>`
- controls process startup, shutdown, and other events, including your own
- Plugins. See :doc:`/tutorial/engine`.
- * :class:`cherrypy.server <cherrypy._cpserver.Server>` configures and controls
- the HTTP server.
- * :class:`cherrypy.request <cherrypy._cprequest.Request>` contains all
- the information that comes with the HTTP request, after it is parsed and
- analyzed by CherryPy.
- * :attr:`cherrypy.request.headers <cherrypy.lib.httputil.HeaderMap>`
- contains a mapping with the header options that were sent as part of
- the request.
- * :class:`cherrypy.session <cherrypy.lib.sessions.Session>` is a special
- mapping that is automatically generated and encoded by CherryPy; it can
- be used to store session-data in a persistent cookie. For it to work you
- have to enable the session functionality by setting 'tools.session.on' to
- True in your :doc:`config </tutorial/config>`.
- * :class:`cherrypy.response <cherrypy._cprequest.Response>` contains the
- data that is used to build the HTTP response.
- * :attr:`cherrypy.response.headers <cherrypy.lib.httputil.HeaderMap>`
- contains a mapping with the header options that will be returned by the
- server, before the contents get sent.
- * :attr:`cherrypy.response.body <cherrypy._cprequest.Response.body>` contains
- the actual contents of the webpage that will be sent as a response.
-
-CherryPy Response
------------------
-
-The :class:`cherrypy.response <cherrypy._cprequest.Response>` object is
-available to affect aspects of the response
-to a request. Like the request, the response object is a thread-local,
-meaning although it appears to be a global variable, its value is specific
-to the current thread, and thus the current request.
-
-One may store arbitrary data in the response object.
-
-HTTP Headers
-------------
-
-CherryPy exposes the :attr:`request headers <cherrypy.lib.httputil.HeaderMap>`
-(as sent from the client), and response headers (to be returned in the
-response) in the `headers` attribute of `cherrypy.request` and
-`cherrypy.response`.
-
-For example, to find out what "host" to which the client intended to connect::
-
- import cherrypy
-
-
- class HelloWorld(object):
- @cherrypy.expose
- def index(self):
- host = cherrypy.request.headers['Host']
- return "You have successfully reached " + host
-
- cherrypy.quickstart(HelloWorld())
-
-Or to set headers on the response::
-
- import cherrypy
-
-
- class HelloWorld(object):
- def _get_jpeg_data(self):
- """This method should return the jpeg data"""
- return ""
-
- @cherrypy.expose
- def index(self):
- cherrypy.response.headers['Content-Type'] = 'application/jpeg'
- return self._get_jpeg_data()
-
- cherrypy.quickstart(HelloWorld())
diff --git a/sphinx/source/tutorial/dispatching.rst b/sphinx/source/tutorial/dispatching.rst
deleted file mode 100644
index fb39a8c2..00000000
--- a/sphinx/source/tutorial/dispatching.rst
+++ /dev/null
@@ -1,433 +0,0 @@
-***********
-Dispatching
-***********
-
- The resource is not the storage object. The resource is not a mechanism
- that the server uses to handle the storage object. The resource is a
- conceptual mapping -- the server receives the identifier (which identifies
- the mapping) and applies it to its current mapping implementation (usually
- a combination of collection-specific deep tree traversal and/or hash tables)
- to find the currently responsible handler implementation and the handler
- implementation then selects the appropriate action+response based on the
- request content. All of these implementation-specific issues are hidden
- behind the Web interface; their nature cannot be assumed by a client that
- only has access through the Web interface.
-
- `Roy Fielding <http://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm>`_
-
-When you wish to serve a resource on the Web, you never actually serve the
-resource, because "resources" are concepts. What you serve are representations
-of a resource, and *page handlers* are what you use in CherryPy to do that.
-Page handlers are functions that you write; CherryPy calls one for each
-request and uses its response (a string of HTML, for example) as the
-representation.
-
-For the user, a web application is just like a website with static files.
-The user types (or clicks) a URL, and gets to the desired webpage. A
-conventional webserver uses the URL to retrieve a static file from the
-filesystem. A web application server, on the other hand, not only serves
-the content from static files; it can also map the URL it receives into some
-object and call it. The result is then sent back to the user's browser,
-where it is rendered into a viewable page. The result is a dynamic web
-application; for each URL, a unique object can be called into action.
-The key to understand how to write a new web application is to understand
-how this mapping occurs.
-
-CherryPy takes the output of the appropriate page handler function, binds it
-to :attr:`cherrypy.response.body <cherrypy._cprequest.Response.body>`,
-and sends it as the HTTP response entity
-body. Your page handler function (and almost any other part of CherryPy) can
-directly set :attr:`cherrypy.response.status <cherrypy._cprequest.Response.status>`
-and :attr:`cherrypy.response.headers <cherrypy._cprequest.Response.headers>`
-as desired.
-
-Dispatchers
-===========
-
-Before CherryPy can call your page handlers, it has to know 1) where they are,
-and 2) which one to call for a given 'identifier' (URI). In CherryPy, we use
-a Dispatcher object to:
-
-1. Understand the arrangement of handlers
-2. Find the appropriate page handler function
-3. Wrap your actual handler function in a
- :class:`PageHandler <cherrypy._cpdispatch.PageHandler>` object (see below)
-4. Set :attr:`cherrypy.request.handler <cherrypy._cprequest.Request.handler>`
- (to the :class:`PageHandler <cherrypy._cpdispatch.PageHandler>` wrapper)
-5. Collect configuration entries into
- :attr:`cherrypy.request.config <cherrypy._cprequest.Request.config>`
-6. Collect "virtual path" components
-
-CherryPy has a default arrangement of handlers (see next), but also allows you
-to trade it for any arrangement you can think up and implement.
-
-.. _defaultdispatcher:
-
-Default Dispatcher
-------------------
-
-By default, CherryPy uses a fairly straightforward mapping procedure. The root
-of the site is the :attr:`Application.root <cherrypy._cptree.Application.root>`
-object. When it receives a URL, it breaks it into its path components, and
-proceeds looking down into the site until it finds an object that is the
-'best match' for that particular URL. For each path component it tries to find
-an object with the same name, starting from ``root``, and going down for each
-component it finds, until it can't find a match. An example shows it better::
-
- root = HelloWorld()
- root.onepage = OnePage()
- root.otherpage = OtherPage()
-
-In the example above, the URL ``http://localhost/onepage`` will point at the
-first object and the URL ``http://localhost/otherpage`` will point at the
-second one. As usual, this search is done automatically. But it goes even further::
-
- root.some = Page()
- root.some.page = Page()
-
-In this example, the URL ``http://localhost/some/page`` will be mapped to the
-``root.some.page`` object. If this object is exposed (or alternatively, its
-``index`` method is), it will be called for that URL.
-
-In our HelloWorld example, adding the ``http://localhost/onepage/`` mapping
-to ``OnePage().index`` could be done like this::
-
- import cherrypy
-
-
- class OnePage(object):
- def index(self):
- return "one page!"
- index.exposed = True
-
-
- class HelloWorld(object):
- onepage = OnePage()
-
- def index(self):
- return "hello world"
- index.exposed = True
-
- cherrypy.quickstart(HelloWorld())
-
-Normal methods
-^^^^^^^^^^^^^^^
-
-.. index:: methods; normal
-
-CherryPy can directly call methods on the mounted objects, if it receives a
-URL that is directly mapped to them. For example::
-
-
- """This example can handle the URIs
- / -> OnePage.index
- /foo -> OnePage.foo -> foo
- """
- import cherrypy
-
-
- class OnePage(object):
- def index(self):
- return "one page!"
- index.exposed = True
-
-
- def foo():
- return 'Foo!'
- foo.exposed = True
-
- if __name__ == '__main__':
- root = OnePage()
- root.foo = foo
- cherrypy.quickstart(root)
-
-
-In the example, ``root.foo`` contains a function object, named ``foo``. When
-CherryPy receives a request for the ``/foo`` URL, it will automatically call
-the ``foo()`` function. Note that it can be a plain function, or a method of
-any object; any callable will do it.
-
-.. _indexmethods:
-
-Index methods
-^^^^^^^^^^^^^
-
-.. index:: index, methods; index
-
-The ``index`` method has a special role in CherryPy: it handles intermediate
-URI's that end in a slash; for example, the URI ``/orders/items/`` might map
-to ``root.orders.items.index``. The ``index`` method can take additional
-keyword arguments if the request includes querystring or POST params; see
-:ref:`kwargs`, next. However,
-unlike all other page handlers, it *cannot* take positional arguments (see
-:ref:`args`, below).
-
-The default dispatcher will always try to find a method named `index` at the
-end of the branch traversal. In the example above, the URI "/onepage/" would
-result in the call: ``app.root.onepage.index()``. Depending on the use of the
-:func:`trailing_slash Tool <cherrypy.lib.cptools.trailing_slash>`,
-that might be interrupted with an HTTPRedirect, but
-otherwise, both ``"/onepage"`` (no trailing slash) and ``"/onepage/"``
-(trailing slash) will result in the same call.
-
-.. _kwargs:
-
-Keyword Arguments
-^^^^^^^^^^^^^^^^^
-
-.. index:: forms, **kwargs
-
-Any page handler that is called by CherryPy (``index``, or any other suitable
-method) can receive additional data from HTML or other forms using
-*keyword arguments*. For example, the following login form sends the
-``username`` and the ``password`` as form arguments using the POST method::
-
- <form action="doLogin" method="post">
- <p>Username</p>
- <input type="text" name="username" value=""
- size="15" maxlength="40"/>
- <p>Password</p>
- <input type="password" name="password" value=""
- size="10" maxlength="40"/>
- <p><input type="submit" value="Login"/></p>
- <p><input type="reset" value="Clear"/></p>
- </form>
-
-The following code can be used to handle this URL::
-
- class Root(object):
- def doLogin(self, username=None, password=None):
- """Check the username & password"""
- doLogin.exposed = True
-
-Both arguments have to be declared as *keyword arguments*. The default value
-can be used either to provide a suitable default value for optional arguments,
-or to provide means for the application to detect if some values were missing
-from the request.
-
-CherryPy supports both the GET and POST method for HTML forms. Arguments are
-passed the same way, regardless of the original method used by the browser to
-send data to the web server.
-
-.. _args:
-
-Positional Arguments
-^^^^^^^^^^^^^^^^^^^^
-
-.. index:: path, virtual path, path segments, *args, positional arguments
-
-When a request is processed, the URI is split into its components, and each
-one is matched in order against the nodes in the tree. Any trailing components
-are "virtual path" components and are passed as positional arguments. For
-example, the URI ``"/branch/leaf/4"`` might result in
-the call: ``app.root.branch.leaf(4)``, or ``app.root.index(branch, leaf, 4)``
-depending on how you have your handlers arranged.
-
-Partial matches can happen when a URL contains components that do not map to
-the object tree. This can happen for a number of reasons. For example, it may
-be an error; the user just typed the wrong URL. But it also can mean that the
-URL contains extra arguments.
-
-For example, assume that you have a blog-like application written in CherryPy
-that takes the year, month and day as part of the URL
-``http://localhost/blog/2005/01/17``. This URL can be handled by the
-following code::
-
- class Root(object):
- def blog(self, year, month, day):
- """Deliver the blog post. According to *year* *month* *day*.
- """
- blog.exposed = True
-
- root = Root()
-
-So the URL above will be mapped as a call to::
-
- root.blog('2005', '01', '17')
-
-In this case, there is a partial match up to the ``blog`` component. The rest
-of the URL can't be found in the mounted object tree. In this case, the
-``blog()`` method will be called, and the positional parameters will
-receive the remaining path segments as arguments. The values are passed as
-strings; in the above mentioned example, the arguments would still need to be
-converted back into numbers, but the idea is correctly presented.
-
-.. _defaultmethods:
-
-Default methods
-^^^^^^^^^^^^^^^
-
-.. index:: default, methods; default
-
-If the default dispatcher is not able to locate a suitable page handler by
-walking down the tree, it has a last-ditch option: it starts walking back
-''up'' the tree looking for `default` methods. Default methods work just like
-any other method with positional arguments, but are defined one level further
-down, in case you have multiple methods to expose. For example, we could have
-written the above "blog" example equivalently with a "default" method instead::
-
- class Blog(object):
- def default(self, year, month, day):
- """This method catch the positional arguments
- *year*,*month*,*day* to delivery the blog content.
- """
- default.exposed = True
-
-
- class Root(object):
- pass
-
- root = Root()
- root.blog = Blog()
-
-So the URL ``http://localhost/blog/2005/01/17`` will be mapped as a call to::
-
- root.blog.default('2005', '01', '17')
-
-You could achieve the same effect by defining a ``__call__`` method in this
-case, but "default" just reads better. ;)
-
-Special characters
-^^^^^^^^^^^^^^^^^^
-
-You can use dots in a URI like ``/path/to/my.html``, but Python method names
-don't allow dots. To work around this, the default dispatcher converts all dots
-in the URI to underscores before trying to find the page handler. In the
-example, therefore, you would name your page handler "def my_html". However,
-this means the page is also available at the URI ``/path/to/my_html``.
-If you need to protect the resource (e.g. with authentication), **you must
-protect both URLs**.
-
-.. versionadded:: 3.2
- The default dispatcher now takes a 'translate' argument, which converts all
- characters in string.punctuation to underscores using the builtin
- :meth:`str.translate <str.translate>` method of string objects.
- You are free to specify any other translation string of length 256.
-
-Other Dispatchers
------------------
-
-But Mr. Fielding mentions two kinds of "mapping implementations" above: trees
-and hash tables ('dicts' in Python). Some web developers claim trees are
-difficult to change as an application evolves, and prefer to use dicts
-(or a list of tuples) instead. Under these schemes, the mapping key is often
-a regular expression, and the value is the handler function. For example::
-
- def root_index(name):
- return "Hello, %s!" % name
-
- def branch_leaf(size):
- return str(int(size) + 3)
-
- mappings = [
- (r'^/([^/]+)$', root_index),
- (r'^/branch/leaf/(\d+)$', branch_leaf),
- ]
-
-CherryPy allows you to use a :class:`Dispatcher<cherrypy._cpdispatch.Dispatcher>`
-other than the default if you wish. By using another
-:class:`Dispatcher <cherrypy._cpdispatch.Dispatcher>` (or writing your own),
-you gain complete control over the arrangement and behavior of your page
-handlers (and config). To use another dispatcher, set the
-``request.dispatch`` config entry to the dispatcher you like::
-
- d = cherrypy.dispatch.RoutesDispatcher()
- d.connect(name='hounslow', route='hounslow', controller=City('Hounslow'))
- d.connect(name='surbiton', route='surbiton', controller=City('Surbiton'),
- action='index', conditions=dict(method=['GET']))
- d.mapper.connect('surbiton', controller='surbiton',
- action='update', conditions=dict(method=['POST']))
-
- conf = {'/': {'request.dispatch': d}}
- cherrypy.tree.mount(root=None, config=conf)
-
-A couple of notes about the example above:
-
-* Since Routes has no controller hierarchy, there's nothing to pass as a
- root to :func:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>`;
- pass ``None`` in this case.
-* Usually you'll use the same dispatcher for an entire app, so specifying it
- at the root ("/") is common. But you can use different dispatchers for
- different paths if you like.
-* Because the dispatcher is so critical to finding handlers (and their
- ancestors), this is one of the few cases where you *cannot* use
- :ref:`_cp_config <cp_config>`; it's a chicken-and-egg problem:
- you can't ask a handler you haven't found yet how it wants to be found.
-* Since Routes are explicit, there's no need to set the ``exposed`` attribute.
- **All routes are always exposed.**
-
-CherryPy ships with additional Dispatchers in :mod:`cherrypy._cpdispatch`.
-
-.. _pagehandlers:
-
-PageHandler Objects
-===================
-
-Because the Dispatcher sets
-:attr:`cherrypy.request.handler <cherrypy._cprequest.Request.handler>`,
-it can also control
-the input and output of that handler function by wrapping the actual handler.
-The default Dispatcher passes "virtual path" components as positional arguments
-and passes query-string and entity (GET and POST) parameters as keyword
-arguments. It uses a :class:`PageHandler <cherrypy._cpdispatch.PageHandler>`
-object for this, which looks a lot like::
-
- class PageHandler(object):
- """Callable which sets response.body."""
-
- def __init__(self, callable, *args, **kwargs):
- self.callable = callable
- self.args = args
- self.kwargs = kwargs
-
- def __call__(self):
- return self.callable(*self.args, **self.kwargs)
-
-The actual default PageHandler is a little bit more complicated (because the
-args and kwargs are bound later), but you get the idea. And you can see how
-easy it would be to provide your own behavior, whether your own inputs or your
-own way of modifying the output. Remember, whatever is returned from the
-handler will be bound to
-:attr:`cherrypy.response.body <cherrypy._cprequest.Response.body>` and will
-be used as the response entity.
-
-Replacing page handlers
------------------------
-
-The handler that's going to be called during a request is available at
-:attr:`cherrypy.request.handler <cherrypy._cprequest.Request.handler>`,
-which means your code has a chance to replace it before the handler runs.
-It's a snap to write a Tool to do so with a
-:class:`HandlerWrapperTool <cherrypy._cptools.HandlerWrapperTool>`::
-
- to_skip = (KeyboardInterrupt, SystemException, cherrypy.HTTPRedirect)
- def PgSQLWrapper(next_handler, *args, **kwargs):
- trans.begin()
- try:
- result = next_handler(*args, **kwargs)
- trans.commit()
- except Exception, e:
- if not isinstance(e, to_skip):
- trans.rollback()
- raise
- trans.end()
- return result
-
- cherrypy.tools.pgsql = cherrypy._cptools.HandlerWrapperTool(PgSQLWrapper)
-
-Configuration
-=============
-
-The default arrangement of CherryPy handlers is a tree. This enables a very
-powerful configuration technique: config can be attached to a node in the tree
-and cascade down to all children of that node. Since the mapping of URI's to
-handlers is not always 1:1, this provides a flexibility which is not as easily
-definable in other, flatter arrangements.
-
-However, because the arrangement of config is directly related to the
-arrangement of handlers, it is the responsibility of the Dispatcher to collect
-per-handler config, merge it with per-URI and global config, and bind the
-resulting dict to :attr:`cherrypy.request.config <cherrypy._cprequest.Request.config>`.
-This dict is of depth 1 and will contain all config entries which are in
-effect for the current request.
-
diff --git a/sphinx/source/tutorial/engine.rst b/sphinx/source/tutorial/engine.rst
deleted file mode 100644
index ebb174ea..00000000
--- a/sphinx/source/tutorial/engine.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-*******************
-The CherryPy Engine
-*******************
-
-The :class:`cherrypy.engine<cherrypy.process.wspbus.Bus>` object contains and
-manages site-wide behavior: daemonization, HTTP server start/stop, process
-reload, signal handling, drop privileges, PID file management, logging for
-all of these, and many more.
-
-Any task that needs to happen outside of the request process is managed by
-the Engine via *Plugins*. You can add your own site-wide
-behaviors, too; see :doc:`/progguide/extending/customplugins`. The Engine
-handles these tasks whether you start your site from a script, from an external
-server process like Apache, or via :doc:`cherryd</deployguide/cherryd>`.
-
-State Management
-================
-
-The Engine manages the *state* of the site. Engine methods like
-:func:`cherrypy.engine.start<cherrypy.process.wspbus.start>` move it
-from one state to another::
-
- O
- |
- V
- STOPPING --> STOPPED --> EXITING -> X
- A A |
- | \___ |
- | \ |
- | V V
- STARTED <-- STARTING
-
-Note in particular that the Engine allows you to stop and restart it again
-without stopping the process. This can be used to build highly dynamic sites,
-and is invaluable for debugging live servers.
-
-.. _channels:
-
-Channels
-========
-
-The Engine uses topic-based publish-subscribe messaging to manage event-driven
-behaviors like autoreload and daemonization. When the Engine moves from one
-state to another, it *publishes* a message on a *channel* named after the
-activity. For example, when you call
-:func:`cherrypy.engine.start<cherrypy.process.wspbus.start>`, the Engine
-moves from the STOPPED state to the STARTING state, publishes a message on
-the "start" *channel*, and then moves to the STARTED state.
-
-.. _plugins:
-
-Plugins
-=======
-
-Engine Plugins package up channel listeners into easy-to-use components.
-
-Engine Plugins have a :func:`subscribe<cherrypy.process.plugins.SimplePlugin.subscribe>`
-method which you can use to "turn them on"; that is, they will start listening
-for messages published on event channels. For example, to turn on PID file
-management::
-
- from cherrypy.process.plugins import PIDFile
- p = PIDFile(cherrypy.engine, "/var/run/myapp.pid")
- p.subscribe()
-
-If you want to turn off a plugin, call ``p.unsubscribe()``.
-
-The following builtin plugins are subscribed by default:
-
- * :doc:`Timeout Monitor</progguide/responsetimeouts>`
- * :class:`Autoreloader<cherrypy.process.plugins.Autoreloader>` (off in the "production" :ref:`environment<environments>`)
- * :class:`cherrypy.server<cherrypy._cpserver.Server>`
- * :class:`cherrypy.checker<cherrypy._cpchecker.Checker>`
- * :doc:`Engine log messages </refman/_cplogging>` go to :class:`cherrypy.log<cherrypy._GlobalLogManager>`.
- * A :class:`Signal Handler<cherrypy.process.plugins.SignalHandler>`.
-
diff --git a/sphinx/source/tutorial/exposing.rst b/sphinx/source/tutorial/exposing.rst
deleted file mode 100644
index 14ca5880..00000000
--- a/sphinx/source/tutorial/exposing.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-********
-Exposing
-********
-
-Any object that is attached to the root object is traversible via the internal
-URL-to-object mapping routine. However, it does not mean that the object itself
-is directly accessible via the Web. For this to happen, the object has to be
-**exposed**.
-
-Exposing objects
-----------------
-
-CherryPy maps URL requests to objects and invokes the suitable callable
-automatically. The callables that can be invoked as a result of external
-requests are said to be **exposed**.
-
-Objects are **exposed** in CherryPy by setting the ``exposed`` attribute.
-Most often, a method on an object is the callable that is to be invoked. In
-this case, one can directly set the exposed attribute::
-
- class Root(object):
- def index(self):
- """Handle the / URI"""
- index.exposed = True
-
-
-or use a decorator::
-
- class Root(object):
- @cherrypy.expose
- def index(self):
- """Handle the / URI"""
-
-
-When it is a special method, such as ``__call__``, that is to be invoked,
-the exposed attribute must be set on the class itself::
-
- class Node(object):
- exposed = True
- def __call__(self):
- """ """
-
-The techniques can be mixed, for example::
-
- """This example can handle the URIs:
- / -> Root.index
- /page -> Root.page
- /node -> Node.__call__
- """
- import cherrypy
-
-
- class Node(object):
- exposed = True
-
- def __call__(self):
- return "The node content"
-
-
- class Root(object):
-
- def __init__(self):
- self.node = Node()
-
- @cherrypy.expose
- def index(self):
- return "The index of the root object"
-
- def page(self):
- return 'This is the "page" content'
- page.exposed = True
-
-
- if __name__ == '__main__':
- cherrypy.quickstart(Root())
-
diff --git a/sphinx/source/tutorial/files/songs.py b/sphinx/source/tutorial/files/songs.py
deleted file mode 100644
index 4dd4ab8e..00000000
--- a/sphinx/source/tutorial/files/songs.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import cherrypy
-
-songs = {
- '1': {
- 'title': 'Lumberjack Song',
- 'artist': 'Canadian Guard Choir'
- },
-
- '2': {
- 'title': 'Always Look On the Bright Side of Life',
- 'artist': 'Eric Idle'
- },
-
- '3': {
- 'title': 'Spam Spam Spam',
- 'artist': 'Monty Python'
- }
-}
-
-class Songs:
-
- exposed = True
-
- def GET(self, id=None):
-
- if id == None:
- return('Here are all the songs we have: %s' % songs)
- elif id in songs:
- song = songs[id]
-
- return('Song with the ID %s is called %s, and the artist is %s' % (id, song['title'], song['artist']))
- else:
- return('No song with the ID %s :-(' % id)
-
- def POST(self, title, artist):
-
- id = str(max([int(_) for _ in songs.keys()]) + 1)
-
- songs[id] = {
- 'title': title,
- 'artist': artist
- }
-
- return ('Create a new song with the ID: %s' % id)
-
- def PUT(self, id, title=None, artist=None):
- if id in songs:
- song = songs[id]
-
- song['title'] = title or song['title']
- song['artist'] = artist or song['artist']
-
- return('Song with the ID %s is now called %s, and the artist is now %s' % (id, song['title'], song['artist']))
- else:
- return('No song with the ID %s :-(' % id)
-
- def DELETE(self, id):
- if id in songs:
- songs.pop(id)
-
- return('Song with the ID %s has been deleted.' % id)
- else:
- return('No song with the ID %s :-(' % id)
-
-if __name__ == '__main__':
-
- cherrypy.tree.mount(
- Songs(), '/api/songs',
- {'/':
- {'request.dispatch': cherrypy.dispatch.MethodDispatcher()}
- }
- )
-
- cherrypy.engine.start()
- cherrypy.engine.block()
diff --git a/sphinx/source/tutorial/index.rst b/sphinx/source/tutorial/index.rst
deleted file mode 100644
index ea1615eb..00000000
--- a/sphinx/source/tutorial/index.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-********
-Tutorial
-********
-
-What is this tutorial about?
-============================
-
-This tutorial covers the basic steps for a newcomer to come to grips with
-CherryPy's unique approach to web application development. After following
-this tutorial, you will be able to understand how CherryPy
-applications work, and also to implement simple but yet powerful applications
-on your own. Some knowledge of the Python programming language is assumed.
-One does not need to be an expert to work with CherryPy, but a good
-understanding of object-oriented basics is strongly recommended.
-
-This tutorial only covers the basic features of CherryPy, but it tries to
-present them in a way that makes it easier for you to discover how to
-use them. The CherryPy distribution comes with several good tutorial applications;
-however, the best way to master CherryPy is to use it to write your own
-Web applications. The embedded web server makes it easy for anyone not only
-to try, but also to deploy local applications, or even small Internet-enabled
-web sites. Try it, and let us know what you did with it!
-
-Knowledge required
-------------------
-
-It is assumed that the user has:
-
- * Some knowledge of the Python programming language
- * Some experience with basic object oriented programming
- * Some knowledge of HTML, which is necessary to build the Web pages
-
-Learning Python
----------------
-
-As stated above, this is not a guide to the Python language. There are plenty of
-good resources for those learning Python (just to name a few among the best:
-`Learn Python The Hard Way <http://learnpythonthehardway.org/>`_,
-`A Byte Of Python <http://swaroopch.com/notes/python/>`_ and
-`Dive into Python <http://www.diveintopython.net/>`_).
-The `official Python website <http://www.python.org>`_ lists some good
-resources, including an excellent
-`tutorial <http://docs.python.org/tut/tut.html>`_.
-
-Start the Tutorial
-==================
-
-.. toctree::
- :maxdepth: 2
-
- basics
- exposing
- dispatching
- config
- tools
- engine
- REST
-
diff --git a/sphinx/source/tutorial/tools.rst b/sphinx/source/tutorial/tools.rst
deleted file mode 100644
index 9076b77f..00000000
--- a/sphinx/source/tutorial/tools.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-*****
-Tools
-*****
-
-CherryPy core is extremely light and clean. It contains only the necessary
-features to support the HTTP protocol and to call the correct object for
-each request. Additional request-time features can be added to it using
-**modular tools**.
-
-Tools are a great way to package up behavior that happens outside your page
-handlers. A tool is an object that has a chance to work on a request as it
-goes through the usual CherryPy processing stages, both before and after it
-gets to your handler. Several tools are provided
-as part of the standard CherryPy library, available in
-:mod:`cherrypy.tools <cherrypy._cptools>`. See :doc:`/progguide/builtintools`.
-
-Tools provide a lot of flexibility. Different tools can be applied to different
-parts of the site, and the order of tools can be changed. The user can write
-custom tools for special applications, changing the behavior of CherryPy
-without the need to change its internals.
-See :doc:`/progguide/extending/customtools`.
-
-Using Tools
-===========
-
-Config Files
-------------
-
-You can turn on Tools in config, whether a file or a dict.
-For example, you can add static directory serving with the builtin
-``staticdir`` tool with just a few lines in your config file::
-
- [/docroot]
- tools.staticdir.on: True
- tools.staticdir.root: "/path/to/app"
- tools.staticdir.dir: 'static'
-
-This turns on the ``staticdir`` tool for all *URLs* that start with "/docroot".
-
-_cp_config
-----------
-
-You can also enable and configure tools *per controller* or *per handler*
-using :ref:`_cp_config <cp_config>`::
-
- class docroot(object):
-
- _cp_config = {'tools.staticdir.on': True,
- 'tools.staticdir.root: "/path/to/app",
- 'tools.staticdir.dir': 'static'}
-
-Decorators
-----------
-
-But we can do even better by using the **builtin decorator support** that all
-Tools have::
-
- class docroot(object):
-
- @tools.staticdir(root="/path/to/app", dir='static')
- def page(self):
- # ...
-
-
-Page Handlers
--------------
-
-...and in this case, we can do even **better** because tools.staticdir is a
-:class:`HandlerTool <cherrypy._cptools.HandlerTool>`, and therefore can be
-used directly as a page handler::
-
- class docroot(object):
-
- static = tools.staticdir.handler(
- section='static', root="/path/to/app", dir='static')
-
-Direct invocation
------------------
-
-Finally, you can use (most) Tools directly, by calling the function they wrap.
-They expose this via the 'callable' attribute::
-
- def page(self):
- tools.response_headers.callable([('Content-Language', 'fr')])
- return "Bonjour, le Monde!"
- page.exposed = True
-
-help(Tool)
-==========
-
-Because the underlying function is wrapped in a tool, you need to call
-``help(tools.whatevertool.callable)`` if you want the docstring for it.
-Using ``help(tools.whatevertool)`` will give you help on how to use it
-as a Tool (for example, as a decorator).
-
-Tools also are also **inspectable** automatically. They expose their own
-arguments as attributes::
-
- >>> dir(cherrypy.tools.session_auth)
- [..., 'anonymous', 'callable', 'check_username_and_password',
- 'do_check', 'do_login', 'do_logout', 'handler', 'login_screen',
- 'on_check', 'on_login', 'on_logout', 'run', 'session_key']
-
-This makes IDE calltips especially useful, even when writing config files!
-
diff --git a/sphinx/util/convert-trac.py b/sphinx/util/convert-trac.py
deleted file mode 100644
index 52538632..00000000
--- a/sphinx/util/convert-trac.py
+++ /dev/null
@@ -1,117 +0,0 @@
-#!python
-
-"""
-%prog <filename>
-
-A utility script for performing some commonly-encountered patterns in
-Trac Wiki format into reStructuredText (rst).
-
-filename is the name of the text file to be saved. If -U is not used,
-the file is converted in-place and filename is also the name of the
-source.
-"""
-
-from __future__ import print_function
-import sys
-import re
-import inspect
-import optparse
-import shutil
-import urllib2
-from StringIO import StringIO
-
-def get_options():
- global options
- parser = optparse.OptionParser(usage=inspect.cleandoc(__doc__))
- parser.add_option('-U', '--url', help="Trac URL from which to retrieve source")
- options, args = parser.parse_args()
- try:
- options.filename = args.pop()
- except IndexError:
- parser.error("Filename required")
-
-# each of the replacement functions should have a docstring
-# which is a regular expression to be matched.
-
-def replace_external_link(matcher):
- r"\[(?P<href>(?P<scheme>\w+)\://.+?) (?P<name>.+?)\]"
- return '`{name} <{href}>`_'.format(**matcher.groupdict())
-
-def replace_wiki_link(matcher):
- r"\[wiki\:(?P<ref>.+?) (?P<name>.+?)\]"
- return '`{name} <TODO-fix wiki target {ref}>`_'.format(**matcher.groupdict())
-
-# character array indexed by level for characters
-heading_characters = [None, '*', '=', '-', '^']
-
-def replace_headings(matcher):
- r"^(?P<level>=+) (?P<name>.*) (?P=level)$"
- level = len(matcher.groupdict()['level'])
- char = heading_characters[level]
- name = matcher.groupdict()['name']
- lines = [name, char*len(name)]
- if level == 1:
- lines.insert(0, char*len(name))
- return '\n'.join(lines)
-
-def indent(block):
- add_indent = lambda s: ' ' + s
- lines = StringIO(block)
- i_lines = map(add_indent, lines)
- return ''.join(i_lines)
-
-def replace_inline_code(matcher):
- r"\{\{\{(?P<code>[^\n]*?)\}\}\}"
- return '``{code}``'.format(**matcher.groupdict())
-
-def replace_code_block(matcher):
- r"\{\{\{\n(?P<code>(.|\n)*?)^\}\}\}"
- return '::\n\n' + indent(matcher.groupdict()['code'])
-
-def replace_page_outline(matcher):
- r"\[\[PageOutline\]\]\n"
- return ''
-
-def replace_bang_symbols(matcher):
- r"!(?P<symbol>\w+)"
- return matcher.groupdict()['symbol']
-
-# a number of the files end in
-"""{{{
-#!html
-<h2 class='compatibility'>Older versions</h2>
-}}}""" # and everything after is garbage, so just remove it.
-def remove_2x_compat_notes(matcher):
- r"\{\{\{\n#!html\n<h2(.|\n)*"
- return ''
-
-replacements = [remove_2x_compat_notes] + \
- [func for name, func in globals().items() if name.startswith('replace_')]
-
-def normalize_linebreaks(text):
- return text.replace('\r\n', '\n')
-
-def convert_file():
- filename = options.filename
- if options.url:
- text = urllib2.urlopen(options.url).read()
- text = normalize_linebreaks(text)
- else:
- shutil.copy(filename, filename+'.bak')
- text = open(filename).read()
- # iterate over each of the replacements and execute it
- new_text = text
- for repl in replacements:
- pattern = re.compile(inspect.getdoc(repl), re.MULTILINE)
- new_text = pattern.sub(repl, new_text)
-
- open(filename, 'w').write(new_text)
- print("done")
-
-
-def handle_command_line():
- get_options()
- convert_file()
-
-if __name__ == '__main__':
- handle_command_line()
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..e68f3e2c
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,8 @@
+[tox]
+envlist = py26, py27, py32, py33, pypy
+
+[testenv]
+deps = nose
+ Routes
+commands =
+ nosetests -s []