开发命令行界面的最佳方式
项目描述
Coleo
Coleo是一个以最小努力在Python中创建命令行界面的方法。
- 在使用的位置声明选项。
- 轻松扩展到具有数十个子命令和选项的扩展CLIs。
基本用法
首先,按照以下方式定义一个命令行界面
from coleo import Option, auto_cli, default
@auto_cli
def main():
# The greeting
greeting: Option = default("Hello")
# The name to greet
name: Option = default("you")
return f"{greeting}, {name}!"
然后您可以在命令行上像这样运行它
$ python hello.py
Hello, you!
$ python hello.py --name Luke
Hello, Luke!
$ python hello.py --name Luke --greeting "Happy birthday"
Happy birthday, Luke!
$ python hello.py -h
usage: hello.py [-h] [--greeting VALUE] [--name VALUE]
optional arguments:
-h, --help show this help message and exit
--greeting VALUE The greeting
--name VALUE The name to greet
- 任何使用
Option
注解的变量将成为一个选项。 - 您可以使用
default(value)
提供默认值,尽管如果参数是必需的,则不需要这样做。 - 如果变量上方有注释,则它将用作选项的文档。
选项类型
默认情况下,所有参数都被解释为字符串,但您可以为参数指定不同的类型
@auto_cli
def main():
# This argument will be converted to an int
x: Option & int
# This argument will be converted to a float
y: Option & float
return x + y
布尔标志
如果类型为bool,则选项将不接受任何参数,例如
@auto_cli
def main():
flag: Option & bool = default(False)
return "yes!" if flag else "no!"
像这样使用它
$ python script.py --flag
yes!
$ python script.py
no!
您也可以否定标志,这意味着您想要提供一个选项,该选项将在变量中存储False而不是True。例如
@auto_cli
def main():
# [negate]
flag: Option & bool = default(True)
return "yes!" if flag else "no!"
默认情况下,上面的代码将创建一个名为--no-
的标志
$ python script.py
yes!
$ python script.py --no-flag
no!
如果您想使选项为--xyz
或-n
,请写入[negate: --xyz -n]
。这将覆盖默认的--no-flag
选项。
请注意,使用[negate]
将删除--flag
,因为我们假设它默认为True,因此不需要此选项。
如果您愿意,您可以使用同时设置标志为True和False的选项,使用[false-options]
。您可以使用[false-options-doc]
(如果未提供,Coleo将使用合理的默认值)来记录这些选项
@auto_cli
def main():
# Set the flag to True
# [options: -y]
# [false-options: -n]
# [false-options-doc: Set the flag to False]
flag: Option & bool = default(None)
return flag
$ python script.py
None
$ python script.py -y
True
$ python script.py -n
False
文件
使用 coleo.FileType
(或 argparse.FileType
,两者相同)来打开文件进行读取或写入
@auto_cli
def main():
grocery_list: Option & coleo.FileType("r")
with grocery_list as f:
for food in f.readlines():
print(f"Gotta buy some {food}")
配置
您可以使用 coleo.config
或 coleo.ConfigFile
来操作配置文件
@auto_cli
def main():
# ConfigFile lets you read or write a configuration file
book: Option & ConfigFile
contents = book.read()
contents["xyz"] = "abc"
book.write(contents)
# config will read the file for you or parse the argument as JSON
magazine: Option & config
print(magazine)
使用方式如下
$ python librarian.py --book alice.json --magazine vogue.json
$ python librarian.py --book history.yaml --magazine gamez.toml
$ python librarian.py --book physics.json --magazine '{"a": 1, "b": 2}'
# etc
支持的扩展名有 json
、yaml
和 toml
(后两个需要安装 pyyaml
或 toml
包)。
其他
任何函数都可以用作参数的“类型”。例如,如果您想在命令行中提供列表和字典,可以简单地使用 json.loads
(尽管通常使用 coleo.config
更好,因为它还可以以各种格式读取文件)
@auto_cli
def main():
obj: Option & json.loads
return type(obj).__name__
$ python json.py --obj 1
int
$ python json.py --obj '"hello"'
str
$ python json.py --obj '{"a": 1, "b": 2}'
dict
如果您非常冲动且不关心安全性,甚至可以使用 eval
@auto_cli
def main():
obj: Option & eval
return type(obj).__name__
$ python eval.py --obj "1 + 2"
int
$ python eval.py --obj "lambda x: x + 1"
function
自定义
使用形式为 # [<instruction>: <args ...>]
的注释可以自定义选项解析器
@auto_cli
def main():
# This argument can be given as either --greeting or -g
# [alias: -g]
greeting: Option = default("Hello")
# This argument is positional
# [positional]
name: Option = default("you")
# This argument can only be given as -n
# [options: -n]
ntimes: Option & int = default(1)
for i in range(ntimes):
print(f"{greeting}, {name}!")
用法如下
$ python hello.py Alice -g Greetings -n 2
Greetings, Alice!
Greetings, Alice!
以下是一些可用的自定义项
[alias: ...]
定义一个或多个选项,它们是主选项的别名。选项之间用空格、逗号或分号分隔。[options: ...]
定义一个或多个选项,用于覆盖默认选项。选项之间用空格、逗号或分号分隔。[positional]
定义一个位置参数。[positional: n]
:n 个位置参数(返回一个列表)。[positional: ?]
:一个可选的位置参数[positional: *]
:零个或多个位置参数[positional: +]
:一个或多个位置参数
[remainder]
代表未被参数解析器匹配的所有参数[nargs: n]
声明该选项接受 n 个参数[nargs: ?]
:一个可选参数[nargs: *]
:零个或多个参数[nargs: +]
:一个或多个参数[nargs: **]
或[nargs: --]
:所有剩余参数,包括 --args
[action: <action>]
自定义要执行的操作[action: append]
允许您多次使用选项,将结果累积在列表中(例如,python app.py -a 1 -a 2 -a 3
,将[1, 2, 3]
放入a
)
[metavar: varname]
更改帮助字符串中选项后面的变量名,例如--opt METAVAR
[group: groupname]
将选项放入一个命名组。同一组中的选项将在帮助中一起出现。- 仅对 bool 选项有效
[negate: ...]
改变选项,使其在给定时将变量设置为 False 而不是 True。可以提供选项的空间/逗号别名,否则标志将命名为--no-<optname>
。[false-options: ]
提供一个选项列表,这些选项将标志设置为 False。[false-options-doc: ]
为使用上述语句给出的选项提供文档。
子命令
您可以通过使用 auto_cli
装饰器来创建一个具有子命令层次结构的界面
@auto_cli
class main:
class calc:
def add():
x: Option & int
y: Option & int
return x + y
def mul():
x: Option & int
y: Option & int
return x * y
def pow():
base: Option & int
exponent: Option & int
return base ** exponent
def greet():
greeting: Option = default("Hello")
name: Option = default("you")
return f"{greeting}, {name}!"
该类仅包含结构,永远不会被实例化,因此不要将这些函数的参数列表中添加 self
。
然后您可以使用它如下
$ python multi.py greet --name Alice --greeting Hi
Hi, Alice!
$ python multi.py calc add --x=3 --y=8
11
共享参数
您可以在子命令之间共享行为和参数,或将复杂功能拆分为多个部分。例如,您的应用程序中的多个子命令可能需要 API 密钥,该密钥可以是命令行中提供的,也可以从文件中读取。这就是您如何在所有子命令之间共享此行为的方式
from coleo import Option, auto_cli, config, default, tooled
@tooled
def apikey():
# The API key to use
key: Option = default(None)
if key is None:
# If no key parameter is given on the command line, try to read it from
# some standard location.
key = config("~/.config/myapp/config.json")["key"]
return key
@auto_cli
class main:
def search():
interface = Application(apikey())
query: Option
return interface.search(query)
def install():
interface = Application(apikey())
package: Option
return interface.install(package)
如果一个函数被 @tooled
装饰,并从一个主函数(或另一个 tooled 函数)中被调用,Coleo 将在该函数中搜索参数。因此,任何调用 apikey()
的子命令都将获得一个 --key
选项。
除了这个,您还可以通过在多个函数中定义具有相同类型的相同参数来“共享”参数。Coleo会将它们都设置为相同的值。
例如,在上面的示例中,您可以很容易地让用户指定包含密钥的文件的路径,只需替换
key = config("~/.config/myapp/config.json")["key"]
# ==>
config_path: Option = default("~/.config/myapp/config.json")
key = config(config_path)["key"]
当然,您也可以在任何需要读取一些配置值的函数中声明config_path
参数。
run_cli
from coleo import Option, auto_cli
@auto_cli
def main():
x: Option
return x
等同于
from coleo import Option, run_cli, tooled
@tooled
def main():
x: Option
return x
result = run_cli(main)
if result is not None:
print(result)
非CLI使用
您可以使用setvars
而不使用auto_cli
来设置参数
from coleo import Option, setvars, tooled
@tooled
def greet():
greeting: Option = default("Hello")
name: Option = default("you")
return f"{greeting} {name}!"
with setvars(greeting="Hi", name="Bob"):
assert greet() == "Hi bob!"
注意
- 使用
setvars
时,您必须用@tooled
装饰函数(这是auto_cli
为您做的事情)。 setvars
完全绕过了选项解析,类型注解将不会用于包装这些值。换句话说,如果变量被注解为Option & int
,您提供“1”的值,它将保持为字符串。
与Ptera一起使用
Coleo基于Ptera,Ptera的所有功能都在@tooled
标记的函数上实际可用。例如,使用上面的示例
# Set the variables in the greet function -- it's a bit like making an object
hibob = greet.new(greeting="Hi", name="Bob")
assert hibob() == "Hi Bob!"
# Same as above but this would also change greeting/name in any other function
# that is called by greet, and so on recursively (a bit like dynamic scoping)
hibob = greet.tweaking({"greeting": "Hi", "name": "Bob"})
assert hibob() == "Hi Bob!"
# More complex behavior
from ptera import overlay
with overlay.tweaking({
"greet(greeting='Bonjour') > name": "Toto"
}):
assert greet() == "Hello you!"
assert greet.new(greeting="Hi")() == "Hi you!"
assert greet.new(greeting="Bonjour")() == "Bonjour toto!"
阅读Ptera的文档以获取更多信息。请注意,Ptera不仅限于带有Option
标签的变量,它可以操作工具函数中的任何变量。
项目详情
下载文件
下载您平台上的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。