Package turbogears :: Module startup

Source Code for Module turbogears.startup

  1  """Things to do when the TurboGears server is started.""" 
  2   
  3  __all__ = [ 
  4      'call_on_startup', 
  5      'call_on_shutdown', 
  6      'reloader_thread', 
  7      'start_bonjour', 
  8      'stop_bonjour', 
  9      'start_server', 
 10      'startTurboGears', 
 11      'stopTurboGears', 
 12      'webpath', 
 13  ] 
 14   
 15  import atexit 
 16  import errno 
 17  import logging 
 18  import os 
 19  import sys 
 20  import signal 
 21  import time 
 22   
 23  from os.path import abspath, exists 
 24   
 25  import pkg_resources 
 26  import cherrypy 
 27  from cherrypy import _cputil, request, server 
 28  from cherrypy._cpwsgi import wsgiApp, CPHTTPRequest 
 29  from cherrypy._cpwsgiserver import CherryPyWSGIServer 
 30   
 31  pkg_resources.require("TurboGears") 
 32   
 33  from turbogears import config, database, scheduler, view 
 34  from turbogears.database import hub_registry, EndTransactionsFilter 
 35  from turbogears.filters import (MonkeyDecodingFilter, NestedVariablesFilter, 
 36      SafeMultipartFilter, VirtualPathFilter) 
 37   
 38   
 39  # module globals 
 40  DNS_SD_PID = None 
 41  call_on_startup = [] 
 42  call_on_shutdown = [] 
 43  webpath = '' 
 44  log = logging.getLogger("turbogears.startup") 
 45   
 46   
 47  # module private functions 
