flask源码解读03: Blueprint(蓝图)

 

先看蓝图的使用

创建一个蓝图

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

Blueprint接受两个参数:蓝本的名字和蓝本所在的包或模块,注意第一个参数, 后面会用到。这里创建了一个名为food的蓝图

将该蓝图注册到app中

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

通过蓝图注册路由

@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__方法是属性绑定

接着看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方法

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实例,记录了蓝图注册是的各种状态信息。

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属性,它在后面通过蓝图注册路由的时候有用

回到蓝图的register方法,生成state后,依次调用deferred_functions属性中注册的函数。

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

猜你喜欢

转载自www.cnblogs.com/lovelaker007/p/8568720.html