0%

flask源码之配置加载(八)

引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, jsonify

flask_app = Flask(__name__)

flask_app.config.from_mapping(
{
"SECRET_KEY": "you never know the secret key"
}
)


@flask_app.route('/', endpoint="11", methods=["GET", "POST"])
def hello_world():
return jsonify(code=0, msg="success")


if __name__ == '__main__':
flask_app.run()

原理

  1. Flask实例的config属性是Config对象
  2. Config类实现了from_mapping等方法

实现

config属性

image-20210115233513142

看来Config对象是make_config方法返回的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def make_config(self, instance_relative=False):
"""Used to create the config attribute by the Flask constructor.
The `instance_relative` parameter is passed in from the constructor
of Flask (there named `instance_relative_config`) and indicates if
the config should be relative to the instance path or the root path
of the application.

.. versionadded:: 0.8
"""
root_path = self.root_path
if instance_relative:
root_path = self.instance_path
defaults = dict(self.default_config)
defaults["ENV"] = get_env()
defaults["DEBUG"] = get_debug_flag()
return self.config_class(root_path, defaults)

我们看到了config类,就是self.config_class

这里暂停一下看 instance_relative,这个为true的话,会把 instance_path也就是你的Flask对象的路径,传给config_class类,毕竟如果你要从配置文件加载配置,我得知道文件路径在哪里,flask允许你根据Flask实例的相对路径来定位

默认是false,那就是应用的根目录绝对路径

Config类的定义

image-20210115234401391

原来是继承自dict

再看from_mapping方法,我去掉了一些

1
2
3
4
5
6
7
8
def from_mapping(self, *mapping, **kwargs):
mappings = []
mappings.append(kwargs.items())
for mapping in mappings:
for (key, value) in mapping:
if key.isupper():
self[key] = value
return True

大概逻辑就是把传入的字典放进list

然后遍历每个字典,把字典的key作为Config的属性名,value作为属性值

如何读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, jsonify, current_app

flask_app = Flask(__name__)

flask_app.config.from_mapping(
{
"SECRET_KEY": "you never know the secret key"
}
)


@flask_app.route('/', endpoint="11", methods=["GET", "POST"])
def hello_world():
# 使用
sk = current_app.config["SECRET_KEY"]
return jsonify(code=0, msg="success",data={"sk":sk})


if __name__ == '__main__':
flask_app.run()

记住,current_app是代理对象,代理的是LocalStack种的Flask实例,这个栈一般在请求来的时候才会被push

所以你无法在视图函数外面写

1
current_app.config["SECRET_KEY"]

因为此时从栈中 top出来的是None,None怎么又config属性呢

自定义配置方法

例如你的写javaleader要你从yaml中读取配置,你能怎么办呢?

首先 fuck一声

然后这么写,其实只需要给Config一个from_yaml方法就行了,但是我们不能改这个类,怎么办呢?

动态添加呗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义好读取方法
def from_yaml(self, filename, silent=False):
filename = os.path.join(self.root_path, filename)
try:
with open(filename) as yaml_file:
obj = yaml.load(yaml_file.read(), Loader=yaml.FullLoader)
except IOError as e:
if silent:
return False
# obj是个字典,那就不要大费周章了
return self.from_mapping(obj)


# 然后为 config 对象动态添加 from_yaml 方法
app.config.from_yaml = types.MethodType(from_yaml, app.config)
app.config.from_yaml("config.yaml")

然后你把config.yaml文件放到flask实例下面就好了

如果你的老板突然又怀念javaproperties

那你怎么写呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def from_properties(self, filename, silent=False, encode=None):
filename = os.path.join(self.root_path, filename)
try:
with open(filename) as properties_file:
obj = {}
for line in properties_file:
if line.find('=') > 0:
s = line.replace('\n', '').split("=")
obj[s[0]] = s[1]
except IOError as e:
if silent:
return False
return self.from_mapping(obj)
# 为 config 对象动态添加 from_properties 方法
app.config.from_properties = types.MethodType(from_properties, app.config)
app.config.from_properties("config.properties")
app.run()

这个工作起码要写两天,谁叫这个leader这么事儿多

我们看到在python中给实例动态添加方法和动态添加属性有点不同

动态添加属性简单

1
2
3
4
app.config.new_property="asdfa"
或者
setattr(app.config,"new_property","asdf")
这种根据字符串找到对象属性的做法又叫反射

但是给对象动态添加方法就不同了,要用到 types.MethodType

1
app.config.from_properties = types.MethodType(from_properties, app.config)

我们甚至还可以这样给类动态添加方法,只要把你的app.config换成类就行了

当然,你也可以动态删除一个对象的属性

1
2
del 对象.属性名
delattr(对象, “属性名”)