Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default (sub)command for Group #430

Closed
PAStheLoD opened this issue Sep 29, 2015 · 21 comments
Closed

default (sub)command for Group #430

PAStheLoD opened this issue Sep 29, 2015 · 21 comments
Assignees

Comments

@PAStheLoD
Copy link

I've a shameless support question or request for enhancement.

We'd like to make a command the default for a group of commands, so basically Group(invoke_without_command=True), plus somehow invoking a command as if it would have been invoked via Command.main().

Currently it turns out that somewhere deep in click the args parsing results in a no such command found error if I use any (positional) arguments.

import click

@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
    if ctx.invoked_subcommand is None:
        click.echo('I was invoked without subcommand')
        a.main()
    else:
        click.echo('I am about to invoke %s' % ctx.invoked_subcommand)

@cli.command()
@click.option('--test')
def a(test):
    click.echo(test)

@cli.command()
def sync():
    click.echo('The subcommand')

cli()

Thanks!

@untitaker
Copy link
Contributor

Replace this:

a.main()

with:

ctx.invoke(a)

@PAStheLoD
Copy link
Author

No go :)

pas@stranger:~/tmp/t$ python i.py --test a
Error: no such option: --test

Oh, and this is python 3.5.0. (No that it likely matters, but just to be sure.)

@untitaker
Copy link
Contributor

Then ctx.forward might do the job.

I'd recommend against conflating parameters and subcommands like that though.

@PAStheLoD
Copy link
Author

Thanks, but no luck with that either.

Sure, we might have to separate this "default" behavior (a task queue runner daemon) from the "run this and this task with such and such parameters" executable.

@untitaker
Copy link
Contributor

It's probably barely implementable, but I guess it's worth a discussion at least.

@sublee
Copy link

sublee commented Oct 31, 2015

I wanted the same behavior. Here's how I implemented an implicit sub command: https://github.com/what-studio/profiling/blob/e6894bcfb975c723b00d764682a785df71c10b70/profiling/__main__.py#L87

@sublee
Copy link

sublee commented Nov 30, 2015

https://github.com/sublee/click-default-group

I made a separate repository for default subcommand.

@llchan
Copy link

llchan commented Apr 8, 2016

@sublee thanks for your example. I wound up using something similar but with a slightly different interface:

class DefaultGroup(click.Group):

    ignore_unknown_options = True

    def __init__(self, *args, **kwargs):
        default_command = kwargs.pop('default_command', None)
        super(DefaultGroup, self).__init__(*args, **kwargs)
        self.default_cmd_name = None
        if default_command is not None:
            self.set_default_command(default_command)

    def set_default_command(self, command):
        if isinstance(command, basestring):
            cmd_name = command
        else:
            cmd_name = command.name
            self.add_command(command)
        self.default_cmd_name = cmd_name

    def parse_args(self, ctx, args):
        if not args and self.default_cmd_name is not None:
            args.insert(0, self.default_cmd_name)
        return super(DefaultGroup, self).parse_args(ctx, args)

    def get_command(self, ctx, cmd_name):
        if cmd_name not in self.commands and self.default_cmd_name is not None:
            ctx.args0 = cmd_name
            cmd_name = self.default_cmd_name
        return super(DefaultGroup, self).get_command(ctx, cmd_name)

    def resolve_command(self, ctx, args):
        cmd_name, cmd, args = super(DefaultGroup, self).resolve_command(ctx, args)
        args0 = getattr(ctx, 'args0', None)
        if args0 is not None:
            args.insert(0, args0)
        return cmd_name, cmd, args

This has the additional benefit that I can call group.add_command(command) with a command decorated with the vanilla click.command decorator, rather than requiring that I use the special decorator.

@llchan
Copy link

llchan commented Apr 8, 2016

To elaborate on the last point, heres two different ways to get the same thing:

@click.group(cls=DefaultGroup, default_command='a')
def cli():
    pass

@cli.command()
@click.option('--test')
def a(test):
    click.echo(test)

@cli.command()
def sync():
    click.echo('The subcommand')

cli()
@click.group(cls=DefaultGroup)
def cli():
    pass

@click.command()
@click.option('--test')
def a(test):
    click.echo(test)

@click.command()
def sync():
    click.echo('The subcommand')

cli.add_command(a)
cli.add_command(sync)
cli.set_default_command(a)
cli()

@untitaker
Copy link
Contributor

Because click-default-group exists, may we close this?

BTW @sublee your project would be a good fit for https://github.com/click-contrib

@untitaker untitaker self-assigned this Apr 8, 2016
@llchan
Copy link

llchan commented Apr 8, 2016

Yeah, that's fine. My interface is different enough that it would require a breaking change for him to incorporate the changes, so it's probably not worth the effort (especially since he's already pushed it to pypi). The snippet is short so I figured I'd just comment here in case anyone else is looking for similar functionality.

If/when we add this to click-contrib we can discuss further there.

@untitaker
Copy link
Contributor

Cool

@sublee
Copy link

sublee commented Apr 11, 2016

@llchan Your approach looks better than mine. Can I borrow the approach on click-default-group?

@llchan
Copy link

llchan commented Apr 11, 2016

Yeah, of course! Btw, you should enable issues on your repo so we can move the discussion outside of this core click repo.

@tony
Copy link

tony commented May 26, 2016

@sublee Please enable issues in your repo @ https://github.com/sublee/click-default-group

@sublee
Copy link

sublee commented May 27, 2016

@tony Oh, I didn't know. I've just enabled.

@glehmann
Copy link

@sublee You should actually consider moving click-default-group to click-contrib as proposed by @untitaker. The project really looks interesting, but I only found it by chance. It would be a lot more visible in click-contrib!

@tony
Copy link

tony commented May 30, 2016

@glehmann @sublee

Agreed. Is there any other things that get shared? Pypi/readthedocs needed to be added?

@sublee
Copy link

sublee commented May 30, 2016

@glehmann I just transferred click-default-group to click-contrib. Check it out!

@oberstet
Copy link

Using @click.group(cls=DefaultGroup, default='shell', default_if_no_args=True) breaks the auto-completition from https://github.com/click-contrib/click-repl

Why is it preferred versus the following anyway (which works, and does not break click-repl?

@click.group(invoke_without_command=True)
@click.option(
    '--profile',
    help='profile to use',
    default=u'default'
)
@click.pass_context
def cli(ctx, profile):
    ctx.obj = Config(_app, profile)
    if ctx.invoked_subcommand is None:
        ctx.invoke(cmd_shell)

@untitaker
Copy link
Contributor

I think this discussion should be continued in a new issue on click-default-group, not in this old thread.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants