Skip to content

✨ Handle *args and **kwargs#1654

Draft
alipatti wants to merge 13 commits intofastapi:masterfrom
alipatti:allow-args-kwargs
Draft

✨ Handle *args and **kwargs#1654
alipatti wants to merge 13 commits intofastapi: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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants