tyro icon indicating copy to clipboard operation
tyro copied to clipboard

Helper for dispatching sub commands? Create the neede dict for subcommand_cli_from_dict() ?

Open jedie opened this issue 1 year ago • 4 comments

I come from click. Tyro has some nice new concepts... But i missed a easy way to compose sub commands to a CLI.

https://brentyi.github.io/tyro/examples/02_nesting/05_subcommands_func/ described to use tyro.extras.subcommand_cli_from_dict(), but then i have to specify a dict to map the functions :(

Think this can be done with a small helper. So that i just need to add a decorator to the sub command function, like clicks @click.command()

It there something like this around?!?

jedie avatar Apr 19 '24 13:04 jedie

Hi @jedie!

This unfortunately doesn't exist, and unless we decide to extend the tyro API you'd have to write a utility yourself. But for the basic functionality, yes, it'd just be a small helper, eg:

# script.py

from cli_helper import command, run_cli

@command
def checkout(branch: str) -> None:
    """Check out a branch."""
    print(f"{branch=}")

@command
def commit(message: str, all: bool = False) -> None:
    """Make a commit."""
    print(f"{message=} {all=}")

if __name__ == "__main__":
    run_cli()
# cli_helper.py

from typing import Callable, TypeVar

import tyro

_subcommands = []
CallableT = TypeVar("CallableT", bound=Callable)

def command(func: CallableT) -> CallableT:
    _subcommands.append(func)
    return func

def run_cli() -> None:
    tyro.extras.subcommand_cli_from_dict({v.__name__: v for v in _subcommands})

We could also add something like this to tyro.extras.

brentyi avatar Apr 20 '24 02:04 brentyi

We could also add something like this to tyro.extras.

Sound a good idea!

jedie avatar Apr 23 '24 18:04 jedie

I use now this:

class TyroCommandCli:
    """
    Helper for tyro.extras.subcommand_cli_from_dict()
    To easily register subcommands via decorator.
    """

    def __init__(self):
        self.subcommands = {}

    def register(self, func: Callable) -> Callable:
        """
        Decorator to register a function as a CLI command.
        """
        func_name = func.__name__
        func_name = func_name.replace('_', '-')
        self.subcommands[func_name] = func
        return func

    def run(self, **kwargs) -> None:
        """
        Run the CLI with the registered commands.
        """
        ordered_subcommands = OrderedDict(sorted(self.subcommands.items()))
        tyro.extras.subcommand_cli_from_dict(
            subcommands=ordered_subcommands,
            **kwargs,
        )

And add this in my small project here: https://github.com/jedie/cli-base-utilities/blob/31ebd3812571dd8068d2dbff9151af5ab6d810da/cli_base/tyro_commands.py#L19C1-L46C10

jedie avatar Sep 26 '24 20:09 jedie

Thanks @jedie! I had also implemented an helper that I hadn't pushed/committed. If you have a moment for feedback / suggestions I just put that version in #169.

brentyi avatar Sep 26 '24 21:09 brentyi