
Typer Callback 完全指南为 CLI 应用注入顶层逻辑与全局参数【免费下载链接】typerTyper, build great CLIs. Easy to code. Based on Python type hints.项目地址: https://gitcode.com/GitHub_Trending/ty/typerapp.callback()是 Typer 中为整个 CLI 应用而非单个子命令声明参数与逻辑的核心机制。本指南以 docs/tutorial/commands/callback.md 为骨架结合仓库源码与测试用例系统讲解回调的四种典型用法——顶层参数声明、创建时注入、覆盖回调、纯文档回调——并延伸介绍回调与typer.Context的联动场景帮助你在构建多命令 CLI 时灵活管理全局状态、帮助文本与前置逻辑。为什么需要 Callback当创建一个app typer.Typer()时它本质上是一个命令组group of commands可以挂载多个app.command()子命令。每个子命令各自拥有独立的CLI 参数例如import typer app typer.Typer() app.command() def create(username: str): print(fCreating user: {username})问题在于这些参数都由各自的子命令处理无法为 CLI 应用本身即命令组这一层声明参数。比如想添加一个影响所有子命令的全局--verbose开关就没有地方可放。这正是app.callback()的用武之地。它与app.command()非常相似但它声明的CLI 参数属于主 CLI 应用本身位于所有子命令之前。用 Callback 声明顶层参数与全局状态以仓库中的示例 docs_src/commands/callback/tutorial001_py310.py 为例import typer app typer.Typer() state {verbose: False} app.command() def create(username: str): if state[verbose]: print(About to create a user) print(fCreating user: {username}) if state[verbose]: print(Just created a user) app.command() def delete(username: str): if state[verbose]: print(About to delete a user) print(fDeleting user: {username}) if state[verbose]: print(Just deleted a user) app.callback() def main(verbose: bool False): Manage users in the awesome CLI app. if verbose: print(Will write verbose output) state[verbose] True if __name__ __main__: app()这里的main()回调声明了一个--verboseCLI 选项布尔类型bool自动映射为--verbose / --no-verbose开关对并在收到该选项后修改全局字典state供后续的create/delete子命令读取。这正是回调的典型价值在子命令执行之前统一处理全局参数。提示通过修改全局state在命令间传递数据只是实现该需求的一种方式。Typer 还支持使用typer.Context传递对象这一点会在后文结合源码说明。无论采用哪种方式回调都是承载命令执行前的全局逻辑的天然位置。运行效果查看帮助文本// Check the help $ uv run python main.py --help // Notice the main help text, extracted from the callback function: Manage users in the awesome CLI app. Usage: main.py [OPTIONS] COMMAND [ARGS]... Manage users in the awesome CLI app. Options: --verbose / --no-verbose [default: no-verbose] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete可以看到回调函数main的docstring 默认会被提取出来作为主 CLI 应用的帮助文本Manage users in the awesome CLI app.这是 Typer 基于 Python 类型注解与 docstring 约定自动完成的行为。正常执行与开启--verbose的对比// Try it normally $ uv run python main.py create Camila Creating user: Camila // And now with --verbose $ uv run python main.py --verbose create Camila Will write verbose output About to create a user Creating user: Camila Just created a user位置约束顶层选项必须位于子命令之前--verbose属于回调即主应用层因此它必须出现在create/delete之前// Notice that --verbose belongs to the callback, it has to go before create or delete ⛔️ $ uv run python main.py create --verbose Camila Usage: main.py create [OPTIONS] {username} Try main.py create --help for help. Error: No such option: --verbose把--verbose放在子命令之后会直接报错No such option因为create子命令并不认识这个属于父级回调的选项。源码印证与测试验证这一行为在仓库测试 tests/test_tutorial/test_commands/test_callback/test_tutorial001.py 中得到了完整覆盖test_help断言帮助文本包含 Manage users in the awesome CLI app. 以及--verbose/--no-verbosetest_create_verbose断言--verbose create Camila的输出依次包含 Will write verbose output、About to create a user、Creating user: Camila、Just created a usertest_wrong_verbose断言delete --verbose Camila会返回非零退出码并输出 No such option: --verbose从测试层面锁定了回调参数的位置约束。从源码结构看app.callback()在 typer/main.py 中的Typer.callback()方法实现它支持invoke_without_command、context_settings等参数详见 typer/main.py 中callback方法定义并把回调配置最终传入底层的 Click 命令组typer/core.py。在创建应用时注入 Callback除了使用装饰器还可以在创建typer.Typer()应用时直接传入callback参数import typer def callback(): print(Running a command) app typer.Typer(callbackcallback) app.command() def create(name: str): print(fCreating user: {name}) if __name__ __main__: app()对应源码见 docs_src/commands/callback/tutorial002_py310.py。这与app.callback()的效果等价执行效果如下$ uv run python main.py create Camila Running a command Creating user: Camila这种方式适合回调逻辑与命令定义解耦的场景例如回调定义在独立模块中让app typer.Typer(callbackcallback)一行的语义更加集中。覆盖已注入的 Callback如果在创建应用时注入了回调之后仍然可以通过app.callback()覆盖它import typer def callback(): print(Running a command) app typer.Typer(callbackcallback) app.callback() def new_callback(): print(Override callback, running a command) app.command() def create(name: str): print(fCreating user: {name}) if __name__ __main__: app()对应源码见 docs_src/commands/callback/tutorial003_py310.py。此时真正生效的是后定义的new_callback()$ uv run python main.py create Camila // Notice that the message is the one from new_callback() Override callback, running a command Creating user: Camila这一覆盖机制的工程意义在于当应用逻辑来自第三方扩展或基础封装时你可以在自己的入口文件中用app.callback()重新定义回调而无需改动原先的Typer(callback...)注入点。仅用于文档的 Callback回调还有一个轻量用途只利用 docstring 提供主帮助文本不执行任何逻辑。当帮助文本有多行时这种写法尤其方便——缩进会自动处理import typer app typer.Typer() app.callback() def callback(): Manage users CLI app. Use it with the create command. A new user with the given NAME will be created. app.command() def create(name: str): print(fCreating user: {name}) if __name__ __main__: app()对应源码见 docs_src/commands/callback/tutorial004_py310.py。此时回调函数体为空它的作用完全在于提取 docstring 生成主帮助文本$ uv run python main.py --help // Notice all the help text extracted from the callback docstring Usage: main.py [OPTIONS] COMMAND [ARGS]... Manage users CLI app. Use it with the create command. A new user with the given name will be created. Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create // And it just works as normally $ uv run python main.py create Camila Creating user: Camiladocstring 中的每一行都会原样呈现为帮助文本的独立段落相比在help参数中书写长文本这种以文档为中心的写法可读性更高、维护更集中。进阶Callback 与 typer.Context 的联动回调并非孤立存在它天然可以与typer.Context协作实现更精细的全局控制。以下场景与回调强相关在仓库的 docs/tutorial/commands/context.md 中有完整讲解。获取被调用的子命令名在回调中声明一个ctx: typer.Context参数即可通过ctx.invoked_subcommand拿到当前即将执行的子命令名import typer app typer.Typer() app.command() def create(username: str): print(fCreating user: {username}) app.command() def delete(username: str): print(fDeleting user: {username}) app.callback() def main(ctx: typer.Context): Manage users in the awesome CLI app. print(fAbout to execute command: {ctx.invoked_subcommand}) if __name__ __main__: app()对应源码见 docs_src/commands/context/tutorial001_py310.py。运行效果$ uv run python main.py create Camila // We get the message from the callback About to execute command: create Creating user: Camila $ uv run python main.py delete Camila // We get the message from the callback, this time with delete About to execute command: delete Deleting user: Camila这让回调可以针对不同子命令执行差异化逻辑例如按命令记录审计日志。无子命令时也执行invoke_without_command默认情况下回调只在即将执行某个子命令时才被触发如果只运行main.py而不带任何子命令会直接显示帮助信息。通过invoke_without_commandTrue可以让回调在无子命令时也执行app.callback(invoke_without_commandTrue) def main(): Manage users in the awesome CLI app. print(Initializing database)对应源码见 docs_src/commands/context/tutorial002_py310.py。从源码看invoke_without_command是Typer.callback()的公开参数typer/main.py最终映射到底层 Click 组配置invoke_without_commandtyper/core.py 中对应字段默认值为False。运行效果$ uv run python main.py // The callback is executed, we dont get the default help message Initializing database // Try with a command $ uv run python main.py create Camila // The callback is still executed Initializing database Creating user: Camila仅当无子命令时执行判断 invoked_subcommand若只想在单独运行主程序时执行回调、有子命令时跳过可以结合ctx.invoked_subcommand is None判断app.callback(invoke_without_commandTrue) def main(ctx: typer.Context): Manage users in the awesome CLI app. if ctx.invoked_subcommand is None: print(Initializing database)对应源码见 docs_src/commands/context/tutorial003_py310.py。ctx.invoked_subcommand为None表示当前没有调用任何子命令而是直接运行主程序回调本身$ uv run python main.py // The callback is executed Initializing database // Check it with a subcommand $ uv run python main.py create Camila // This time the callback is not executed Creating user: Camila这一组合非常适合初始化数据库 / 加载配置 / 环境检查这类应当只执行一次的前置逻辑。捕获未声明的额外参数创建命令或回调时还可以传入context_settings例如配合ignore_unknown_options与allow_extra_args接收未在 CLI 中声明的原始参数import typer app typer.Typer() app.command( context_settings{allow_extra_args: True, ignore_unknown_options: True} ) def main(ctx: typer.Context): for extra_arg in ctx.args: print(fGot extra arg: {extra_arg}) if __name__ __main__: app()对应源码见 docs_src/commands/context/tutorial004_py310.py。此时所有未声明的CLI 参数会以list[str]形式保存在ctx.args中选项名与值一起按原始顺序存放$ uv run python main.py --name Camila --city Berlin Got extra arg: --name Got extra arg: Camila Got extra arg: --city Got extra arg: Berlin提示注意ctx.args是包含选项名和值在内的原始字符串列表如--name、Camila、--city、Berlin全部混在一起解析时需要自行按顺序消费。这为转发透传场景如包装其他 CLI 工具提供了便利。小结与源码脉络app.callback()是 Typer 多命令应用的总入口钩子围绕它形成了清晰的源码脉络装饰器注册Typer.callback()在 typer/main.py 中实现支持invoke_without_command、context_settings、help等参数底层映射回调最终被构建为 Click 的Group.callback并携带invoke_without_command配置typer/core.py文档提取回调函数的 docstring 默认被提取为主 CLI 帮助文本与 docs/tutorial/options/callback-and-context.md 中介绍的选项级回调一脉相承测试保障回调的顶层参数、帮助文本、位置约束等行为均由 tests/test_tutorial/test_commands/test_callback/ 下的测试用例锁定。掌握回调你就掌握了在 Typer 中组织应用级逻辑的能力声明全局选项、注入与覆盖前置逻辑、定制主帮助文档以及与typer.Context协作实现无子命令执行、子命令感知和额外参数透传等进阶控制。【免费下载链接】typerTyper, build great CLIs. Easy to code. Based on Python type hints.项目地址: https://gitcode.com/GitHub_Trending/ty/typer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考