Package turbogears :: Module startup

Source Code for Module turbogears.startup

  1  """Things to do when the TurboGears server is started.""" 
  2   
  3  __all__ = ['call_on_startup', 'call_on_shutdown', 
  4      'reloader_thread', 'webpath', 
  5      'start_bonjour', 'stop_bonjour', 'start_server', 
  6      'start_turbogears', 'stop_turbogears'] 
  7   
  8  import atexit 
  9  import logging 
 10  import os 
 11  import signal 
 12  import sys 
 13   
 14  from os.path import abspath, exists 
 15   
 16  import pkg_resources 
 17  import cherrypy 
 18   
 19  pkg_resources.require("TurboGears") 
 20   
 21  from turbogears import config, database, scheduler, view 
 22  from turbogears.visit.api import VisitTool 
 23  from turbogears.identity.exceptions import IdentityConfigurationException 
 24  from turbogears.identity.base import verify_identity_status 
 25  from turbogears.database import hub_registry 
 26  from turbogears.dispatchers import VirtualPathDispatcher 
 27  from turbogears.hooks import NestedVariablesHook 
 28   
 29   
 30  # module globals 
 31   
 32  log = logging.getLogger("turbogears.startup") 
 33   
 34  dns_sd_pid = None 
 35  call_on_startup = [] 
 36  call_on_shutdown = [] 
 37  webpath = '' 
 38  started = False 
 39   
 40   
 41  # module public functions 
 42   
