Skip to content

✨ Handle *args and **kwargs#1654

Draft
alipatti wants to merge 14 commits into
fastapi:masterfrom
alipatti:allow-args-kwargs
Draft

✨ Handle *args and **kwargs#1654
alipatti wants to merge 14 commits into
fastapi:masterfrom
alipatti:allow-args-kwargs

Conversation

@alipatti
Copy link
Copy Markdown

@alipatti alipatti commented Mar 25, 2026

This PR adds support for *args and **kwargs in function signatures. See #163 for a discussion of this feature.

# -- command.py --

import json
from typing import Any

import typer

@app.command()
def cmd(
    filepath: Path,
    option: str = "",
    flag: bool = False,
    *args: str,
    **kwargs: Any
) -> None:
    dump = json.dumps({
        "filepath" : filepath,
        "option" : option,
        "flag" : flag,
        "args" : args,
        "kwargs" : kwargs,

    })
    typer.echo(dump)

app()

Unknown key-value pairs are captured in kwargs. Unknown trailing arguments are captured in *args.

./command.py --unknown-key value input.txt arg1 arg2
# kwargs = { "unknown_key" : "value" }; args = ( "arg1", "arg2" )

To avoid ambiguity, everything after -- will be absorbed into *args regardless of
whether or not it matches a known argument. (This is consistent with POSIX.)

./command.py input.txt --flag # flag = True
./command.py input.txt -- --flag # args = ("--flag",)

./command.py input.txt arg1 --unknown option # args = ( "arg1" ); kwargs = { "unknown" : "option" }
./command.py input.txt -- arg1 --unknown option # args = ( "arg1", "--unknown", "option" )

This feature will not disrupt explicitly declared flags/options.

# all of the following commands are parsed equivalently:
# option = "val"; flag = True; args = ( "arg1", "arg2" ); kwargs = { "unknown", "val2" }

./command.py --option val --flag --unknown val2 input.txt arg1 arg2 
./command.py --flag --unknown val2 input.txt --option val arg1 arg2
./command.py --flag input.txt --option val --unknown val2 arg1 arg2
./command.py --flag input.txt --option val --unknown val2 -- arg1 arg2

Empty args are handled gracefully.

# both of the following produce args = ()
./command.py input.txt
./command.py input.txt --

Unknown options must have values.
(To emulate a boolean flag, simply pass a value so that kwargs.get("unknown_flag") is truthy.)

./command.py --unknown-flag input.txt arg1 arg2 # not okay
./command.py --unknown-flag true input.txt arg1 arg2 # okay

Both *args and **kwargs can of course be declared without the other.

# -- command2.py --

from typing import Any

import typer


@app.command()
def args(filepath: Path, *args: str) -> None:
    typer.echo(args)

@app.command()
def kwargs(filepath: Path, **kwargs: Any) -> None:
    typer.echo(kwargs)

app()
./command2.py args input.txt arg1 arg2 # args = ( "arg1", "arg2" )
./command2.py kwargs input.txt --key value # kwargs = { "key": "value" }

Very open to feedback on both the design and implementation. Thank you for such a useful project!

@alipatti alipatti marked this pull request as draft March 25, 2026 20:08
@alipatti
Copy link
Copy Markdown
Author

Marking as draft for now. Will need to add more tests + documentation if it's decided that this feature should be merged.

@alipatti alipatti changed the title 📝 Handle *args and **kwargs ✨ Handle *args and **kwargs Mar 25, 2026
@alipatti alipatti force-pushed the allow-args-kwargs branch from e6ebb30 to 2a0b8eb Compare March 25, 2026 20:13
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label May 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has a merge conflict that needs to be resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically generated when a PR has a merge conflict waiting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants