In a normal situation, my application will load a set of configuration values into the context and these will be passed to the subcommands with pass_context
. There is only one situation where this will not work - the first time the application is run and the configuration hasn't been set up.
My goal is to allow the user to run one subcommand and generate the appropriate configuration so that the CLI works the rest of the time.
My cli.py
code:
import sys
import click
from ruamel.yaml import YAML
from pathlib import Path
from commands.config_cmds import configcmd
MYAPP = "AwesomeCLI"
@click.group()
@click.version_option()
@click.pass_context
def cli(ctx):
"""command line application"""
ctx.ensure_object(dict)
ctx.obj['APPLICATION_NAME'] = MYAPP
config_file = Path(click.get_app_dir(ctx.obj[MYAPP])) / "config.yml"
yaml = YAML(typ="safe")
try:
config_yml = yaml.load(config_file)
except FileNotFoundError:
click.secho("Run command: awesome-cli configcmd first-run", fg='red')
raise click.FileError(config_file.name, "Missing configuration file.")
ctx.obj['CONFIG'] = yaml.dump(config_yml)
cli.add_command(configcmd)
My configcmd
code:
@click.group()
def configcmd():
"""Manage configuration of this tool
\f
The configuration file is saved in $HOME/.config/awesome-cli
"""
@config.command()
@click.pass_context
def first_run(ctx):
"""
Set up CLI configuration.
"""
api_key = click.prompt("Your API Key")
# More stuff here about saving this file...
If I run python awesome-cli configcmd
I receive the following error (as expected):
Run command: awesome-cli configcmd first-run
Error: Could not open file config.yml: Missing configuration file.
However, if I run that command python awesome-cli configcmd first-run
I receive the same error, which not my goal. Obviously I should be getting that error with this code, but that's because I don't know how to add an exception based on the command/subcommand being called.
What do I need to add in my cli
function in cli.py
so that I don't try to load the configuration file if (and only if), the user is running configcmd first-run
? Any other command/subcommand will require that this configuration file exists, so I want the check to remain for those.
To invoke some specific code prior to executing a subcommand, which is based on the specific subcommand invoked, you can look at ctx.invoked_subcommand
like:
if ctx.invoked_subcommand != 'configcmd':
In your example you will need examine the ctx.invoked_subcommand
at each level like:
import sys
import click
from ruamel.yaml import YAML
from pathlib import Path
MYAPP = "AwesomeCLI"
@click.group()
@click.pass_context
def cli(ctx):
"""command line application"""
ctx.ensure_object(dict)
ctx.obj['APPLICATION_NAME'] = MYAPP
ctx.obj['CONFIG_FILEPATH'] = Path(click.get_app_dir(MYAPP), "config.yml")
if ctx.invoked_subcommand != 'configcmd':
load_config(ctx)
@cli.group()
@click.pass_context
def configcmd(ctx):
"""Configuration management for this CLI"""
click.echo("In config")
if ctx.invoked_subcommand != 'first-run':
load_config(ctx)
def load_config(ctx):
yaml = YAML(typ="safe")
try:
config_yml = yaml.load(ctx.obj['CONFIG_FILEPATH'])
except FileNotFoundError:
click.secho("Run command: awesome-cli configcmd first-run",
fg='red')
raise click.FileError(str(ctx.obj['CONFIG_FILEPATH']),
"Missing configuration file.")
ctx.obj['CONFIG'] = yaml.load(config_yml)
@configcmd.command('first-run')
@click.pass_context
def first_run(ctx):
"""Set up CLI configuration."""
click.echo("In first-run")
@configcmd.command('test-cmd')
@click.pass_context
def test_cmd(ctx):
""" This command will not be reachable without config file"""
click.echo("In first-run")
if __name__ == "__main__":
commands = (
'configcmd first-run',
'configcmd test-cmd',
'configcmd --help',
'--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
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)]
-----------
> configcmd first-run
In config
In first-run
-----------
> configcmd test-cmd
In config
Run command: awesome-cli configcmd first-run
Error: Could not open file C:\Users\stephen\AppData\Roaming\AwesomeCLI\config.yml: Missing configuration file.
-----------
> configcmd --help
Usage: test.py configcmd [OPTIONS] COMMAND [ARGS]...
Configuration management for this CLI
Options:
--help Show this message and exit.
Commands:
first-run Set up CLI configuration.
test-cmd This command will not be reachable without...
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
command line application
Options:
--help Show this message and exit.
Commands:
configcmd Configuration management for this CLI
-----------
>
Usage: test.py [OPTIONS] COMMAND [ARGS]...
command line application
Options:
--help Show this message and exit.
Commands:
configcmd Configuration management for this CLI