anntzer / defopt

Effortless argument parser
https://pypi.org/project/defopt/
MIT License
213 stars 11 forks source link

Add a registry class #105

Open lordmauve opened 2 years ago

lordmauve commented 2 years ago

I really like the fact that defopt works without mandatory decorators.

But, practically, I find myself repeatedly writing some small utility that allows using decorators to register subcommands to pass into defopt.run(). My most recent one looks like this:

main = CLI(short={}, parsers=...)

@main.cmd('open')
def open_(target: str, *paths: str):
    ...

@main.cmd
def close(target: str):
    ...

if __name__ == '__main__':
    main()

The decorator serves as an annotation about which functions are exposed as CLI subcommands, which is handy in a longer file where many of the functions are not exposed.

I think an optional registration utility like this would be a handy thing for defopt to have.

anntzer commented 2 years ago

This seems reasonable modulo API bikeshedding, but I will not have the bandwidth to consider all the options at least for the coming month(s). But I'll keep this open for later.

anntzer commented 2 years ago

One question, though, is how you propose to handle subcommands in this API. Perhaps foo = main.add_subcommand("foo") and then an @foo.cmd decorator? (perhaps that would be better named @foo.add_command, for consistency?)

lordmauve commented 2 years ago

Using that pattern in Click and argparse, I find those then become a maze of indirection through a module. One alternative would be to avoid creating the intermediate objects with partially bound state and just allow the decorators to accept the path where a function will be exposed:

@main.command('project', 'new')
def new_project():
    print("creating new project")
$ myproj project new
creating new project
anntzer commented 2 years ago

Agreed that @main.command("foo", "bar") is likely the most user-friendly.

Do you want to try your hand at proposing a PR? :-)