Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/_cpchecker.py

Revision 1841 (checked in by fumanchu, 4 months ago)

Fix for #754 (Checker raises false alarm with application-level config namespace).

  • Property svn:eol-style set to native
Line 
1 import os
2 import warnings
3
4 import cherrypy
5
6
7 class Checker(object):
8     """A checker for CherryPy sites and their mounted applications.
9     
10     on: set this to False to turn off the checker completely.
11     
12     When this object is called at engine startup, it executes each
13     of its own methods whose names start with "check_". If you wish
14     to disable selected checks, simply add a line in your global
15     config which sets the appropriate method to False:
16     
17     [global]
18     checker.check_skipped_app_config = False
19     
20     You may also dynamically add or replace check_* methods in this way.
21     """
22    
23     on = True
24    
25     def __init__(self):
26         self._populate_known_types()
27    
28     def __call__(self):
29         """Run all check_* methods."""
30         if self.on:
31             oldformatwarning = warnings.formatwarning
32             warnings.formatwarning = self.formatwarning
33             try:
34                 for name in dir(self):
35                     if name.startswith("check_"):
36                         method = getattr(self, name)
37                         if method and callable(method):
38                             method()
39             finally:
40                 warnings.formatwarning = oldformatwarning
41    
42     def formatwarning(self, message, category, filename, lineno):
43         """Function to format a warning."""
44         return "CherryPy Checker:\n%s\n\n" % message
45    
46     # This value should be set inside _cpconfig.
47     global_config_contained_paths = False
48    
49     def check_skipped_app_config(self):
50         for sn, app in cherrypy.tree.apps.iteritems():
51             if not isinstance(app, cherrypy.Application):
52                 continue
53             if not app.config:
54                 msg = "The Application mounted at %r has an empty config." % sn
55                 if self.global_config_contained_paths:
56                     msg += (" It looks like the config you passed to "
57                             "cherrypy.config.update() contains application-"
58                             "specific sections. You must explicitly pass "
59                             "application config via "
60                             "cherrypy.tree.mount(..., config=app_config)")
61                 warnings.warn(msg)
62                 return
63    
64     def check_static_paths(self):
65         # Use the dummy Request object in the main thread.
66         request = cherrypy.request
67         for sn, app in cherrypy.tree.apps.iteritems():
68             if not isinstance(app, cherrypy.Application):
69                 continue
70             request.app = app
71             for section in app.config:
72                 # get_resource will populate request.config
73                 request.get_resource(section + "/dummy.html")
74                 conf = request.config.get
75                
76                 if conf("tools.staticdir.on", False):
77                     msg = ""
78                     root = conf("tools.staticdir.root")
79                     dir = conf("tools.staticdir.dir")
80                     if dir is None:
81                         msg = "tools.staticdir.dir is not set."
82                     else:
83                         fulldir = ""
84                         if os.path.isabs(dir):
85                             fulldir = dir
86                             if root:
87                                 msg = ("dir is an absolute path, even "
88                                        "though a root is provided.")
89                                 testdir = os.path.join(root, dir[1:])
90                                 if os.path.exists(testdir):
91                                     msg += ("\nIf you meant to serve the "
92                                             "filesystem folder at %r, remove "
93                                             "the leading slash from dir." % testdir)
94                         else:
95                             if not root:
96                                 msg = "dir is a relative path and no root provided."
97                             else:
98                                 fulldir = os.path.join(root, dir)
99                                 if not os.path.isabs(fulldir):
100                                     msg = "%r is not an absolute path." % fulldir
101                        
102                         if fulldir and not os.path.exists(fulldir):
103                             if msg:
104                                 msg += "\n"
105                             msg += ("%r (root + dir) is not an existing "
106                                     "filesystem path." % fulldir)
107                    
108                     if msg:
109                         warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
110                                       % (msg, section, root, dir))
111    
112    
113     # -------------------------- Compatibility -------------------------- #
114    
115     obsolete = {
116         'server.default_content_type': 'tools.response_headers.headers',
117         'log_access_file': 'log.access_file',
118         'log_config_options': None,
119         'log_file': 'log.error_file',
120         'log_file_not_found': None,
121         'log_request_headers': 'tools.log_headers.on',
122         'log_to_screen': 'log.screen',
123         'show_tracebacks': 'request.show_tracebacks',
124         'throw_errors': 'request.throw_errors',
125         'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
126                         'cherrypy.Application(Root())))'),
127         }
128    
129     deprecated = {}
130    
131     def _compat(self, config):
132         """Process config and warn on each obsolete or deprecated entry."""
133         for section, conf in config.iteritems():
134             if isinstance(conf, dict):
135                 for k, v in conf.iteritems():
136                     if k in self.obsolete:
137                         warnings.warn("%r is obsolete. Use %r instead.\n"
138                                       "section: [%s]" %
139                                       (k, self.obsolete[k], section))
140                     elif k in self.deprecated:
141                         warnings.warn("%r is deprecated. Use %r instead.\n"
142                                       "section: [%s]" %
143                                       (k, self.deprecated[k], section))
144             else:
145                 if section in self.obsolete:
146                     warnings.warn("%r is obsolete. Use %r instead."
147                                   % (section, self.obsolete[section]))
148                 elif section in self.deprecated:
149                     warnings.warn("%r is deprecated. Use %r instead."
150                                   % (section, self.deprecated[section]))
151    
152     def check_compatibility(self):
153         """Process config and warn on each obsolete or deprecated entry."""
154         self._compat(cherrypy.config)
155         for sn, app in cherrypy.tree.apps.iteritems():
156             if not isinstance(app, cherrypy.Application):
157                 continue
158             self._compat(app.config)
159    
160    
161     # ------------------------ Known Namespaces ------------------------ #
162    
163     extra_config_namespaces = []
164    
165     def _known_ns(self, app):
166         ns = ["wsgi"]
167         ns.extend(app.toolboxes.keys())
168         ns.extend(app.namespaces.keys())
169         ns.extend(app.request_class.namespaces.keys())
170         ns.extend(cherrypy.config.namespaces.keys())
171         ns += self.extra_config_namespaces
172        
173         for section, conf in app.config.iteritems():
174             is_path_section = section.startswith("/")
175             if is_path_section and isinstance(conf, dict):
176                 for k, v in conf.iteritems():
177                     atoms = k.split(".")
178                     if len(atoms) > 1:
179                         if atoms[0] not in ns:
180                             # Spit out a special warning if a known
181                             # namespace is preceded by "cherrypy."
182                             if (atoms[0] == "cherrypy" and atoms[1] in ns):
183                                 msg = ("The config entry %r is invalid; "
184                                        "try %r instead.\nsection: [%s]"
185                                        % (k, ".".join(atoms[1:]), section))
186                             else:
187                                 msg = ("The config entry %r is invalid, because "
188                                        "the %r config namespace is unknown.\n"
189                                        "section: [%s]" % (k, atoms[0], section))
190                             warnings.warn(msg)
191                         elif atoms[0] == "tools":
192                             if atoms[1] not in dir(cherrypy.tools):
193                                 msg = ("The config entry %r may be invalid, "
194                                        "because the %r tool was not found.\n"
195                                        "section: [%s]" % (k, atoms[1], section))
196                                 warnings.warn(msg)
197    
198     def check_config_namespaces(self):
199         """Process config and warn on each unknown config namespace."""
200         for sn, app in cherrypy.tree.apps.iteritems():
201             if not isinstance(app, cherrypy.Application):
202                 continue
203             self._known_ns(app)
204
205
206    
207    
208     # -------------------------- Config Types -------------------------- #
209    
210     known_config_types = {}
211    
212     def _populate_known_types(self):
213         import __builtin__
214         builtins = [x for x in vars(__builtin__).values()
215                     if type(x) is type(str)]
216        
217         def traverse(obj, namespace):
218             for name in dir(obj):
219                 vtype = type(getattr(obj, name, None))
220                 if vtype in builtins:
221                     self.known_config_types[namespace + "." + name] = vtype
222        
223         traverse(cherrypy.request, "request")
224         traverse(cherrypy.response, "response")
225         traverse(cherrypy.server, "server")
226         traverse(cherrypy.engine, "engine")
227         traverse(cherrypy.log, "log")
228    
229     def _known_types(self, config):
230         msg = ("The config entry %r in section %r is of type %r, "
231                "which does not match the expected type %r.")
232        
233         for section, conf in config.iteritems():
234             if isinstance(conf, dict):
235                 for k, v in conf.iteritems():
236                     if v is not None:
237                         expected_type = self.known_config_types.get(k, None)
238                         vtype = type(v)
239                         if expected_type and vtype != expected_type:
240                             warnings.warn(msg % (k, section, vtype.__name__,
241                                                  expected_type.__name__))
242             else:
243                 k, v = section, conf
244                 if v is not None:
245                     expected_type = self.known_config_types.get(k, None)
246                     vtype = type(v)
247                     if expected_type and vtype != expected_type:
248                         warnings.warn(msg % (k, section, vtype.__name__,
249                                              expected_type.__name__))
250    
251     def check_config_types(self):
252         """Assert that config values are of the same type as default values."""
253         self._known_types(cherrypy.config)
254         for sn, app in cherrypy.tree.apps.iteritems():
255             if not isinstance(app, cherrypy.Application):
256                 continue
257             self._known_types(app.config)
258    
259    
260     # -------------------- Specific config warnings -------------------- #
261    
262     def check_localhost(self):
263         """Warn if any socket_host is 'localhost'. See #711."""
264         for k, v in cherrypy.config.iteritems():
265             if k == 'server.socket_host' and v == 'localhost':
266                 warnings.warn("The use of 'localhost' as a socket host can "
267                     "cause problems on newer systems, since 'localhost' can "
268                     "map to either an IPv4 or an IPv6 address. You should "
269                     "use '127.0.0.1' or '[::1]' instead.")
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets