Controllers
Introduction to the Controller Interface
Cement defines a Controller Interface, but does not enable any handlers that implement the interface by default.
For convenience, the preferred ArgparseController
and expose()
decorator from the Argparse Extension are available as Controller
and ex
in the top level cement module.
Using application controllers is not necessary, but enables rapid development by wrapping pieces of the framework like adding arguments, and linking commands with controller methods. The examples below use the ArgparseController
as examples (again, imported as cement.Controller
for convenience).
Cement often includes multiple handler implementations of an interface that may or may not have additional features or functionality than the interface requires. The documentation below only references usage based on the interface and default handler (not the full capabilities of an implementation).
Cement Extensions That Provide Controller Handlers
API References:
Application Base Controllers
When using application controllers there must be a single base
controller responsible for handling runtime dispatch. All other controllers are then stacked on top of the base controller (or other controllers already stacked on base). The base controller is the root of the application's command-line namespace.
The initial base controller must have a Controller.Meta.label
of base
to designate it as the application's route of handing over runtime (argument parsing, mapping sub-commands to controllers, etc).
If no controller handler is registered with a base
label, Cement will register a minimal controller in it's place that doesn't do anything other than allow extensions to stack properly.
The above example demonstrates registering an application base controller. You will note in the cli
tab that running python myapp.py
without any arguments produces help output (same as if passing --help
). This is the default action of the ArgparseController
, but can be modified by overriding the _default
method.
The Controller._default
method is hidden from the command-line (ex: --help
output), and does not require the ex()
decorator to be active. You can modify the default method by setting it via Controller.Meta.default_func
.
Controller Arguments
Command line arguments defined by controllers are handled in two ways:
Controller level
Sub-command level
Controller level arguments are defined via Controller.Meta.arguments
and are displayed/used at the top level of that controller, while sub-command level arguments are defined via the ex()
decorator and only displayed and used under that sub-command namespace.
Controller level arguments should be considered global, and relevant to the entire controller namespace. Sub-command level arguments are only relevant to that sub-command.
How and where arguments are defined determines how they are used. An argument defined under a controller must be passed before any sub-command namespaces, otherwise the sub-parser will not recognize it and result in an unrecognized argument error
.
Processing Controller Level Arguments
In the above example we demonstrated accessing the controller level argument via app.pargs.foo
in the Base.cmd1()
method/sub-command. This worked fine. However, because controller level arguments should be considered global to the entire namespace, we should reduce duplicate code and handle controller level argument parsing in one place, regardless of what sub-command is passed.
The ArgparseController
defines both a _pre_argument_parsing
and_post_argument_parsing
method for providing direct access to that controller's sub-parser (self._parser
) and processing arguments. This is very similar to the pre_argument_parsing
and post_argument_parsing
framework hooks, but local in scope to the controller.
Additional Controllers and Namespaces
Any number of additional controllers can be added to your application after a base controller is registered. Additionally, these controllers can be stacked
onto the base controller (or any other controller) in one of two ways:
embedded
- The controllers commands and arguments are included under the parent controller's name space.nested
- The controller label is added as a sub-command under the parent controller's namespace (effectively this is a sub-command with additional sub-sub-commands under it)
For example, the base
controller is accessed when calling myapp.py
directly. Any commands under the base
controller would be accessible as myapp.py <cmd1>
, or myapp.py <cmd2>
, etc. An embedded
controller will merge its commands and options into the base
controller namespace and appear to be part of the base controller... meaning you would still access the embedded
controllers commands as myapp.py <embedded_cmd1>
, etc (same for options).
For nested
controllers, a prefix will be created with that controller's label under its parent's namespace. Therefore you would access that controller's commands and options as myapp.py <controller_label> <controller_cmd1>
.
Controllers can be stacked on other controllers as many levels deep as necessary. An embedded
controller can be stacked on top of a nested
controller, and vice versa. There is little, if any, limitation.
Last updated