Index: third_party/cherrypy/_cpchecker.py |
=================================================================== |
--- third_party/cherrypy/_cpchecker.py (revision 0) |
+++ third_party/cherrypy/_cpchecker.py (revision 0) |
@@ -0,0 +1,327 @@ |
+import os |
+import warnings |
+ |
+import cherrypy |
+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 |
+ of its own methods whose names start with ``check_``. If you wish |
+ to disable selected checks, simply add a line in your global |
+ config which sets the appropriate method to False:: |
+ |
+ [global] |
+ checker.check_skipped_app_config = False |
+ |
+ You may also dynamically add or replace ``check_*`` methods in this way. |
+ """ |
+ |
+ on = True |
+ """If True (the default), run all checks; if False, turn off all checks.""" |
+ |
+ |
+ def __init__(self): |
+ self._populate_known_types() |
+ |
+ def __call__(self): |
+ """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 hasattr(method, '__call__'): |
+ method() |
+ finally: |
+ warnings.formatwarning = oldformatwarning |
+ |
+ def formatwarning(self, message, category, filename, lineno, line=None): |
+ """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_app_config_entries_dont_start_with_script_name(self): |
+ """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 |
+ if not app.config: |
+ continue |
+ if sn == '': |
+ continue |
+ sn_atoms = sn.strip("/").split("/") |
+ for key in app.config.keys(): |
+ 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)) |
+ |
+ def check_site_config_entries_in_app_config(self): |
+ """Check for mounted Applications that have site-scoped config.""" |
+ for sn, app in iteritems(cherrypy.tree.apps): |
+ if not isinstance(app, cherrypy.Application): |
+ continue |
+ |
+ msg = [] |
+ for section, entries in iteritems(app.config): |
+ if section.startswith('/'): |
+ 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)) |
+ 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) |
+ warnings.warn(os.linesep.join(msg)) |
+ |
+ def check_skipped_app_config(self): |
+ """Check for mounted Applications that have no config.""" |
+ for sn, app in cherrypy.tree.apps.items(): |
+ if not isinstance(app, cherrypy.Application): |
+ continue |
+ if not app.config: |
+ msg = "The Application mounted at %r has an empty config." % sn |
+ if self.global_config_contained_paths: |
+ msg += (" It looks like the config you passed to " |
+ "cherrypy.config.update() contains application-" |
+ "specific sections. You must explicitly pass " |
+ "application config via " |
+ "cherrypy.tree.mount(..., config=app_config)") |
+ warnings.warn(msg) |
+ return |
+ |
+ def check_app_config_brackets(self): |
+ """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 |
+ if not app.config: |
+ continue |
+ for key in app.config.keys(): |
+ if key.startswith("[") or key.endswith("]"): |
+ warnings.warn( |
+ "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)) |
+ |
+ def check_static_paths(self): |
+ """Check Application config for incorrect static paths.""" |
+ # Use the dummy Request object in the main thread. |
+ request = cherrypy.request |
+ for sn, app in cherrypy.tree.apps.items(): |
+ if not isinstance(app, cherrypy.Application): |
+ continue |
+ request.app = app |
+ for section in app.config: |
+ # get_resource will populate request.config |
+ request.get_resource(section + "/dummy.html") |
+ conf = request.config.get |
+ |
+ if conf("tools.staticdir.on", False): |
+ msg = "" |
+ root = conf("tools.staticdir.root") |
+ dir = conf("tools.staticdir.dir") |
+ if dir is None: |
+ msg = "tools.staticdir.dir is not set." |
+ else: |
+ fulldir = "" |
+ if os.path.isabs(dir): |
+ fulldir = dir |
+ if root: |
+ 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) |
+ else: |
+ if not root: |
+ 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 |
+ |
+ if fulldir and not os.path.exists(fulldir): |
+ if msg: |
+ msg += "\n" |
+ 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', |
+ 'log_config_options': None, |
+ 'log_file': 'log.error_file', |
+ 'log_file_not_found': None, |
+ 'log_request_headers': 'tools.log_headers.on', |
+ '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())))'), |
+ } |
+ |
+ deprecated = {} |
+ |
+ def _compat(self, config): |
+ """Process config and warn on each obsolete or deprecated entry.""" |
+ for section, conf in config.items(): |
+ if isinstance(conf, dict): |
+ for k, v in conf.items(): |
+ if k in self.obsolete: |
+ warnings.warn("%r is obsolete. Use %r instead.\n" |
+ "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)) |
+ else: |
+ if section in self.obsolete: |
+ warnings.warn("%r is obsolete. Use %r instead." |
+ % (section, self.obsolete[section])) |
+ elif section in self.deprecated: |
+ warnings.warn("%r is deprecated. Use %r instead." |
+ % (section, self.deprecated[section])) |
+ |
+ def check_compatibility(self): |
+ """Process config and warn on each obsolete or deprecated entry.""" |
+ self._compat(cherrypy.config) |
+ for sn, app in cherrypy.tree.apps.items(): |
+ if not isinstance(app, cherrypy.Application): |
+ continue |
+ self._compat(app.config) |
+ |
+ |
+ # ------------------------ Known Namespaces ------------------------ # |
+ |
+ extra_config_namespaces = [] |
+ |
+ def _known_ns(self, app): |
+ ns = ["wsgi"] |
+ ns.extend(copykeys(app.toolboxes)) |
+ ns.extend(copykeys(app.namespaces)) |
+ ns.extend(copykeys(app.request_class.namespaces)) |
+ ns.extend(copykeys(cherrypy.config.namespaces)) |
+ ns += self.extra_config_namespaces |
+ |
+ for section, conf in app.config.items(): |
+ is_path_section = section.startswith("/") |
+ if is_path_section and isinstance(conf, dict): |
+ for k, v in conf.items(): |
+ atoms = k.split(".") |
+ if len(atoms) > 1: |
+ 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)) |
+ else: |
+ 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)) |
+ warnings.warn(msg) |
+ |
+ def check_config_namespaces(self): |
+ """Process config and warn on each unknown config namespace.""" |
+ for sn, app in cherrypy.tree.apps.items(): |
+ if not isinstance(app, cherrypy.Application): |
+ continue |
+ self._known_ns(app) |
+ |
+ |
+ |
+ |
+ # -------------------------- Config Types -------------------------- # |
+ |
+ known_config_types = {} |
+ |
+ def _populate_known_types(self): |
+ b = [x for x in vars(builtins).values() |
+ if type(x) is type(str)] |
+ |
+ def traverse(obj, namespace): |
+ for name in dir(obj): |
+ # Hack for 3.2's warning about body_params |
+ if name == 'body_params': |
+ continue |
+ vtype = type(getattr(obj, name, None)) |
+ if vtype in b: |
+ 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.items(): |
+ if isinstance(conf, dict): |
+ for k, v in conf.items(): |
+ 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.items(): |
+ if not isinstance(app, cherrypy.Application): |
+ 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.") |
Property changes on: third_party/cherrypy/_cpchecker.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |