summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cherrypy/__init__.py3
-rw-r--r--cherrypy/_cpchecker.py117
-rw-r--r--cherrypy/_cpconfig.py12
-rw-r--r--cherrypy/_cpengine.py6
-rw-r--r--cherrypy/test/checkerdemo.py2
5 files changed, 111 insertions, 29 deletions
diff --git a/cherrypy/__init__.py b/cherrypy/__init__.py
index 264925d5..58f61bc6 100644
--- a/cherrypy/__init__.py
+++ b/cherrypy/__init__.py
@@ -269,3 +269,6 @@ from cherrypy import _cpconfig
# Use _global_conf_alias so quickstart can use 'config' as an arg
# without shadowing cherrypy.config.
config = _global_conf_alias = _cpconfig.Config()
+
+from cherrypy import _cpchecker
+checker = _cpchecker.Checker()
diff --git a/cherrypy/_cpchecker.py b/cherrypy/_cpchecker.py
index 104f7db2..8fed2fb7 100644
--- a/cherrypy/_cpchecker.py
+++ b/cherrypy/_cpchecker.py
@@ -6,24 +6,32 @@ import cherrypy
class Checker(object):
- global_config_contained_paths = False
+ on = True
+
+ def __init__(self):
+ self._populate_known_types()
def __call__(self):
- oldformatwarning = warnings.formatwarning
- warnings.formatwarning = self.formatwarning
- try:
- for name in dir(self):
- if name.startswith("check_"):
- method = getattr(self, name)
- if method and callable(method):
- method()
- finally:
- warnings.formatwarning = oldformatwarning
+ """Run all check_* methods."""
+ if self.on:
+ oldformatwarning = warnings.formatwarning
+ warnings.formatwarning = self.formatwarning
+ try:
+ for name in dir(self):
+ if name.startswith("check_"):
+ method = getattr(self, name)
+ if method and callable(method):
+ method()
+ finally:
+ warnings.formatwarning = oldformatwarning
def formatwarning(self, message, category, filename, lineno):
"""Function to format a warning."""
return "CherryPy Checker:\n%s\n\n" % message
+ # This value should be set inside _cpconfig.
+ global_config_contained_paths = False
+
def check_skipped_app_config(self):
for sn, app in cherrypy.tree.apps.iteritems():
if not app.config:
@@ -58,11 +66,13 @@ class Checker(object):
if os.path.isabs(dir):
fulldir = dir
if root:
- msg = "dir is an absolute path, even though a root is provided."
+ msg = ("dir is an absolute path, even "
+ "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."
@@ -74,12 +84,16 @@ class Checker(object):
if fulldir and not os.path.exists(fulldir):
if msg:
msg += "\n"
- msg += "%r (root + dir) is not an existing filesystem path." % fulldir
+ msg += ("%r (root + dir) is not an existing "
+ "filesystem path." % fulldir)
if msg:
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',
@@ -90,7 +104,8 @@ class Checker(object):
'log_to_screen': 'log.screen',
'show_tracebacks': 'request.show_tracebacks',
'throw_errors': 'request.throw_errors',
- 'profiler.on': 'cherrypy.tree.mount(profiler.make_app(cherrypy.Application(Root())))',
+ 'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
+ 'cherrypy.Application(Root())))'),
}
deprecated = {}
@@ -102,10 +117,12 @@ class Checker(object):
for k, v in conf.iteritems():
if k in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead.\n"
- "section: [%s]" % (k, self.obsolete[k], section))
+ "section: [%s]" %
+ (k, self.obsolete[k], section))
elif k in self.deprecated:
warnings.warn("%r is deprecated. Use %r instead.\n"
- "section: [%s]" % (k, self.deprecated[k], section))
+ "section: [%s]" %
+ (k, self.deprecated[k], section))
else:
if section in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead."
@@ -120,8 +137,13 @@ class Checker(object):
for sn, app in cherrypy.tree.apps.iteritems():
self._compat(app.config)
- known_config_namespaces = ["engine", "hooks", "log", "request",
- "response", "server", "tools", "wsgi"]
+
+ # ------------------------ Known Namespaces ------------------------ #
+
+ known_config_namespaces = ["checker", "engine", "hooks", "log",
+ "request", "response", "server",
+ "tools",
+ "wsgi"]
def _known_ns(self, config):
for section, conf in config.iteritems():
@@ -131,7 +153,8 @@ class Checker(object):
atoms = k.split(".")
if len(atoms) > 1:
if atoms[0] not in self.known_config_namespaces:
- if atoms[0] == "cherrypy" and atoms[1] in self.known_config_namespaces:
+ if (atoms[0] == "cherrypy" and
+ atoms[1] in self.known_config_namespaces):
msg = ("The config entry %r is invalid; "
"try %r instead.\nsection: [%s]"
% (k, ".".join(atoms[1:]), section))
@@ -145,3 +168,55 @@ class Checker(object):
"""Process config and warn on each unknown config namespace."""
for sn, app in cherrypy.tree.apps.iteritems():
self._known_ns(app.config)
+
+
+ # -------------------------- Config Types -------------------------- #
+
+ known_config_types = {}
+
+ def _populate_known_types(self):
+ import __builtin__
+ builtins = [x for x in vars(__builtin__).values()
+ if type(x) is type(str)]
+
+ def traverse(obj, namespace):
+ for name in dir(obj):
+ vtype = type(getattr(obj, name, None))
+ if vtype in builtins:
+ self.known_config_types[namespace + "." + name] = vtype
+
+ traverse(cherrypy.request, "request")
+ traverse(cherrypy.response, "response")
+ traverse(cherrypy.server, "server")
+ traverse(cherrypy.engine, "engine")
+ traverse(cherrypy.log, "log")
+
+ def _known_types(self, config):
+ msg = ("The config entry %r in section %r is of type %r, "
+ "which does not match the expected type %r.")
+
+ for section, conf in config.iteritems():
+ if isinstance(conf, dict):
+ for k, v in conf.iteritems():
+ if v is not None:
+ expected_type = self.known_config_types.get(k, None)
+ vtype = type(v)
+ if expected_type and vtype != expected_type:
+ warnings.warn(msg % (k, section, vtype.__name__,
+ expected_type.__name__))
+ else:
+ k, v = section, conf
+ if v is not None:
+ expected_type = self.known_config_types.get(k, None)
+ vtype = type(v)
+ if expected_type and vtype != expected_type:
+ warnings.warn(msg % (k, section, vtype.__name__,
+ expected_type.__name__))
+
+ def check_config_types(self):
+ """Assert that config values are of the same type as default values."""
+ self._known_types(cherrypy.config)
+ for sn, app in cherrypy.tree.apps.iteritems():
+ self._known_types(app.config)
+
+
diff --git a/cherrypy/_cpconfig.py b/cherrypy/_cpconfig.py
index 2c9a84e4..fb2101c3 100644
--- a/cherrypy/_cpconfig.py
+++ b/cherrypy/_cpconfig.py
@@ -69,6 +69,9 @@ Current namespaces:
server: Controls the default HTTP server via cherrypy.server.
These can only be declared in the global config.
tools: Runs and configures additional request-processing packages.
+ checker: Controls the 'checker', which looks for common errors in
+ app state (including config) when the engine starts.
+ Global config only.
The only key that does not exist in a namespace is the "environment" entry.
This special entry 'imports' other config entries from a template stored in
@@ -92,20 +95,20 @@ import cherrypy
environments = {
"staging": {
'engine.autoreload_on': False,
- 'engine.checker': None,
+ 'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
},
"production": {
'engine.autoreload_on': False,
- 'engine.checker': None,
+ 'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'log.screen': False,
},
"test_suite": {
'engine.autoreload_on': False,
- 'engine.checker': None,
+ 'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': True,
'log.screen': False,
@@ -203,6 +206,7 @@ class Config(dict):
namespaces = {"server": lambda k, v: setattr(cherrypy.server, k, v),
"engine": lambda k, v: setattr(cherrypy.engine, k, v),
"log": lambda k, v: setattr(cherrypy.log, k, v),
+ "checker": lambda k, v: setattr(cherrypy.checker, k, v),
}
def __init__(self):
@@ -228,7 +232,7 @@ class Config(dict):
if isinstance(config.get("global", None), dict):
if len(config) > 1:
- cherrypy.engine.checker.global_config_contained_paths = True
+ cherrypy.checker.global_config_contained_paths = True
config = config["global"]
if 'environment' in config:
diff --git a/cherrypy/_cpengine.py b/cherrypy/_cpengine.py
index 114a27e0..efb5ee31 100644
--- a/cherrypy/_cpengine.py
+++ b/cherrypy/_cpengine.py
@@ -9,7 +9,7 @@ import threading
import time
import cherrypy
-from cherrypy import _cprequest, _cpchecker
+from cherrypy import _cprequest
# Use a flag to indicate the state of the application engine.
STOPPED = 0
@@ -52,7 +52,6 @@ class Engine(object):
autoreload_on = True
autoreload_frequency = 1
autoreload_match = ".*"
- checker = _cpchecker.Checker()
def __init__(self):
self.state = STOPPED
@@ -75,8 +74,7 @@ class Engine(object):
"""Start the application engine."""
self.state = STARTING
- if self.checker:
- self.checker()
+ cherrypy.checker()
for func in self.on_start_engine_list:
func()
diff --git a/cherrypy/test/checkerdemo.py b/cherrypy/test/checkerdemo.py
index 37fc2901..fdc229a5 100644
--- a/cherrypy/test/checkerdemo.py
+++ b/cherrypy/test/checkerdemo.py
@@ -33,5 +33,7 @@ if __name__ == '__main__':
'/unknown': {'toobles.gzip.on': True},
# Warn special on cherrypy.<known ns>.*
'/cpknown': {'cherrypy.tools.encode.on': True},
+ # Warn on mismatched types
+ '/conftype': {'request.show_tracebacks': 14},
}
cherrypy.quickstart(Root(), config=conf)