# 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.

## The Source

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

### main.py

The main application is defined in `main.py`:

     1import sys
     2
     3from cliff.app import App
     4from cliff.commandmanager import CommandManager
     5
     6
     7class DemoApp(App):
     8    def __init__(self):
     9        super().__init__(
    10            description='cliff demo app',
    11            version='0.1',
    12            command_manager=CommandManager('cliff.demo'),
    13            deferred_help=True,
    14        )
    15
    16    def initialize_app(self, argv):
    17        self.LOG.debug('initialize_app')
    18
    19    def prepare_to_run_command(self, cmd):
    20        self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)
    21
    22    def clean_up(self, cmd, result, err):
    23        self.LOG.debug('clean_up %s', cmd.__class__.__name__)
    24        if err:
    25            self.LOG.debug('got an error: %s', err)
    26
    27
    28def main(argv=sys.argv[1:]):
    29    myapp = DemoApp()
    30    return myapp.run(argv)
    31
    32
    33if __name__ == '__main__':
    34    sys.exit(main(sys.argv[1:]))

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`:

     1import logging
     2
     3from cliff.command import Command
     4
     5
     6class Simple(Command):
     7    "A simple command that prints a message."
     8
     9    log = logging.getLogger(__name__)
    10
    11    def take_action(self, parsed_args):
    12        self.log.info('sending greeting')
    13        self.log.debug('debugging')
    14        self.app.stdout.write('hi!\n')
    15
    16
    17class Error(Command):
    18    "Always raises an error"
    19
    20    log = logging.getLogger(__name__)
    21
    22    def take_action(self, parsed_args):
    23        self.log.info('causing error')
    24        raise RuntimeError('this is the expected exception')

`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.

     1import logging
     2import os
     3
     4from cliff.lister import Lister
     5
     6
     7class Files(Lister):
     8    """Show a list of files in the current directory.
     9
    10    The file name and size are printed by default.
    11    """
    12
    13    log = logging.getLogger(__name__)
    14
    15    def take_action(self, parsed_args):
    16        return (
    17            ('Name', 'Size'),
    18            ((n, os.stat(n).st_size) for n in os.listdir('.')),
    19        )

