Skip to content

Commit f98ca25

Browse files
committed
Merge branch 'dev'
2 parents 7518bf4 + a1f5c4f commit f98ca25

File tree

7 files changed

+199
-12
lines changed

7 files changed

+199
-12
lines changed

Changelog.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# 0.08
1+
# 0.0.9
2+
3+
## 新特性
4+
5+
+ 可以使用`@regist_config_file_parser(config_file_name)`来注册如何解析特定命名的配置文件
6+
7+
# 0.0.8
28

39
## 新特性
410

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ class Test_A(EntryPoint):
347347

348348
我们可以使用字段`default_config_file_paths`指定从固定的几个路径中读取配置文件,配置文件支持`json``yaml`两种格式.
349349
我们也可以通过字段`config_file_only_get_need`定义从配置文件中读取配置的行为(默认为`True`),
350-
当置为`True`时我们只会在配置文件中读取schema中定义的字段,否则则会加载全部字段.
350+
当置为`True`时我们只会在配置文件中读取schema中定义的字段,否则则会加载全部字段.
351351

352352
也可以通过设置`load_all_config_file = True`来按设定顺序读取全部预设的配置文件位置
353353

@@ -362,8 +362,53 @@ class Test_A(EntryPoint):
362362
default_config_file_paths = [
363363
"/test_config.json",
364364
str(Path.home().joinpath(".test_config.json")),
365-
"./test_config.json"
365+
"./test_config.json",
366+
"./test_config_other.json"
367+
]
368+
```
369+
370+
##### 指定特定命名的配置文件的解析方式
371+
372+
可以使用`@regist_config_file_parser(config_file_name)`来注册如何解析特定命名的配置文件.这一特性可以更好的定制化配置文件的读取
373+
374+
```python
375+
class Test_AC(EntryPoint):
376+
load_all_config_file = True
377+
default_config_file_paths = [
378+
"./test_config.json",
379+
"./test_config1.json",
380+
"./test_other_config2.json"
381+
]
382+
root = Test_AC()
383+
384+
@root.regist_config_file_parser("test_other_config2.json")
385+
def _1(p: Path) -> Dict[str, Any]:
386+
with open(p) as f:
387+
temp = json.load(f)
388+
return {k.lower(): v for k, v in temp.items()}
389+
390+
```
391+
392+
如果想在定义子类时固定好,也可以定义`_config_file_parser_map:Dict[str,Callable[[Path], Dict[str, Any]]]`
393+
394+
```python
395+
def test_other_config2_parser( p: Path) -> Dict[str, Any]:
396+
with open(p) as f:
397+
temp = json.load(f)
398+
return {k.lower(): v for k, v in temp.items()}
399+
class Test_AC(EntryPoint):
400+
load_all_config_file = True
401+
default_config_file_paths = [
402+
"./test_config.json",
403+
"./test_config1.json",
404+
"./test_other_config2.json"
366405
]
406+
_config_file_parser_map = {
407+
"test_other_config2.json": test_other_config2_parser
408+
}
409+
410+
root = Test_AC()
411+
367412
```
368413

369414
#### 从环境变量中读取配置参数
@@ -435,6 +480,29 @@ def main(a,b):
435480

436481
```
437482

483+
另一种指定入口函数的方法是重写子类的`do_main(self)->None`方法
484+
485+
```python
486+
class Test_A(EntryPoint):
487+
argparse_noflag = "a"
488+
argparse_check_required=True
489+
schema = {
490+
"$schema": "http://json-schema.org/draft-07/schema#",
491+
"type": "object",
492+
"properties": {
493+
"a": {
494+
"type": "number"
495+
},
496+
"b": {
497+
"type": "number"
498+
}
499+
},
500+
"required": ["a","b"]
501+
}
502+
def do_main(self)->None:
503+
print(self.config)
504+
```
505+
438506
#### 直接从节点对象中获取配置
439507

440508
节点对象的`config`属性会在每次调用时copy一份当前的配置值,config是不可写的.

