You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Click框架问题:链式命令中调用其他命令函数(上下文对象)报错求助

Fixing Click Command Chaining Issues with Context Objects

Hey there! Let's break down this Click CLI chaining issue you're facing—this is a super common gotcha when working with Click's context and command functions directly.

First, let's fill in that minimal example you started with to mirror the typical problematic setup:

import click

@click.group()
@click.pass_context
def cli(ctx):
    # Initialize context object with shared data
    ctx.obj = {"shared_value": "Hello from context!"}

@cli.command()
@click.pass_context
def first_command(ctx):
    click.echo(f"First command: {ctx.obj['shared_value']}")
    # ❌ Wrong way: Directly calling the second command function
    second_command()

@cli.command()
@click.pass_context
def second_command(ctx):
    click.echo(f"Second command: {ctx.obj['shared_value']}")

if __name__ == "__main__":
    cli()

If you run this, you'll get an error like TypeError: second_command() missing 1 required positional argument: 'ctx'. The root issue is that when you use @cli.command(), Click wraps your original function into a Command object. This wrapped object expects the Click context as an argument, but when you call it directly, you're not passing that context at all.

Click provides a built-in method to safely call other commands from within a command while preserving the context:

import click

@click.group()
@click.pass_context
def cli(ctx):
    ctx.obj = {"shared_value": "Hello from context!"}

@cli.command()
@click.pass_context
def first_command(ctx):
    click.echo(f"First command: {ctx.obj['shared_value']}")
    # ✅ Correct way: Use ctx.invoke to pass context automatically
    ctx.invoke(second_command)

@cli.command()
@click.pass_context
def second_command(ctx):
    click.echo(f"Second command: {ctx.obj['shared_value']}")

if __name__ == "__main__":
    cli()

ctx.invoke() handles all the Click-specific parameter binding, including passing the context, and even works if your target command has its own options/arguments—you can pass those directly like:

ctx.invoke(second_command, custom_option="test_value")

Solution 2: Extract Reusable Logic (Cleaner for Shared Code)

If you're trying to reuse business logic across commands instead of invoking the entire command, it's better to separate the CLI wrapper from the core logic:

import click

@click.group()
@click.pass_context
def cli(ctx):
    ctx.obj = {"shared_value": "Hello from context!"}

# Core reusable logic
def shared_second_command_logic(ctx):
    click.echo(f"Second command logic: {ctx.obj['shared_value']}")

@cli.command()
@click.pass_context
def first_command(ctx):
    click.echo(f"First command: {ctx.obj['shared_value']}")
    # ✅ Call the shared logic directly with context
    shared_second_command_logic(ctx)

@cli.command()
@click.pass_context
def second_command(ctx):
    shared_second_command_logic(ctx)

if __name__ == "__main__":
    cli()

This approach decouples your CLI structure from your business logic, making it easier to test and maintain.

Key Takeaways

  • Never directly call a function decorated with @cli.command()—it's wrapped into a Click Command object, not your original function.
  • Use ctx.invoke() when you need to run an entire other command with its full CLI behavior.
  • Extract shared logic into standalone functions if you just need to reuse code across commands.

内容的提问来源于stack exchange,提问作者Cord Kaldemeyer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:21:08