CLIFF(1)

๐Ÿ“ฆ
version 4.17.0 ยท 4 versions
, in nixpkgs unstable

๐Ÿ’– CLIFF(1) โ€” the cliff manual, bestie edition ๐Ÿ’–

๐Ÿ“› NAME ๐Ÿ“›

cliff โ€” cliff Documentation. Thatโ€™s the official name, bestie, and yes, this whole page is the cliff Documentation.

cliff is a framework for building command line programs. It uses plugins to define sub-commands, output formatters, and other extensions. Periodt. ๐Ÿ’…

๐Ÿ› ๏ธ INSTALLATION ๐Ÿ› ๏ธ

๐Ÿ Python Versions ๐Ÿ

cliff is being developed under Python 3. Thatโ€™s the vibe.

๐Ÿ“ฆ Basic Installation ๐Ÿ“ฆ

cliff should be installed into the same site-packages area where the application and extensions are installed โ€” either a virtualenv or the global site-packages. You may need administrative privileges to do that. The easiest way to install it is using pip โ€” the link is literally <http://pypi.python.org/pypi/pip>. For example:

$ pip install cliff

๐ŸŒ Source Code ๐ŸŒ

The source is hosted on OpenDev: <https://opendev.org/openstack/cliff>.

๐Ÿž Reporting Bugs ๐Ÿž

Please report bugs through the storyboard: <https://storyboard.openstack.org/#!/project/openstack/cliff>.

๐ŸŒˆ USING CLIFF ๐ŸŒˆ

๐Ÿ‘‹ Introduction ๐Ÿ‘‹

The cliff framework is meant to be used to create multi-level commands such as subversion and git, where the main program handles some basic argument parsing and then invokes a sub-command to do the work. Iconic behavior, honestly. โœจ

๐Ÿ”Œ Command Plugins ๐Ÿ”Œ

cliff takes advantage of Pythonโ€™s ability to load code dynamically to allow the sub-commands of a main program to be implemented, packaged, and distributed separately from the main program. This organization provides a unified view of the command for users, while giving developers the opportunity to organize source code in any way they see fit. Slay for modularity. ๐Ÿงฉ

๐Ÿงฉ Cliff Objects ๐Ÿงฉ

cliff is organized around five objects that are combined to create a useful command line program.

๐Ÿš€ The Application ๐Ÿš€

A cliff.app.App is the main program that you run from the shell command prompt. It is responsible for global operations that apply to all of the commands, such as configuring logging and setting up I/O streams. Main character energy, lowkey. ๐Ÿ‘‘

๐Ÿ—‚๏ธ The CommandManager ๐Ÿ—‚๏ธ

The cliff.commandmanager.CommandManager knows how to load individual command plugins. The default implementation uses entry points โ€” <https://packaging.python.org/specifications/entry-points/> โ€” but any mechanism for loading commands can be used by replacing the default CommandManager when instantiating an App.

โšก The Command โšก

The cliff.command.Command class is where the real work happens. The rest of the framework is present to help the user discover the command plugins and invoke them, and to provide runtime support for those plugins. Each Command subclass is responsible for taking action based on instructions from the user. It defines its own local argument parser (usually using argparse โ€” <http://docs.python.org/library/argparse.html>) and a take_action() method that does the appropriate work.

๐Ÿช The CommandHook ๐Ÿช

The cliff.hooks.CommandHook class can extend a Command by modifying the command line arguments available, for example to add options used by a driver. Each CommandHook subclass must implement the full hook API, defined by the base class. Extensions should be registered using an entry point namespace based on the application namespace and the command name:

application_namespace + '.' + command_name.replace(' ', '_')

๐Ÿ’ฌ The Interactive Application ๐Ÿ’ฌ

The main program uses a cliff.interactive.InteractiveApp instance to provide a command-shell mode in which the user can type multiple commands before the program exits. Many cliff-based applications will be able to use the default implementation of InteractiveApp without subclassing it. Cute and convenient. ๐ŸŽ€

๐ŸŽฎ 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:

import sys

from cliff.app import App
from cliff.commandmanager import CommandManager

class DemoApp(App):
    def __init__(self):
        super().__init__(
            description='cliff demo app',
            version='0.1',
            command_manager=CommandManager('cliff.demo'),
            deferred_help=True,
        )

    def initialize_app(self, argv):
        self.LOG.debug('initialize_app')

    def prepare_to_run_command(self, cmd):
        self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)

    def clean_up(self, cmd, result, err):
        self.LOG.debug('clean_up %s', cmd.__class__.__name__)
        if err:
            self.LOG.debug('got an error: %s', err)

def main(argv=sys.argv[1:]):
    myapp = DemoApp()
    return myapp.run(argv)

if __name__ == '__main__':
    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:

import logging

from cliff.command import Command

class Simple(Command):
    "A simple command that prints a message."

    log = logging.getLogger(__name__)

    def take_action(self, parsed_args):
        self.log.info('sending greeting')
        self.log.debug('debugging')
        self.app.stdout.write('hi!\n')

class Error(Command):
    "Always raises an error"

    log = logging.getLogger(__name__)

    def take_action(self, parsed_args):
        self.log.info('causing error')
        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.

import logging
import os

from cliff.lister import Lister

class Files(Lister):
    """Show a list of files in the current directory.

    The file name and size are printed by default.
    """

    log = logging.getLogger(__name__)

    def take_action(self, parsed_args):
        return (
            ('Name', 'Size'),
            ((n, os.stat(n).st_size) for n in os.listdir('.')),
        )

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

...and here's what that actually looks like when you run it, bestie ๐Ÿ’–

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

and then the same command with the formatter switched up ๐Ÿฆ‹

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

๐Ÿ show.py

show.py has exactly one command in it, and it's a ShowOne subclass <#cliff.show.ShowOne> โ€” so like, it prints out the properties of whatever file you point it at. iconic behaviour honestly โœจ

import logging
import os

from cliff.show import ShowOne

class File(ShowOne):
    "Show details about a file"

    log = logging.getLogger(__name__)

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument('filename', nargs='?', default='.')
        return parser

    def take_action(self, parsed_args):
        stat_data = os.stat(parsed_args.filename)
        columns = (
            'Name',
            'Size',
            'UID',
            'GID',
            'Modified Time',
        )
        data = (
            parsed_args.filename,
            stat_data.st_size,
            stat_data.st_uid,
            stat_data.st_gid,
            stat_data.st_mtime,
        )
        return (columns, data)