schema_entry/entrypoint.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import functools
1919
from copy import deepcopy
2020
from pathlib import Path
21-
from typing import Callable, Sequence, Dict, List, Any, Tuple
21+
from typing import Callable, Sequence, Dict, List, Any, Tuple, Optional
2222
from jsonschema import validate
2323
from yaml import load as yaml_load
2424

@@ -43,7 +43,8 @@ class EntryPoint(EntryPointABC):
4343
parse_env = True
4444

4545
argparse_check_required = False
46-
argparse_noflag = None
46+
argparse_noflag: Optional[str] = None
47+
_config_file_parser_map: Dict[str, Callable[[Path], Dict[str, Any]]] = {}
4748

4849
def _check_schema(self) -> None:
4950
if self.schema is not None:
@@ -248,13 +249,26 @@ def parse_yaml_configfile_args(self, p: Path) -> Dict[str, Any]:
248249
return res
249250
return result
250251

252+
def regist_config_file_parser(self, file_name: str) -> Callable[[Callable[[Path], Dict[str, Any]]], Callable[[Path], Dict[str, Any]]]:
253+
def decorate(func: Callable[[Path], Dict[str, Any]]) -> Callable[[Path], Dict[str, Any]]:
254+
@functools.wraps(func)
255+
def wrap(p: Path) -> Dict[str, Any]:
256+
return func(p)
257+
self._config_file_parser_map[file_name] = func
258+
return wrap
259+
return decorate
260+
251261
def parse_configfile_args(self) -> Dict[str, Any]:
252262
if not self.default_config_file_paths:
253263
return {}
254264
if not self.load_all_config_file:
255265
for p_str in self.default_config_file_paths:
256266
p = Path(p_str)
257267
if p.is_file():
268+
parfunc = self._config_file_parser_map.get(p.name)
269+
if parfunc:
270+
print("&&&&&&")
271+
return parfunc(p)
258272
if p.suffix == ".json":
259273
return self.parse_json_configfile_args(p)
260274
elif p.suffix == ".yml":
@@ -269,12 +283,17 @@ def parse_configfile_args(self) -> Dict[str, Any]:
269283
for p_str in self.default_config_file_paths:
270284
p = Path(p_str)
271285
if p.is_file():
272-
if p.suffix == ".json":
273-
result.update(self.parse_json_configfile_args(p))
274-
elif p.suffix == ".yml":
275-
result.update(self.parse_yaml_configfile_args(p))
286+
parfunc = self._config_file_parser_map.get(p.name)
287+
if parfunc:
288+
print("&&&&&&@@@")
289+
result.update(parfunc(p))
276290
else:
277-
warnings.warn(f"跳过不支持的配置格式的文件{str(p)}")
291+
if p.suffix == ".json":
292+
result.update(self.parse_json_configfile_args(p))
293+
elif p.suffix == ".yml":
294+
result.update(self.parse_yaml_configfile_args(p))
295+
else:
296+
warnings.warn(f"跳过不支持的配置格式的文件{str(p)}")
278297
return result
279298

280299
def validat_config(self) -> bool:

schema_entry/entrypoint_base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""入口类的抽象基类."""
22
import abc
33
import argparse
4+
from pathlib import Path
45
from typing import Callable, Sequence, Dict, Any, Optional, List, Union, Tuple
56

67

@@ -40,6 +41,7 @@ class EntryPointABC(abc.ABC):
4041

4142
_subcmds: Dict[str, "EntryPointABC"]
4243
_main: Optional[Callable[..., None]]
44+
_config_file_parser_map: Dict[str, Callable[[Path], Dict[str, Any]]]
4345
_config: Dict[str, Any]
4446

4547
@abc.abstractproperty
@@ -79,6 +81,19 @@ def regist_sub(self, subcmdclz: type) -> "EntryPointABC":
7981
[EntryPointABC]: 注册类的实例
8082
8183
'''
84+
85+
@abc.abstractmethod
86+
def regist_config_file_parser(self, file_name: str) -> Callable[[Callable[[Path], Dict[str, Any]]], Callable[[Path], Dict[str, Any]]]:
87+
'''注册特定配置文件名的解析方式.
88+
89+
Args:
90+
file_name (str): 指定文件名
91+
92+
Returns:
93+
Callable[[Callable[[Path], None]], Callable[[Path], None]]: 注册的解析函数
94+
95+
'''
96+
8297
@abc.abstractmethod
8398
def as_main(self, func: Callable[..., None]) -> Callable[..., None]:
8499
"""注册函数在解析参数成功后执行.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = schema_entry
3-
version = 0.0.8
3+
version = 0.0.9
44
url = https://github.com/Python-Tools/schema_entry
55
author = hsz
66
author_email = hsz1273327@gmail.com

