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__. This essentially
says, if the script was called directly (not imported by another Python
library) then execute the
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
SomeClass should not rely on
CementApp (i.e. the
In this case, the code under
myapp/cli/ would import from
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
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()