Application Design

Cement does not enforce any form of application layout, or design. That said, there are a number of best practices that can help newcomers get settled into using Cement as a foundation to build their application.

Single File Scripts

Cement can easily be used for quick applications and scripts that are based out of a single file. The following is a minimal example that creates a CementApp with several sub-commands:

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


class BaseController(CementBaseController):
    class Meta:
        label = 'base'
        description = "MyApp Does Amazing Things"
        arguments = [
            (['-f, '--foo'], dict(help='notorious foo option')),
            (['-b', '--bar'], dict(help='infamous bar option')),
            ]

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

    @expose(help="this is some help text about the cmd1")
    def cmd1(self):
        print("Inside BaseController.cmd1()")

    @expose(help="this is some help text about the cmd2")
    def cmd2(self):
        print("Inside BaseController.cmd2()")

class MyApp(CementApp):
    class Meta:
        label = 'myapp'
        base_controller = BaseController


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

if __name__ == '__main__':
    main()

In this example, we’ve defined a base controller to handler the heavy lifting of what this script does, while providing sub-commands to handler different tasks. We’ve also included a number of command line arguments/options that can be used to alter how the script operates, and to allow user input.

Notice that we have defined a main() function, and then beyond that where we call main() if __name__ is __main__. This essentially says, if the script was called directly (not imported by another Python library) then execute the main() function.

Multi-File Applications

Larger applications need to be properly organized to keep code clean, and to keep a high level of maintainability (read: to keep things from getting shitty). The Boss Project provides our recommended application layout, and is a great starting point for anyone new to Cement.

The primary detail about how to layout your code is this: All CLI/Cement related code should live separate from the “core logic” of your application. Most likely, you will have some code that is re-usable by other people and you do not want to mix this with your Cement code, because that will rely on Cement being loaded to function properly (like it is when called from command line).

For this reason, we recommend a structure similar to the following:

- myapp/
- myapp/cli
- myapp/core

All code related to your CLI, which relies on Cement, should live in myapp/cli/, and all code that is the “core logic” of your application should live in a module like myapp/core. The idea being that, should anyone wish to re-use your library, they should not be required to run your CLI application to do so. You want people to be able to do the following:

from yourapp.core.some_library import SomeClass

The SomeClass should not rely on CementApp (i.e. the app object). In this case, the code under myapp/cli/ would import from myapp/core/ and add the “CLI” stuff on top of it.

In short, the CLI code should handle interaction with the user via the shell, and the core code should handle application logic un-reliant on the CLI being loaded.

See the Starting Projects from Boss Templates section for more info on using Boss.

Handling High Level Exceptions

The following expands on the above to give an example of how you might handle exceptions at the highest level (wrapped around the app object). It is very well known that exception handling should happen as close to the source of the exception as possible, and you should do that. However at the top level (generally in your main.py or similar) you want to handle certain exceptions (such as argument errors, or user interaction related errors) so that they are presented nicely to the user. End-users don’t like stack traces!

The below example catches common framework exceptions that Cement might throw, but you could also catch your own application specific exception this way:

import sys

from cement.core.foundation import CementApp
from cement.core.exc import FrameworkError, CaughtSignal


def main():
    with CementApp('myapp') as app:
        try:
            app.run()

        except CaughtSignal as e:
            # determine what the signal is, and do something with it?
            from signal import SIGINT, SIGABRT

            if e.signum == SIGINT:
                # do something... maybe change the exit code?
                app.exit_code = 110
            elif e.signum == SIGABRT:
                # do something else...
                app.exit_code = 111

        except FrameworkError as e:
            # do something when a framework error happens
            print("FrameworkError => %s" % e)

            # and maybe set the exit code to something unique as well
            app.exit_code = 300

        finally:
            # Maybe we want to see a full-stack trace for the above
            # exceptions, but only if --debug was passed?
            if app.debug:
                import traceback
                traceback.print_exc()

if __name__ == '__main__':
    main()