先看蓝图的使用
创建一个蓝图
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