예제 #1
0
파일: app.py 프로젝트: comlounge/starflyer
    def __init__(self, import_name, config={}, **kw):
        """initialize the Application 

        :param import_name: the __name__ of the module we are running under.
        :param config: a dictionary of configuration values
        """

        self.import_name = import_name

        # initialize URL mapping variables
        self.url_map = werkzeug.routing.Map()
        self.handlers = {}

        # initialize configuration
        self.config = AttributeMapper(self.enforced_defaults or {})

        # initialize module configuration
        for module in self.modules:
            self.config.modules.setdefault(module.name, AttributeMapper())

        # update configuration from our
        self.config.update(self.defaults)

        # first read module configuration from a file if given
        if "module_config_file" in config:
            cfg = ConfigParser()
            cfg.read(config['module_config_file'])
            for section in cfg.sections():
                for option in cfg.options(section):
                    config["modules.%s.%s" % (section, option)] = cfg.get(
                        section, option)

        # now override defaults from config eventually
        self.config.update(fix_types(config, self.config_types))

        # lastly override via keyword arguments
        self.config.update(fix_types(kw, self.config_types))
        # TODO: update from environment vars?

        self.finalize_modules()  # let user dynamically add some modules

        # now bind all the modules to our app and create a mapping
        # we also make sure he that URLs of modules are registered first so their namespaces work correctly
        for module in self.modules:
            module.bind_to_app(self)
            self.module_map[module.name] = module

        # initialize the actual routes
        for route in self.routes:
            self.add_url_rule(route.path, route.endpoint, route.handler,
                              **route.options)

        # clean up static url path
        if self.config.static_folder is not None:
            sup = self.config.static_url_path
            if sup.endswith("/"):
                sup = sup[:-1]  # remove any trailing slash
            if not sup.startswith("/"):
                sup = "/" + sup  # add a leading slash if missing
            self.add_url_rule(sup + '/<path:filename>',
                              endpoint='static',
                              handler=static.StaticFileHandler)

        # now call the hook for changing the setup after initialization
        self.finalize_setup()

        # for testing purposes. Set app.config.testing = True and this will be populated.
        self.last_handler = None

        # we did not have any request yet
        self._got_first_request = False

        # check config
        if self.config.server_name is None and not self.config.testing:
            print "*" * 80
            print "WARNING: you don't have a server_name set in your configuration and because"
            print "of that we cannot create proper URLs."
            print "We will now use localhost as server name but this might create broken URLs."
            print "*" * 80
            print
            self.config.server_name = "localhost"

        if self.config.session_cookie_domain is None and not self.config.testing:
            print "*" * 80
            print "WARNING: session_cookie_domain is not set in your configuration"
            print "this means that depending on the browser, the session cookie"
            print "can not be stored. This also means that no flash messages will appear"
            print "*" * 80
            print

        if self.config.testing:
            self.config.server_name = 'example.org'
            self.config.session_cookie_domain = 'example.org'
예제 #2
0
    def __init__(self, import_name, config={}, **kw):
        """initialize the Application 

        :param import_name: the __name__ of the module we are running under.
        :param config: a dictionary of configuration values
        """

        self.import_name = import_name

        # initialize URL mapping variables
        self.url_map = werkzeug.routing.Map()
        self.handlers = {}

       
        # initialize configuration
        self.config = AttributeMapper(self.enforced_defaults or {})


        # initialize module configuration
        for module in self.modules:
            self.config.modules.setdefault(module.name, AttributeMapper())

        # update configuration from our
        self.config.update(self.defaults)

        # first read module configuration from a file if given
        if "module_config_file" in config:
            cfg = ConfigParser()
            cfg.read(config['module_config_file'])
            for section in cfg.sections():
                for option in cfg.options(section):
                    config["modules.%s.%s" %(section,option)] = cfg.get(section, option)

        # now override defaults from config eventually
        self.config.update(fix_types(config, self.config_types))
        
        # lastly override via keyword arguments
        self.config.update(fix_types(kw, self.config_types))
        # TODO: update from environment vars?
        
        self.finalize_modules() # let user dynamically add some modules

        # now bind all the modules to our app and create a mapping 
        # we also make sure he that URLs of modules are registered first so their namespaces work correctly
        for module in self.modules:
            module.bind_to_app(self)
            self.module_map[module.name] = module

        # initialize the actual routes 
        for route in self.routes:
            self.add_url_rule(
                route.path,
                route.endpoint,
                route.handler,
                **route.options)

        # clean up static url path
        if self.config.static_folder is not None:
            sup = self.config.static_url_path
            if sup.endswith("/"):
                sup = sup[:-1] # remove any trailing slash
            if not sup.startswith("/"):
                sup = "/"+sup # add a leading slash if missing
            self.add_url_rule(sup+ '/<path:filename>',
                            endpoint='static',
                            handler=static.StaticFileHandler)


        # now call the hook for changing the setup after initialization
        self.finalize_setup()

        # for testing purposes. Set app.config.testing = True and this will be populated.
        self.last_handler = None

        # we did not have any request yet
        self._got_first_request = False

        # check config
        if self.config.server_name is None and not self.config.testing:
            print "*"*80
            print "WARNING: you don't have a server_name set in your configuration and because"
            print "of that we cannot create proper URLs."
            print "We will now use localhost as server name but this might create broken URLs."
            print "*"*80
            print
            self.config.server_name = "localhost" 

        if self.config.session_cookie_domain is None and not self.config.testing:
            print "*"*80
            print "WARNING: session_cookie_domain is not set in your configuration"
            print "this means that depending on the browser, the session cookie"
            print "can not be stored. This also means that no flash messages will appear"
            print "*"*80
            print

        if self.config.testing:
            self.config.server_name = 'example.org'
            self.config.session_cookie_domain = 'example.org'