`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.

     1import logging
     2import os
     3
     4from cliff.show import ShowOne
     5
     6
     7class File(ShowOne):
     8    "Show details about a file"
     9
    10    log = logging.getLogger(__name__)
    11
    12    def get_parser(self, prog_name):
    13        parser = super().get_parser(prog_name)
    14        parser.add_argument('filename', nargs='?', default='.')
    15        return parser
    16
    17    def take_action(self, parsed_args):
    18        stat_data = os.stat(parsed_args.filename)
    19        columns = (
    20            'Name',
    21            'Size',
    22            'UID',
    23            'GID',
    24            'Modified Time',
    25        )
    26        data = (
    27            parsed_args.filename,
    28            stat_data.st_size,
    29            stat_data.st_uid,
    30            stat_data.st_gid,
    31            stat_data.st_mtime,
    32        )
    33        return (columns, data)

`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.

     1#!/usr/bin/env python
     2
     3from setuptools import find_packages
     4from setuptools import setup
     5
     6PROJECT = 'cliffdemo'
     7
     8# Change docs/sphinx/conf.py too!
     9VERSION = '0.1'
    10
    11try:
    12    long_description = open('README.rst').read()
    13except OSError:
    14    long_description = ''
    15
    16setup(
    17    name=PROJECT,
    18    version=VERSION,
    19    description='Demo app for cliff',
    20    long_description=long_description,
    21    author='Doug Hellmann',
    22    author_email='doug.hellmann@gmail.com',
    23    url='https://github.com/openstack/cliff',
    24    download_url='https://github.com/openstack/cliff/tarball/master',
    25    classifiers=[
    26        'Development Status :: 3 - Alpha',
    27        'License :: OSI Approved :: Apache Software License',
    28        'Programming Language :: Python',
    29        'Programming Language :: Python :: 3',
    30        'Programming Language :: Python :: 3 :: Only',
    31        'Intended Audience :: Developers',
    32        'Environment :: Console',
    33    ],
    34    platforms=['Any'],
    35    scripts=[],
    36    provides=[],
    37    install_requires=['cliff'],
    38    namespace_packages=[],
    39    packages=find_packages(),
    40    include_package_data=True,
    41    entry_points={
    42        'console_scripts': ['cliffdemo = cliffdemo.main:main'],
    43        'cliff.demo': [
    44            'simple = cliffdemo.simple:Simple',
    45            'two_part = cliffdemo.simple:Simple',
    46            'error = cliffdemo.simple:Error',
    47            'list files = cliffdemo.list:Files',
    48            'files = cliffdemo.list:Files',
    49            'file = cliffdemo.show:File',
    50            'show file = cliffdemo.show:File',
    51            'unicode = cliffdemo.encoding:Encoding',
    52            'hooked = cliffdemo.hook:Hooked',
    53        ],
    54        'cliff.demo.hooked': [
    55            'sample-hook = cliffdemo.hook:Hook',
    56        ],
    57    },
    58    zip_safe=False,
    59)

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.

     1# All Rights Reserved.
     2#
     3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
     4#    not use this file except in compliance with the License. You may obtain
     5#    a copy of the License at
     6#
     7#         http://www.apache.org/licenses/LICENSE-2.0
     8#
     9#    Unless required by applicable law or agreed to in writing, software
    10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12#    License for the specific language governing permissions and limitations
    13#    under the License.
    14
    15import logging
    16
    17from cliff.command import Command
    18from cliff.hooks import CommandHook
    19
    20
    21class Hooked(Command):
    22    "A command to demonstrate how the hooks work"
    23
    24    log = logging.getLogger(__name__)
    25
    26    def take_action(self, parsed_args):
    27        self.app.stdout.write('this command has an extension\n')
    28
    29
    30class Hook(CommandHook):
    31    """Hook sample for the 'hooked' command.
    32
    33    This would normally be provided by a separate package from the
    34    main application, but is included in the demo app for simplicity.
    35
    36    """
    37
    38    def get_parser(self, parser):
    39        print('sample hook get_parser()')
    40        parser.add_argument('--added-by-hook')
    41        return parser
    42
    43    def get_epilog(self):
    44        return 'extension epilog text'
    45
    46    def before(self, parsed_args):
    47        self.cmd.app.stdout.write('before\n')
    48
    49    def after(self, parsed_args, return_code):
    50        self.cmd.app.stdout.write('after\n')

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

See also

