Abstract Base Controllers for Shared Arguments and Commands¶

For larger, complex applications it is often very useful to have abstract base controllers that hold shared arguments and commands that a number of other controllers have in common. Note that in the example below, you can not override the Meta.arguments in a sub-class or you overwrite the shared arguments, but it is possible to append to them in order to maintain the defaults while having unique options/arguments for the sub-classed controller. As well, you can add any number of additional commands in the sub-class but still maintain the existing shared commands (or override them as necessary).

from cement.core.foundation import CementApp
from cement.core.controller import CementBaseController, expose


class AbstractBaseController(CementBaseController):
    """
    This is an abstract base class that is useless on its own, but used
    by other classes to sub-class from and to share common commands and
    arguments.  This should not be confused with the `MyAppBaseController`
    used as the ``base_controller`` namespace.

    """
    class Meta:
        stacked_on = 'base'
        stacked_type = 'nested'
        arguments = [
            ( ['-f', '--foo'], dict(help='notorious foo option')),
            ]

    def _setup(self, base_app):
        super(AbstractBaseController, self)._setup(base_app)

        # add a common object that will be used in any sub-class
        self.reusable_dict = dict()

    @expose(hide=True)
    def default(self):
        """
        This command will be shared within all controllers that sub-class
        from here.  It can also be overridden in the sub-class, but for
        this example we are making it dynamic.

        """
        # do something with self.my_shared_obj here?
        if 'some_key' in self.reusable_dict.keys():
            pass

        # or do something with parsed args?
        if self.app.pargs.foo:
            print "Foo option was passed with value: %s" % self.app.pargs.foo

        # or maybe do something dynamically
        print("Inside %s.default()" % self.__class__.__name__)

class MyAppBaseController(CementBaseController):
    """
    This is the application base controller, but we don't want to use our
    abstract base class here.

    """
    class Meta:
        label = 'base'

    @expose(hide=True)
    def default(self):
        print("Inside MyAppBaseController.default()")

class Controller1(AbstractBaseController):
    """
    This controller sub-classes from the abstract base class as to inherite
    shared arguments, and commands.

    """
    class Meta:
        label = 'controller1'

    @expose()
    def command1(self):
        print("Inside Controller1.command1()")

class Controller2(AbstractBaseController):
    """
    This controller also sub-classes from the abstract base class as to
    inherite shared arguments, and commands.

    """
    class Meta:
        label = 'controller2'

    @expose()
    def command2(self):
        print("Inside Controller2.command2()")


class MyApp(CementApp):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [
            MyAppBaseController,
            Controller1,
            Controller2,
            ]

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

if __name__ == '__main__':
    main()

And:

$ python myapp.py
Inside MyAppBaseController.default()

$ python myapp.py --help
usage: myapp.py (sub-commands ...) [options ...] {arguments ...}

Base Controller

commands:

  controller1
    Controller1 Controller

  controller2
    Controller2 Controller

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


$ python myapp.py controller1
Inside Controller1.default()

$ python myapp.py controller1 --foo=bar
Foo option was passed with value: bar
Inside Controller1.default()

$ python myapp.py controller2
Inside Controller2.default()