如何用Python-Click将命令行选项映射为Dataclass/Pydantic对象?
实现Click与Pydantic模型的自动绑定
针对你的需求,我们可以通过自定义装饰器和Click参数类型两种方式,实现命令行选项与Pydantic/dataclass对象的自动转换,无需手动组装实例。
一、自动生成多选项,直接接收Pydantic实例
通过自定义装饰器遍历Pydantic模型的字段,自动生成对应的Click选项,并在命令函数中直接传入组装好的模型实例。
步骤1:定义Pydantic模型和装饰器
import click from pydantic import BaseModel, Optional, Field from getpass import getlogin class Host(BaseModel): username: str = Field(getlogin(), description="Remote host login username") password: Optional[str] = Field(None, description="Remote host login password") address: str = Field('localhost', description="Remote host IP/domain") port: int = Field(22, description="Remote host SSH port") def click_pydantic_options(model: type[BaseModel]): def decorator(func): # 反向遍历字段,保证选项顺序与模型定义一致(装饰器从下往上应用) for field_name in reversed(model.model_fields): field = model.model_fields[field_name] short_opt = f"-{field_name[0]}" long_opt = f"--{field_name}" # 动态添加Click选项 func = click.option( short_opt, long_opt, type=field.annotation, default=field.default if not field.is_required() else None, help=field.description )(func) # 包装函数,将参数组装为模型实例 def wrapper(**kwargs): model_args = {k: v for k, v in kwargs.items() if k in model.model_fields} return func(host=model(**model_args)) return wrapper return decorator
步骤2:使用装饰器定义命令
@click.group() def remote_tools(): pass @remote_tools.command( name='remote', help='Execute command on remote host', context_settings=dict(max_content_width=120) ) @click_pydantic_options(Host) def command(host: Host): print(host)
运行命令示例:
remote_tools remote -a 192.168.1.100 -o 2222 -u admin
输出会是自动组装好的Host实例:
username='admin' password=None address='192.168.1.100' port=2222
二、单个选项解析为Pydantic实例,保留字段帮助信息
通过自定义Click参数类型,解析单个选项的键值对输入为Pydantic实例,并在帮助信息中展示每个字段的说明。
步骤1:定义自定义参数类型
class HostParamType(click.ParamType): name = "host" def convert(self, value, param, ctx): # 解析"key=value,key2=value2"格式的输入 try: field_map = Host.model_fields kwargs = {} for part in value.split(','): key, val = part.split('=', 1) # 根据模型字段类型转换输入值 kwargs[key] = field_map[key].annotation(val) return Host(**kwargs) except Exception as e: self.fail( f"Invalid format: {e}. Use 'address=ADDR[,port=PORT][,username=USER][,password=PASS]'", param, ctx ) def get_metavar(self, param): return "address=ADDR[,port=PORT][,username=USER][,password=PASS]" def get_help_record(self, param): # 生成包含字段详情的帮助信息 help_lines = [ f" {self.get_metavar(param)}:", " Available fields:", ] for field_name, field in Host.model_fields.items(): default_note = f" (default: {repr(field.default)})" if not field.is_required() else "" help_lines.append(f" {field_name}: {field.description}{default_note}") return ('', '\n'.join(help_lines))
步骤2:使用自定义类型定义命令
@remote_tools.command( name='remote-single', help='Execute command on remote host with single host option', context_settings=dict(max_content_width=120) ) @click.option('-h', '--host', type=HostParamType(), help='Remote host details') def command_single(host: Host): print(host)
运行帮助命令remote_tools remote-single --help,会看到详细的字段说明:
Usage: remote_tools remote-single [OPTIONS] Execute command on remote host with single host option Options: -h, --host address=ADDR[,port=PORT][,username=USER][,password=PASS] Available fields: username: Remote host login username (default: 'your-username') password: Remote host login password (default: None) address: Remote host IP/domain (default: 'localhost') port: Remote host SSH port (default: 22) --help Show this message and exit.
运行命令示例:
remote_tools remote-single -h address=192.168.1.100,port=2222,username=admin
输出同样是组装好的Host实例。
针对dataclass的适配
如果使用dataclass,只需将代码中model.model_fields替换为dataclasses.fields(model),并调整字段属性的获取方式(比如field.default、field.type),逻辑与上述一致。
内容的提问来源于stack exchange,提问作者nikoladsp
相关产品推荐
相关产品推荐