43 -def start_bonjour(package=None):
44 """Register the TurboGears server with Apple's Bonjour framework. 45 46 Currently only Unix-like systems are supported where either the 'avahi' 47 daemon (Linux etc.) is available or the 'dns-sd' program (Mac OS X). 48 49 """ 50 global dns_sd_pid 51 if dns_sd_pid: 52 return 53 if sys.platform in ('win32', 'os2'): 54 dns_sd_pid = -1 # service not available 55 return 56 57 if not package: 58 app = cherrypy.tree.apps.get('') 59 if not app: 60 return 61 package = app.root.__module__ 62 package = package.split('.', 1)[0] 63 64 host = config.get('server.socket_host', '0.0.0.0') 65 port = str(config.get('server.socket_port')) 66 env = config.get('environment') or 'development' 67 name = '%s:%s' % (package, env) 68 typ = '_http._tcp' 69 cmds = [ 70 ('/usr/bin/avahi-publish-service', ['-H', host, name, typ, port]), 71 ('/usr/bin/dns-sd', ['-R', name, typ, '.' + host, port, 'path=/'])] 72 73 for cmd, args in cmds: 74 # TODO: This check is flawed. If one has both services installed and 75 # avahi isn't the one running, then this won't work. We should either 76 # try registering with both or checking what service is running and use 77 # that. Program availability on the file system was never enough... 78 if exists(cmd): 79 dns_sd_pid = os.spawnv(os.P_NOWAIT, cmd, [cmd] + args) 80 atexit.register(stop_bonjour) 81 break 82 else: 83 dns_sd_pid = -1 # service not available
84 85
86 -def stop_bonjour():
87 """Stop the Bonjour publishing daemon if it is running.""" 88 if not dns_sd_pid or dns_sd_pid < 0: 89 return 90 try: 91 os.kill(dns_sd_pid, signal.SIGTERM) 92 except OSError: 93 pass
94 95
96 -def config_static():
97 """Configure serving static content used by TurboGears.""" 98 config.update({'/tg_static': { 99 'tools.staticdir.on': True, 100 'tools.staticdir.dir': abspath( 101 pkg_resources.resource_filename(__name__, 'static'))}}) 102 config.update({'/tg_js': { 103 'tools.staticdir.on': True, 104 'tools.staticdir.dir': abspath( 105 pkg_resources.resource_filename(__name__, 'static/js'))}})
106 107
108 -def config_root():
109 """Configure the encoding and virtual path for the root controller.""" 110 global webpath 111 encoding = config.get('genshi.default_encoding', 112 config.get('kid.encoding', 'utf-8')) 113 config.update({'/': { 114 'tools.decode.on': True, 115 'tools.decode.encoding': encoding, 116 'tools.encode.on': False, 117 'tools.encode.encoding': encoding, 118 'tools.encode.text_only': False, 119 'tools.encode.add_charset': False}}) 120 webpath = config.get('server.webpath') or '' 121 if webpath: 122 # sanitize server.webpath setting 123 webpath = webpath.strip('/') 124 if webpath: 125 webpath = '/' + webpath 126 config.update({'server.webpath': webpath}) 127 # configure virtual path dispatcher for webpath 128 if webpath: 129 config.update({'/': {'request.dispatch': VirtualPathDispatcher( 130 config.get('request.dispatch'), webpath)}}) 131 if config.get('tg.fancy_exception', False): 132 from paste import evalexception 133 config.update({'request.throw_errors': True, '/': { 134 'wsgi.pipeline': [('evalexc', evalexception.EvalException)], 135 'wsgi.evalexc.global_conf': {}, 136 'wsgi.evalexc.xmlhttp_key': "_xml"}})
137 138
139 -def start_turbogears():
140 """Handles TurboGears tasks when the CherryPy server starts. 141 142 This performs the following initialization tasks (in given order): 143 144 * Loads the template engines and the base templates. 145 * Turns off CherryPy access and error logging to screen since 146 it disrupts with our own logging configuration. You can use 147 the qualnames cherrypy.access and cherrypy.error for these messages. 148 * Adds a static tool for TurboGears's static files (URL '/tg_static'). 149 * Adds a static tool for TurboGears's JavaScript files (URL '/tg_js'). 150 * Adds a tool for decoding request parameters to Unicode. 151 * Adds a virtual path dispatcher if enabled in the configuration. 152 * Adds CherryPy tools and hooks for visit tracking, identity, 153 database and decoding parameters into nested dictionaries. 154 * Registers the server with the Bonjour framework, if available. 155 * Calls 'turbogears.database.bind_metadata' when using SQLAlchemy. 156 * Loads all turbogears.extensions entry points and calls their 157 'start_extension' method. 158 * Calls the callables registered in 'turbogears.call_on_startup'. 159 * Starts the TurboGears scheduler if enabled in the configuration. 160 161 """ 162 global started 163 if started: 164 log.info("TurboGears has already been started.") 165 return 166 log.info("Starting TurboGears...") 167 168 # Initialize template engines and load base templates 169 log.info("Loading template engines...") 170 view.load_engines() 171 view.loadBaseTemplates() 172 173 # Add CherryPy request hooks 174 log.info("Adding CherryPy tools, hooks and dispatchers...") 175 config_static() 176 config_root() 177 hooks = cherrypy.request.hooks 178 cherrypy.request.original_hooks = hooks.copy() 179 hooks.attach('before_finalize', verify_identity_status) 180 hooks.attach('on_end_resource', database.EndTransactions) 181 # The NestedVariablesHook needs to happen after cherrypy.tools.decode 182 # so that request params are properly decoded before it runs 183 hooks.attach('before_handler', NestedVariablesHook, priority=64) 184 if config.get('visit.on', False): 185 # The VisitTool needs to happen after cherrypy.tools.decode 186 # so that request params are properly decoded before it runs, 187 # but it must run before the NestedVariablesHook to work properly 188 cherrypy.tools.visit = cherrypy.Tool( 189 'before_handler', VisitTool(), priority=62) 190 191 # Register server with Bonjour framework 192 bonjour = config.get('tg.bonjour', None) 193 env = config.get('environment') or 'development' 194 if bonjour or env == 'development': 195 log.info("Starting the Bonjour service...") 196 start_bonjour(bonjour) 197 198 # Bind metadata for SQLAlchemy 199 if config.get('sqlalchemy.dburi'): 200 log.info("Binding metadata for SQLAlchemy...") 201 database.bind_metadata() 202 203 # Start all TurboGears extensions 204 extensions = pkg_resources.iter_entry_points('turbogears.extensions') 205 for entrypoint in extensions: 206 # We try to load the extension and run its 'start_extension' method, 207 # if present. If either fails, we simply log the exception and 208 # continue, because a) when the autoreloader is active, unhandled 209 # exceptions in the startup phase will not stop the server and 210 # b) faulty extensions (which may be from a different package) 211 # should not crash the server. 212 log.info("Starting TurboGears extension %s..." % entrypoint) 213 try: 214 ext = entrypoint.load() 215 except Exception, e: 216 log.exception("Error loading TurboGears extension plugin %s: %s", 217 entrypoint, e) 218 continue 219 if hasattr(ext, 'start_extension'): 220 try: 221 ext.start_extension() 222 except Exception, e: 223 log.exception("Error starting TurboGears extension %s: %s", 224 entrypoint, e) 225 if isinstance(e, IdentityConfigurationException): 226 raise # don't swallow internal configuration error 227 228 # Call registered startup functions 229 if call_on_startup: 230 log.info("Running the registered startup functions...") 231 for startup_function in call_on_startup: 232 startup_function() 233 234 # Start the scheduler 235 if config.get('tg.scheduler', False): 236 log.info("Starting the scheduler...") 237 scheduler.start_scheduler() 238 239 started = True 240 log.info("TurboGears has been started.")
241 242
243 -def stop_turbogears():
244 """Handles TurboGears tasks when the CherryPy server stops. 245 246 Ends all open database transactions, shuts down all extensions, 247 calls user provided shutdown functions and stops the scheduler. 248 249 """ 250 global started 251 if not started: 252 log.info("TurboGears has already been stopped.") 253 return 254 log.info("Stopping TurboGears...") 255 256 if config.get('tg.scheduler', False): 257 log.info("Stopping the scheduler...") 258 scheduler.stop_scheduler() 259 260 # Call registered shutdown functions 261 if call_on_shutdown: 262 log.info("Running the registered shutdown functions...") 263 for shutdown_function in call_on_shutdown: 264 shutdown_function() 265 266 # Shut down all TurboGears extensions 267 extensions = pkg_resources.iter_entry_points('turbogears.extensions') 268 for entrypoint in extensions: 269 log.info("Stopping TurboGears extension %s" % entrypoint) 270 try: 271 ext = entrypoint.load() 272 except Exception, e: 273 log.exception("Error loading TurboGears extension plugin '%s': %s", 274 entrypoint, e) 275 continue 276 if hasattr(ext, 'shutdown_extension'): 277 try: 278 ext.shutdown_extension() 279 except Exception, e: 280 log.exception( 281 "Error shutting down TurboGears extension '%s': %s", 282 entrypoint, e) 283 284 # End all transactions and clear out the hubs to help ensure 285 # proper reloading in autoreload situations 286 if hub_registry: 287 log.info("Ending all registered database hubs...") 288 for hub in hub_registry: 289 hub.end() 290 hub_registry.clear() 291 292 log.info("Stopping the Bonjour service...") 293 stop_bonjour() 294 295 # Restore CherryPy request hooks 296 log.info("Removing additional CherryPy hooks...") 297 try: 298 cherrypy.request.hooks = cherrypy.request.original_hooks 299 except AttributeError: 300 log.debug("CherryPy hooks could not be restored.") 301 302 started = False 303 log.info("TurboGears has been stopped.")
304 305
306 -def start_server(root):
307 """Start the CherryPy Server.""" 308 if started: 309 log.info("The server has already been started.") 310 return 311 app = cherrypy.tree.mount(root, config=config.app) 312 config.update({'log.screen': False}) 313 embedded = config.get('environment') == 'embedded' 314 if config.get('engine.start', not embedded): 315 cherrypy.engine.start() 316 if config.get('engine.block', not embedded): 317 cherrypy.engine.block() 318 else: 319 start_turbogears() 320 atexit.register(stop_turbogears) 321 return app
322 323 324 # Subscribe to engine events at import time so that our callbacks get used 325 # regardless of how the server is started. 326 cherrypy.engine.subscribe('start', start_turbogears) 327 cherrypy.engine.subscribe('stop', stop_turbogears) 328