cliff/doc/source/user/demoapp.rst
Akihiro Motoki 9a29859cf2 sphinxext: Support cliff application
This commit adds a feature to generate a reference of global
optoions of a cliff application to autoprogram-cliff directive.
If a class path of a cliff application is specified as the argument of
autoprogram-cliff directive, it will be interpreted as a cliff application
and global options of the specified application are rendered.

Change-Id: I20e46521a137ca721fae28f10c5cf75d26069e45
2017-10-30 20:38:26 +00:00

8.8 KiB

Exploring the Demo App

The cliff source package includes a demoapp directory containing an example main program with several command plugins.

Setup

To install and experiment with the demo app you should create a virtual environment and activate it. This will make it easy to remove the app later, since it doesn't do anything useful and you aren't likely to want to hang onto it after you understand how it works:

$ pip install virtualenv
$ virtualenv .venv
$ . .venv/bin/activate
(.venv)$

Next, install cliff in the same environment:

(.venv)$ python setup.py install

Finally, install the demo application into the virtual environment:

(.venv)$ cd demoapp
(.venv)$ python setup.py install

Usage

Both cliff and the demo installed, you can now run the command cliffdemo.

For basic command usage instructions and a list of the commands available from the plugins, run:

(.venv)$ cliffdemo -h

or:

(.venv)$ cliffdemo --help

Run the simple command by passing its name as argument to cliffdemo:

(.venv)$ cliffdemo simple

The simple command prints this output to the console:

sending greeting
hi!

To see help for an individual command, use the help command:

(.venv)$ cliffdemo help files

or the --help option:

(.venv)$ cliffdemo files --help

For more information, refer to the autogenerated documentation below <demoapp-sphinx>.

The Source

The cliffdemo application is defined in a cliffdemo package containing several modules.

main.py

The main application is defined in main.py:

../../../demoapp/cliffdemo/main.py

The DemoApp class inherits from App and overrides __init__ to set the program description and version number. It also passes a CommandManager instance configured to look for plugins in the cliff.demo namespace.

The initialize_app method of DemoApp will be invoked after the main program arguments are parsed, but before any command processing is performed and before the application enters interactive mode. This hook is intended for opening connections to remote web services, databases, etc. using arguments passed to the main application.

The prepare_to_run_command method of DemoApp will be invoked after a command is identified, but before the command is given its arguments and run. This hook is intended for pre-command validation or setup that must be repeated and cannot be handled by initialize_app.

The clean_up method of DemoApp is invoked after a command runs. If the command raised an exception, the exception object is passed to clean_up. Otherwise the err argument is None.

The main function defined in main.py is registered as a console script entry point so that DemoApp can be run from the command line (see the discussion of setup.py below).

simple.py

Two commands are defined in simple.py:

../../../demoapp/cliffdemo/simple.py

Simple demonstrates using logging to emit messages on the console at different verbose levels:

(.venv)$ cliffdemo simple
sending greeting
hi!

(.venv)$ cliffdemo -v simple
prepare_to_run_command Simple
sending greeting
debugging
hi!
clean_up Simple

(.venv)$ cliffdemo -q simple
hi!

Error always raises a RuntimeError exception when it is invoked, and can be used to experiment with the error handling features of cliff:

(.venv)$ cliffdemo error
causing error
ERROR: this is the expected exception

(.venv)$ cliffdemo -v error
prepare_to_run_command Error
causing error
ERROR: this is the expected exception
clean_up Error
got an error: this is the expected exception

(.venv)$ cliffdemo --debug error
causing error
this is the expected exception
Traceback (most recent call last):
  File ".../cliff/app.py", line 218, in run_subcommand
    result = cmd.run(parsed_args)
  File ".../cliff/command.py", line 43, in run
    self.take_action(parsed_args)
  File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
    raise RuntimeError('this is the expected exception')
RuntimeError: this is the expected exception
Traceback (most recent call last):
  File "/Users/dhellmann/Envs/cliff/bin/cliffdemo", line 9, in <module>
    load_entry_point('cliffdemo==0.1', 'console_scripts', 'cliffdemo')()
  File ".../demoapp/cliffdemo/main.py", line 33, in main
    return myapp.run(argv)
  File ".../cliff/app.py", line 160, in run
    result = self.run_subcommand(remainder)
  File ".../cliff/app.py", line 218, in run_subcommand
    result = cmd.run(parsed_args)
  File ".../cliff/command.py", line 43, in run
    self.take_action(parsed_args)
  File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
    raise RuntimeError('this is the expected exception')
RuntimeError: this is the expected exception

list.py

list.py includes a single command derived from cliff.lister.Lister which prints a list of the files in the current directory.

../../../demoapp/cliffdemo/list.py

Files prepares the data, and Lister manages the output formatter and printing the data to the console:

(.venv)$ cliffdemo files
+---------------+------+
|      Name     | Size |
+---------------+------+
| build         |  136 |
| cliffdemo.log | 2546 |
| Makefile      | 5569 |
| source        |  408 |
+---------------+------+

(.venv)$ cliffdemo files -f csv
"Name","Size"
"build",136
"cliffdemo.log",2690
"Makefile",5569
"source",408

show.py

show.py includes a single command derived from cliff.show.ShowOne which prints the properties of the named file.

../../../demoapp/cliffdemo/show.py

File prepares the data, and ShowOne manages the output formatter and printing the data to the console:

(.venv)$ cliffdemo file setup.py
+---------------+--------------+
|     Field     |    Value     |
+---------------+--------------+
| Name          | setup.py     |
| Size          | 5825         |
| UID           | 502          |
| GID           | 20           |
| Modified Time | 1335569964.0 |
+---------------+--------------+

setup.py

The demo application is packaged using setuptools.

../../../demoapp/setup.py

The important parts of the packaging instructions are the entry_points settings. All of the commands are registered in the cliff.demo namespace. Each main program should define its own command namespace so that it only loads the command plugins that it should be managing.

Command Extension Hooks

Individual subcommands of an application can be extended via hooks registered as separate plugins. In the demo application, the hooked command has a single extension registered.

The namespace for hooks is a combination of the application namespace and the command name. In this case, the application namespace is cliff.demo and the command is hooked, so the extension namespace is cliff.demo.hooked. If the subcommand name includes spaces, they are replaced with underscores ("_") to build the namespace.

../../../demoapp/cliffdemo/hook.py

Although the hooked command does not add any arguments to the parser it creates, the help output shows that the extension adds a single --added-by-hook option.

(.venv)$ cliffdemo hooked -h
sample hook get_parser()
usage: cliffdemo hooked [-h] [--added-by-hook ADDED_BY_HOOK]

A command to demonstrate how the hooks work

optional arguments:
  -h, --help            show this help message and exit
  --added-by-hook ADDED_BY_HOOK

extension epilog text

(.venv)$ cliffdemo hooked
sample hook get_parser()
before
this command has an extension
after

cliff.hooks.CommandHook -- The API for command hooks.

Autogenerated Documentation

The following documentation is generated using the following directive, which is provided by the cliff Sphinx extension <sphinxext>.

.. autoprogram-cliff:: cliffdemo.main.DemoApp
   :application: cliffdemo

.. autoprogram-cliff:: cliff.demo
   :application: cliffdemo

Output

Global Options

cliffdemo.main.DemoApp

Command Options

cliff.demo