so File does the actual data-gathering (that's its whole job, no cap), and ShowOne is the one handling the output formatter and printing everything to the console โ€” teamwork, we love to see it ๐Ÿซถ

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

๐Ÿ“ฆ setup.py

the demo app gets packaged with setuptools, like so:

#!/usr/bin/env python

from setuptools import find_packages
from setuptools import setup

PROJECT = 'cliffdemo'

# Change docs/sphinx/conf.py too!
VERSION = '0.1'

try:
    long_description = open('README.rst').read()
except OSError:
    long_description = ''

setup(
    name=PROJECT,
    version=VERSION,
    description='Demo app for cliff',
    long_description=long_description,
    author='Doug Hellmann',
    author_email='doug.hellmann@gmail.com',
    url='https://github.com/openstack/cliff',
    download_url='https://github.com/openstack/cliff/tarball/master',
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3 :: Only',
        'Intended Audience :: Developers',
        'Environment :: Console',
    ],
    platforms=['Any'],
    scripts=[],
    provides=[],
    install_requires=['cliff'],
    namespace_packages=[],
    packages=find_packages(),
    include_package_data=True,
    entry_points={
        'console_scripts': ['cliffdemo = cliffdemo.main:main'],
        'cliff.demo': [
            'simple = cliffdemo.simple:Simple',
            'two_part = cliffdemo.simple:Simple',
            'error = cliffdemo.simple:Error',
            'list files = cliffdemo.list:Files',
            'files = cliffdemo.list:Files',
            'file = cliffdemo.show:File',
            'show file = cliffdemo.show:File',
            'unicode = cliffdemo.encoding:Encoding',
            'hooked = cliffdemo.hook:Hooked',
        ],
        'cliff.demo.hooked': [
            'sample-hook = cliffdemo.hook:Hook',
        ],
    },
    zip_safe=False,
)

okay so the part that actually matters here (the main character, truly) is the entry_points settings ๐Ÿ‘‘ every single command gets registered in the cliff.demo namespace. and here's the rule, bestie, write it down: each main program should define its own command namespace so it only ever loads the command plugins it's actually supposed to be managing. no crossover, no chaos ๐Ÿ’…

๐Ÿช Command Extension Hooks

plot twist: individual subcommands of an app can be extended through hooks that are registered as separate plugins. in the demo app, the hooked command has exactly one extension registered. cute ๐ŸŒธ

the namespace for hooks is a combo of the application namespace plus the command name. here that means the application namespace is cliff.demo and the command is hooked, so the extension namespace comes out as cliff.demo.hooked. and if the subcommand name has spaces in it? those get swapped for underscores ("_") when building the namespace. iykyk ๐Ÿงš

# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import logging

from cliff.command import Command
from cliff.hooks import CommandHook

class Hooked(Command):
    "A command to demonstrate how the hooks work"

    log = logging.getLogger(__name__)

    def take_action(self, parsed_args):
        self.app.stdout.write('this command has an extension\n')

class Hook(CommandHook):
    """Hook sample for the 'hooked' command.

    This would normally be provided by a separate package from the
    main application, but is included in the demo app for simplicity.

    """

    def get_parser(self, parser):
        print('sample hook get_parser()')
        parser.add_argument('--added-by-hook')
        return parser

    def get_epilog(self):
        return 'extension epilog text'

    def before(self, parsed_args):
        self.cmd.app.stdout.write('before\n')

    def after(self, parsed_args, return_code):
        self.cmd.app.stdout.write('after\n')

so even though the hooked command itself doesn't add any arguments to the parser it creates, the help output proves the extension sneaks in a single --added-by-hook option. the way that works >>> ๐Ÿ˜ญ