`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.

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

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

### Output

#### Global Options

cliff demo app

    cliffdemo [--version] [-v | -q] [--log-file LOG_FILE] [--debug]

--version

  show program’s version number and exit

-v, --verbose

  Increase verbosity of output. Can be repeated.

-q, --quiet

  Suppress output except warnings and errors.

--log-file <LOG_FILE>

  Specify a file to log output. Disabled by default.

--debug

  Show tracebacks on errors.

#### Command Options

##### error

Always raises an error

    cliffdemo error

##### file

Show details about a file

    cliffdemo file
        [-f {json,shell,table,value,yaml}]
        [-c COLUMN]
        [--noindent]
        [--prefix PREFIX]
        [--max-width <integer>]
        [--fit-width]
        [--print-empty]
        [filename]

-f <FORMATTER>, --format <FORMATTER>

  the output format, defaults to table

-c COLUMN, --column COLUMN

  specify the column(s) to include, can be repeated to show multiple columns

--noindent

  whether to disable indenting the JSON

--prefix <PREFIX>

  add a prefix to all variable names

--max-width <integer>

  Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.

--fit-width

  Fit the table to the display width. Implied if –max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable

--print-empty

  Print empty table if there is no data to show.

filename

##### files

Show a list of files in the current directory.

The file name and size are printed by default.

    cliffdemo files
        [-f {csv,json,table,value,yaml}]
        [-c COLUMN]
        [--quote {all,minimal,none,nonnumeric}]
        [--noindent]
        [--max-width <integer>]
        [--fit-width]
        [--print-empty]
        [--sort-column SORT_COLUMN]
        [--sort-ascending
        |
        --sort-descending
        ]

-f <FORMATTER>, --format <FORMATTER>

  the output format, defaults to table

-c COLUMN, --column COLUMN

  specify the column(s) to include, can be repeated to show multiple columns

--quote <QUOTE_MODE>

  when to include quotes, defaults to nonnumeric

--noindent

  whether to disable indenting the JSON

--max-width <integer>

  Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.

--fit-width

  Fit the table to the display width. Implied if –max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable

--print-empty

  Print empty table if there is no data to show.

--sort-column SORT_COLUMN

  specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated

--sort-ascending

  sort the column(s) in ascending order

--sort-descending

  sort the column(s) in descending order

##### hooked

A command to demonstrate how the hooks work

    cliffdemo hooked

##### list files

Show a list of files in the current directory.

The file name and size are printed by default.

    cliffdemo list files
        [-f {csv,json,table,value,yaml}]
        [-c COLUMN]
        [--quote {all,minimal,none,nonnumeric}]
        [--noindent]
        [--max-width <integer>]
        [--fit-width]
        [--print-empty]
        [--sort-column SORT_COLUMN]
        [--sort-ascending | --sort-descending]

-f <FORMATTER>, --format <FORMATTER>

  the output format, defaults to table

-c COLUMN, --column COLUMN

  specify the column(s) to include, can be repeated to show multiple columns

--quote <QUOTE_MODE>

  when to include quotes, defaults to nonnumeric

--noindent

  whether to disable indenting the JSON

--max-width <integer>

  Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.

--fit-width

  Fit the table to the display width. Implied if –max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable

--print-empty

  Print empty table if there is no data to show.

--sort-column SORT_COLUMN

  specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated

--sort-ascending

  sort the column(s) in ascending order

--sort-descending

  sort the column(s) in descending order

##### show file

Show details about a file

    cliffdemo show file
        [-f {json,shell,table,value,yaml}]
        [-c COLUMN]
        [--noindent]
        [--prefix PREFIX]
        [--max-width <integer>]
        [--fit-width]
        [--print-empty]
        [filename]

-f <FORMATTER>, --format <FORMATTER>

  the output format, defaults to table

-c COLUMN, --column COLUMN

  specify the column(s) to include, can be repeated to show multiple columns

--noindent

  whether to disable indenting the JSON

--prefix <PREFIX>

  add a prefix to all variable names

--max-width <integer>

  Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.

--fit-width

  Fit the table to the display width. Implied if –max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable

--print-empty

  Print empty table if there is no data to show.

filename

##### simple

A simple command that prints a message.

    cliffdemo simple

##### two part

A simple command that prints a message.

    cliffdemo two part

##### unicode

Show some unicode text

    cliffdemo unicode
        [-f {csv,json,table,value,yaml}]
        [-c COLUMN]
        [--quote {all,minimal,none,nonnumeric}]
        [--noindent]
        [--max-width <integer>]
        [--fit-width]
        [--print-empty]
        [--sort-column SORT_COLUMN]
        [--sort-ascending
        |
        --sort-descending
        ]

-f <FORMATTER>, --format <FORMATTER>

  the output format, defaults to table

-c COLUMN, --column COLUMN

  specify the column(s) to include, can be repeated to show multiple columns

--quote <QUOTE_MODE>

  when to include quotes, defaults to nonnumeric

--noindent

  whether to disable indenting the JSON

--max-width <integer>

  Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.

--fit-width

  Fit the table to the display width. Implied if –max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable

--print-empty

  Print empty table if there is no data to show.

--sort-column SORT_COLUMN

  specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated

--sort-ascending

  sort the column(s) in ascending order

--sort-descending

  sort the column(s) in descending order

this page last updated: 2026-02-20 17:00:00

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.

found an error? report a bug