neithere / argh

An argparse wrapper that doesn't make you say "argh" each time you deal with it.
http://argh.rtfd.org
GNU Lesser General Public License v3.0
369 stars 55 forks source link

Using argh.argh(args=... , default=...) causes double nesting of passed arguments #212

Closed pioio closed 8 months ago

pioio commented 8 months ago

Given the code

import argh

@argh.arg("paths", nargs="*", default=["x", "z"])
def fun(paths:list[str]):
    print(paths)

if __name__ == "__main__":
    argh.dispatch_command(fun)
> ./app.py
["x", "z"]    # as expected - the value of 'default'

> ./app.py a b  
[["a"], ["b"]]   # Bug? I would expect ["a", "b"]

If I use argparse, it works fine

parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="*", default=["x", "y"])
print(parser.parse_args([]))
print(parser.parse_args(["a", "b"]))
> Namespace(paths=['x', 'y'])
> Namespace(paths=['a', 'b'])

If I don't specify the 'default' arg things work fine:


@argh.arg("paths", nargs="*")
def with_argh(paths:list[str]):
    print(paths)
>  ./app.py a b  
["a", "b"]

Using latest 0.30.4
neithere commented 8 months ago

Hi @pioio, thank you for the bug report!

Current status and plans

Reproduced, confirmed, will be fixed in Argh v.0.30.5 (PR #213).

Workaround

A workaround it to simply expect paths: list[str] | None and then do paths = paths or ["x", "y"].

Additional notes

As a bonus I've added the assumption of nargs="*" based on a list default value, so you don't have to use the @arg decorator at all:

def func(paths: list[str] = ["default-1", "default-2"]) -> None:
    print(paths)

if __name__ == "__main__":
    parser = ArgumentParser()
    argh.set_default_command(
        parser, func, name_mapping_policy=argh.assembling.NameMappingPolicy.BY_NAME_IF_KWONLY
    )
    argh.dispatch(parser)

The name mapping policy has to be explicitly specified until it becomes the default one in an upcoming version of Argh.