flask/config.py 这段代码是关于 Flask 应用的配置类 Config 的实现。Config 类继承了字典(dict)类,提供了一些特殊的方法,来方便进行配置参数的设置和获取。以下是这些方法的作用:
- Config.from_envvar():从环境变量载入配置,变量名为 variable_name;
- Config.from_prefixed_env():快速从以某个前缀(prefix)开头的环境变量中载入配置;
- Config.from_pyfile():从一个 Python 文件中载入配置,文件名为 filename;
- Config.from_object():从 Python 对象(obj)中载入配置,对象可以是一个字符串,也可以是一个实际对象的引用;
- Config.from_file():从一个文件中根据指定函数(load)将数据载入到配置中;
- Config.from_mapping():从一个 Mapping 对象或一组关键字参数中载入配置;
- Config.get_namespace():获取和指定命名空间(namespace)匹配的配置参数字典。
另外,这个 Config 类还提供了 ConfigAttribute 类,可以将类的属性转为对应 Config 对象中的 key,实现方便的调用和设置。
以下是代码具体注释:
# 引入一些依赖库
import errno
import json
import os
import types
import typing as t
from werkzeug.utils import import_string
class ConfigAttribute:
"""
使属性可以指向配置文件中的相应值
"""
def __init__(self, name: str, get_converter: t.Callable | None = None) -> None:
self.__name__ = name # 属性名称
self.get_converter = get_converter # 根据需要,对属性进行转换的函数
def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any:
"""
访问器方法,用于获取对象的属性值
"""
if obj is None:
return self # 如果 obj 是 None,则返回自身
rv = obj.config[self.__name__] # 从 config 字典中获取相应的值
if self.get_converter is not None: # 如果 get_converter 函数存在
rv = self.get_converter(rv) # 调用转换函数
return rv
def __set__(self, obj: t.Any, value: t.Any) -> None:
"""
访问器方法,用于设置对象的属性值
"""
obj.config[self.__name__] = value # 将 value 设置为 config 字典中相应的值
class Config(dict):
"""
提供了一种从文件中加载配置的方式
"""
def __init__(self, root_path: str | os.PathLike,
defaults: dict | None = None) -> None:
"""
构造函数,初始化对象
"""
super().__init__(defaults or {})
self.root_path = root_path # 设置根目录
def from_envvar(self, variable_name: str, silent: bool = False) -> bool:
"""
从环境变量中指定的文件加载配置
"""
rv = os.environ.get(variable_name)
if not rv:
if silent:
return False
raise RuntimeError(
f"The environment variable {variable_name!r} is not set"
" and as such configuration could not be loaded. Set"
" this variable and make it point to a configuration"
" file"
)
return self.from_pyfile(rv, silent=silent) # 从 Python 文件中加载配置
def from_prefixed_env(self, prefix: str = "FLASK", *,
loads: t.Callable[[str], t.Any] = json.loads) -> bool:
"""
从以指定前缀开头的环境变量中加载配置
"""
prefix = f"{prefix}_"
len_prefix = len(prefix)
for key in sorted(os.environ):
if not key.startswith(prefix):
continue
value = os.environ[key]
try:
value = loads(value)
except Exception:
pass
key = key[len_prefix:]
if "__" not in key:
self[key] = value
continue
current = self
*parts, tail = key.split("__")
for part in parts:
if part not in current:
current[part] = {}
current = current[part]
current[tail] = value
return True
def from_pyfile(self, filename: str | os.PathLike, silent: bool = False) -> bool:
"""
从 Python 文件中更新配置
"""
filename = os.path.join(self.root_path, filename)
d = types.ModuleType("config") # 创建模块对象
d.__file__ = filename
try:
with open(filename, mode="rb") as config_file: # 打开文件
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
except OSError as e:
if silent and e.errno in (
errno.ENOENT, errno.EISDIR,
errno.ENOTDIR): # 如果发生错误并设置了 silent,则返回 False
return False
e.strerror = f"Unable to load configuration file ({e.strerror})"
raise
self.from_object(d) # 从对象中更新配置
return True
def from_object(self, obj: object | str) -> None:
"""
从给定的对象或名称中更新配置
"""
if isinstance(obj, str):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
def from_file(
self,
filename: str | os.PathLike,
load: t.Callable[[t.IO[t.Any]], t.Mapping],
silent: bool = False,
text: bool = True,
) -> bool:
"""
从指定文件中加载配置
"""
filename = os.path.join(self.root_path, filename)
try:
with open(filename, "r" if text else "rb") as f: # 打开文件,如果 text 为真则以文本模式打开
obj = load(f) # 通过 load 函数将文件内容加载到对象中
except OSError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False
e.strerror = f"Unable to load configuration file ({e.strerror})"
raise
return self.from_mapping(obj) # 从对象中更新配置
def from_mapping(
self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any
) -> bool:
"""
更新配置映射的值
"""
mappings: dict[str, t.Any] = {}
if mapping is not None:
mappings.update(mapping)
mappings.update(kwargs)
for key, value in mappings.items():
if key.isupper():
self[key] = value
return True
def get_namespace(
self, namespace: str, lowercase: bool = True, trim_namespace: bool = True
) -> dict[str, t.Any]:
"""
获取指定命名空间的配置字典
"""
rv = {}
for k, v in self.items():
if not k.startswith(namespace):
continue
if trim_namespace:
key = k[len(namespace):]
else:
key = k
if lowercase:
key = key.lower()
rv[key] = v
return rv
def __repr__(self) -> str:
"""
返回对象的字符串表示形式
"""
return f"<{type(self).__name__} {dict.__repr__(self)}>"