I would like to implement each subcommand in a different file for a better clarity.
Right now I have only one but the idea will be to add more with the time.
For that I tried 2 ways and it ended with a big failure...
Basically I try to have this result:
$ test_cli
Usage: test_cli [OPTIONS] COMMAND [ARGS]...
Cli command
Options:
--help Show this message and exit.
Commands:
test Test command.
$ test_cli test hello
Hello !
Here are files
$ tree
.
├── cli.py
├── setup.py
└── test.py
I'm using virtualenv and I use the following command to test my application:
$ pip install --editable .
The code of setup.py is the same for both :
from setuptools import setup
setup(
name = 'test_cli',
version = '1.0',
py_modules = [ 'cli', 'test' ],
install_requires = [
'Click',
],
entry_points = '''
[console_scripts]
test_cli=cli:cli
''',
)
Try 1 - FAILURE
Code based on this link, but it did not work with me...
Here is the code of each file:
cli.py
import click
import test
@click.group()
def cli():
''' Cli command '''
pass
cli.add_command(test)
test.py
import click
@click.group()
def test():
''' Test command. '''
pass
@test.command()
def hello():
click.echo('Hello !')
Here is the error I have :
Traceback (most recent call last):
File "/tmp/myenv/bin/test_cli", line 33, in <module>
sys.exit(load_entry_point('test-cli', 'console_scripts', 'test_cli')())
File "/tmp/myenv/bin/test_cli", line 25, in importlib_load_entry_point
return next(matches).load()
File "/tmp/myenv/lib/python3.6/site-packages/importlib_metadata/__init__.py", line 105, in load
module = import_module(match.group('module'))
File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/tmp/toto/cli.py", line 9, in <module>
cli.add_command(test)
File "/tmp/myenv/lib/python3.6/site-packages/click/core.py", line 1347, in add_command
name = name or cmd.name
AttributeError: module 'test' has no attribute 'name'
Try 2 - FAILED
This time I found a code here. I don't have any error but I cannot execute any subcommand :-/
cli.py
import click
import test
@click.group()
def cli():
''' Cli command '''
pass
import click import cli
test.py
import click
import cli
@cli.group()
def test():
''' Test command. '''
pass
@test.command()
def hello():
click.echo('Hello !')
When I execute try to execute the subcommand I have this issue:
$ test_cli test hello
Usage: test_cli [OPTIONS] COMMAND [ARGS]...
Try 'test_cli --help' for help.
Error: No such command 'test'.
Any idea of the issue ?
Thank you.
The easiest way to do this is to implement a separate .py file for your CLI and import the functions into it. I've implemented this in my own program with:
console.py
and in main.py
This method separates your CLI setup from the actual code, while allowing full access to all of clicks functions including groups, subcommands, etc. It also allows you to call commands from many different modules or even calling external commands within your CLI (for example, if you want a CLI that conglomerates multiple functions from various packages, setting aside the discussion over whether that is appropriate behavior or not).