详细文档¶
配置数据路径语法¶
一种简便的配置数据访问路径表示方法,
在一些高级方法如 retrieve() 中使用
由三种语法组成
属性键
由 \. 开头,紧随的字符串组成
r"\.key1\.key2\.key3"
索引键
由 \[ 开头,紧随的数字与 \] 组成
r"\[0\]"
元信息
由 \{ 开头,紧随的字符串与 \} 组成
元信息会被附加到紧随的键上
r"\{meta 1\}\.key\{meta 2\}\[0\]"
# 这相当于
[AttrKey("key", meta="meta 1"), IndexKey(0, meta="meta 2")]
转义
键名或元信息中如有 \ 需要转义 为 \\
r"\{1\\2\}\.\\key\{2\\2\}\[0\]"
# 这将解析为
[AttrKey(r"\key", meta=r"1\2"), IndexKey(0, meta=r"2\2")]
可以简单的通过 str.replace("\\", "\\\\") 来转义
注意
如果没有转义,且 \ 后面的字符不是以上特殊转义字符,则会原样保留并发出警告
不应依赖此行为
requireConfig¶
用于加载并验证配置数据的高层方法
有手动调用和装饰器两种获取验证数据的方式
1from c41811.config import MappingConfigData
2from c41811.config import JsonSL
3from c41811.config import requireConfig
4
5JsonSL().register_to()
6
7require = requireConfig(
8 "", "config.json", {
9 "config": "data",
10 },
11)
12
13# 调用check手动获取配置数据
14config: MappingConfigData = require.check()
15print(config) # 打印:{'config': 'data'}
16
17
18# 使用装饰器自动注入配置数据
19@require
20def test(cfg):
21 print(cfg) # 打印:{'config': 'data'}
22
23
24test()
25
26
27class Test:
28 @require
29 def __init__(self, cfg):
30 print(self, cfg) # 打印:<__main__.Test object at 0x0000025B37D812E0> {'config': 'data'}
31
32 @classmethod
33 @require
34 def cls_func(cls, cfg):
35 print(cls, cfg) # 打印:<class '__main__.Test'> {'config': 'data'}
36 return cls
37
38 @staticmethod
39 @require
40 def static_func(cfg):
41 print(cfg) # 打印:{'config': 'data'}
42
43
44Test().cls_func().static_func()
Pydantic验证器工厂¶
提示
pydantic 验证器工厂不支持 skip_missing 选项
这是因为pydantic自带该功能
如果提供了该参数会产生一个警告 不会起到任何实际作用
validator_factory 参数设为 PYDANTIC 或 "pydantic" 时使用该验证工厂
validator 参数为任意合法的 BaseModel
1from pydantic import BaseModel
2from pydantic import Field
3
4from c41811.config import MappingConfigData
5from c41811.config import ConfigFile
6from c41811.config import JsonSL
7from c41811.config import requireConfig
8from c41811.config import save
9
10JsonSL().register_to()
11
12save("", "test.json", config=ConfigFile(MappingConfigData({
13 "key": "value"
14})))
15
16
17class Config(BaseModel):
18 key: str = "default value"
19 unknown_key: dict = Field(default_factory=dict)
20
21
22print(requireConfig("", "test.json", Config, "pydantic").check())
23# 打印:{'key': 'value', 'unknown_key': {}}
默认验证器工厂¶
validator_factory 参数设为 DEFAULT 或 None 时使用该验证工厂
validator 参数可以为 Iterable[str] 或 Mapping[str | ABCPath, Any]
Iterable 的元素或 Mapping 的键会被作为
配置数据路径语法 解析,如非特殊配置结果将一定包含这些 配置数据路径
备注
[path, path1, path2, ...] 与 {path: Any, path1: Any, path2: Any, ...} 等价
小技巧
如果validator同时包含路径和路径的父路径
例: r"\.first\.second\.third" 与 r"\.first" 同时出现
这时 first 中不仅包含 second ,还会允许任意额外的键
1from c41811.config import MappingConfigData
2from c41811.config import ConfigFile
3from c41811.config import JsonSL
4from c41811.config import requireConfig
5from c41811.config import save
6
7JsonSL().register_to()
8
9save("", "test.json", config=ConfigFile(MappingConfigData({
10 "first": {
11 "second": {
12 "third": 111,
13 "foo": 222
14 },
15 "bar": 333
16 },
17 "baz": 444
18})))
19
20print(requireConfig("", "test.json", ["first", "first\\.second\\.third"]).check())
21# 打印:{'first': {'second': {'third': 111}, 'bar': 333}}
Iterable[str]¶
一组需求的 配置数据路径 ,会检查路径存在与否,不会校验数据类型
1from c41811.config import MappingConfigData
2from c41811.config import ConfigFile
3from c41811.config import JsonSL
4from c41811.config import requireConfig
5from c41811.config import save
6
7JsonSL().register_to()
8
9save("", "test.json", config=ConfigFile(MappingConfigData({
10 "foo": {
11 "bar": {
12 "baz": "value"
13 },
14 "bar1": "value1"
15 },
16 "foo1": "value2"
17})))
18
19print(requireConfig("", "test.json", ["foo", "foo\\.bar\\.baz", "foo1"]).check())
20# 打印:{'foo': {'bar': {'baz': 'value'}, 'bar1': 'value1'}, 'foo1': 'value2'}
Mapping[str | ABCPath, Any]¶
键为 配置数据路径 ,值为需求的数据类型,会检查路径存在与否,并校验数据类型
小技巧
r"first\.second\.third": int 与 "first": {"second": {"third": int}} 等价
允许混用路径与嵌套字典
1from c41811.config import MappingConfigData
2from c41811.config import ConfigFile
3from c41811.config import JsonSL
4from c41811.config import requireConfig
5from c41811.config import save
6
7JsonSL().register_to()
8
9save("", "test.json", config=ConfigFile(MappingConfigData({
10 "first": {
11 "second": {
12 "third": 111,
13 "foo": 222
14 },
15 "bar": 333
16 },
17 "baz": 444
18})))
19
20paths = requireConfig("", "test.json", {
21 r"first\.second\.third": int,
22 r"first\.bar": int,
23}).check()
24nested_dict = requireConfig("", "test.json", {
25 "first": {
26 "second": {
27 "third": int
28 },
29 "bar": int
30 }
31}).check()
32
33print(paths) # 打印: {'first': {'second': {'third': 111}, 'bar': 333}}
34print(nested_dict) # 打印: {'first': {'second': {'third': 111}, 'bar': 333}}
35print(paths == nested_dict) # 打印: True
以下是两种验证器语法
1from c41811.config import MappingConfigData
2from c41811.config import ConfigFile
3from c41811.config import JsonSL
4from c41811.config import requireConfig
5from c41811.config import save
6
7JsonSL().register_to()
8
9save("", "test.json", config=ConfigFile(MappingConfigData({
10 "first": {
11 "second": {
12 "third": 111,
13 "foo": 222
14 },
15 "bar": 333
16 },
17 "baz": 444
18})))
19
20# 使用路径字符串
21print(requireConfig("", "test.json", {
22 "first\\.second\\.third": int,
23 "first\\.bar": int,
24}).check()) # 打印:{'first': {'second': {'third': 111}, 'bar': 333}}
25
26# 使用嵌套字典
27print(requireConfig("", "test.json", {
28 "first": {
29 "second": {
30 "third": int
31 },
32 "bar": int
33 }
34}).check()) # 打印:{'first': {'second': {'third': 111}, 'bar': 333}}
35
36# 混搭
37print(requireConfig("", "test.json", {
38 "first": {
39 "second\\.third": int,
40 "second": dict,
41 "bar": int
42 },
43 "baz": int
44}).check()) # 打印: {'first': {'second': {'third': 111, 'foo': 222}, 'bar': 333}, 'baz': 444}
类型检查和填充默认值功能
1from typing import Sequence
2
3from c41811.config import MappingConfigData
4from c41811.config import ConfigFile
5from c41811.config import FieldDefinition
6from c41811.config import JsonSL
7from c41811.config import requireConfig
8from c41811.config import save
9from c41811.config.errors import ConfigDataTypeError
10
11JsonSL().register_to()
12
13save("", "test.json", config=ConfigFile(MappingConfigData({
14 "first": {
15 "second": {
16 "third": 111,
17 "foo": 222
18 },
19 "bar": 333
20 },
21 "baz": [444]
22})))
23
24# 类型检查,如果不满足会报错
25print(requireConfig("", "test.json", {
26 "first\\.second": dict[str, int],
27 "baz": list[int],
28}).check()) # 打印:{'first': {'second': {'third': 111, 'foo': 222}}, 'baz': [444]}
29
30try:
31 requireConfig("", "test.json", {
32 "first\\.second": dict[str, str] # 类型不匹配
33 }).check()
34except ConfigDataTypeError as err:
35 print(err) # 打印:\.first\.second\.third -> \.third (3 / 3) Must be '<class 'str'>', Not '<class 'int'>'
36
37try:
38 requireConfig("", "test.json", {
39 "baz": list[str]
40 }).check()
41except ConfigDataTypeError as err:
42 print(err) # 打印:\.baz\[0\] -> \[0\] (2 / 2) Must be '<class 'str'>', Not '<class 'int'>'
43
44# 默认值,路径不存在时自动填充
45print(requireConfig("", "test.json", {
46 "first\\.second\\.third": 999, # 因为路径已存在所以不会填充
47 "not\\.exists": 987
48}).check()) # 打印: {'first': {'second': {'third': 111}}, 'not': {'exists': 987}}
49
50# 在提供默认值的同时提供类型检查
51# 一般情况下用不着,因为会自动根据默认值的类型来设置类型检查
52# 一般在传入的默认值类型与类型检查的类型不同或规避特殊语法时使用
53print(requireConfig("", "test.json", {
54 "first\\.second\\.third": FieldDefinition(int, 999),
55 "not\\.exists": FieldDefinition(int, 987),
56 "baz": FieldDefinition(Sequence[int], [654]),
57}).check()) # 打印:{'first': {'second': {'third': 111}}, 'not': {'exists': 987}, 'baz': [444]}
58print(requireConfig("", "test.json", {
59 "first\\.second": FieldDefinition(dict, {"key": int}, allow_recursive=False), # 并不会被递归处理,会被当作默认值处理
60 "not exists": FieldDefinition(dict, {"key": int}, allow_recursive=False),
61 "type": FieldDefinition(type, frozenset),
62}).check())
63# 打印:
64# {'first': {'second': {'third': 111, 'foo': 222}}, 'not exists': {'key': <class 'int'>}, 'type': <class 'frozenset'>}
65
66# 含有非字符串键的子Mapping不会被递归处理
67print(requireConfig("", "test.json", {
68 "first\\.second": {"third": str, 3: 4},
69 # 效果等同于FieldDefinition(dict, {"third": str, 3: 4}, allow_recursive=False)
70 "not exists": {1: 2},
71}).check()) # 打印:{'first': {'second': {'third': 111, 'foo': 222}}, 'not exists': {'key': <class 'int'>}}
几个关键字参数
1from c41811.config import MappingConfigData
2from c41811.config import ConfigFile
3from c41811.config import JsonSL
4from c41811.config import requireConfig
5from c41811.config import save
6from c41811.config.errors import RequiredPathNotFoundError
7
8JsonSL().register_to()
9
10raw_data = MappingConfigData({
11 "first": {
12 "second": {
13 "third": 111,
14 "foo": 222
15 },
16 "bar": 333
17 },
18 "baz": [444]
19})
20
21save("", "test.json", config=ConfigFile(raw_data))
22
23# allow_modify, 在填充默认值时将默认值填充到源数据
24requireConfig("", "test.json", {
25 "not\\.exists": 987
26}).check(allow_modify=False)
27
28# 未提供allow_modify参数时不会影响源数据
29print(raw_data.exists("not\\.exists")) # 打印:False
30
31# ConfigRequirementDecorator.__init__将allow_modify默认值设为True
32requireConfig("", "test.json", {
33 "not\\.exists": 987
34}).check()
35
36print(raw_data.exists("not\\.exists")) # 打印:True
37raw_data.delete("not\\.exists")
38
39# skip_missing, 在没提供默认值且键不存在时忽略
40try:
41 requireConfig("", "test.json", {
42 "not\\.exists": int
43 }).check()
44except RequiredPathNotFoundError as err:
45 print(err) # 打印:\.not\.exists -> \.exists (2 / 2) Operate: Read
46
47data: MappingConfigData = requireConfig("", "test.json", {
48 "not\\.exists": int
49}).check(skip_missing=True)
50
51print(data.exists("not\\.exists")) # 打印:False
自定义验证器¶
validator_factory 参数设为 CUSTOM 或 "custom" 时采用该策略
这将直接把 validator 参数当作验证器 Callable[[Ref[D], ValidatorOptions], D] 来使用,如果 validator 为 None
则验证器默认为 lambda ref:ref.value ,即无验证
1from copy import deepcopy
2from typing import Any
3
4from c41811.config import ConfigFile
5from c41811.config import JsonSL
6from c41811.config import MappingConfigData
7from c41811.config import ValidatorOptions
8from c41811.config import requireConfig
9from c41811.config import save
10from c41811.config.utils import Ref
11
12
13def modify_value_validator[D: MappingConfigData[Any]](ref: Ref[D], cfg: ValidatorOptions) -> D:
14 data = deepcopy(ref.value)
15 for path in data.keys(recursive=True, end_point_only=True):
16 data.modify(path, "modified!")
17 if cfg.allow_modify:
18 ref.value = data
19 return data
20
21
22JsonSL().register_to()
23
24save("", "test.json", config=ConfigFile(MappingConfigData({
25 "key": "value"
26})))
27print(requireConfig("", "test.json", modify_value_validator, "custom").check())
28# 输出:{'key': 'modified!'}
组件验证工厂¶
validator_factory 参数设为 COMPONENT 或 "component" 时使用该验证工厂
validator 参数为 Mapping[str | None, Any]
键为组件成员文件名,值为成员对应的验证器,组件成员文件名为None则为元配置信息验证器
危险
永远不应该尝试验证 NoneConfigData ,这将创建一个
parser 为
None 的 ComponentMeta,如果你没有在
额外验证器选项 传入默认的
组件元数据验证器 这将可能导致(至少目前默认情况下会)无法将组件元配置同步到组件元信息,最终导致元信息和组件成员不匹配抛出错误
ConfigDataFactory¶
此类本身不提供任何实际配置数据操作,仅根据传入的参数类型从注册表中选择对应的类实例化并返回
注册表存储在 TYPES
传入的数据类型及其对应类
优先级从上倒下,取初始化参数的第一个参数的类型进行判断,未传入参数时取 None
数据类型 |
配置数据类 |
|---|---|
原样返回 |
|
|
|
备注
是的, ComponentConfigData 不在这里面,仅由
componentSL 或
ComponentValidatorFactory 创建
参见
具体原因与 组件验证工厂 所述大同小异
若希望作为类型提示请考虑下表
配置数据类型 |
描述 |
|---|---|
所有配置数据的抽象基类,仅提供了最基础的 |
|
支持复杂嵌套数据的抽象基类,提供了 |
|
单文件配置数据的基类,提供的单文件配置数据的基本实现,如 |
NoneConfigData¶
无参数调用 ConfigDataFactory 的默认值,也是 initialize() 的默认返回值
初始化参数永远必须为 None 或压根不传,允许传参更大是为了兼容父类接口
MappingConfigData¶
最常见的配置数据类型,提供了 MutableMapping 的完整实现。
retrieve() 等高级方法当返回值为 Mapping 或
Sequence 时, retrieve() 会返回
MappingConfigData 或 SequenceConfigData
SequenceConfigData¶
提供了 MutableSequence 的完整实现
retrieve() 等高级方法当返回值为 Mapping 或
Sequence 时, retrieve() 会返回
MappingConfigData 或 SequenceConfigData
StringConfigData¶
字符串与字节串的配置数据
尚未完整实现 UserString 的接口
NumberConfigData¶
提供了 numbers.Integral 与 numbers.Real 的大部分实现
BoolConfigData¶
继承自 NumberConfigData ,提供了 bool 的实现
ComponentConfigData¶
组件配置数据由元信息与成员配置组成
元信息¶
存储了 元配置 、 成员定义 、 处理顺序 、 解析器 几部分必须的值。
元配置
元信息默认存储在 __meta__ 配置文件内,元配置就是 __meta__ 内的原始配置数据,文件名由 meta_file 指定
以 MappingConfigData 存储
成员定义
成员 文件名 , 别名 ,及其 配置格式
文件名 应严格与 成员 一一对应
别名 可以在 处理顺序 中或 键元信息语法指定成员进行操作 中使用
配置格式 会在保存加载期间优先使用
处理顺序
retrieve() 等方法从成员的搜索顺序,参见下表:
顺序表 |
使用该表的方法 |
|---|---|
|
|
|
|
|
|
|
解析器
负责将 元配置 与 元信息 以一定格式互相转换,由
parser 属性存储,可以在
ComponentSL() 中通过初始化方法的 meta_parser 参数指定
参见
默认实现: ComponentMetaParser 讲解: 元配置数据结构
成员¶
成员配置文件的配置数据,支持所有 ABCIndexedConfigData 的子类
键元信息语法指定成员进行操作
retrieve() 等方法支持使用 键元信息 指定成员进行操作
comp_data.retrieve(r"\{member.json\}\.key")
# 如果有别名也可以使用别名
comp_data.retrieve(r"\{alies-member\}\.key")
# 如果成员为SequenceConfigData
comp_data.retrieve(r"\{member.json\}\[0\]")
具体来说,会读取 path[0].meta ,所以只有第一个键的元信息起到作用
EnvironmentConfigData¶
继承自 MappingConfigData ,内部维护了与初始化参数的键差异
参见
SL处理器¶
项目中的 SL 都是 SaveLoad 的缩写
配置格式 |
处理器 |
注册名 |
支持的文件后缀 |
简介 |
|---|---|---|---|---|
JSON |
json |
.json |
基于内置 |
|
HJSON |
hjson |
.hjson |
基于第三方库 |
|
Pickle |
pickle |
.pickle .pkl |
基于内置 |
|
YAML |
yaml |
.yaml .yml |
基于第三方库 |
|
YAML |
ruamel_yaml |
.yaml .yml |
基于第三方库 |
|
TOML |
toml |
.rtoml |
基于第三方库 |
|
TOML |
tomlkit |
.toml |
基于第三方库 |
|
CBOR |
cbor2 |
.cbor |
基于第三方库 |
|
Properties |
jproperties |
.properties |
基于第三方库 |
|
Python |
python |
.py |
||
PythonLiteral |
python_literal |
.python_literal .pyl .py |
基于 |
|
PlainText |
plaintext |
.md .markdown .rst .txt |
纯文本格式,支持额外参数
|
|
TarFile |
tarfile:$compression_shortname$ |
.tar .tar.$compression_shortname$ .tar.$compression_fullname$ |
基于内置 |
|
ZipFile |
zipfile:$compression_shortname$-$compress_level$ |
.$compress_level$.zip .zip .$compress_level$.$compression_fullname$ .$compress_level$.$compression_shortname$ .$compression_shortname$ .$compression_fullname$ |
基于内置 |
|
Component |
component |
.component .comp |
组合多个 |
|
OSEnv |
os.environ |
.os.env .os.environ |
基于内置 |
ComponentMetaParser¶
ComponentSL 的默认 元配置 解析器。
元配置数据结构
{
"members": [ # 声明组件成员,此处声明的文件名必须与提供的组件成员严格匹配
"filename.json", # 可以直接提供文件名
{
"filename": "name", # 也可以通过键值对声明
# 通过键值对声明可以附带额外信息
"alias": "na", # 此成员别名为"na",可被`键元信息`语法或下面的操作顺序使用
"config_format": "ruamel_yaml", # 此成员配置格式为ruamel.yaml的YAML解析器
},
{"filename": "my-member.pickle"}, # 当然上面这些额外信息是可选的
],
# 操作顺序,并没有严格的检查,已经声明的组件成员可以不被使用但是禁止出现完全重复的名称
"order": [ # 定义基础的操作顺序,若未提供则顺序敏感地使用members所声明的文件名(若存在则优先使用别名),可以提供一个空列表以禁用默认行为
# "filename.json", # 越靠前优先级越高
# "na", # 这里是允许别名的
# 这里order为空以禁用默认行为(只要提供了此项就会禁用默认行为,无论是否完全为空列表或只填充了部分/全部成员)
],
"orders": { # order会被同步追加到orders的create/read/update/delete,所以orders优先级最高
# 会简单的检查将要追加的名称是否已经在表中(如果是则跳过),这并不会同时检查文件名与别名是否同时存在
"create": [], # 禁止创建新的键,此项目控制setdefault方法在路径不存在时的写入顺序与modify无法替换一个现有的数据时来创建数据
"read": ["filename.json", "my-member.pickle"], # retrieve等方法仅按照此顺序读取配置数据
"update": ["filename.json", "na"], # 显然这是针对modify一类方法替换现有数据的
"delete": ["filename.json", "my-member.pickle", "na"], # delete,unset一类涉及删除路径的操作
# 注意,当unset等方法最终得到的orders其中某项为空时(例如"delete": [])
# 会抛出RequiredPathNotFoundError且未找到路径一定为根键
},
} # 显然,以上的顺序表都是允许完全为空(且不必包含全部成员)的,这将导致永远无法通过普通的顺序查找检索到未在顺序表中成员,但是仍然可以通过`键元信息`语法指定成员访问