예제 #3
0
파일: app.py 프로젝트: comlounge/starflyer
class Application(object):
    """a base class for dispatching WSGI requests"""

    defaults = {}

    routes = []  # list of rules
    error_handlers = {}  # mapping from error code to error handler classes

    handlers = {}  # mapping from endpoint to handler classes

    # class to be used for URL Routes
    url_rule_class = werkzeug.routing.Rule

    # session interface
    session_interface = sessions.SecureCookieSessionInterface()

    # class to be used as test client class
    test_client_class = None

    # response class to use
    response_class = wrappers.Response
    request_class = wrappers.Request

    # last handler for testing
    last_handler = None

    # enforeced defaults (these have to be existent in the config
    # for starflyer to work (DO NOT CHANGE!)
    enforced_defaults = {
        'session_cookie_name': "s",
        'secret_key': None,
        'permanent_session_lifetime': datetime.timedelta(days=31),
        'session_cookie_domain': None,
        'session_cookie_path': None,
        'session_cookie_httponly': True,
        'session_cookie_secure': False,
        'logger_name': None,
        'server_name': None,
        'application_root': None,
        'preferred_url_scheme': "http",
        'propagate_exceptions':
        None,  # this is used for testing and debugging and means to re-raise it and not use an error handler for uncaught exceptions
        'debug': False,
        'testing': False,
        'force_exceptions':
        False,  # True = make also http exceptions raise and not return a response (for testing)
        'static_cache_timeout': 12 * 60 * 60,
        'template_folder': "templates/",
        'static_folder': "static/",
        'static_url_path': "/static",
        'modules': AttributeMapper(),  # config placeholder for modules
    }

    # here you can define which types the config parameters are supposed to be in
    config_types = {
        'debug': bool,
        'testing': bool,
        'session_cookie_httponly': bool,
        'session_cookie_secure': bool,
    }

    jinja_options = ImmutableDict(extensions=[
        'jinja2.ext.autoescape', 'jinja2.ext.with_', 'jinja2.ext.i18n'
    ])

    jinja_filters = ImmutableDict()

    modules = []  # list of modules
    module_map = AttributeMapper()  # mapped version of modules

    def __init__(self, import_name, config={}, **kw):
        """initialize the Application 

        :param import_name: the __name__ of the module we are running under.
        :param config: a dictionary of configuration values
        """

        self.import_name = import_name

        # initialize URL mapping variables
        self.url_map = werkzeug.routing.Map()
        self.handlers = {}

        # initialize configuration
        self.config = AttributeMapper(self.enforced_defaults or {})

        # initialize module configuration
        for module in self.modules:
            self.config.modules.setdefault(module.name, AttributeMapper())

        # update configuration from our
        self.config.update(self.defaults)

        # first read module configuration from a file if given
        if "module_config_file" in config:
            cfg = ConfigParser()
            cfg.read(config['module_config_file'])
            for section in cfg.sections():
                for option in cfg.options(section):
                    config["modules.%s.%s" % (section, option)] = cfg.get(
                        section, option)

        # now override defaults from config eventually
        self.config.update(fix_types(config, self.config_types))

        # lastly override via keyword arguments
        self.config.update(fix_types(kw, self.config_types))
        # TODO: update from environment vars?

        self.finalize_modules()  # let user dynamically add some modules

        # now bind all the modules to our app and create a mapping
        # we also make sure he that URLs of modules are registered first so their namespaces work correctly
        for module in self.modules:
            module.bind_to_app(self)
            self.module_map[module.name] = module

        # initialize the actual routes
        for route in self.routes:
            self.add_url_rule(route.path, route.endpoint, route.handler,
                              **route.options)

        # clean up static url path
        if self.config.static_folder is not None:
            sup = self.config.static_url_path
            if sup.endswith("/"):
                sup = sup[:-1]  # remove any trailing slash
            if not sup.startswith("/"):
                sup = "/" + sup  # add a leading slash if missing
            self.add_url_rule(sup + '/<path:filename>',
                              endpoint='static',
                              handler=static.StaticFileHandler)

        # now call the hook for changing the setup after initialization
        self.finalize_setup()

        # for testing purposes. Set app.config.testing = True and this will be populated.
        self.last_handler = None

        # we did not have any request yet
        self._got_first_request = False

        # check config
        if self.config.server_name is None and not self.config.testing:
            print "*" * 80
            print "WARNING: you don't have a server_name set in your configuration and because"
            print "of that we cannot create proper URLs."
            print "We will now use localhost as server name but this might create broken URLs."
            print "*" * 80
            print
            self.config.server_name = "localhost"

        if self.config.session_cookie_domain is None and not self.config.testing:
            print "*" * 80
            print "WARNING: session_cookie_domain is not set in your configuration"
            print "this means that depending on the browser, the session cookie"
            print "can not be stored. This also means that no flash messages will appear"
            print "*" * 80
            print

        if self.config.testing:
            self.config.server_name = 'example.org'
            self.config.session_cookie_domain = 'example.org'

    ####
    #### hooks for first request, finalizing and error handling
    ####

    def before_first_request(self, request):
        """if you want to run something on the first incoming request then put it here"""
        pass

    def before_handler(self, handler):
        """this is run before handler processing starts but the handler is already initialized.
        You can check request, session and all that and maybe add some variables to the handler.

        If you return something else than None, handler processing will not happen and the
        return value will be taken as response instead. 
        """

    def after_handler(self, handler, response):
        """This hook is run after the handler processing is done but before the response is sent
        out. You can check the handler or response and maybe return your own response here. In case
        you do that this response will be used instead. If you return None then the original handler
        response will be used.
        """

    def finalize_response(self, response):
        """with this hook you can do something very generic to a response after all processing.

        Please not that the response can also be an :class:`~werkzeug.exception.HTTPException` instance
        which does not have a status code. 

        TODO: Shall we only do finalize on non-exception responses?
        """
        return response

    def finalize_setup(self):
        """a hook you can use to finalize the setup. You can add new routes, change configuration
        values etc.
        """

    def finalize_modules(self):
        """a hook you can use to add modules to the modules list more dynamically and while using the app's config for it. Simply add them to the ``self.modules`` list via ``append()``.
        """

    ####
    #### handler related hooks you can override
    ####

    def get_render_context(self, handler):
        """create the global app wide render context which is then passed to the template for
        rendering. Here you can pass in global variables etc. You also get the active handler
        for inspecting session, request and so on or for choosing based on the handler what to 
        pass.

        Note though that handler based parameters are probably better located in the handler itself.

        :param handler: The active handler instance
        """
        return {}

    ####
    #### TEMPLATE related
    ####

    @property
    def jinja_loader(self):
        """create the jinja template loader for this app.

        Also modules can create those loaders and the ``create_global_jinja_loader``
        method will gather all those together. Hence we have these two functions for
        obviously doing the same thing.

        In case you want to change the loader for app templates, simply override
        this property in your subclassed app.
        """
        if self.config.template_folder is not None:
            return jinja2.PackageLoader(self.import_name,
                                        self.config.template_folder)
        return None

    @property
    def global_jinja_loader(self):
        """create the global jinja loader by collecting all the loaders of the app
        and the modules together to one big loader
        """
        return DispatchingJinjaLoader(self)

    @werkzeug.cached_property
    def jinja_env(self):
        """create the jinja environment"""
        options = dict(self.jinja_options)
        if 'loader' not in options:
            options['loader'] = self.global_jinja_loader
        #if 'autoescape' not in options:
        #options['autoescape'] = self.select_jinja_autoescape
        rv = jinja2.Environment(**options)
        for name, flt in self.jinja_filters.items():
            rv.filters[name] = flt
        return rv

    ####
    #### SESSION related
    #### (directly copied from flask)
    ####

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.  Instead of overriding this method
        we recommend replacing the :class:`session_interface`.

        :param request: an instance of :attr:`request_class`.
        """
        return self.session_interface.open_session(self, request)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.  Instead of overriding this
        method we recommend replacing the :class:`session_interface`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        return self.session_interface.save_session(self, session, response)

    def make_null_session(self):
        """Creates a new instance of a missing session.  Instead of overriding
        this method we recommend replacing the :class:`session_interface`.

        .. versionadded:: 0.7
        """
        return self.session_interface.make_null_session(self)

    ####
    #### CONFIGURATION related
    ####

    def add_url_rule(self,
                     url_or_path,
                     endpoint=None,
                     handler=None,
                     **options):
        """add another url rule to the url map"""
        if isinstance(url_or_path, URL):
            path = url_or_path.path
            endpoint = url_or_path.endpoint
            handler = url_or_path.handler
            options = url_or_path.options
        else:
            path = url_or_path
        if endpoint is None:
            assert handler is not None, "handler and endpoint not provided"
            endpoint = handler.__name__
        options['endpoint'] = endpoint
        options['defaults'] = options.get('defaults') or None

        rule = self.url_rule_class(path, **options)
        self.url_map.add(rule)
        if handler is not None:
            self.handlers[endpoint] = handler

    ####
    #### request processing
    ####

    @property
    def propagate_exceptions(self):
        """Returns the value of the `propagate_exceptions` configuration
        value in case it's set, otherwise a sensible default is returned.
        """
        rv = self.config.propagate_exceptions
        if rv is not None:
            return rv
        return self.config.testing or self.config.debug

    def check_first_request(self, request):
        """check if we have already run the hooks for the first request. If not, do so now"""
        if not self._got_first_request:
            self.before_first_request(request)
            self._got_first_request = True

    def find_handler(self, request):
        """retrieve the handler for the given URL. If it is not found, a routing exception
        is raised.

        :returns:   an instance of :class:`~starflyer.Handler` 
        """

        # create the url adapter
        urls = self.create_url_adapter(request)

        try:
            url_rule, request.view_args = urls.match(return_rule=True)
            request.url_rule = url_rule
            request.url_adapter = urls
        except werkzeug.exceptions.HTTPException, e:
            # this basically means 404 but maybe some debugging can occur
            # this is reraised then though
            self.raise_routing_exception(request, e)

        # check if we are called from a module
        module = None
        endpoint = url_rule.endpoint
        parts = endpoint.split(".")
        if len(parts) == 2:
            module_name = parts[0]
            module = self.module_map.get(module_name, None)

        # try to find the right handler for this url and instantiate it
        return self.handlers[url_rule.endpoint](self, request, module=module)
예제 #4
0
class Application(object):
    """a base class for dispatching WSGI requests"""

    defaults = {}

    routes = [] # list of rules
    error_handlers = {} # mapping from error code to error handler classes

    handlers = {} # mapping from endpoint to handler classes

    # class to be used for URL Routes
    url_rule_class = werkzeug.routing.Rule

    # session interface
    session_interface = sessions.SecureCookieSessionInterface()

    # class to be used as test client class
    test_client_class = None

    # response class to use
    response_class = wrappers.Response
    request_class = wrappers.Request

    # last handler for testing
    last_handler = None

    # enforeced defaults (these have to be existent in the config
    # for starflyer to work (DO NOT CHANGE!)
    enforced_defaults = {
        'session_cookie_name'           : "s",
        'secret_key'                    : None,
        'permanent_session_lifetime'    : datetime.timedelta(days=31),
        'session_cookie_domain'         : None,
        'session_cookie_path'           : None,
        'session_cookie_httponly'       : True,
        'session_cookie_secure'         : False,
        'logger_name'                   : None,
        'server_name'                   : None,
        'application_root'              : None,
        'preferred_url_scheme'          : "http",
        'propagate_exceptions'          : None, # this is used for testing and debugging and means to re-raise it and not use an error handler for uncaught exceptions
        'debug'                         : False,
        'testing'                       : False,
        'force_exceptions'              : False, # True = make also http exceptions raise and not return a response (for testing)
        'static_cache_timeout'          : 12 * 60 * 60,
        'template_folder'               : "templates/",
        'static_folder'                 : "static/",
        'static_url_path'               : "/static",
        'modules'                       : AttributeMapper(), # config placeholder for modules
    }

    # here you can define which types the config parameters are supposed to be in 
    config_types = {
        'debug' : bool,
        'testing' : bool,
        'session_cookie_httponly' : bool,
        'session_cookie_secure' : bool,
    }

    jinja_options = ImmutableDict(
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_', 'jinja2.ext.i18n']
    )
    
    jinja_filters = ImmutableDict()

    modules = [] # list of modules
    module_map = AttributeMapper() # mapped version of modules

    def __init__(self, import_name, config={}, **kw):
        """initialize the Application 

        :param import_name: the __name__ of the module we are running under.
        :param config: a dictionary of configuration values
        """

        self.import_name = import_name

        # initialize URL mapping variables
        self.url_map = werkzeug.routing.Map()
        self.handlers = {}

       
        # initialize configuration
        self.config = AttributeMapper(self.enforced_defaults or {})


        # initialize module configuration
        for module in self.modules:
            self.config.modules.setdefault(module.name, AttributeMapper())

        # update configuration from our
        self.config.update(self.defaults)

        # first read module configuration from a file if given
        if "module_config_file" in config:
            cfg = ConfigParser()
            cfg.read(config['module_config_file'])
            for section in cfg.sections():
                for option in cfg.options(section):
                    config["modules.%s.%s" %(section,option)] = cfg.get(section, option)

        # now override defaults from config eventually
        self.config.update(fix_types(config, self.config_types))
        
        # lastly override via keyword arguments
        self.config.update(fix_types(kw, self.config_types))
        # TODO: update from environment vars?
        
        self.finalize_modules() # let user dynamically add some modules

        # now bind all the modules to our app and create a mapping 
        # we also make sure he that URLs of modules are registered first so their namespaces work correctly
        for module in self.modules:
            module.bind_to_app(self)
            self.module_map[module.name] = module

        # initialize the actual routes 
        for route in self.routes:
            self.add_url_rule(
                route.path,
                route.endpoint,
                route.handler,
                **route.options)

        # clean up static url path
        if self.config.static_folder is not None:
            sup = self.config.static_url_path
            if sup.endswith("/"):
                sup = sup[:-1] # remove any trailing slash
            if not sup.startswith("/"):
                sup = "/"+sup # add a leading slash if missing
            self.add_url_rule(sup+ '/<path:filename>',
                            endpoint='static',
                            handler=static.StaticFileHandler)


        # now call the hook for changing the setup after initialization
        self.finalize_setup()

        # for testing purposes. Set app.config.testing = True and this will be populated.
        self.last_handler = None

        # we did not have any request yet
        self._got_first_request = False

        # check config
        if self.config.server_name is None and not self.config.testing:
            print "*"*80
            print "WARNING: you don't have a server_name set in your configuration and because"
            print "of that we cannot create proper URLs."
            print "We will now use localhost as server name but this might create broken URLs."
            print "*"*80
            print
            self.config.server_name = "localhost" 

        if self.config.session_cookie_domain is None and not self.config.testing:
            print "*"*80
            print "WARNING: session_cookie_domain is not set in your configuration"
            print "this means that depending on the browser, the session cookie"
            print "can not be stored. This also means that no flash messages will appear"
            print "*"*80
            print

        if self.config.testing:
            self.config.server_name = 'example.org'
            self.config.session_cookie_domain = 'example.org'


    ####
    #### hooks for first request, finalizing and error handling
    ####

    def before_first_request(self, request):
        """if you want to run something on the first incoming request then put it here"""
        pass

    def before_handler(self, handler):
        """this is run before handler processing starts but the handler is already initialized.
        You can check request, session and all that and maybe add some variables to the handler.

        If you return something else than None, handler processing will not happen and the
        return value will be taken as response instead. 
        """

    def after_handler(self, handler, response):
        """This hook is run after the handler processing is done but before the response is sent
        out. You can check the handler or response and maybe return your own response here. In case
        you do that this response will be used instead. If you return None then the original handler
        response will be used.
        """

    def finalize_response(self, response):
        """with this hook you can do something very generic to a response after all processing.

        Please not that the response can also be an :class:`~werkzeug.exception.HTTPException` instance
        which does not have a status code. 

        TODO: Shall we only do finalize on non-exception responses?
        """
        return response

    def finalize_setup(self):
        """a hook you can use to finalize the setup. You can add new routes, change configuration
        values etc.
        """

    def finalize_modules(self):
        """a hook you can use to add modules to the modules list more dynamically and while using the app's config for it. Simply add them to the ``self.modules`` list via ``append()``.
        """

    ####
    #### handler related hooks you can override
    ####
    
    def get_render_context(self, handler):
        """create the global app wide render context which is then passed to the template for
        rendering. Here you can pass in global variables etc. You also get the active handler
        for inspecting session, request and so on or for choosing based on the handler what to 
        pass.

        Note though that handler based parameters are probably better located in the handler itself.

        :param handler: The active handler instance
        """
        return {}


    ####
    #### TEMPLATE related
    ####

    @property
    def jinja_loader(self):
        """create the jinja template loader for this app.

        Also modules can create those loaders and the ``create_global_jinja_loader``
        method will gather all those together. Hence we have these two functions for
        obviously doing the same thing.

        In case you want to change the loader for app templates, simply override
        this property in your subclassed app.
        """
        if self.config.template_folder is not None:
            return jinja2.PackageLoader(self.import_name, self.config.template_folder)
        return None

    @property
    def global_jinja_loader(self):
        """create the global jinja loader by collecting all the loaders of the app
        and the modules together to one big loader
        """
        return DispatchingJinjaLoader(self)


    @werkzeug.cached_property
    def jinja_env(self):
        """create the jinja environment"""
        options = dict(self.jinja_options)
        if 'loader' not in options:
            options['loader'] = self.global_jinja_loader
        #if 'autoescape' not in options:
            #options['autoescape'] = self.select_jinja_autoescape
        rv = jinja2.Environment(**options)
        for name, flt in self.jinja_filters.items():
            rv.filters[name] = flt
        return rv
        

    ####
    #### SESSION related
    #### (directly copied from flask)
    ####

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.  Instead of overriding this method
        we recommend replacing the :class:`session_interface`.

        :param request: an instance of :attr:`request_class`.
        """
        return self.session_interface.open_session(self, request)


    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.  Instead of overriding this
        method we recommend replacing the :class:`session_interface`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        return self.session_interface.save_session(self, session, response)


    def make_null_session(self):
        """Creates a new instance of a missing session.  Instead of overriding
        this method we recommend replacing the :class:`session_interface`.

        .. versionadded:: 0.7
        """
        return self.session_interface.make_null_session(self)


    ####
    #### CONFIGURATION related
    ####
    
    def add_url_rule(self, url_or_path, endpoint = None, handler = None, **options):
        """add another url rule to the url map""" 
        if isinstance(url_or_path, URL):
            path = url_or_path.path
            endpoint = url_or_path.endpoint
            handler = url_or_path.handler
            options = url_or_path.options
        else:
            path = url_or_path
        if endpoint is None:
            assert handler is not None, "handler and endpoint not provided"
            endpoint = handler.__name__
        options['endpoint'] = endpoint
        options['defaults'] = options.get('defaults') or None

        rule = self.url_rule_class(path, **options)
        self.url_map.add(rule)
        if handler is not None:
            self.handlers[endpoint] = handler


    ####
    #### request processing
    ####

    @property
    def propagate_exceptions(self):
        """Returns the value of the `propagate_exceptions` configuration
        value in case it's set, otherwise a sensible default is returned.
        """
        rv = self.config.propagate_exceptions
        if rv is not None:
            return rv
        return self.config.testing or self.config.debug

    def check_first_request(self, request):
        """check if we have already run the hooks for the first request. If not, do so now"""
        if not self._got_first_request:
            self.before_first_request(request)
            self._got_first_request = True


    def find_handler(self, request):
        """retrieve the handler for the given URL. If it is not found, a routing exception
        is raised.

        :returns:   an instance of :class:`~starflyer.Handler` 
        """

        # create the url adapter
        urls = self.create_url_adapter(request)

        try:
            url_rule, request.view_args = urls.match(return_rule=True)
            request.url_rule = url_rule
            request.url_adapter = urls
        except werkzeug.exceptions.HTTPException, e:
            # this basically means 404 but maybe some debugging can occur
            # this is reraised then though
            self.raise_routing_exception(request, e)

        # check if we are called from a module
        module = None
        endpoint = url_rule.endpoint
        parts = endpoint.split(".")
        if len(parts)==2:
            module_name = parts[0]
            module = self.module_map.get(module_name, None)

        # try to find the right handler for this url and instantiate it
        return self.handlers[url_rule.endpoint](self, request, module = module)