flask 源码梗概

flask 源码梗概

flask 中的线程主要基于LocalStack进行使用,在global中维护这个类的两个对象。

# context locals
_request_ctx_stack = LocalStack() # 请求上下文:主要有 request 和 session 两个对象
_app_ctx_stack = LocalStack() # 应用上下文 :主要有 app 和 g 两个值。

梗概.drawio

额外补充:点击下图框选的图标,可以直接定位到源码所在的文件以及相关的文件路径。

image-20220724183136487

flask 源码启动阶段

说明:启动阶段即为,flask 初始化创建网站开始,请求还未到来之时的阶段。

启动阶段的代码主要如下所示:

from flask import Flask

app = Flask(__name__) # 创建app 对象

    app.config.from_object("config.settings") # 加载配置文件

@app.route("/index") # 注册视图函数
def index():
    return "Hello World"
if __name__ == '__main__':
    app.run() #程序启动

程序启动的步骤主要是有werkzug部分执行app__call__()方法,上述步骤在前面已经剖析过此处不在进行剖析。

  • 实例化对象

    app = Flask(__name__) # 创建app 对象
    

    该步骤会去执行Flask的初始化函数__init__()方法。该方法的剖析如下

    class Flask(Scaffold):# 继承与内部实现的类
        config_class = Config
        secret_key = ConfigAttribute("SECRET_KEY")
        url_map_class = Map
        '''
        	存储了rule 对象
        '''
        url_rule_class = Rule
        '''
        	存储了 路由,以及相关的请求方式,
        	'/index',['GET','Post',],endpoint
        '''
        
        config_class = Config# 此处与配置文件有关
        default_config = ImmutableDict( # ImmutableDict是实现一个不可变的字典
            {
                "ENV": None,
                "DEBUG": None,
                "SECRET_KEY": None,
                "SESSION_COOKIE_NAME": "session",
                "PREFERRED_URL_SCHEME": "http",
                "JSONIFY_PRETTYPRINT_REGULAR": False,
                "JSONIFY_MIMETYPE": "application/json",
    			# 更多的默认配置...
            }
        )
    
        '''... 更多其他的静态变量,不在展示'''
        def __init__(
            self,
            import_name: str,
            static_url_path: t.Optional[str] = None,
            static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
            static_host: t.Optional[str] = None,
            host_matching: bool = False,
            subdomain_matching: bool = False,
            template_folder: t.Optional[str] = "templates",
            instance_path: t.Optional[str] = None,
            instance_relative_config: bool = False,
            root_path: t.Optional[str] = None,
        ):
            # 实例属性
            self.instance_path = instance_path
            self.url_map = self.url_map_class() # 加(),此处实例化了一个 Map()对象,静态属性url_map_class = Map
            self.config = self.make_config(instance_relative_config)
            self.view_functions: t.Dict[str, t.Callable] = {} # view_functions 为一个字典
        	# ... 更多的实例化属性以及注释,此处不在展示。
            
            if self.has_static_folder:
                assert (
                    bool(static_host) == host_matching
                ), "Invalid static_host/host_matching combination"
                # Use a weakref to avoid creating a reference cycle between the app
                # and the view function (see #3761).
                self_ref = weakref.ref(self)
                self.add_url_rule(
                    # 此阶段的语句表示,即便是没有注册过任何视图函数以及路由,也会有相关的静态路由直接存储在对象中。
                    f"{self.static_url_path}/<path:filename>",
                    endpoint="static",
                    host=static_host,
                    view_func=lambda **kw: self_ref().send_static_file(**kw),
                )
    
  • 加载配置文件

    app.config.from_object("config.settings") # 加载配置文件
    '''
    此步骤便于理解可以写为以下两步
    v = app.config
    v.from_object("config.settings")
    '''
    

    from_object()源码如下图

    def from_object(self, obj: t.Union[object, str]) -> None:
        if isinstance(obj, str): # 判断参数类型
            obj = import_string(obj)
        for key in dir(obj): # 循环对象中的所有键值对
            if key.isupper():# 检查对否都为大写字母
                self[key] = getattr(obj, key) # 通过反射的方式为(self)当前对象进行赋值
    

    当前self是谁的对象需要继续看是谁在调用它。

    # app.config 是因为实例属性中包含了相关的变量
    self.config = self.make_config(instance_relative_config) # 执行的是make_config方法
    

    make_config 的源码如下:

    def make_config(self, instance_relative: bool = False) -> Config: #此处表明返回的是一个Config对象
        root_path = self.root_path
        if instance_relative:
            root_path = self.instance_path 
        defaults = dict(self.default_config) #默认配置,导入的是Flask内部的默认配置
        defaults["ENV"] = get_env()
        defaults["DEBUG"] = get_debug_flag()
        return self.config_class(root_path, defaults) 
    

    由上述源码可知,当我们不去加载自己的配置文件的时候,Flask 会默认加载相关的配置。

    此处的返回值为config_class(),在flask源码中,静态变量有config_class = Config,表明该方法是该类的一个赋值,但是并未实例化,此处加上(),表示实例化该对象,相关的源码如下所示:

    class Config(dict):
    	# 执行初始胡方法
        def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None:
            super().__init__(defaults or {}) # 执行父类的传参,默认配置,或者是一个空字典
            self.root_path = root_path # 将路径设置到相关的属性上
    

    由于该类继承的是空字典或者是flask内部的不可变字典ImmutableDict,所以回到本质上讲,配置文件的加载是将相关的配置,以键值的形式加载到对应的字典中,配置文件的对象是一个字典

  • 注册路由及视图函数

    # @app.route("/index",methods=['GET','POST'])
    @app.route("/index") # 注册视图函数
    def index():
        return "Hello World"
    

    上述可知Flask类继承与Scaffold类,而装饰器route则属于该父类多定义的方法

    class Scaffold:
        # 该方法符合装饰器的定义
        def route(self, rule: str, **options: t.Any) -> t.Callable[[F], F]:
            def decorator(f: F) -> F: # 定义函数
                endpoint = options.pop("endpoint", None) # 检查参数中是否存在路由的反向解释符。
                self.add_url_rule(rule, endpoint, f, **options) # 执行相关的路由添加函数。
                # !!! 特别注意此处执行的`add_url_rule`并不一定是父类中的该方法,flask中重写了该方法,此时的self,代指的是当前的 app 对象
                return f
    
            return decorator # 返回内部所执行的函数。
    

    add_url_rule相关源码如下

    补充:字典的pop()方法。

    pop() 方法从字典中删除指定的项目。被删除的项目的是这个 pop() 方法的返回值,第二个参数可以设置默认返回值。请看下面的例子。

    dic = {1:"aa","methods":['GET','POST']}
    print(dic.pop("methods",None)) # ['GET', 'POST']
    print(dic) # {1: 'aa'}
    print(dic.pop("methodsasss",None)) # None
    

    flask 源码中的请求方法的限定正式基于此方法实现获取传入的值。

    @setupmethod
    def add_url_rule(
        self,
        rule: str,# 路由字符串
        endpoint: t.Optional[str] = None,# '路由的名字'
        view_func: t.Optional[t.Callable] = None,# 视图函数
        provide_automatic_options: t.Optional[bool] = None,
        **options: t.Any,
    ) -> None:
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)  # endpoint 如果为空,则能与函数的名
        options["endpoint"] = endpoint # 将 endpoint 值写入到字典中去,
        # 通过此方法获取相应的 methods的列表值,没有传入则返回值为None
        methods = options.pop("methods", None) # 此处因为route装饰器可以传入参数 methods=['GET','POST']
    
        # 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: # 使用反射,为view_func 设置相关的值,没有传入方法的时候,或者将methods使用GET 作为默认年至进行赋值给
            methods = getattr(view_func, "methods", None) or ("GET",)
            # view_func 为装饰器传入的函数,使用 getattr 获取相关的值
        if isinstance(methods, str): # 检查为字符串类型
            raise TypeError(
                "Allowed methods must be a list of strings, for"
                ' example: @app.route(..., methods=["POST"])'
            )
        methods = {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
            )
    	# 是否禁用http,OPTIONS 自动响应的实现。
        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 # 更新集合,添加所有其他元素,Python2.6 版本更新的语法。	
    	
        # url_rule_class这个变量在Flask的类属性中进行了声明,    url_rule_class = Rule
        rule = self.url_rule_class(rule, methods=methods,**options) # 将路由规则和请求方式传入,返回的是Rule对象
        # 此时rule对象中存储了路由和请求方式,如果存在endpoint,options中也一并写入了endpoint。Rule的参数会接收这些参数
       
        # 将自动响应的方式,赋值到rule对象中去。
        rule.provide_automatic_options = provide_automatic_options  # type: ignore
    
        self.url_map.add(rule) # 将rule对象添加到对应的map对象中,
        if view_func is not None: # 如果视图函数不为空
            old_func = self.view_functions.get(endpoint) # 字典中根据endpoint 获取相关的函数
            if old_func is not None and old_func != view_func: # endponit 重复,抛出相关的异常
                raise AssertionError(
                    "View function mapping is overwriting an existing"
                    f" endpoint function: {endpoint}"
                )
            self.view_functions[endpoint] = view_func # 将试图函数写入对应的字典中,使用endpoint作为字典的键
    

    相关联的源码

    def _endpoint_from_view_func(view_func: t.Callable) -> str:
        assert view_func is not None, "expected view func if endpoint is not provided."
        return view_func.__name__ # 返回函数的名称。
    

    总结:

    1.将url='/index' 和 methods= ['GET','Post'] 和 endpoint ='index'封装到Rule对象,注:endpoint 默认是函数名
    
    2.将Rule 对象添加到 url_map 中去
    
    3.把 endpoint 和函数的对应关系放到了 view_functions 字典中去。
    
  • 目前总结

    # 源码中使用的主要属性为一下步骤。
    app.config
    app.url_map
    app.view_functions
    
  • 运行

    app.run(),本步骤与上述的启动函数相同。

posted @ 2022-07-26 19:53  紫青宝剑  阅读(114)  评论(0编辑  收藏  举报