cement.ext.ext_argparse

The Argparse Extension provides argument handling based on argparse.ArgumentParser, and is the default argument handler used by cement.core.foundation.CementApp. In addition, this extension also provides ArgparseController that enables rapid development via application controllers based on Argparse.

Requirements

  • Python 2.7+, Python 3+
  • Some features of ArgparseController are only available in Python 3 including controller and function/command aliases (Python 3+) and controller default functions/command (Python 3.4+).

Configuration

This extension does not have any application configuration settings.

Usage

The following is an example application using both the ArgparseArgumentHandler and ArgparseController. Note that the default arg_handler is already set to ArgparseArgumentHandler` by CementApp.

from cement.core.foundation import CementApp
from cement.ext.ext_argparse import ArgparseController, expose


class BaseController(ArgparseController):
    class Meta:
        label = 'base'
        arguments = [
            (['--base-foo'], dict(help='base foo option')),
        ]

    @expose(hide=True)
    def default(self):
        # Note: Default commands are only available in Python 3.4+
        print('Inside BaseController.default')

        if self.app.pargs.base_foo:
            # do something with self.app.pargs.base_foo
            print('Base Foo > %s' % self.app.pargs.base_foo)

    @expose(
        arguments=[
            (['--command1-opt'],
             dict(help='option under command1', action='store_true'))
        ],
        aliases=['cmd1'],
        help='command1 is a sub-command under myapp base controller',
    )
    def command1(self):
        print('Inside BaseController.command1')

        if self.app.pargs.command1_opt:
            # do something with self.app.pargs.command1_opt
            pass


class EmbeddedController(ArgparseController):
    class Meta:
        label = 'embedded_controller'
        stacked_on = 'base'
        stacked_type = 'embedded'

    @expose(help="command2 embedded under base controller")
    def command2(self):
        print('Inside EmbeddedController.command2')


class NestedController(ArgparseController):
    class Meta:
        label = 'nested_controller'
        stacked_on = 'base'
        stacked_type = 'nested'
        arguments = [
            (['--nested-opt'],
             dict(help='option under nested-controller')),
        ]

    @expose(help="command3 under nested-controller")
    def command3(self):
        print('Inside NestedController.command3')


class MyApp(CementApp):
    class Meta:
        label = 'myapp'
        handlers = [
            BaseController,
            EmbeddedController,
            NestedController
        ]


with MyApp() as app:
    app.run()

The above looks like:

$ python myapp.py --help
usage: myapp.py [-h] [--debug] [--quiet] [--base-foo BASE_FOO]
                {nested-controller,command1,cmd1,default,command2} ...

optional arguments:
  -h, --help            show this help message and exit
  --debug               toggle debug output
  --quiet               suppress all output
  --base-foo BASE_FOO   base foo option

sub-commands:
  {nested-controller,command1,cmd1,default,command2}
    nested-controller   nested-controller controller
    command1 (cmd1)     command1 is a sub-command under base controller
    command2            command2 embedded under base controller


$ python myapp.py --base-foo bar
Inside BaseController.default
Base Foo > bar


$ python myapp.py command1 --help
usage: myapp.py command1 [-h] [--command1-opt]

optional arguments:
  -h, --help      show this help message and exit
  --command1-opt  option under command1


$ python myapp.py command1
Inside BaseController.command1


$ python myapp.py command2
Inside EmbeddedController.command2


$ python myapp.py nested-controller --help
usage: myapp.py nested-controller [-h] [--nested-opt] {command3} ...

optional arguments:
  -h, --help            show this help message and exit
  --nested-opt          option under nested-controller

sub-commands:
  {command3}
    command3            command3 under nested-controller


$ python myapp.py nested-controller command3
Inside NestedController.command3
class cement.ext.ext_argparse.ArgparseArgumentHandler(*args, **kw)

Bases: argparse.ArgumentParser, cement.core.arg.CementArgumentHandler

This class implements the cement.core.arg.IArgument interface, and sub-classes from argparse.ArgumentParser. Please reference the argparse documentation for full usage of the class.

Arguments and Keyword arguments are passed directly to ArgumentParser on initialization.

class Meta

Bases: object

Handler meta-data.

ignore_unknown_arguments = False

Whether or not to ignore any arguments passed that are not defined. Default behavoir by Argparse is to raise an “unknown argument” exception by Argparse.

This affectively triggers the difference between using parse_args and parse_known_args. Unknown arguments will be accessible as unknown_args.

interface

The interface that this class implements.

alias of IArgument

label = 'argparse'

The string identifier of the handler.

ArgparseArgumentHandler.add_argument(*args, **kw)

Add an argument to the parser. Arguments and keyword arguments are passed directly to ArgumentParser.add_argument(). See the argparse.ArgumentParser documentation for help.

ArgparseArgumentHandler.parse(arg_list)

Parse a list of arguments, and return them as an object. Meaning an argument name of ‘foo’ will be stored as parsed_args.foo.

Parameters:arg_list – A list of arguments (generally sys.argv) to be parsed.
Returns:object whose members are the arguments parsed.
class cement.ext.ext_argparse.ArgparseController(*args, **kw)

Bases: cement.core.handler.CementBaseHandler

This is an implementation of the cement.core.controller.IController interface, but as a base class that application controllers should subclass from. Registering it directly as a handler is useless.

NOTE: This handler requires that the applications arg_handler be argparse. If using an alternative argument handler you will need to write your own controller base class or modify this one.

NOTE: This is a re-implementation of cement.core.controller.CementBaseController. In the future, this class will eventually replace it as the default.

Usage:

from cement.ext.ext_argparse import ArgparseController

class Base(ArgparseController):
    class Meta:
        label = 'base'
        description = 'description at the top of --help'
        epilog = "the text at the bottom of --help."
        arguments = [
            (['-f', '--foo'], dict(help='my foo option', dest='foo')),
        ]

class Second(ArgparseController):
    class Meta:
        label = 'second'
        stacked_on = 'base'
        stacked_type = 'embedded'
        arguments = [
            (['--foo2'], dict(help='my foo2 option', dest='foo2')),
        ]
class Meta

Bases: object

Controller meta-data (can be passed as keyword arguments to the parent class).

aliases = []

A list of aliases for the controller/sub-parser. Only available in Python > 3.

arguments = []

Arguments to pass to the argument_handler. The format is a list of tuples whos items are a ( list, dict ). Meaning:

[ ( ['-f', '--foo'], dict(help='foo option', dest='foo') ), ]

This is equivelant to manually adding each argument to the argument parser as in the following example:

add_argument('-f', '--foo', help='foo option', dest='foo')

config_defaults = {}

Configuration defaults (type: dict) that are merged into the applications config object for the config_section mentioned above.

config_section = None

A config [section] to merge config_defaults into. Cement will default to controller.<label> if None is set.

default_func = 'default'

Function to call if no sub-command is passed. Note that this can not start with an _ due to backward compatibility restraints in how Cement discovers and maps commands.

Note: Currently, default function/sub-command only functions on Python > 3.4. Previous versions of Python/Argparse will throw the exception error: too few arguments.

description = None

Description for the sub-parser group in help output.

epilog = None

The text that is displayed at the bottom when --help is passed.

help = None

Text for the controller/sub-parser group in help output (for nested stacked controllers only).

hide = False

Whether or not to hide the controller entirely.

interface

alias of IController

label = 'base'

The string identifier for the controller.

parser_options = {}

Additional keyword arguments passed when ArgumentParser.add_parser() is called to create this controller sub-parser. WARNING: This could break things, use at your own risk. Useful if you need additional features from Argparse that is not built into the controller Meta-data.

stacked_on = 'base'

A label of another controller to ‘stack’ commands/arguments on top of.

stacked_type = 'embedded'

Whether to embed commands and arguments within the parent controller’s namespace, or to nest this controller under the parent controller (making it a sub-command). Must be one of ['embedded', 'nested'].

subparser_options = {}

Additional keyword arguments passed when ArgumentParser.add_subparsers() is called to create this controller namespace. WARNING: This could break things, use at your own risk. Useful if you need additional features from Argparse that is not built into the controller Meta-data.

title = 'sub-commands'

The title for the sub-parser group in help output.

usage = None

The text that is displayed at the top when --help is passed. Defaults to Argparse standard usage.

ArgparseController._post_argument_parsing()

Called on every controller just after arguments are parsed (assuming that the parser hasn’t thrown an exception). Provides an alternative means of handling passed arguments. Note that, this function is called on every controller, regardless of what namespace and sub-command is eventually going to be called. Therefore, every controller can handle their arguments if the user passed them.

For example:

$ myapp --foo bar some-controller --foo2 bar2 some-command

In the above, the base controller (or a nested controller) would handle --foo, while some-controller would handle foo2 before some-command is executed.

class Base(ArgparseController):
    class Meta:
        label = 'base'

        arguments = [
            (['-f', '--foo'],
             dict(help='my foo option', dest=foo)),
        ]

    def _post_argument_parsing(self):
        if self.app.pargs.foo:
            print('Got Foo Option Before Controller Dispatch')

Note that self._parser within a controller is that individual controllers sub-parser, and is not the root parser app.args (unless you are the base controller, in which case self._parser is synonymous with app.args).

ArgparseController._pre_argument_parsing()

Called on every controller just before arguments are parsed. Provides an alternative means of adding arguments to the controller, giving more control than using Meta.arguments.

class Base(ArgparseController):
    class Meta:
        label = 'base'

    def _pre_argument_parsing(self):
        p = self._parser
        p.add_argument('-f', '--foo',
                       help='my foo option',
                       dest='foo')

    def _post_argument_parsing(self):
        if self.app.pargs.foo:
            print('Got Foo Option Before Controller Dispatch')
ArgparseController._setup(app)

See IController._setup().

class cement.ext.ext_argparse.expose(hide=False, arguments=[], **parser_options)

Bases: object

Used to expose functions to be listed as sub-commands under the controller namespace. It also decorates the function with meta-data for the argument parser.

Parameters:
  • hide (boolean) – Whether the command should be visible.
  • arguments – List of tuples that define arguments to add to this commands sub-parser.
  • parser_options (dict) – Additional options to pass to Argparse.

Usage:

from cement.ext.ext_argparse import ArgparseController, expose

class Base(ArgparseController):
    class Meta:
        label = 'base'

    # Note: Default functions only work in Python > 3.4
    @expose(hide=True)
    def default(self):
        print("In Base.default()")

    @expose(
        help='this is the help message for my_command',
        aliases=['my_cmd'], # only available in Python 3+
        arguments=[
            (['-f', '--foo'],
             dict(help='foo option', action='store', dest='foo')),
        ]
    )
    def my_command(self):
        print("In Base.my_command()")