flask源碼解讀03: Blueprint(藍圖)

藍圖的使用

  建立一個藍圖json

from flask import Blueprint
food = Blueprint('food', __name__)

  Blueprint接受兩個參數:藍本的名字和藍本所在的包或模塊,注意第一個參數, 後面會用到。這裏建立了一個名爲food的藍圖flask

  將該藍圖註冊到app中app

from .food import food as food_blueprint
app.register_blueprint(food_blueprint, url_prefix='/food')

  經過藍圖註冊路由dom

@food.route('/', methods=['GET', 'POST'])
def index():
    pass

 

BLueprint源碼

class Blueprint(_PackageBoundObject):
    warn_on_modifications = False
    _got_registered_once = False

    json_encoder = None
    json_decoder = None

    import_name = None

    template_folder = None

    root_path = None

    def __init__(self, name, import_name, static_folder=None,
                 static_url_path=None, template_folder=None,
                 url_prefix=None, subdomain=None, url_defaults=None,
                 root_path=None):
        _PackageBoundObject.__init__(self, import_name, template_folder,
                                     root_path=root_path)
        self.name = name
        self.url_prefix = url_prefix
        self.subdomain = subdomain
        self.static_folder = static_folder
        self.static_url_path = static_url_path
        self.deferred_functions = []
        if url_defaults is None:
            url_defaults = {}
        self.url_values_defaults = url_defaults

  __init__方法是屬性綁定ide

  接着看app.register_blueprint方法函數

class Flask(_PackageBoundObject):    
    @setupmethod
    def register_blueprint(self, blueprint, **options):
       
        first_registration = False

        if blueprint.name in self.blueprints:
            assert self.blueprints[blueprint.name] is blueprint, (
                'A name collision occurred between blueprints %r and %r. Both'
                ' share the same name "%s". Blueprints that are created on the'
                ' fly need unique names.' % (
                    blueprint, self.blueprints[blueprint.name], blueprint.name
                )
            )
        else:
            self.blueprints[blueprint.name] = blueprint
            self._blueprint_order.append(blueprint)
            first_registration = True

        blueprint.register(self, options, first_registration)

  app.register_blueprint方法簡單的檢測了註冊的藍圖是否在app中已存在以後,調用了藍圖的register方法ui

class Blueprint(_PackageBoundObject): 
     def register(self, app, options, first_registration=False):
        """Called by :meth:`Flask.register_blueprint` to register all views
        and callbacks registered on the blueprint with the application. Creates
        a :class:`.BlueprintSetupState` and calls each :meth:`record` callback
        with it.

        :param app: The application this blueprint is being registered with.
        :param options: Keyword arguments forwarded from
            :meth:`~Flask.register_blueprint`.
        :param first_registration: Whether this is the first time this
            blueprint has been registered on the application.
        """
        self._got_registered_once = True
        state = self.make_setup_state(app, options, first_registration)

        if self.has_static_folder:
            state.add_url_rule(
                self.static_url_path + '/<path:filename>',
                view_func=self.send_static_file, endpoint='static'
            )

        for deferred in deferred_functions:
       deferred(state)

  先生成state,state是BlueprintSetupState實例,記錄了藍圖註冊是的各類狀態信息。this

class BlueprintSetupState(object):

    def __init__(self, blueprint, app, options, first_registration):
        #: a reference to the current application
        self.app = app

        #: a reference to the blueprint that created this setup state.
        self.blueprint = blueprint

        #: a dictionary with all options that were passed to the
        #: :meth:`~flask.Flask.register_blueprint` method.
        self.options = options

        #: as blueprints can be registered multiple times with the
        #: application and not everything wants to be registered
        #: multiple times on it, this attribute can be used to figure
        #: out if the blueprint was registered in the past already.
        self.first_registration = first_registration

        subdomain = self.options.get('subdomain')
        if subdomain is None:
            subdomain = self.blueprint.subdomain

        #: The subdomain that the blueprint should be active for, ``None``
        #: otherwise.
        self.subdomain = subdomain

        url_prefix = self.options.get('url_prefix')
        if url_prefix is None:
            url_prefix = self.blueprint.url_prefix

        #: The prefix that should be used for all URLs defined on the
        #: blueprint.
        self.url_prefix = url_prefix

        #: A dictionary with URL defaults that is added to each and every
        #: URL that was defined with the blueprint.
        self.url_defaults = dict(self.blueprint.url_values_defaults)
        self.url_defaults.update(self.options.get('url_defaults', ()))

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """A helper method to register a rule (and optionally a view function)
        to the application.  The endpoint is automatically prefixed with the
        blueprint's name.
        """
        if self.url_prefix:
            rule = self.url_prefix + rule
        options.setdefault('subdomain', self.subdomain)
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        defaults = self.url_defaults
        if 'defaults' in options:
            defaults = dict(defaults, **options.pop('defaults'))
        self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
                              view_func, defaults=defaults, **options)

  請注意url_prefix屬性,它在後面經過藍圖註冊路由的時候有用url

 

  回到藍圖的register方法,生成state後,依次調用deferred_functions屬性中註冊的函數。spa

class Blueprint(_PackageBoundObject):    
  def record(self, func): """Registers a function that is called when the blueprint is registered on the application. This function is called with the state as argument as returned by the :meth:`make_setup_state` method. """ if self._got_registered_once and self.warn_on_modifications: from warnings import warn warn(Warning('The blueprint was already registered once ' 'but is getting modified now. These changes ' 'will not show up.')) self.deferred_functions.append(func) def route(self, rule, **options): """Like :meth:`Flask.route` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ def decorator(f): endpoint = options.pop("endpoint", f.__name__) self.add_url_rule(rule, endpoint, f, **options) return f return decorator def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ if endpoint: assert '.' not in endpoint, "Blueprint endpoints should not contain dots" if view_func: assert '.' not in view_func.__name__, "Blueprint view function name should not contain dots" self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))

   deferred_functions列表在實例初始化的時候是一個空列表,經過record方法向deferred_functions添加函數。

  當咱們經過@blueprint.route這個帶參數的裝飾器定義路由的時候,經過上面的代碼能夠看到,裝飾器經過endpoint = options.pop("endpoint", f.__name__)語句找到了endpoint,若是options中指定了endpoint則使用指定的,不然使用被裝飾函數的名稱做爲endpoint。 self.add_url_rule(rule, endpoint, f, **options)語句調用了add_url_rule方法,觀察add_url_rule方法發現經過record註冊函數。到此即可知道咱們經過藍圖註冊全部路由state.add_url_rule方法註冊到app中。

  回到前面BlueprintSetupState中的add_url_rule方法,rule = self.url_prefix + rule代表會在每一個rule以前添加url_prefix,這個url_prefix就是前面註冊藍圖時指定的。這就是咱們實現網頁分類功能的基礎,對於不一樣的類別定義不一樣的藍圖,註冊藍圖的時候指定不一樣的url_prefix,這樣不一樣類別的網頁自動的加上了各自的前綴。

 

下面是app註冊路由的函數,能夠發如今flask中,rule和endpoint關聯(),endpoint和view_func關聯(經過字典實現)

    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
        if isinstance(methods, string_types):
            raise TypeError('Allowed methods have to be iterables of strings, '
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(view_func,
                'provide_automatic_options', None)

        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func
相關文章
相關標籤/搜索