Subtle behaviours, like which callback sees which value or why defaults misbehave, follow from this pipeline.
When cli.main(args) runs:
- make_context creates a
Context(applyingcontext_settings) and callsparse_args. - Parsing. Click's parser (modelled on
optparse) splits the argument list into options and positional values according to each parameter's declaration (nargs, flags,--handling). For groups, parsing stops at the first positional that names a subcommand; the rest is kept inctx.protected_args/ctx.argsfor the child. - Processing. Each parameter's
handle_parse_resultresolves the value from its source in precedence order (command line, environment,default_map, default; an option withprompt=asks instead of silently using the last two), converts it with its type, then runs its callback, and stores it inctx.params. Parameters are processed in the order the user typed them on the command line, with eager parameters first and unspecified ones after. That is why callbacks should not assume another parameter is already inctx.params. ctx.get_parameter_source(name)reports where a value came from (COMMANDLINE,ENVIRONMENT,DEFAULT,DEFAULT_MAP,PROMPT).- invoke. The command callback is called with
**ctx.params. For groups, the group callback runs, then the subcommand's context is created as a child (parent=ctx) and the process repeats.
Command.to_info_dict(ctx) dumps the whole structure as data, which tools use to generate documentation. Knowing this pipeline explains things like defaults bypassing callbacks during completion (resilient_parsing) and why options must precede subcommands.
Example
import click
from click.core import ParameterSource
seen = []
def trace(ctx, param, value):
seen.append(param.name)
return value
@click.command()
@click.option("--a", default=1, callback=trace)
@click.option("--b", default=2, callback=trace, envvar="B_VAL")
@click.option("--c", default=3, callback=trace)
@click.pass_context
def cmd(ctx, a, b, c):
for name in ("a", "b", "c"):
click.echo(f"{name}={ctx.params[name]} from {ctx.get_parameter_source(name).name}")
cmd.main(["--c", "30", "--a", "10"], standalone_mode=False)
# a=10 from COMMANDLINE
# b=2 from DEFAULT
# c=30 from COMMANDLINE
print(seen) # ['c', 'a', 'b'] (command-line order first, then the rest)Remember
- make_context -> parse -> process each parameter -> invoke callback
- Parameters are processed in command-line order (eager first), unspecified ones last
- get_parameter_source tells where each value came from
Watch out for
- Writing a callback for --b that reads ctx.params['a'], which may not be set yet.