Fixed python_path problem.
[smonitor.git] / lib / cherrypy / _cpchecker.py
blob7ccfd89dc23702d854bb89e40c94180ecc120790
1 import os
2 import warnings
4 import cherrypy
5 from cherrypy._cpcompat import iteritems, copykeys, builtins
8 class Checker(object):
9 """A checker for CherryPy sites and their mounted applications.
11 When this object is called at engine startup, it executes each
12 of its own methods whose names start with ``check_``. If you wish
13 to disable selected checks, simply add a line in your global
14 config which sets the appropriate method to False::
16 [global]
17 checker.check_skipped_app_config = False
19 You may also dynamically add or replace ``check_*`` methods in this way.
20 """
22 on = True
23 """If True (the default), run all checks; if False, turn off all checks."""
26 def __init__(self):
27 self._populate_known_types()
29 def __call__(self):
30 """Run all check_* methods."""
31 if self.on:
32 oldformatwarning = warnings.formatwarning
33 warnings.formatwarning = self.formatwarning
34 try:
35 for name in dir(self):
36 if name.startswith("check_"):
37 method = getattr(self, name)
38 if method and hasattr(method, '__call__'):
39 method()
40 finally:
41 warnings.formatwarning = oldformatwarning
43 def formatwarning(self, message, category, filename, lineno, line=None):
44 """Function to format a warning."""
45 return "CherryPy Checker:\n%s\n\n" % message
47 # This value should be set inside _cpconfig.
48 global_config_contained_paths = False
50 def check_app_config_entries_dont_start_with_script_name(self):
51 """Check for Application config with sections that repeat script_name."""
52 for sn, app in cherrypy.tree.apps.items():
53 if not isinstance(app, cherrypy.Application):
54 continue
55 if not app.config:
56 continue
57 if sn == '':
58 continue
59 sn_atoms = sn.strip("/").split("/")
60 for key in app.config.keys():
61 key_atoms = key.strip("/").split("/")
62 if key_atoms[:len(sn_atoms)] == sn_atoms:
63 warnings.warn(
64 "The application mounted at %r has config " \
65 "entries that start with its script name: %r" % (sn, key))
67 def check_site_config_entries_in_app_config(self):
68 """Check for mounted Applications that have site-scoped config."""
69 for sn, app in iteritems(cherrypy.tree.apps):
70 if not isinstance(app, cherrypy.Application):
71 continue
73 msg = []
74 for section, entries in iteritems(app.config):
75 if section.startswith('/'):
76 for key, value in iteritems(entries):
77 for n in ("engine.", "server.", "tree.", "checker."):
78 if key.startswith(n):
79 msg.append("[%s] %s = %s" % (section, key, value))
80 if msg:
81 msg.insert(0,
82 "The application mounted at %r contains the following "
83 "config entries, which are only allowed in site-wide "
84 "config. Move them to a [global] section and pass them "
85 "to cherrypy.config.update() instead of tree.mount()." % sn)
86 warnings.warn(os.linesep.join(msg))
88 def check_skipped_app_config(self):
89 """Check for mounted Applications that have no config."""
90 for sn, app in cherrypy.tree.apps.items():
91 if not isinstance(app, cherrypy.Application):
92 continue
93 if not app.config:
94 msg = "The Application mounted at %r has an empty config." % sn
95 if self.global_config_contained_paths:
96 msg += (" It looks like the config you passed to "
97 "cherrypy.config.update() contains application-"
98 "specific sections. You must explicitly pass "
99 "application config via "
100 "cherrypy.tree.mount(..., config=app_config)")
101 warnings.warn(msg)
102 return
104 def check_app_config_brackets(self):
105 """Check for Application config with extraneous brackets in section names."""
106 for sn, app in cherrypy.tree.apps.items():
107 if not isinstance(app, cherrypy.Application):
108 continue
109 if not app.config:
110 continue
111 for key in app.config.keys():
112 if key.startswith("[") or key.endswith("]"):
113 warnings.warn(
114 "The application mounted at %r has config " \
115 "section names with extraneous brackets: %r. "
116 "Config *files* need brackets; config *dicts* "
117 "(e.g. passed to tree.mount) do not." % (sn, key))
119 def check_static_paths(self):
120 """Check Application config for incorrect static paths."""
121 # Use the dummy Request object in the main thread.
122 request = cherrypy.request
123 for sn, app in cherrypy.tree.apps.items():
124 if not isinstance(app, cherrypy.Application):
125 continue
126 request.app = app
127 for section in app.config:
128 # get_resource will populate request.config
129 request.get_resource(section + "/dummy.html")
130 conf = request.config.get
132 if conf("tools.staticdir.on", False):
133 msg = ""
134 root = conf("tools.staticdir.root")
135 dir = conf("tools.staticdir.dir")
136 if dir is None:
137 msg = "tools.staticdir.dir is not set."
138 else:
139 fulldir = ""
140 if os.path.isabs(dir):
141 fulldir = dir
142 if root:
143 msg = ("dir is an absolute path, even "
144 "though a root is provided.")
145 testdir = os.path.join(root, dir[1:])
146 if os.path.exists(testdir):
147 msg += ("\nIf you meant to serve the "
148 "filesystem folder at %r, remove "
149 "the leading slash from dir." % testdir)
150 else:
151 if not root:
152 msg = "dir is a relative path and no root provided."
153 else:
154 fulldir = os.path.join(root, dir)
155 if not os.path.isabs(fulldir):
156 msg = "%r is not an absolute path." % fulldir
158 if fulldir and not os.path.exists(fulldir):
159 if msg:
160 msg += "\n"
161 msg += ("%r (root + dir) is not an existing "
162 "filesystem path." % fulldir)
164 if msg:
165 warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
166 % (msg, section, root, dir))
169 # -------------------------- Compatibility -------------------------- #
171 obsolete = {
172 'server.default_content_type': 'tools.response_headers.headers',
173 'log_access_file': 'log.access_file',
174 'log_config_options': None,
175 'log_file': 'log.error_file',
176 'log_file_not_found': None,
177 'log_request_headers': 'tools.log_headers.on',
178 'log_to_screen': 'log.screen',
179 'show_tracebacks': 'request.show_tracebacks',
180 'throw_errors': 'request.throw_errors',
181 'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
182 'cherrypy.Application(Root())))'),
185 deprecated = {}
187 def _compat(self, config):
188 """Process config and warn on each obsolete or deprecated entry."""
189 for section, conf in config.items():
190 if isinstance(conf, dict):
191 for k, v in conf.items():
192 if k in self.obsolete:
193 warnings.warn("%r is obsolete. Use %r instead.\n"
194 "section: [%s]" %
195 (k, self.obsolete[k], section))
196 elif k in self.deprecated:
197 warnings.warn("%r is deprecated. Use %r instead.\n"
198 "section: [%s]" %
199 (k, self.deprecated[k], section))
200 else:
201 if section in self.obsolete:
202 warnings.warn("%r is obsolete. Use %r instead."
203 % (section, self.obsolete[section]))
204 elif section in self.deprecated:
205 warnings.warn("%r is deprecated. Use %r instead."
206 % (section, self.deprecated[section]))
208 def check_compatibility(self):
209 """Process config and warn on each obsolete or deprecated entry."""
210 self._compat(cherrypy.config)
211 for sn, app in cherrypy.tree.apps.items():
212 if not isinstance(app, cherrypy.Application):
213 continue
214 self._compat(app.config)
217 # ------------------------ Known Namespaces ------------------------ #
219 extra_config_namespaces = []
221 def _known_ns(self, app):
222 ns = ["wsgi"]
223 ns.extend(copykeys(app.toolboxes))
224 ns.extend(copykeys(app.namespaces))
225 ns.extend(copykeys(app.request_class.namespaces))
226 ns.extend(copykeys(cherrypy.config.namespaces))
227 ns += self.extra_config_namespaces
229 for section, conf in app.config.items():
230 is_path_section = section.startswith("/")
231 if is_path_section and isinstance(conf, dict):
232 for k, v in conf.items():
233 atoms = k.split(".")
234 if len(atoms) > 1:
235 if atoms[0] not in ns:
236 # Spit out a special warning if a known
237 # namespace is preceded by "cherrypy."
238 if (atoms[0] == "cherrypy" and atoms[1] in ns):
239 msg = ("The config entry %r is invalid; "
240 "try %r instead.\nsection: [%s]"
241 % (k, ".".join(atoms[1:]), section))
242 else:
243 msg = ("The config entry %r is invalid, because "
244 "the %r config namespace is unknown.\n"
245 "section: [%s]" % (k, atoms[0], section))
246 warnings.warn(msg)
247 elif atoms[0] == "tools":
248 if atoms[1] not in dir(cherrypy.tools):
249 msg = ("The config entry %r may be invalid, "
250 "because the %r tool was not found.\n"
251 "section: [%s]" % (k, atoms[1], section))
252 warnings.warn(msg)
254 def check_config_namespaces(self):
255 """Process config and warn on each unknown config namespace."""
256 for sn, app in cherrypy.tree.apps.items():
257 if not isinstance(app, cherrypy.Application):
258 continue
259 self._known_ns(app)
264 # -------------------------- Config Types -------------------------- #
266 known_config_types = {}
268 def _populate_known_types(self):
269 b = [x for x in vars(builtins).values()
270 if type(x) is type(str)]
272 def traverse(obj, namespace):
273 for name in dir(obj):
274 # Hack for 3.2's warning about body_params
275 if name == 'body_params':
276 continue
277 vtype = type(getattr(obj, name, None))
278 if vtype in b:
279 self.known_config_types[namespace + "." + name] = vtype
281 traverse(cherrypy.request, "request")
282 traverse(cherrypy.response, "response")
283 traverse(cherrypy.server, "server")
284 traverse(cherrypy.engine, "engine")
285 traverse(cherrypy.log, "log")
287 def _known_types(self, config):
288 msg = ("The config entry %r in section %r is of type %r, "
289 "which does not match the expected type %r.")
291 for section, conf in config.items():
292 if isinstance(conf, dict):
293 for k, v in conf.items():
294 if v is not None:
295 expected_type = self.known_config_types.get(k, None)
296 vtype = type(v)
297 if expected_type and vtype != expected_type:
298 warnings.warn(msg % (k, section, vtype.__name__,
299 expected_type.__name__))
300 else:
301 k, v = section, conf
302 if v is not None:
303 expected_type = self.known_config_types.get(k, None)
304 vtype = type(v)
305 if expected_type and vtype != expected_type:
306 warnings.warn(msg % (k, section, vtype.__name__,
307 expected_type.__name__))
309 def check_config_types(self):
310 """Assert that config values are of the same type as default values."""
311 self._known_types(cherrypy.config)
312 for sn, app in cherrypy.tree.apps.items():
313 if not isinstance(app, cherrypy.Application):
314 continue
315 self._known_types(app.config)
318 # -------------------- Specific config warnings -------------------- #
320 def check_localhost(self):
321 """Warn if any socket_host is 'localhost'. See #711."""
322 for k, v in cherrypy.config.items():
323 if k == 'server.socket_host' and v == 'localhost':
324 warnings.warn("The use of 'localhost' as a socket host can "
325 "cause problems on newer systems, since 'localhost' can "
326 "map to either an IPv4 or an IPv6 address. You should "
327 "use '127.0.0.1' or '[::1]' instead.")