带有CLI支持的简单基于字典的脚本配置
项目描述
阅读文档 |
|
Gitlab (主要) |
|
Github (镜像) |
|
Pypi |
scriptconfig 的目标是通过简单地定义一个字典来定义默认配置,然后允许通过以下方式修改该配置:
使用另一个Python字典(例如 kwargs)更新它
读取YAML/JSON配置文件,或者
检查 sys.argv 上的值,在这种情况下,我们提供了一个强大的命令行界面(CLI)。
创建脚本配置的最简单方法是创建一个继承自 scriptconfig.DataConfig 的类。然后,使用类变量定义预期的键和默认值。
import scriptconfig as scfg
class ExampleConfig(scfg.DataConfig):
"""
The docstring will be the description in the CLI help
"""
# Wrap defaults with `Value` to provide metadata
option1 = scfg.Value('default1', help='option1 help')
option2 = scfg.Value('default2', help='option2 help')
option3 = scfg.Value('default3', help='option3 help')
# Wrapping a default with `Value` is optional
option4 = 'default4'
配置对象的实例将类似于数据类,但它还实现了方法来实现鸭子类型字典。因此,可以将scriptconfig对象放入使用现有字典配置或现有argparse命名空间配置的代码中。
# Use as a dictionary with defaults
config = ExampleConfig(option1=123)
print(config)
# Can access items like a dictionary
print(config['option1'])
# OR Can access items like a namespace
print(config.option1)
使用.cli类方法创建扩展的argparse命令行界面。cli方法的选项类似于argparse.ArgumentParser.parse_args。
# Use as a argparse CLI
config = ExampleConfig.cli(argv=['--option2=overruled'])
print(config)
在所有这些之后,如果你仍然不喜欢scriptconfig,或者不能将其作为生产环境中的依赖项使用,你可以要求它通过print(ExampleConfig().port_to_argparse())转换为纯argparse,它将打印出
import argparse
parser = argparse.ArgumentParser(
prog='ExampleConfig',
description='The docstring will be the description in the CLI help',
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('--option1', help='option1 help', default='default1', dest='option1', required=False)
parser.add_argument('--option2', help='option2 help', default='default2', dest='option2', required=False)
parser.add_argument('--option3', help='option3 help', default='default3', dest='option3', required=False)
parser.add_argument('--option4', help='', default='default4', dest='option4', required=False)
当然,上述操作也移除了scriptconfig的一些额外功能——因此它并不是完全1对1,但非常接近。它也是一个很好的工具,可以将对argparse的现有直觉转移到scriptconfig。
同样,还有一个方法可以接受现有的ArgumentParser作为输入,并生成scriptconfig定义。给定上述parser对象,print(scg.Config.port_from_argparse(parser, style))将打印出
import ubelt as ub
import scriptconfig as scfg
class MyConfig(scfg.DataConfig):
"""
The docstring will be the description in the CLI help
"""
option1 = scfg.Value('default1', help='option1 help')
option2 = scfg.Value('default2', help='option2 help')
option3 = scfg.Value('default3', help='option3 help')
option4 = scfg.Value('default4', help='')
目标
我们的想法是能够用简单的配置开始编写简单的程序,并允许它以最小的重构方式演进。在早期阶段,我们将坚持要求尽量减少样板代码,但随着程序的发展,我们将添加样板代码来增强程序的功能。
当我们开始编码时,我们应该努力做到像这样
def my_function():
config = {
'simple_option1': 1,
'simple_option2': 2,
}
# Early algorithmic and debugging logic
...
随着我们代码的演进,我们可以像这样插入scriptconfig
def my_function():
default_config = {
'simple_option1': 1,
'simple_option2': 2,
}
import scriptconfig
class MyConfig(scriptconfig.DataConfig):
__default__ = default_config
config = MyConfig()
# Transition algorithmic and debugging logic
...
这看起来并不美观,但它让我们能够立即获得一个相当先进的CLI(即通过调用.cli类方法),而不会对代码的简洁性造成任何重大牺牲。然而,随着项目的演进,我们可能最终希望重构我们的CLI以完全控制配置和CLI中的元数据。Scriptconfig也有一个工具可以帮助我们做到这一点。给定这个蹩脚的定义,我们可以将其转换为更优雅的风格。我们可以运行print(config.port_to_dataconf()),它将打印出
import ubelt as ub
import scriptconfig as scfg
class MyConfig(scfg.DataConfig):
"""
argparse CLI generated by scriptconfig 0.7.12
"""
simple_option1 = scfg.Value(1, help=None)
simple_option2 = scfg.Value(2, help=None)
然后使用它使重构更容易。scriptconfig程序的最后状态可能看起来像这样
import ubelt as ub
import scriptconfig as scfg
class MyConfig(scfg.DataConfig):
"""
This is my CLI description
"""
simple_option1 = scfg.Value(1, help=ub.paragraph(
'''
A reasonably detailed but concise description of an argument.
About one paragraph is reasonable.
''')
simple_option2 = scfg.Value(2, help='more help is better')
@classmethod
def main(cls, cmdline=1, **kwargs):
config = cls.cli(cmdline=cmdline, data=kwargs)
my_function(config)
def my_function(config):
# Continued algorithmic and debugging logic
...
请注意,对...的基本影响(即函数的有趣部分)完全保持不变!从它的角度来看,你从未对原始的config字典做过任何事情,因为scriptconfig在每个阶段都进行了鸭子类型。
安装
scriptconfig软件包可以通过pip安装
pip install scriptconfig
要使用argcomplete和rich-argparse支持安装,要么单独安装这些软件包,要么使用
pip install scriptconfig[optional]
功能
序列化为JSON
字典式接口。默认情况下,Config对象在独立于配置文件或命令行的情况下运行。
可以创建命令行界面
可以直接创建独立的argparse对象
可以使用特殊的命令行加载,使用self.load(cmdline=True)。这通过以下方式扩展了基本的argparse接口:
可以选择以--option value或--option=value的形式指定选项
默认配置选项允许“智能”转换值,如列表和路径
在通过load读取命令行时自动添加--config、--dumps和--dump CLI选项
模糊连字符匹配:例如,--foo-bar=2和--foo_bar=2对于argparse选项被处理为相同(注意:模态命令还没有此选项)
继承联合配置。
模态配置(见scriptconfig.modal)
与argcomplete集成以实现shell自动完成。
与rich_argparse集成,以实现彩色的CLI帮助页面。
示例脚本
Scriptconfig用于定义一个平面配置字典,其值可以通过Python关键字参数、命令行参数或YAML配置文件指定。考虑以下脚本,该脚本打印其配置、打开文件、计算其哈希值,然后将结果打印到stdout。
import scriptconfig as scfg
import hashlib
class FileHashConfig(scfg.DataConfig):
"""
The docstring will be the description in the CLI help
"""
fpath = scfg.Value(None, position=1, help='a path to a file to hash')
hasher = scfg.Value('sha1', choices=['sha1', 'sha512'], help='a name of a hashlib hasher')
def main(**kwargs):
config = FileHashConfig.cli(data=kwargs)
print('config = {!r}'.format(config))
fpath = config['fpath']
hasher = getattr(hashlib, config['hasher'])()
with open(fpath, 'rb') as file:
hasher.update(file.read())
hashstr = hasher.hexdigest()
print('The {hasher} hash of {fpath} is {hashstr}'.format(
hashstr=hashstr, **config))
if __name__ == '__main__':
main()
如果此脚本在模块hash_demo.py中(例如,在此存储库的示例文件夹中),可以通过以下方式调用它。
仅从命令行
# Get help
python hash_demo.py --help
# Using key-val pairs
python hash_demo.py --fpath=$HOME/.bashrc --hasher=sha1
# Using a positional arguments and other defaults
python hash_demo.py $HOME/.bashrc
使用YAML配置从命令行
# Write out a config file
echo '{"fpath": "hashconfig.json", "hasher": "sha512"}' > hashconfig.json
# Use the special `--config` cli arg provided by scriptconfig
python hash_demo.py --config=hashconfig.json
# You can also mix and match, this overrides the hasher in the config with sha1
python hash_demo.py --config=hashconfig.json --hasher=sha1
最后,您可以像老式的Python那样调用它。
import hash_demo
hash_demo.main(fpath=hash_demo.__file__, hasher='sha512')
模态CLI
ModalCLI定义了一种将多个较小的scriptconfig CLI组合成一个单独的父CLI的方法,该父CLI可以在它们之间“模态”选择。例如,如果我们定义了两个配置:do_foo和do_bar,我们使用ModalCLI来定义一个可以运行一个或另一个的父程序。让我们使其更加具体。
考虑以下代码examples/demo_modal.py
import scriptconfig as scfg
class DoFooCLI(scfg.DataConfig):
__command__ = 'do_foo'
option1 = scfg.Value(None, help='option1')
@classmethod
def main(cls, cmdline=1, **kwargs):
self = cls.cli(cmdline=cmdline, data=kwargs)
print('Called Foo with: ' + str(self))
class DoBarCLI(scfg.DataConfig):
__command__ = 'do_bar'
option1 = scfg.Value(None, help='option1')
@classmethod
def main(cls, cmdline=1, **kwargs):
self = cls.cli(cmdline=cmdline, data=kwargs)
print('Called Bar with: ' + str(self))
class MyModalCLI(scfg.ModalCLI):
__version__ = '1.2.3'
foo = DoFooCLI
bar = DoBarCLI
if __name__ == '__main__':
MyModalCLI().main()
运行:python examples/demo_modal.py --help,结果为
usage: demo_modal.py [-h] [--version] {do_foo,do_bar} ...
options:
-h, --help show this help message and exit
--version show version number and exit (default: False)
commands:
{do_foo,do_bar} specify a command to run
do_foo argparse CLI generated by scriptconfig 0.7.12
do_bar argparse CLI generated by scriptconfig 0.7.12
如果您指定了命令,python examples/demo_modal.py do_bar --help,您将得到该子命令的帮助信息
usage: DoBarCLI [-h] [--option1 OPTION1]
argparse CLI generated by scriptconfig 0.7.12
options:
-h, --help show this help message and exit
--option1 OPTION1 option1 (default: None)
自动补全
如果您安装了可选的argcomplete包,您会发现按下tab键将自动补全scriptconfig CLI的已注册参数。有关详细信息,请参阅项目说明,但在标准的Linux发行版中,您可以通过以下方式启用全局补全:
pip install argcomplete
mkdir -p ~/.bash_completion.d
activate-global-python-argcomplete --dest ~/.bash_completion.d
source ~/.bash_completion.d/python-argcomplete
然后,将这些行添加到您的.bashrc
if [ -f "$HOME/.bash_completion.d/python-argcomplete" ]; then
source ~/.bash_completion.d/python-argcomplete
fi
最后,确保您的Python脚本顶部有以下两个注释
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
项目设计目标
编写可以通过命令行或Python本身调用的Python程序。
任何基于字典的配置系统的直接替代品。
直观解析(目前正在此方面努力),理想情况下,如果可能,改进argparse。这意味着可以轻松指定简单的列表、数字、字符串和路径。
要开始使用,让我们考虑一些示例用法
>>> import scriptconfig as scfg
>>> # In its simplest incarnation, the config class specifies default values.
>>> # For each configuration parameter.
>>> class ExampleConfig(scfg.DataConfig):
>>> num = 1
>>> mode = 'bar'
>>> ignore = ['baz', 'biz']
>>> # Creating an instance, starts using the defaults
>>> config = ExampleConfig()
>>> assert config['num'] == 1
>>> # Or pass in known data. (load as shown in the original example still works)
>>> kwargs = {'num': 2}
>>> config = ExampleConfig.cli(default=kwargs, cmdline=False)
>>> assert config['num'] == 2
>>> # The `load` method can also be passed a JSON/YAML file/path.
>>> config_fpath = '/tmp/foo'
>>> open(config_fpath, 'w').write('{"mode": "foo"}')
>>> config.load(config_fpath, cmdline=False)
>>> assert config['num'] == 2
>>> assert config['mode'] == "foo"
>>> # It is possbile to load only from CLI by setting cmdline=True
>>> # or by setting it to a custom sys.argv
>>> config = ExampleConfig.cli(argv=['--num=4'])
>>> assert config['num'] == 4
>>> # Note that using `config.load(cmdline=True)` will just use the
>>> # contents of sys.argv
在上面的示例中,默认字典中的键是命令行参数,值是它们的默认值。您可以通过将它们包装在scriptconfig.Value对象中来增强默认值,以封装有关帮助文档或类型信息的信息。
>>> import scriptconfig as scfg
>>> class ExampleConfig(scfg.Config):
>>> __default__ = {
>>> 'num': scfg.Value(1, help='a number'),
>>> 'mode': scfg.Value('bar', help='mode1 help'),
>>> 'mode2': scfg.Value('bar', type=str, help='mode2 help'),
>>> 'ignore': scfg.Value(['baz', 'biz'], help='list of ignore vals'),
>>> }
>>> config = ExampleConfig()
>>> # smartcast can handle lists as long as there are no spaces
>>> config.load(cmdline=['--ignore=spam,eggs'])
>>> assert config['ignore'] == ['spam', 'eggs']
>>> # Note that the Value type can influence how data is parsed
>>> config.load(cmdline=['--mode=spam,eggs', '--mode2=spam,eggs'])
(注意上面的示例使用的是较旧的Config用法模式,其中属性是__default__字典的成员。从版本0.6.2以后,应优先考虑使用DataConfig类。但是,如果需要包装现有的字典,始终可以使用__default__属性。)
注意事项
CLI值中的逗号
当使用scriptconfig生成命令行界面时,它使用一个名为smartcast的函数来尝试在未显式给出时确定输入类型。如果您曾经使用过试图“智能”的程序,您会知道这可能会导致一些奇怪的行为。这里发生这种情况的情况是当您传递一个包含逗号的值时。如果您没有指定默认值作为具有指定类型的scriptconfig.Value,它将解释您的输入为值的列表。在未来,我们可能会更改smartcast的行为,或者阻止它用作默认值。
布尔标志和位置参数
scriptconfig 总是提供一种键值方式来表示参数。然而,它也认识到有时你可能只想输入 --flag,而不是 --flag=1。对于具有 isflag=1 的 Values,我们允许这样做,但这会导致位置参数的边缘情况歧义。对于以下示例
class MyConfig(scfg.DataConfig):
arg1 = scfg.Value(None, position=1)
flag1 = scfg.Value(False, isflag=True, position=1)
对于 --flag 1,我们无法确定你是否想要 {'arg1': 1, 'flag1': False} 还是 {'arg1': None, 'flag1': True}。
可以通过以下方式解决这个问题:使用严格的关键字/值参数,在使用标志参数之前表达所有位置参数,或使用 `` – `` 结构并将所有位置参数放在末尾。将来,我们可能会在指定此类参数时引发 AmbiguityError,但现在我们将此行为留为未定义。
常见问题解答(FAQ)
问题:我该如何使用 JSON 文件覆盖 scriptconfig 对象的默认值?
答案:这取决于你是否想通过命令行传递该 JSON 文件的路径,或者你是否已经将该文件加载到内存中。有方法可以实现这两种情况。在第一种情况下,你可以传递 --config=<path-to-your-file>(假设你在创建配置对象时设置了 cmdline=True 关键字参数,例如: config = MyConfig(cmdline=True)。在第二种情况下,当你创建 scriptconfig 对象的实例时,在创建对象时传递 default=<your dict>:例如 config = MyConfig(default=json.load(open(fpath, 'r')))。但特殊的 --config --dump 和 --dumps 命令行参数已经嵌入到脚本配置中,以简化这一过程。
待办事项
[ ] 嵌套模式 CLI
][ ] 模型CLI中的模糊连字符
[X] 嵌套层次结构的策略(目前不允许)- 这里将是jsonargparse的解决方案。
[ ] 如何与jsonargparse最佳集成
[ ] 智能广播策略(目前已启用)
[ ] 找一种优雅的方式让智能广播做得更少。(例如,不解析列表,但整数是可以的,我们可以考虑接受YAML)
[X] 位置参数的策略(目前处于实验阶段)- 我们已以允许的方式实现了它们,但有一个未定义的角落案例。
[X] 固定长度 - 不行
[X] 可变长度
[X] argparse是否可以修改为始终允许它们出现在开头或结尾? - 可能不行。
[x] 我们能否让argparse允许位置参数更改前缀参数的值,同时仍然有合理的帮助菜单?
[x] 布尔标志的策略 - 请参阅scriptconfig.Value的isflag参数
[x] 改进argparse默认自动生成的帮助文档(需要探索argparse的可能性和扩展的可行性)
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。
源分布
构建分布
scriptconfig-0.8.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | d641365029d784cee577cfb8c4a213b83f05e9c2ea17329af0f707941326c975 |
|
MD5 | 72506dd4b1884667ec068df11fb27452 |
|
BLAKE2b-256 | ef96664789e620baccec16ff7d7464d283659841a74a14bcc5ec4a438fa85ff6 |
scriptconfig-0.8.0-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | c327e5cc4f136d4c87d08b97b4ae7ac20cbcb4f9120fd029442066758c3d2d36 |
|
MD5 | 7c17345b2d42feeed244ccea63976f03 |
|
BLAKE2b-256 | 1e45840c8c2ba599173f31f04b23b6e8f9af17acded74f37fa923bb18e889e00 |