How can I split my Click commands, each with a set of sub-commands, into multiple files?
The downside of using CommandCollection
for this is that it merges your commands and works only with command groups. The imho better alternative is to use add_command
to achieve the same result.
I have a project with the following tree:
cli/
├── __init__.py
├── cli.py
├── group1
│ ├── __init__.py
│ ├── commands.py
└── group2
├── __init__.py
└── commands.py
Each subcommand has its own module, what makes it incredibly easy to manage even complex implementations with many more helper classes and files. In each module, the commands.py
file contains the @click
annotations. Example group2/commands.py
:
import click
@click.command()
def version():
"""Display the current version."""
click.echo(_read_version())
If necessary, you could easily create more classes in the module, and import
and use them here, thus giving your CLI the full power of Python's classes and modules.
My cli.py
is the entry point for the whole CLI:
import click
from .group1 import commands as group1
from .group2 import commands as group2
@click.group()
def entry_point():
pass
entry_point.add_command(group1.command_group)
entry_point.add_command(group2.version)
With this setup, it is very easy to separate your commands by concerns, and also build additional functionality around them that they might need. It has served me very well so far...
Reference: http://click.pocoo.org/6/quickstart/#nesting-commands
Suppose your project have the following structure:
project/
├── __init__.py
├── init.py
└── commands
├── __init__.py
└── cloudflare.py
Groups are nothing more than multiple commands and groups can be nested. You can separate your groups into modules and import them on you init.py
file and add them to the cli
group using the add_command.
Here is a init.py
example:
import click
from .commands.cloudflare import cloudflare
@click.group()
def cli():
pass
cli.add_command(cloudflare)
You have to import the cloudflare group which lives inside the cloudflare.py file. Your commands/cloudflare.py
would look like this:
import click
@click.group()
def cloudflare():
pass
@cloudflare.command()
def zone():
click.echo('This is the zone subcommand of the cloudflare command')
Then you can run the cloudflare command like this:
$ python init.py cloudflare zone
This information is not very explicit on the documentation but if you look at the source code, which is very well commented, you can see how groups can be nested.
I'm looking for something like this at the moment, in your case is simple because you have groups in each of the files, you can solve this problem as explained in the documentation:
In the init.py
file:
import click
from command_cloudflare import cloudflare
from command_uptimerobot import uptimerobot
cli = click.CommandCollection(sources=[cloudflare, uptimerobot])
if __name__ == '__main__':
cli()
The best part of this solution is that is totally compliant with pep8 and other linters because you don't need to import something you wouldn't use and you don't need to import * from anywhere.