(.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 <#cliff.hooks.CommandHook> โ€” the API for command hooks ๐Ÿ’œ

๐Ÿ“š Autogenerated Documentation

the following docs are generated using this 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, in all its glory:

cliffdemo [--version] [-v | -q] [--log-file LOG_FILE] [--debug]
  • --version โ€” show the program's version number and exit โœŒ๏ธ
  • -v, --verbose โ€” crank up the verbosity of the output. you can repeat it, so go off, stack 'em ๐Ÿ”ฅ
  • -q, --quiet โ€” suppress output except warnings and errors. shhh ๐Ÿคซ
  • --log-file <LOG_FILE> โ€” point at a file to log output into. disabled by default, so you gotta opt in ๐Ÿ’…
  • --debug โ€” show tracebacks when errors happen. super helpful when things go sideways ngl ๐Ÿ˜ญ

โš™๏ธ Command Options

๐Ÿ’€ error

always raises an error. that's it. that's the command. it's giving chaos on purpose ๐Ÿ˜ญ

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 which column(s) to include. repeat it to show multiple columns ๐Ÿ‘‘
  • --noindent โ€” whether to disable indenting the JSON ๐Ÿฆ‹
  • --prefix <PREFIX> โ€” add a prefix to all the variable names โœจ
  • --max-width <integer> โ€” maximum display width, <1 to disable. you can also use the CLIFF_MAX_TERM_WIDTH environment variable, but heads up: the parameter takes precedence ๐Ÿ’…
  • --fit-width โ€” fit the table to the display width. it's implied if --max-width is greater than 0. set the environment variable CLIFF_FIT_WIDTH=1 to always enable it ๐ŸŒˆ
  • --print-empty โ€” print an empty table if there's no data to show ๐Ÿซถ
  • filename โ€” the file you're asking about ๐Ÿ“„

๐Ÿ“ files

show a list of files in the current directory. by default the file name and size are what gets printed ๐Ÿ“

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 which column(s) to include. repeat it 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. it's implied if --max-width is greater than 0. set the environment variable CLIFF_FIT_WIDTH=1 to always enable it ๐ŸŒˆ
  • --print-empty โ€” print an empty table if there's no data to show ๐Ÿซถ
  • --sort-column SORT_COLUMN โ€” tells it which column(s) to sort by. The ones you list first get priority, columns that don't even exist are just like... ignored bestie, no drama. Names are case-insensitive and you can use underscores instead of spaces. You can repeat it as many times as you want ๐Ÿ’…
  • --sort-ascending โ€” sorts the column(s) ascending (little โ†’ big, A โ†’ Z, that whole vibe).
  • --sort-descending โ€” sorts the column(s) descending. Iconic reversal behavior.

๐Ÿช hooked

A command to demonstrate how the hooks work ๐ŸŽ€

cliffdemo hooked

๐Ÿ“‚ list files

Shows you a list of files in the current directory. By default it prints the file name and the size โ€” that's it, that's the whole flex โœจ

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 โ€” picks the column(s) to include. Repeat it to show multiple columns, go off ๐Ÿ‘‘
  • --quote <QUOTE_MODE> โ€” when to include quotes, defaults to nonnumeric.
  • --noindent โ€” whether to disable indenting the JSON (yes, the flag name and the description are lowkey opposites, we don't ask questions).
  • --max-width <integer> โ€” maximum display width. Anything <1 disables it. You can also set the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence, so the flag wins the argument fr fr.
  • --fit-width โ€” fits the table to the display width. It's implied if --max-width is greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable it.
  • --print-empty โ€” prints an empty table if there's no data to show. No sad empty terminal energy ๐Ÿซถ
  • --sort-column SORT_COLUMN โ€” which column(s) to sort the data by. Columns specified first have priority, non-existing columns are ignored, names are case-insensitive and accept underscores for spaces, and you can repeat it.
  • --sort-ascending โ€” sort the column(s) in ascending order.
  • --sort-descending โ€” sort the column(s) in descending order.

๐Ÿ” show file

Shows you details about a file. Detective mode: activated ๐Ÿ•ต๏ธโ€โ™€๏ธ

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 โ€” the column(s) to include, repeatable so you can show multiple columns.
  • --noindent โ€” whether to disable indenting the JSON.
  • --prefix <PREFIX> โ€” adds a prefix to all variable names. Very demure, very mindful naming ๐Ÿ’–
  • --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 is greater than 0. Set CLIFF_FIT_WIDTH=1 to always enable.
  • --print-empty โ€” print an empty table if there's no data to show.
  • filename โ€” yeah, the file. No description given, it's giving mysterious main character energy ๐Ÿ’€

๐Ÿ’ฌ simple

A simple command that prints a message. That's literally the whole description and honestly? Respect the minimalism โœจ

cliffdemo simple

โœŒ๏ธ two part

A simple command that prints a message. (Yes, same description as simple โ€” that's not a typo, that's canon.)

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 โ€” 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 is 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 โ€” the column(s) to sort the data by โ€” columns specified first have priority, non-existing columns are ignored, names are case-insensitive and accept underscores for spaces, and it's repeatable.
  • --sort-ascending โ€” sort the column(s) in ascending order.
  • --sort-descending โ€” sort the column(s) in descending order.

๐Ÿ“‹ List Commands

Okay so one of the most common patterns with command line programs is that you need to print lists of data. And cliff is like, "I got you bestie" โ€” it provides a base class for commands of this type so they only have to prepare the data, and then the user gets to choose from one of several output formatter plugins to see the list of data in their preferred format. Slay. ๐ŸŽ€

๐Ÿงš Lister

The cliff.lister.Lister <#cliff.lister.Lister> base class API extends Command to allow take_action() to return data to be formatted using a user-selectable formatter. Subclasses should provide a take_action() implementation that returns a two member tuple containing a tuple with the names of the columns in the dataset, and an iterable that will yield the data to be output. See the description of the files command in the demoapp <#demoapp-list> for the details. ๐Ÿ’–

๐ŸŽจ List Output Formatters

cliff ships with two output formatters for list commands. Lister adds a command line switch so the user can specify which formatter they want โ€” so you don't have to do any extra work in your application. Periodt, that's the whole point ๐Ÿ‘‘

csv

The csv formatter produces a comma-separated-values document as output. CSV data can be imported into a database or spreadsheet for further manipulation. It's giving "I'm data and I'm ready for a glow-up" ๐Ÿ“

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

table

The table formatter uses PrettyTable <https://pypi.org/project/prettytable/> to produce output formatted for human consumption. (Humans only. Sorry bots.) ๐Ÿ’…

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

value

The value formatter produces a space separated output with no headers. Clean, minimal, no notes โœจ

(.venv)$ cliffdemo files -f value
build 136
cliffdemo.log 2690
Makefile 5569
source 408

This format can be very convenient when you want to pipe the output to a script. Like, this is the whole reason it exists:

(.venv)$ cliffdemo files -f value | while read NAME SIZE
do
  echo $NAME is $SIZE bytes
done
build is 136 bytes
cliffdemo.log is 2690 bytes
Makefile is 5569 bytes
source is 408 bytes

yaml

The yaml formatter uses PyYAML <http://pyyaml.org/> to produce a YAML sequence of mappings. Indentation girlies, this one's for you ๐Ÿงš

(.venv)$ cliffdemo files -f yaml
- Name: dist
  Size: 4096
- Name: cliffdemo.egg-info
  Size: 4096
- Name: README.rst
  Size: 960
- Name: setup.py
  Size: 1807
- Name: build
  Size: 4096
- Name: cliffdemo
  Size: 4096

json

The json formatter produces an array of objects in indented JSON format. Curly braces and vibes ๐Ÿฆ‹

(.venv)$ cliffdemo files -f json
[
  {
    "Name": "source",
    "Size": 4096
  },
  {
    "Name": "Makefile",
    "Size": 5569
  },
  {
    "Name": "build",
    "Size": 4096
  }
]

๐Ÿงฐ Other Formatters

A formatter using tablib <https://github.com/kennethreitz/tablib> to produce HTML is available as part of cliff-tablib <https://github.com/dreamhost/cliff-tablib>. HTML representation, we love to see it ๐ŸŒธ

๐Ÿ› ๏ธ Creating Your Own Formatter

If the standard formatters do not meet your needs, you can bundle another formatter with your program by subclassing from cliff.formatters.base.ListFormatter <#cliff.formatters.base.ListFormatter> and registering the plugin in the cliff.formatter.list namespace. Build your own, bestie, I believe in you ๐Ÿซถ


๐Ÿ”Ž Show Commands

One of the most common patterns with command line programs is needing to print properties of objects. cliff provides a base class for commands of this type so they only need to prepare the data, and the user can choose from one of several output formatter plugins to see the data in their preferred format. Same energy as the list situation, different vibe ๐Ÿ’œ

๐Ÿ‘‘ ShowOne

The cliff.show.ShowOne <#cliff.show.ShowOne> base class API extends Command to allow take_action() to return data to be formatted using a user-selectable formatter. Subclasses should provide a take_action() implementation that returns a two member tuple containing a tuple with the names of the columns in the dataset, and an iterable that contains the data values associated with those names. See the description of the file command in the demoapp <#demoapp-show> for details. โœจ

๐ŸŒŸ ShowOneWithDetails

The cliff.show.ShowOneWithDetails <#cliff.show.ShowOneWithDetails> base class displays a single object's fields with additional columns โ€” things like descriptions, types, or sources. Unlike ShowOne <#cliff.show.ShowOne>, it does not limit table output to Field and Value columns. It said "expand the table, we have room" ๐Ÿ˜ค

Subclasses implement take_action() <#cliff.show.ShowOneWithDetails.take_action> to return a two-member tuple containing:

  • A sequence of output column names.
  • An iterable of rows, each containing values in output column order. The first value in each row must be a string containing the resource's field name.

For example:

from cliff import show

class ShowResource(show.ShowOneWithDetails):
    def take_action(self, parsed_args):
        return (
            ('Field', 'Value', 'Description'),
            (
                ('name', 'demo', 'Resource name'),
                ('status', 'ACTIVE', 'Current resource status'),
            ),
        )

As with ShowOne, -c / --column selects resource fields (rows), not output columns. Selection is case-insensitive, ignores surrounding whitespace, and accepts underscores in place of spaces. Selected rows retain their original order and all their output columns. Unrecognized field names are ignored if at least one requested field is recognized; otherwise, selection raises an error. So like... pick a real one or the whole thing throws hands ๐Ÿ˜ญ

okay so, for example, resource show -c status would give you this little table moment:

+--------+--------+-------------------------+
| Field  | Value  | Description             |
+--------+--------+-------------------------+
| status | ACTIVE | Current resource status |
+--------+--------+-------------------------+

ShowOneWithDetails uses the cliff.formatter.list plugins we talked about in the List Commands <> section, with table as the default. So JSON and YAML output contain a list of row mappings โ€” not the single field-to-value mapping that ShowOne produces. Like, resource show -f json -c status would give you:

[
  {
    "Field": "status",
    "Value": "ACTIVE",
    "Description": "Current resource status"
  }
]

CSV and value output also include every output column of each selected row. The show-only shell formatter and Lister sorting options are not available. not available, bestie, don't go looking for them ๐Ÿ’…

โœจ Show Output Formatters โœจ

cliff is delivered with output formatters for show commands. ShowOne adds a command line switch so the user can pick the formatter they want โ€” so you don't have to do any extra work in your application. iconic ๐ŸŽ€

๐ŸŽ€ table ๐ŸŽ€

The table formatter uses PrettyTable (https://pypi.org/project/prettytable) to produce output formatted for human consumption. This is the default formatter.

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

๐Ÿฆ‹ shell ๐Ÿฆ‹

The shell formatter produces output that can be parsed directly by a typical UNIX shell as variable assignments. This avoids extra parsing overhead in shell scripts.

(.venv)$ cliffdemo file -f shell setup.py
name="setup.py"
size="5916"
uid="527"
gid="501"
modified_time="1335655655.0"

(.venv)$ eval "$(cliffdemo file -f shell --prefix example_ setup.py)"
(.venv)$ echo $example_size
5916

๐ŸŒธ value ๐ŸŒธ

The value formatter produces output that only contains the value of the field or fields.

(.venv)$ cliffdemo file -f value -c Size setup.py
5916
(.venv)$ SIZE="$(cliffdemo file -f value -c Size setup.py)"
(.venv)$ echo $SIZE
5916

๐Ÿงš yaml ๐Ÿงš

The yaml formatter uses PyYAML (http://pyyaml.org/) to produce a YAML mapping where the field name is the key.

(.venv)$ cliffdemo file -f yaml setup.py
Name: setup.py
Size: 1807
UID: 1000
GID: 1000
Modified Time: 1393531476.9587486

๐Ÿ’œ json ๐Ÿ’œ

The json formatter produces a JSON object where the field name is the key.

(.venv)$ cliffdemo file -f json setup.py
{
  "Modified Time": 1438726433.8055942,
  "GID": 1000,
  "UID": 1000,
  "Name": "setup.py",
  "Size": 1028
}

๐ŸŒˆ Other Formatters ๐ŸŒˆ

A formatter using tablib (https://github.com/kennethreitz/tablib) to produce HTML is available as part of cliff-tablib (https://github.com/dreamhost/cliff-tablib). slay, options for days โœจ

๐Ÿ’… Creating Your Own Formatter ๐Ÿ’…

If the standard formatters do not meet your needs, you can bundle another formatter with your program by subclassing from cliff.formatters.base.ShowFormatter and registering the plugin in the cliff.formatter.show namespace. build your own icon, bestie ๐Ÿ‘‘

๐Ÿซถ Command Completion ๐Ÿซถ

A generic command completion command is available to generate a bash-completion script. Currently, the command will generate a script for bash versions 3 or 4. There is also a mode that generates only data that can be used in your own script. The command completion script is generated based on the commands and options that you have specified in cliff.

โญ Usage โญ

In order for your command to support command completions, you need to add the cliff.complete.CompleteCommand class to your command manager.

self.command_manager.add_command('complete', cliff.complete.CompleteCommand)

When you run the command, it will generate a bash-completion script:

(.venv)$ mycmd complete
_mycmd()
{
  local cur prev words
  COMPREPLY=()
  _get_comp_words_by_ref -n : cur prev words

  # Command data:
  cmds='agent aggregate backup'
  cmds_agent='--name'
...
  if [ -z "${completed}" ] ; then
    COMPREPLY=( $( compgen -f -- "$cur" ) $( compgen -d -- "$cur" ) )
  else
    COMPREPLY=( $(compgen -W "${completed}" -- ${cur}) )
  fi
  return 0
}
complete -F _mycmd mycmd

๐Ÿ‘‘ Interactive Mode ๐Ÿ‘‘

In addition to running single commands from the command line, cliff supports an interactive mode in which the user is presented with a separate command shell. All of the command plugins available from the command line are automatically configured as commands within the shell.

Refer to the cmd2 (http://packages.python.org/cmd2/index.html) documentation for more details about features of the shell.

๐Ÿ“ Example ๐Ÿ“

The cliffdemo application enters interactive mode if no command is specified on the command line.

(.venv)$ cliffdemo
(cliffdemo) help

Shell commands (type help <topic>):
===================================
cmdenvironment  edit  hi       l   list  pause  r    save  shell      show
ed              help  history  li  load  py     run  set   shortcuts

Undocumented commands:
======================
EOF  eof  exit  q  quit

Application commands (type help <topic>):
=========================================
files  help  simple  file  error  two part

To obtain instructions for a built-in or application command, use the help command:

(cliffdemo) help simple
usage: simple [-h]

A simple command that prints a message.

optional arguments:
  -h, --help  Show help message and exit.

The commands can be run, including options and arguments, as on the regular command line:

(cliffdemo) simple
sending greeting
hi!
(cliffdemo) files
+----------------------+-------+
|         Name         |  Size |
+----------------------+-------+
| .git                 |   578 |
| .gitignore           |   268 |
| .tox                 |   238 |
| .venv                |   204 |
| announce.rst         |  1015 |
| announce.rst~        |   708 |
| cliff                |   884 |
| cliff.egg-info       |   340 |
| cliffdemo.log        |  2193 |
| cliffdemo.log.1      | 10225 |
| demoapp              |   408 |
| dist                 |   136 |
| distribute_setup.py  | 15285 |
| distribute_setup.pyc | 15196 |
| docs                 |   238 |
| LICENSE              | 11358 |
| Makefile             |   376 |
| Makefile~            |    94 |
| MANIFEST.in          |   186 |
| MANIFEST.in~         |   344 |
| README.rst           |  1063 |
| setup.py             |  5855 |
| setup.py~            |  8128 |
| tests                |   204 |
| tox.ini              |    76 |
| tox.ini~             |   421 |
+----------------------+-------+
(cliffdemo)

๐Ÿ”ฅ Sphinx Integration ๐Ÿ”ฅ

๐ŸŒŸ Usage ๐ŸŒŸ

cliff supports integration with Sphinx by way of a Sphinx directives (http://www.sphinx-doc.org/en/stable/extdev/markupapi.html). iykyk, docs bestie ๐Ÿซถ

๐ŸŽ€ Preparation ๐ŸŽ€

Before using the autoprogram-cliff directive you must add 'cliff.sphinxext' extension module to a list of extensions in conf.py:

extensions = ['cliff.sphinxext']

๐Ÿ’ซ Directive ๐Ÿ’ซ

.. autoprogram-cliff:: <namespace> or <app class> โ€” automatically document an instance of cliff.command.Command <#cliff.command.Command> or cliff.app.App <#cliff.app.App> including a description, usage summary, and overview of all options.

Important: There are two modes in this directive: command mode and app mode. The directive takes one required argument and the mode is determined based on the argument specified.

The command mode documents various information of a specified instance of cliff.command.Command <#cliff.command.Command>. The command mode takes the namespace that the command(s) can be found in as the argument. This is generally defined in the entry_points section of either setup.cfg or setup.py. You can specify which command(s) should be displayed using :command: option.

.. autoprogram-cliff:: openstack.compute.v2
   :command: server add fixed ip

The app mode documents various information of a specified instance of cliff.app.App <#cliff.app.App>. The app mode takes the python path of the corresponding class as the argument. In the app mode, :application: option is usually specified so that the command name is shown in the rendered output.

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

Refer to the example below for more information.

In addition, the following directive options can be supplied:

  • :command: โ€” The name of the command, as it would appear if called from the command line without the executable name. This will be defined in setup.cfg or setup.py albeit with underscores. This is optional and fnmatch-style (https://docs.python.org/3/library/fnmatch.html) wildcarding is supported. Refer to the example below for more information.

This option is effective only in the command mode.

  • :arguments โ€” The arguments to be passed when the cliff application is instantiated. Some cliff applications requires arguments when instantiated. This option can be used to specify such arguments.

This option is effective only in the app mode.

  • :application: โ€” The top-level application name, which will be prefixed before all commands. This option overrides the global option autoprogram_cliff_application described below. In most cases the global configuration is enough, but this option is useful if your sphinx document handles multiple cliff applications.
See also: The autoprogram_cliff_application configuration option. ๐Ÿซถ
  • :ignored: โ€” a comma-separated list of options to exclude from the docs for this option. It's for options that are, like, low value, bestie โ€” no need to give every tiny thing main-character billing. Also see the autoprogram_cliff_ignored configuration option.

Okay so the following global configuration values are supported, and they go in conf.py:

  • autoprogram_cliff_application โ€” the top-level application name, which gets prefixed before all commands. This is generally defined in the console_scripts attribute of the entry_points section of either setup.cfg or setup.py. Peep the example below for more info:
autoprogram_cliff_application = 'my-sample-application'

Defaults to ''.

See also: the :command: directive option. See also: the :application: directive option.
  • autoprogram_cliff_ignored โ€” a global list of options to exclude from documentation. Use this to stop duplicating common options, like the pagination ones, across all options. For example:
autoprogram_cliff_ignored = ['--help', '--page', '--order']

Defaults to ['--help'].

See also: the :ignored: directive option.
  • autoprogram_cliff_app_dist_name โ€” the name of the python distribution (the name used with pip, as opposed to the package name used for importing) that provides the commands/applications being documented. Generated documentation for plugin components includes a message indicating the name of the plugin. Setting this option tells cliff the name of the distribution providing components natively, so their documentation does not include this message.
See also: - Module sphinxcontrib.autoprogram โ€” an equivalent library for use with plain-old argparse applications. - Module sphinx-click โ€” an equivalent library for use with click applications.
Important: the autoprogram-cliff directive emits code-block snippets marked up as shell code. This requires pygments >= 0.6.

Examples ๐Ÿ’–

Simple Example (demoapp) ๐ŸŽ€

cliff provides a sample application, Exploring the Demo App <>, to demonstrate some of the features of cliff. This application is documented <#demoapp-sphinx> using the cliff.sphinxext Sphinx extension. Cute, right?

Advanced Example (python-openstackclient) ๐Ÿ”ฅ

It is also possible to document larger applications, such as python-openstackclient (https://docs.openstack.org/python-openstackclient/). Take a sample setup.cfg file, which is a minimal version of the setup.cfg provided by the python-openstackclient project:

[entry_points]
console_scripts =
    openstack = openstackclient.shell:main

openstack.compute.v2 =
    host_list = openstackclient.compute.v2.host:ListHost
    host_set = openstackclient.compute.v2.host:SetHost
    host_show = openstackclient.compute.v2.host:ShowHost

This will register three commands โ€” host list, host set and host show โ€” for a top-level executable called openstack. To document the first of these, add the following:

.. autoprogram-cliff:: openstack.compute.v2
   :command: host list

You could also register all of these at once like so:

.. autoprogram-cliff:: openstack.compute.v2
   :command: host *

Finally, if these are the only commands available in that namespace, you can omit the :command: parameter entirely:

.. autoprogram-cliff:: openstack.compute.v2

In all cases, you should add the following to your conf.py to ensure all usage examples show the full command name:

autoprogram_cliff_application = 'openstack'

CLIFF CLASS REFERENCE ๐Ÿ’…

Application ๐Ÿงš

App ๐Ÿ‘‘

class cliff.app.App(description: str | None, version: str | None,
command_manager: _commandmanager.CommandManager, stdin: TextIO | None =
None, stdout: TextIO | None = None, stderr: TextIO | None = None,
interactive_app_factory: type[_interactive.InteractiveApp] | None =
None, deferred_help: bool = False)

Application base class. Iconic behavior only.

Parameters

  • description (str) โ€” one-liner explaining the program purpose.
  • version (str) โ€” application version number.
  • command_manager (cliff.commandmanager.CommandManager <#cliff.commandmanager.CommandManager>) โ€” plugin loader.
  • stdin (readable I/O stream) โ€” Standard input stream.
  • stdout (writable I/O stream) โ€” Standard output stream.
  • stderr (writable I/O stream) โ€” Standard error output stream.
  • interactive_app_factory (cliff.interactive.InteractiveApp <#cliff.interactive.InteractiveApp>) โ€” callable to create an interactive application.
  • deferred_help (bool) โ€” True โ€” Allow subcommands to accept --help with allowing to defer help print after initialize_app.
build_option_parser(description: str | None, version: str | None,
argparse_kwargs: dict[str, Any] | None = None) -> ArgumentParser

Return an argparse option parser for this application. Subclasses may override this method to extend the parser with more global options.

Parameters

  • description (str) โ€” full description of the application.
  • version (str) โ€” version number for the application.
  • argparse_kwargs โ€” extra keyword argument passed to the ArgumentParser constructor.
clean_up(cmd: _command.Command, result: int, err: BaseException |
None) -> None

Hook run after a command is done to shutdown the app.

Parameters

  • cmd (cliff.command.Command <#cliff.command.Command>) โ€” command processor being invoked.
  • result (int) โ€” return value of cmd.
  • err (Exception) โ€” exception or None.
configure_logging() -> None

Create logging handlers for any log output.

get_fuzzy_matches(cmd: str) -> list[str]

return fuzzy matches of unknown command.

initialize_app(argv: list[str]) -> None

Hook for subclasses to take global initialization action after the arguments are parsed but before a command is run. Invoked only once, even in interactive mode.

Parameters

  • argv โ€” List of arguments, including the subcommand to run. Empty for interactive mode.
prepare_to_run_command(cmd: _command.Command) -> None

Perform any preliminary work needed to run a command.

Parameters

  • cmd (cliff.command.Command <#cliff.command.Command>) โ€” command processor being invoked.
print_help_if_requested() -> None

Print help and exits if deferred help is enabled and requested.

'--help' shows the help message and exits:

  • without calling initialize_app if not self.deferred_help (default),
  • after initialize_app call if self.deferred_help,
  • during initialize_app call if self.deferred_help and subclass calls explicitly this method in initialize_app.
run(argv: list[str]) -> int

Equivalent to the main program for the application.

Parameters

  • argv (list of str) โ€” input arguments and options.

InteractiveApp ๐ŸŒˆ

class cliff.interactive.InteractiveApp(parent_app: _app.App,
command_manager: _commandmanager.CommandManager, stdin: TextIO | None,
stdout: TextIO | None, errexit: bool = False)

Provides "interactive mode" features.

Refer to the cmd2 (https://cmd2.readthedocs.io/en/latest/) and cmd (http://docs.python.org/library/cmd.html) documentation for details about subclassing and configuring this class.

Parameters

  • parent_app โ€” The calling application (expected to be derived from cliff.app.App).
  • command_manager โ€” A cliff.commandmanager.CommandManager instance.
  • stdin โ€” Standard input stream.
  • stdout โ€” Standard output stream.
cmdloop(intro: str | None = None) -> None

Deal with extra features provided by cmd2, this is an outer wrapper around _cmdloop().

_cmdloop() provides the main loop. This provides the following extra features provided by cmd2:

  • intro banner
  • exit code

Parameters

  • intro โ€” if provided this overrides self.intro and serves as the intro banner printed once at start.

Returns

exit code

completedefault(text: str, line: str, begidx: int, endidx: int)
-> list[str]

Default tab-completion for command prefix with completer delimiter.

This method filters only cliff style commands matching provided command prefix (line) as cmd2 style commands cannot contain spaces. This method returns text + missing command part of matching commands. This method does not handle options in cmd2/cliff style commands, you must define complete_$method to handle them.

default(line: str) -> bool | None

Execute when the command given isn't a recognized command implemented by a do_* method.

Parameters

  • statement โ€” Statement object with parsed input.
do_exit(_: Namespace) -> bool | None

Exit this application.

do_help(arg: str | None) -> None

List available commands or provide detailed help for a specific command.

get_names() -> list[str] โ€” ok so this one returns an alphabetized list of names that make up the attributes on the cmd2 class instance. ๐Ÿ“šโœจ

precmd(statement: Statement | str) -> Statement โ€” hook method that fires right before onecmd() executes the command, and after itโ€™s been added to history. literally the โ€œjust a secโ€ checkpoint. ๐Ÿ•ฐ๏ธ

Parameters

  • statement -- subclass of str which also contains the parsed input

Returns

  • a potentially modified version of the input Statement object

CommandManager ๐Ÿ‘‘

class cliff.commandmanager.CommandManager(namespace: str | None = None, convert_underscores: bool = True, *, ignored_modules: Iterable[str] | None = None)

Discovers commands and handles lookup based on argv data. very main-character behavior. ๐Ÿ’…

Parameters

  • namespace -- String containing the entrypoint namespace for the plugins to be loaded. For example, 'cliff.formatter.list'. If provided, commands will be loaded from this namespace initially, though it remains possible to load from additional namespaces with CommandManager.load_commands().
  • convert_underscores -- Whether cliff should convert underscores to spaces in entry_point commands.
  • ignored_modules -- A list of module names to ignore when loading commands. This will be matched partial, so be as specific as needed.

add_command_group(group: str | None = None) -> None โ€” adds another group of command entrypoints. ๐ŸŽ€

add_legacy_command(old_name: str, new_name: str) -> None โ€” maps an old command name to the new name. ๐Ÿ”

Parameters

  • old_name (str) -- The old command name.
  • new_name (str) -- The new command name.

find_command(argv: list[str]) -> tuple[type[Command <#cliff .command.Command>], str, list[str]] โ€” given an argument list, this finds a command and gives you back the processor plus any remaining arguments. ๐Ÿ”Ž

get_command_groups() -> list[str] โ€” returns a list of the loaded command groups. ๐Ÿ“‹

get_command_names(group: str | None = None) -> list[str] โ€” returns a list of commands loaded for the specified group. ๐Ÿ—‚๏ธ

load_commands(namespace: str) -> None โ€” loads all the commands from an entrypoint. ๐Ÿšš

Parameters

  • namespace -- The namespace to load commands from.

Returns

  • None

Command ๐Ÿงš

class cliff.command.Command(app: _app.App, app_args: Namespace | None, cmd_name: str | None = None)

Base class for command plugins. when the command is instantiated, it loads extensions from a namespace based on the parent application namespace and the command name:

app.namespace + '.' + cmd_name.replace(' ', '_')

Parameters

  • app -- Application instance invoking the command.
  • app_args -- Parsed arguments from options associated with the application instance..
  • cmd_name -- The name of the command.

get_description() -> str โ€” return the command description. ๐Ÿ“

The default is to use the first line of the classโ€™ docstring as the description. Set the _description class attribute to a one-line description of a command to use a different value. This is useful for enabling translations, for example, with _description set to a string wrapped with a gettext translation marker.

get_epilog() -> str โ€” return the command epilog. ๐Ÿ“œ

get_parser(prog_name: str) -> ArgumentParser โ€” return an argparse.ArgumentParser. ๐Ÿ› ๏ธ

run(parsed_args: Namespace) -> int โ€” invoked by the application when the command is run. ๐Ÿƒโ€โ™€๏ธ

Developers implementing commands should override take_action().

Developers creating new command base classes (such as Lister and ShowOne) should override this method to wrap take_action().

Return the value returned by take_action() or 0.

abstractmethod take_action(parsed_args: Namespace) -> Any โ€” override to do something useful. โœจ

The returned value will be returned by the program.


CommandHook ๐Ÿช

class cliff.hooks.CommandHook(command: Command <#cliff.command.Command>)

Base class for command hooks.

Hook methods are executed in the following order:

  1. get_epilog()
  2. get_parser()
  3. before()
  4. after()

Parameters

  • command -- Command instance being invoked

abstractmethod after(parsed_args: Namespace, return_code: int) -> int โ€” called after the commandโ€™s take_action() method. ๐Ÿซถ

Parameters

  • parsed_args -- The arguments to the command.
  • return_code (int) -- The value returned from take_action().

Returns

  • int

abstractmethod before(parsed_args: Namespace) -> Namespace โ€” called before the commandโ€™s take_action() method. ๐ŸŒธ

Parameters

  • parsed_args -- The arguments to the command.

Returns

  • argparse.Namespace

abstractmethod get_epilog() -> str | None โ€” return text to add to the command help epilog. ๐Ÿ“–

Returns

  • An epilog string or None.

abstractmethod get_parser(parser: ArgumentParser) -> ArgumentParser | None โ€” modify the command argparse.ArgumentParser. ๐Ÿงฐ

The provided parser is modified in-place, and the return value is not used.

Parameters

  • parser -- An existing ArgumentParser instance to be modified.

Returns

  • ArgumentParser or None

ShowOne ๐Ÿ‘‘

class cliff.show.ShowOne(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)

Command base class for displaying data about a single object. very โ€œone lil guy, full spotlightโ€ energy. ๐Ÿ”ฆ

dict2columns(data: dict[str, Any]) -> tuple[tuple[str, ...], tuple[Any, ...]] โ€” implements the common task of converting a dict-based object to the two-column output that ShowOne expects. ๐Ÿงฎ

property formatter_default: str โ€” string specifying the name of the default formatter. ๐ŸŽจ

property formatter_namespace: str โ€” namespace to use for loading formatter plugins. ๐Ÿงฉ

produce_output(parsed_args: Namespace, column_names: Sequence[str], data: Sequence[Any]) -> int โ€” use the formatter to generate the output. ๐Ÿ–จ๏ธ

Parameters

  • parsed_args -- argparse.Namespace instance with argument values
  • column_names -- sequence of strings containing names of output columns
  • data -- iterable with values matching the column names

Returns

  • a status code

abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Any]] โ€” run command. ๐Ÿ

Return a tuple containing the column names and an iterable containing the data to be listed.


ShowOneWithDetails ๐Ÿ’–

class cliff.show.ShowOneWithDetails(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)

Display a single objectโ€™s fields with additional columns.

Each row starts with a field name, followed by its value and any additional details. The -c option selects fields (rows), not output columns. Output is rendered using list formatters.

property formatter_default: str โ€” string specifying the name of the default formatter. ๐ŸŽ€

property formatter_namespace: str โ€” namespace to use for loading formatter plugins. ๐Ÿงฉ

produce_output(parsed_args: Namespace, column_names: Sequence[str], data: Iterable[Sequence[Any]]) -> int โ€” use the formatter to generate the output. ๐Ÿ–จ๏ธ

Parameters

  • parsed_args -- argparse.Namespace instance with argument values
  • column_names -- sequence of strings containing names of output columns
  • data -- iterable with values matching the column names

Returns

  • a status code

abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Sequence[Any]]] โ€” return output column names and an iterable of field rows. ๐Ÿ—ƒ๏ธ

The first item in each row must be a string containing the field name used for selection with -c. The remaining items contain the fieldโ€™s value and additional details, in column order.


Lister ๐ŸŒˆ

class cliff.lister.Lister(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)

Command base class for providing a list of data as output. list girlies rise. ๐Ÿ“‹โœจ

property formatter_default: str โ€” string specifying the name of the default formatter. ๐ŸŽจ

property formatter_namespace: str โ€” namespace to use for loading formatter plugins. ๐Ÿงฉ

get_parser(prog_name: str) -> ArgumentParser โ€” return an argparse.ArgumentParser. ๐Ÿ› ๏ธ

property need_sort_by_cliff: bool โ€” whether sort procedure is performed by cliff itself. ๐Ÿ”€

Should be overridden (return False) when there is a need to implement custom sorting procedure or data is already sorted.

produce_output(parsed_args: Namespace, column_names: Sequence[str], data: Iterable[Sequence[Any]]) -> int โ€” use the formatter to generate the output. ๐Ÿ–จ๏ธ

Parameters

  • parsed_args -- argparse.Namespace instance with argument values
  • column_names -- sequence of strings containing names of output columns
  • data โ€” an iterable whose values line up with the column names

Returns โ€” a status code ๐Ÿซถ

abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Any]] : Run the command. โœจ

Returns a tuple with the column names plus an iterable holding the data that should be listed. Bestie, that tuple is the whole personality of your command.


๐ŸŽ€ Formatting Output ๐ŸŽ€

Formatter

class cliff.formatters.base.Formatter

  • abstractmethod add_argument_group(parser: ArgumentParser) -> None โ€” adds whatever options you need to the argument parser. And like, use your own argument group, okay? Don't be that formatter colonizing someone else's group ๐Ÿ’…

ListFormatter

class cliff.formatters.base.ListFormatter

The base class for formatters that know how to deal with many objects. Many objects slay.

  • abstractmethod emit_list(column_names: Sequence[str], data: Iterable[Sequence[Any]], stdout: TextIO, parsed_args: Namespace) -> None โ€” formats and prints the list from that iterable data source. ๐Ÿฆ‹

Data values can be plain primitive types like ints and strings, or they can be an instance of a FormattableColumn for the situations where your value is all complex and needs to be handled differently for human-readable output vs. machine-readable output. iykyk ๐Ÿ™ˆ

Parameters

  • column_names โ€” the names of the columns
  • data โ€” the iterable data source, one tuple per object, values in the order of the column names
  • stdout โ€” the output stream where the data should be written
  • parsed_args โ€” the argparse namespace from our local options

SingleFormatter

class cliff.formatters.base.SingleFormatter

Base class for formatters that work with a single object. One (1) main character ๐Ÿ’œ

  • abstractmethod emit_one(column_names: Sequence[str], data: Sequence[Any], stdout: TextIO, parsed_args: Namespace) -> None โ€” formats and prints the values associated with that single object. ๐ŸŒธ

Same deal as before: data values can be primitives like ints and strings, or an instance of a FormattableColumn when the value is complex and may need to be handled differently for human-readable output vs. machine-readable output.

Parameters

  • column_names โ€” the names of the columns
  • data โ€” the iterable data source with values in the order of the column names
  • stdout โ€” the output stream where the data should be written
  • parsed_args โ€” the argparse namespace from our local options

โœจ FOR CONTRIBUTORS โœจ

Okay so if you wanna contribute to cliff directly, these instructions are literally here to get you started, bestie. ๐Ÿซถ Bug reports and feature requests are both totally welcome through the Storyboard project at https://storyboard.openstack.org/#!/project/openstack/cliff.

Changes to cliff should be submitted for review via the Gerrit tool, following the workflow documented at https://docs.openstack.org/infra/manual/developers.html#development-workflow. โœจ

๐Ÿšจ Pull requests submitted through GitHub will be ignored. Not "reviewed later", not "maybe" โ€” ignored. Periodt. ๐Ÿ’€

Bugs should be filed under the Storyboard project at https://storyboard.openstack.org/#!/project/openstack/cliff.

Note: before contributing new features to clif core, please consider whether they should be implemented as an extension instead. The architecture is highly pluggable precisely to keep the core small โ€” that's not a bug, that's the whole vibe ๐Ÿ˜Œ

๐Ÿงช Running Tests ๐Ÿงช

The test suite for cliff uses tox (that's https://tox.readthedocs.io/), which has to be installed separately: pip install tox. Yes, separately. It's not gonna manifest itself, bestie ๐ŸŽ€

To run the standard set of tests, run tox from the top level directory of the git repository. Fr fr, top level.

To run a single environment, specify it using the -e parameter. Like for example:

$ tox -e pep8

Add new tests by modifying an existing file or creating a new script in the tests directory. Go off ๐Ÿ‘‘

๐Ÿ“š Building Documentation ๐Ÿ“š

The documentation for cliff is written in reStructuredText and converted to HTML using Sphinx. Like tests, the documentation can be built using tox:

$ tox -e docs

The output version of the documentation ends up in ./docs/build/html. Precious. ๐Ÿฆ‹

Indices and tables

  • Index <>
  • Search Page <>

๐Ÿ‘‘ Author ๐Ÿ‘‘

Doug Hellmann โ€” the icon, the legend ๐Ÿ’…

2012-1980, Doug Hellmann. (Yes, that's the order. We don't question the timeline, we just respect it ๐Ÿ™ˆ)


And that's the whole page, bestie! This edition is cliff 4.17.0, dated Jan 02, 1980 โ€” same source, same version, same eternally iconic date. Go forth and format your output, you absolute legend ๐Ÿ’–โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from