48 -def reloader_thread(freq):
49 """Monkeypatch for the reloader provided by CherryPy. 50 51 This reloader is designed to reload a single package. This is 52 more efficient and, more important, compatible with zipped 53 libraries that may not provide access to the individual files.""" 54 55 def archive_selector(module): 56 if hasattr(module, '__loader__'): 57 if hasattr(module.__loader__, 'archive'): 58 return module.__loader__.archive 59 return module
60 61 mtimes = {} 62 package = config.get("autoreload.package", None) 63 if package is None: 64 print ("TurboGears requires autoreload.package to be set. " 65 "It can be an empty value, which will use CherryPy's default " 66 "behavior which is to check every module. Setting an actual " 67 "package makes the check much faster.") 68 return 69 while cherrypy.lib.autoreload.RUN_RELOADER: 70 if package: 71 modnames = filter(lambda modname: modname.startswith(package), 72 sys.modules.keys()) 73 modlist = [sys.modules[modname] for modname in modnames] 74 else: 75 modlist = map(archive_selector, sys.modules.values()) 76 for filename in filter(lambda v: v, 77 map(lambda m: getattr(m, "__file__", None), modlist)): 78 if filename.endswith(".kid") or filename == "<string>": 79 continue 80 orig_filename = filename 81 if filename.endswith(".pyc"): 82 filename = filename[:-1] 83 try: 84 mtime = os.stat(filename).st_mtime 85 except OSError, e: 86 if orig_filename.endswith('.pyc') and e[0] == errno.ENOENT: 87 # This prevents us from endlessly restarting 88 # if there is an old .pyc lying around 89 # after a .py file has been deleted 90 try: os.unlink(orig_filename) 91 except: pass 92 sys.exit(3) # force reload 93 if filename not in mtimes: 94 mtimes[filename] = mtime 95 continue 96 if mtime > mtimes[filename]: 97 sys.exit(3) # force reload 98 time.sleep(freq) 99 100 cherrypy.lib.autoreload.reloader_thread = reloader_thread 101 102 old_object_trail = _cputil.get_object_trail 103 104 # hang on to object trail to use it to find an app root if need be
105 -def get_object_trail(object_path=None):
106 trail = old_object_trail(object_path) 107 try: 108 request.object_trail = trail 109 except AttributeError: 110 pass 111 return trail
112 113 _cputil.get_object_trail = get_object_trail 114 115 116 # module public functions
117 -def start_bonjour(package=None):
118 """Register the TurboGears server with the Bonjour framework. 119 120 Currently only Unix-like systems are supported where either the 'avahi' 121 daemon (Linux etc.) is available or the 'dns-sd' program (Mac OS X). 122 123 """ 124 global DNS_SD_PID 125 if DNS_SD_PID: 126 return 127 if not getattr(cherrypy, 'root', None): 128 return 129 if not package: 130 package = cherrypy.root.__module__ 131 package = package[:package.find('.')] 132 133 host = config.get('server.socket_host', '') 134 port = str(config.get('server.socket_port')) 135 env = config.get('server.environment') 136 name = package + ": " + env 137 type = "_http._tcp" 138 139 cmds = [['/usr/bin/avahi-publish-service', ["-H", host, name, type, port]], 140 ['/usr/bin/dns-sd', ['-R', name, type, "."+host, port, "path=/"]]] 141 142 for cmd, args in cmds: 143 # TODO:. This check is flawed. If one has both services installed and 144 # avahi isn't the one running, then this won't work. We should either 145 # try registering with both or checking what service is running and use 146 # that. Program availability on the filesystem was never enough... 147 if exists(cmd): 148 DNS_SD_PID = os.spawnv(os.P_NOWAIT, cmd, [cmd]+args) 149 atexit.register(stop_bonjour) 150 break
151
152 -def stop_bonjour():
153 """Stop the bonjour publishing daemon if it is running.""" 154 if not DNS_SD_PID: 155 return 156 try: 157 os.kill(DNS_SD_PID, signal.SIGTERM) 158 except OSError: 159 pass
160
161 -def startTurboGears():
162 """Handles TurboGears tasks when the CherryPy server starts. 163 164 This performs the following initialization tasks (in given order): 165 166 * Turns off CherryPy's logging filter when in development mode 167 * If logging is not already set up, turns on old-style stdlib logging. 168 * Adds a static filter for TurboGears's static files (URL '/tg_static'). 169 * Adds a static filter for TurboGears's JavaScript files (URL '/tg_js'). 170 * Loads the template engines and the base templates. 171 * Adds the CherryPy request filters to the root controller. 172 * Adds the decoding filter to the root URL ('/') if enabled in the 173 configuration. 174 * Registers the server with the Bonjour framework, if available. 175 * Calls 'turbogears.database.bind_metadata' when using SQLAlchemy. 176 * Loads all turbogears.extensions entry points and calls their 177 'start_extension' method. 178 * Calls the callables registered in 'turbogears.call_on_startup'. 179 * Starts the TurboGears scheduler. 180 181 """ 182 global webpath 183 conf = config.get 184 rfn = pkg_resources.resource_filename 185 186 cherrypy.config.environments['development'][ 187 'log_debug_info_filter.on'] = False 188 189 # XXX: obsolete --> to be removed 190 # Set up old-style logging 191 if not conf('tg.new_style_logging', False): 192 if conf('server.log_to_screen'): 193 setuplog = logging.getLogger() 194 setuplog.setLevel(logging.DEBUG) 195 fmt = logging.Formatter( 196 "%(asctime)s %(name)s %(levelname)s %(message)s") 197 handler = logging.StreamHandler(sys.stdout) 198 handler.setLevel(logging.DEBUG) 199 handler.setFormatter(fmt) 200 setuplog.addHandler(handler) 201 202 logfile = conf('server.log_file') 203 if logfile: 204 setuplog = logging.getLogger('turbogears.access') 205 setuplog.propagate = 0 206 fmt = logging.Formatter("%(message)s") 207 handler = logging.FileHandler(logfile) 208 handler.setLevel(logging.INFO) 209 handler.setFormatter(fmt) 210 setuplog.addHandler(handler) 211 212 # Add static filters 213 config.update({'/tg_static': { 214 'static_filter.on': True, 215 'static_filter.dir': abspath(rfn(__name__, 'static')), 216 'log_debug_info_filter.on': False, 217 }}) 218 config.update({'/tg_js': { 219 'static_filter.on': True, 220 'static_filter.dir': abspath(rfn(__name__, 'static/js')), 221 'log_debug_info_filter.on': False, 222 }}) 223 # Add decoding filter 224 if conf('decoding_filter.on', path='/') is None: 225 config.update({'/': { 226 'decoding_filter.on': True, 227 'decoding_filter.encoding': conf('kid.encoding', 'utf8') 228 }}) 229 230 # Initialize template engines and load base templates 231 view.load_engines() 232 view.loadBaseTemplates() 233 234 # Add request filters 235 webpath = conf('server.webpath') or '' 236 237 if getattr(cherrypy, 'root', None): 238 if not hasattr(cherrypy.root, '_cp_filters'): 239 cherrypy.root._cp_filters = [] 240 extra_filters = [ 241 VirtualPathFilter(webpath), 242 EndTransactionsFilter(), 243 NestedVariablesFilter(), 244 SafeMultipartFilter() 245 ] 246 # Do not add filters twice which are already present 247 for cp_filter in cherrypy.root._cp_filters[:]: 248 for candidate in extra_filters: 249 if candidate.__class__ is cp_filter.__class__: 250 extra_filters.remove(candidate) 251 break 252 cherrypy.root._cp_filters.extend(extra_filters) 253 254 # Monkey patch CherryPy Decoding filter: injects our replacement filter 255 # MonkeyDecodingFilter into the CherryPy filter chain 256 decoding_filter = MonkeyDecodingFilter() 257 for index, active_filter in enumerate( 258 cherrypy.filters._filterhooks.get('before_main', [])): 259 if (active_filter.im_class is 260 cherrypy.filters.decodingfilter.DecodingFilter): 261 cherrypy.filters._filterhooks['before_main'].pop(index) 262 if conf('decoding_filter.on', False, path='/'): 263 log.info("Request decoding filter activated.") 264 cherrypy.filters._filterhooks['before_main'].insert( 265 index, decoding_filter.before_main) 266 267 268 webpath = webpath.lstrip('/') 269 if webpath and not webpath.endswith('/'): 270 webpath += '/' 271 272 # Register server with Bonjour framework 273 bonjoursetting = conf('tg.bonjour', None) 274 if bonjoursetting or conf('server.environment') == 'development': 275 start_bonjour(bonjoursetting) 276 277 # Bind metadata for SQLAlchemy 278 if conf('sqlalchemy.dburi'): 279 database.bind_metadata() 280 281 # Start all TurboGears extensions 282 extensions = pkg_resources.iter_entry_points('turbogears.extensions') 283 for entrypoint in extensions: 284 # We try to load the extension and run its 'start_extension' 285 # method ,if present. If either fails, we simply log the exception and 286 # continue, because a) when the autoreloader is active, unhandled 287 # exceptions in the startup phase will not stop the server and 288 # b) faulty extensions (which may be from a different package) 289 # should not crash the server. 290 try: 291 ext = entrypoint.load() 292 except Exception, e: 293 log.exception("Error loading TurboGears extension plugin '%s': %s", 294 entrypoint, e) 295 continue 296 if hasattr(ext, 'start_extension'): 297 try: 298 ext.start_extension() 299 except Exception, e: 300 log.exception("Error starting TurboGears extension '%s': %s", 301 entrypoint, e) 302 303 # Call registered startup functions 304 for item in call_on_startup: 305 item() 306 307 # Start the scheduler 308 if conf('tg.scheduler', False): 309 scheduler._start_scheduler() 310 log.info("Scheduler started")
311
312 -def stopTurboGears():
313 """Handles TurboGears tasks when the CherryPy server stops. 314 315 Ends all open database transactions, shuts down all extensions, calls user 316 provided shutdown functions and stops the scheduler. 317 318 """ 319 # end all transactions and clear out the hubs to 320 # help ensure proper reloading in autoreload situations 321 for hub in hub_registry: 322 hub.end() 323 hub_registry.clear() 324 325 stop_bonjour() 326 327 # Shut down all TurboGears extensions 328 extensions= pkg_resources.iter_entry_points( "turbogears.extensions" ) 329 for entrypoint in extensions: 330 try: 331 ext = entrypoint.load() 332 except Exception, e: 333 log.exception("Error loading TurboGears extension plugin '%s': %s", 334 entrypoint, e) 335 continue 336 if hasattr(ext, "shutdown_extension"): 337 try: 338 ext.shutdown_extension() 339 except Exception, e: 340 log.exception( 341 "Error shutting down TurboGears extension '%s': %s", 342 entrypoint, e) 343 344 for item in call_on_shutdown: 345 item() 346 347 if config.get("tg.scheduler", False): 348 scheduler._stop_scheduler() 349 log.info("Scheduler stopped")
350
351 -def start_server(root):
352 cherrypy.tree.mount(root) 353 if config.get('tg.fancy_exception', False): 354 server.start(server=SimpleWSGIServer()) 355 else: 356 server.start()
357 358 # module classes
359 -class SimpleWSGIServer(CherryPyWSGIServer):
360 """A WSGI server that accepts a WSGI application as a parameter.""" 361 RequestHandlerClass = CPHTTPRequest 362
363 - def __init__(self):
364 conf = cherrypy.config.get 365 wsgi_app = wsgiApp 366 if conf('server.environment') == 'development': 367 try: 368 from paste.evalexception.middleware import EvalException 369 except ImportError: 370 pass 371 else: 372 wsgi_app = EvalException(wsgi_app, global_conf={}) 373 cherrypy.config.update({'server.throw_errors':True}) 374 bind_addr = (conf('server.socket_host'), conf('server.socket_port')) 375 CherryPyWSGIServer.__init__(self, bind_addr, wsgi_app, 376 conf("server.thread_pool"), conf("server.socket_host"), 377 request_queue_size = conf("server.socket_queue_size"))
378 379 380 if startTurboGears not in server.on_start_server_list: 381 server.on_start_server_list.append(startTurboGears) 382 383 if stopTurboGears not in server.on_stop_server_list: 384 server.on_stop_server_list.append(stopTurboGears) 385