Most web projects will need at some point a command line interface (CLI) for administrative purposes. I am wrapping up my current idea of how to approach this for a typical Pyramid project based on the library Click.
The role of Click
Click provides a bunch of decorators which help to easily create consistent and nice-to-use user interfaces on the command line.
Minimal example
The following excerpt shows the basic principle:
import click @click.command() def my_command(): pass
You would typically register your command as a console_script inside your setup.py. The Click documentation is quite comprehensive and contains more useful examples.
Using sub-commands
One very nice feature of Click is the support for sub-commands. I envision my CLI to work somewhat like the following examples:
my-project
This should show a help message and overview of available commands.
my-project --config=local.ini
For all commands it should be possible to specify the configuration to use. Depending on the project, this is either a mandatory option or it will have a default value.
Especially for projects where most things are based on environment variables, the need for the parameter config is probably not given.
my-project --config=local.ini command --option=value
Each command could have its own bunch of options, depending on the need.
The Pyramid side
On this end, I would like the Pyramid framework to be initialized and also logging to be set up. This seems to be the safest bet for the default case.
This means, the following pieces of the API should be used:
pyramid.paster.bootstrap()
This will initialize the Pyramid framework itself. It needs the path to the INI file.
pyramid.paster.setup_logging()
This function will set up the package logging from the standard library based on the given INI file.
All details of these API calls are documented in the API documentation of pyramid.paster.
Putting the fragments together
The idea is to use the base command for bringing up the framework and then attach sub-commands to it for the functionality.
The base command
First of all, define the base command via click.group:
@click.group() @click.argument('ini') @click.pass_context def cli(ctx, ini): setup_logging(ini) env = bootstrap(ini) ctx.obj = env
This should configure logging and bring up the Pyramid framework. ctx.obj is a way to make objects available to subcommands. env is a dict which contains relevant objects like a Request instance, the WSGI application object and similar things. Note that it may be useful to pass in a custom Request instance if you want to construct URLs in your tasks.
An example command
An example command could then be created as follows:
@cli.command() @click.option('--option', help='Example option') @click.pass_context def command(ctx, option): """ Documentation of the example command. It will be available when running it with "--help". """ pass
Open questions
Should there be a CLI interface at all?
I am not quite sure about this question, my best guess at the moment is that it depends on the project.
If the project has already a web API for administrative purposes, it could very well make sense to just extend it with the needed functionality. Pyramid itself has a command prequest which allows to "send" a request from the command line. In case this is good enough, I would avoid to create an additional CLI interface.
Opt-out of the framework initialization
Bootstrapping the full Pyramid framework might be way too much for some commands. It might be worth to look for a way how to opt out of the automatic setup on a per sub-command basis.
Conclusions
On the first glance this looks like a quite nice fit to me. The base command could be placed in a central base package like project.cli and others could use it from there to build specific commands. This would lead to a nice decoupling of the base setup and the individual logic of the sub-command.
Follow up
There is a follow up about the Pyramid integration based on the Configurator object.