import os import types import sys import logging import traceback import sketch from sketch.util.modtools import import_string from google.appengine.ext.webapp.util import run_bare_wsgi_app from google.appengine.ext.webapp import WSGIApplication from google.appengine.runtime import DeadlineExceededError from google.appengine.api import app_identity class AppAuthError(Exception): pass class SketchException(Exception): pass class Application(object): """ Sketch base Application class. Implements WSGI interface and will dispatch requests example. """ ALLOWED_METHODS = frozenset(['GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE']) static_uri = '/static' vendor_path = 'vendor' # pointer to app instance app = None # pointer to current active app instance active_app = None # pointer to current response response = None _Plugins = {} plugins_registered = False #: Default class used for the request object. request_class = sketch.Request #: Default class used for the response object. response_class = sketch.Response #: Default class used for the router object. router_class = sketch.Router #: Default class used for the config object. config_class = sketch.Config #: Request variables. active_instance = app = request = None def __init__(self, config=None, appname=None, debug=False, routes=None): """Application object constructor returns :mod:`Sketch` application :param config_file: (optional) full path to configuration file :type config_file: string :param appname: (optional) name of application :param debug: (optional) debug mode for the aplication :param routes: (optional) default routes :return: :class:`myclass` :rtype: sketch.Application :raises sketch.HttpNotFound: If route not found @TODO phase out config_file and detect if string/path etc. """ # Config # @TODO try loading default config if none specified # @TODO intelligent caching so that we don't always reparse config if isinstance(config, sketch.Config): self.config = config else: self.config = sketch.Config(config) self.appname = appname or self.config.get('appname', False) or "app" # Plugins if 'plugins' in self.config: # plugins = self.import_app_module('plugins', 'plugins', silent = True) self._init_plugins(self.config['plugins']) # Routing if type(routes) == type([]): self.routes = routes else: self.routes = self.get_routes() self.router = self.router_class(self, self.routes) # Config # self.config.save_config() self.set_vendor() self._handlers = {} Application.app = self def get_routes(self, routes = None): """Imports routes for the application Returns a dictionary of routes imported from a module or file :param routes: (optional) path to module or object with routing info """ return self.import_app_module_new('routes', 'routes') def set_environ(self): """Returns the running environment and sets debug options, hostname etc. :param env: Defined environments in config """ enviro_set = False self.config.hostname = hostname = self.get_current_hostname() self.config.appid = app_identity.get_application_id() self.config.gae_hostname = app_identity.get_default_version_hostname() for env in self.config.enviroments: self.config.enviroments[env]['name'] = env if 'hosts' in self.config.enviroments[env] and hostname in self.config.enviroments[env]['hosts']: self.config.set_enviro = env enviro_set = True if not enviro_set: logging.error("Could not set environ: %s %s" % (hostname, self.config.appid)) self.config.set_enviro = '' # logging.info(self.config.enviro) # logging.info("%s - %s - %s" % (hostname, id_gae, host_gae)) # self.debug = self.config.get('debug', debug) # self.debug = debug or os.environ.get('SERVER_SOFTWARE', '').startswith('Dev') def setup_logging(self, debug=False): """Setup application logging level based on being in debug mode or not :param debug: (optional) If debug is one then log more info """ if debug: logging.getLogger().setLevel(logging.INFO) def set_vendor(self, vendor_dir = None): """Sets a vendor directory containing third-party modules and files into the system path so that they can be imported directly :param vendor_dir: full path to directory to import """ if not vendor_dir: vendor_dir = os.path.join(os.path.dirname(__file__), self.vendor_path) if os.path.isdir(vendor_dir): sys.path.insert(0, vendor_dir) def get_current_hostname(self, port=False): """Returns the current hostname :param port: (optional) include port information """ if self.request and 'HTTP_HOST' in self.request.environ: host_full = self.request.environ['HTTP_HOST'] or 'localhost' hostname = host_full.split(':')[0] else: hostname = 'localhost' return hostname @property def debug(self): """Conveniance method to return current debug level """ if 'debug' in self.config: return self.config.debug return False def import_app_module_new(self, module, obj = None): # TODO work out what went wrong here mod = __import__('%s.%s' % (self.appname, module), globals(), None, self.appname) return getattr(mod, obj) def import_app_module(self, module, obj = None, silent = False): # TODO do some sanitation on import and move the import_string module over imp = "%s.%s" % (self.appname, module) if obj: imp = "%s:%s" % (imp, obj) try: ret = import_string(imp, silent) except AttributeError, e: logging.info("Import Error: %s" % str(e)) return None return ret def _init_plugin_class(self, plugin_name): fromlist = [plugin_name] try: module = __import__("%s.plugins.%s.%s" % (self.appname, plugin_name, plugin_name), globals(), {}, fromlist) except ImportError: module = __import__("%s.plugins.%s" % (self.appname, plugin_name), globals(), {}, fromlist) # logging.info(module) return getattr(module, plugin_name)() def _init_plugins(self, plugins): if plugins: for plugin in plugins: # try: plug_inst = self._init_plugin_class(plugin) if plug_inst.set_config(plugins[plugin]): self._Plugins[plugin] = plug_inst # except ImportError, e: # logging.error('could not import pluging %s: %s' % (plugin, e)) # except Exception, e: # logging.error('Exception: %s' % e) else: self.plugins_registered = False # TODO : implement def _safe_import(self, module_name): try: module = __import__("plugins.%s.%s" % (plugin_name, plugin_name), globals(), {}, fromlist) except ImportError: module = __import__("plugins.%s" % (plugin_name), globals(), {}, fromlist) return getattr(module, plugin_name)() def _init_project(self, url_mapping, plugins, debug=False): pass # TODO implement GAE warmup def warmup(self): """ Very cool feature which will warm up and prime the caches etc. via a GET req """ # add route /_ah/warmup # warmup cache etc. # TODO implement url_for() def url_for(self): pass def dispatch(self, match, request, response, method = None): """docstring for dispatch @TODO lots - see body """ handler_class, args, kwargs = match method = method or request.method.lower().replace('-', '_') if isinstance(handler_class, basestring): if handler_class not in self._handlers: # @TODO implement import_controller self._handlers[handler_class] = import_string(handler_class) handler_class = self._handlers[handler_class] # 1. Initiate controller handler = handler_class(self, request, response) # 2. Attach middleware # @TODO Implement functions for this and caches handler.config = self.config session = sketch.Session(handler, 'sess') handler.attach_session(session) # 3. Attach user if 'key' in handler.session: # logging.info(handler.session) try: handler.user = sketch.users.User.get_by_key(handler.session['key']) except Exception, e: logging.error('Error: could not get user. destroying session (%s)' % handler.session.key) handler.session.destroy() handler.user = False else: handler.user = False # 4. Register plugins if self.plugins_registered: if hasattr(handler, 'plugin_register'): for pl in self._Plugins: handler.plugin_register(pl, self._Plugins[pl]) else: logging.error("Handler %s does not support plugin resitration" % handler.__name__) # 5. Pre hook call if hasattr(handler, 'pre_hook'): handler.pre_hook() if not hasattr(handler, method): raise sketch.exception.NotImplemented() # 6. Main controller method call # logging.info("Calling: %s %s %s %s" % (handler, method, args, kwargs)) getattr(handler, method, None)(*args, **kwargs) # 7. Post hook call if hasattr(handler, 'post_hook'): handler.post_hook() def wsgi_app(self, environ, start_response): """Called by WSGI when a request comes in. """ Application.active_app = Application.app = self Application.request = request = self.request_class(environ) response = self.response_class() if 'enviroments' in self.config: self.set_environ() try: if request.method not in self.ALLOWED_METHODS: raise sketch.exception.NotImplemented() match = self.router.match(request) if not match: raise sketch.exception.NotFound() self.dispatch(match, request, response) except sketch.exception.HTTPException, response: pass except Exception, e: logging.exception(e) if self.config.is_dev_server: tb = ''.join(traceback.format_exception(*sys.exc_info())) else: tb = 'None' response = sketch.exception.InternalServerError(traceback = tb, content_type=request.response_type()) finally: Application.active_app = Application.app = Application.request = None return response(environ, start_response) def run_appengine(self, bare=False): run_bare_wsgi_app(self) def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response)