test_other_config2.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"A": 1,
3+
"D": 43,
4+
"C": 13
5+
}

tests/test_entrypoint.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
2+
import json
23
import unittest
34
from pathlib import Path
5+
from typing import Dict, Any
46
import jsonschema.exceptions
57

68
from schema_entry.entrypoint import EntryPoint
@@ -52,13 +54,32 @@ class Test_A(EntryPoint):
5254
root = Test_A()
5355

5456
@root.as_main
55-
def _(a_a: float) -> None:
57+
def _(**kwargs: Any) -> None:
5658
pass
5759

5860
root([])
5961

6062
assert root.usage == "test_a [options]"
6163

64+
def test_override_do_main(self) -> None:
65+
class Test_A(EntryPoint):
66+
schema = {
67+
"$schema": "http://json-schema.org/draft-07/schema#",
68+
"type": "object",
69+
"properties": {
70+
"a_a": {
71+
"type": "number",
72+
"default": 33.3
73+
}
74+
},
75+
"required": ["a_a"]
76+
}
77+
78+
def do_main(self) -> None:
79+
assert self.config["a_a"] == 33.3
80+
root = Test_A()
81+
root([])
82+
6283
def test_default_subcmd_usage(self) -> None:
6384
class A(EntryPoint):
6485
pass
@@ -243,6 +264,59 @@ def _(a: int, b: int, c: int, d: int) -> None:
243264

244265
root([])
245266

267+
def test_load_configfile_with_custom_parser(self) -> None:
268+
class Test_AC(EntryPoint):
269+
load_all_config_file = True
270+
default_config_file_paths = [
271+
"./test_config.json",
272+
"./test_config1.json",
273+
"./test_other_config2.json"
274+
]
275+
root = Test_AC()
276+
277+
@root.regist_config_file_parser("test_other_config2.json")
278+
def _1(p: Path) -> Dict[str, Any]:
279+
with open(p) as f:
280+
temp = json.load(f)
281+
return {k.lower(): v for k, v in temp.items()}
282+
283+
@root.as_main
284+
def _2(a: int, b: int, c: int, d: int) -> None:
285+
assert a == 1
286+
assert b == 2
287+
assert c == 13
288+
assert d == 43
289+
290+
root([])
291+
292+
def test_load_configfile_with_custom_parser_in_class(self) -> None:
293+
def test_other_config2_parser( p: Path) -> Dict[str, Any]:
294+
with open(p) as f:
295+
temp = json.load(f)
296+
return {k.lower(): v for k, v in temp.items()}
297+
class Test_AC(EntryPoint):
298+
load_all_config_file = True
299+
default_config_file_paths = [
300+
"./test_config.json",
301+
"./test_config1.json",
302+
"./test_other_config2.json"
303+
]
304+
_config_file_parser_map = {
305+
"test_other_config2.json": test_other_config2_parser
306+
}
307+
308+
309+
root = Test_AC()
310+
311+
@root.as_main
312+
def _2(a: int, b: int, c: int, d: int) -> None:
313+
assert a == 1
314+
assert b == 2
315+
assert c == 13
316+
assert d == 43
317+
318+
root([])
319+
246320
def test_load_ENV_config(self) -> None:
247321
class Test_A(EntryPoint):
248322
env_prefix = "app"

0 commit comments

Comments
 (0)