Wolkenarchitekt
Wolkenarchitekt

Reputation: 21238

Feature flag for Python click commands

In my python-click-CLI script, I'm building some commands for features that should not be visible for users (to not confuse them), but visible for e.g. developers.

Is it possible to use feature flags for Python-click commands?

I would like to be able to configure (via a config file, etc) if a command is available or not. If a command-feature is disabled, the command should not be callable and the help should not show it.

Like this:

FLAG_ENABLED = False

# This command should not be shown and not be callable as long as the flag is disabled
@cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
    ....

Obviously, I could change the body of my function:

@cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
    if FLAG_ENABLED:
        ...
    else:
        ...

But then my command would still show up in help, which I would like to avoid.

Upvotes: 4

Views: 1293

Answers (1)

Stephen Rauch
Stephen Rauch

Reputation: 49794

You can add a feature flag functionality with a custom class like:

Custom Class

This class over rides the click.Group.command() method which is used to decorate command functions. It adds the ability to pass an active flag, which when False will skip adding the command to the group.

import click

class FeatureFlagCommand(click.Group):

    def command(self, *args, active=True, **kwargs):
        """Behaves the same as `click.Group.command()` except added an
        `active` flag which can be used to disable to command.
        """
        if active:
            return super(FeatureFlagCommand, self).command(*args, **kwargs)
        else:
            return lambda f: f

Using the Custom Class

By passing the cls parameter to the click.group() decorator, any commands added to the group via the the group.command() will be gated with the active flag.

@click.group(cls=FeatureFlagCommand)
def cli():
    ...

@cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
    ...

How does this work?

This works because click is a well designed OO framework. It is easy to inherit from click.Group and build a new command() decorator. In the new command() decorator if the active flag is False we return the undecorated function instead of adding the function to the group.

Test Code:

@click.group(cls=FeatureFlagCommand)
def cli():
    """My Awesome Click Program"""

@cli.command(active=False)
def command1():
    click.echo('Command 1')

@cli.command(active=True)
def command2():
    click.echo('Command 2')

@cli.command()
def command3():
    click.echo('Command 3')


if __name__ == "__main__":
    commands = (
        'command1',
        'command2',
        'command3',
        '--help',
        '',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            cli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

Results:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> command1
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Error: No such command "command1".
-----------
> command2
Command 2
-----------
> command3
Command 3
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  My Awesome Click Program

Options:
  --help  Show this message and exit.

Commands:
  command2
  command3
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  My Awesome Click Program

Options:
  --help  Show this message and exit.

Commands:
  command2
  command3

Upvotes: 3

Related Questions