Framework Overview
An in-depth overview of the key features of the framework that all developers should be familiar with.
This section is intended to give a brief overview of some of the most commonly used core features of Cement. Please do not be discouraged if you don't "get it" right away. Please also do not think, "is this it?". This is not intended to be an exhaustive end-all-be-all coverage of every feature of the framework.
Some assumptions are being made here. Primarily, we assume that you've used and are familiar with Python. The overview is intended to give a high level look at using Cement. Please dive deeper into the individual sections after the overview in order to gain a better understanding of each component.
The Application Object
The core of your project starts with the Cement App
object, which we will refer to throughout this documentation in several ways:
App
- The uninstantiated CementApp
base classMyApp
- The uninstatiated/sub-classed Cement application you are creatingapp
- The instantiated application object
Technically, Cement App
can be used direcly (as in the example), however in practice you will almost always sub-class App
in order to configure it for your needs (I.e. MyApp
).
MetaMixin
Cement uses MetaMixin
classes everywhere, which allows the framework to define default functionality but also provides an easy mechanism for developers to override and customize.
This is implemented by declaring a Meta
class, under your application and/or other Cement Handler classes.
Ex: Defining Meta Classes
All Meta-options can also be overridden by any **kwargs
that are passed to the parent class that is being instantiated.
Ex: Passing meta options via **kwargs
:
Nearly every Cement class has an associated Meta
class, which we often refer to as App.Meta
, SomeHandlerClass.Meta
, etc. The instantiated object is refered to in code as app._meta
, some_handler._meta
, etc.
Interfaces and Handlers
All aspects of the framework are broken up into interfaces, and handlers. Interfaces define some functionality, while handlers implement that functionality. Cement defines the following builtin core interfaces:
Interface
Descripton
Framework extension loading.
Messaging to console, and/or file via common log facilities (INFO, WARNING, ERROR, FATAL, DEBUG).
Merging of application configuration defaults, configuration files, and environment settings into a single config object.
Remote message sending (email, smtp, etc).
Application plugin loading.
Rendering of template data (content, files, etc).
Rendering of data/content to end-user output (console text from template, JSON, Yaml, etc). Often uses an associated template handler backend.
Command line argument/option parsing.
Command dispatch (sub-commands, arguments, etc)
Key/Value data store (memcached, redis, etc)
To accompany the above interfaces, Cement also defines and registers default Handlers that implement the required functionality. For example, the builtin configuration handler ConfigParserConfigHandler
, implements the config
interface.
Developers can override the default functionality by creating their own handlers, or by sub-classing what is provided to alter its implementation. The following example demonstrates how you would sub-class and override an existing handler:
Ex: Overriding Default Framework Handlers
Overriding Via Configuration Files
The App.Meta
options define the builtin handlers for all of the above listed core handlers. Whatever the application code defines is the default, however you can also override via the configuration file(s).
For example, imagine that your default mail_handler
is smtp
for sending email via your local SMTP server. This is a configuration that might vary on a per-user/environment basis. Via the application configuration, you could override this with an alternative mail handler like mail_handler = some_other_mail_handler
Ex: Overriding Via Configuration File
Configuration
Cement looks for configuration files in the most common places by default. For example:
/etc/myapp/myapp.yml
~/.config/myapp/myapp.yml
~/.config/myapp/config
~/.config/myapp.yml
~/.myapp.yml
~/.myapp/config
The builtin configuration handler ConfigParserConfigHandler
uses common unix-like config files where blocks
or sections
are defined with brackets; [myapp]
, [plugin.myplugin]
, [interface.handler]
, etc.
Additional support for the following file formats is provided via optional extensions:
All extensions and application plugins can support customizations loaded from the application configuration files under the section [interface.handler]
. For example, the ColorLogHandler
extension reads it's configuration from [log.colorlog]
.
Ex: Application Configuration Settings
Alternative Configuration Handler Example
The following is an example of overriding the default config handler with an alternative, drop-in replacement YamlConfighandler
:
Ex: Alternative Configuration Handler (Yaml):
Overriding Configuration Settings with Environment Variables
All configuration settings can be overridden by their associated environment variables. For example config['myapp']['foo']
is overridable by $MYAPP_FOO
.
Note that all environment variable configurations are prefixed with the application label, therefore secondary namespaces such as config['log.logging']['level']
would be overridable by MYAPP_LOG_LOGGING_LEVEL
.
Arguments
Ex: Simple Arguments Defined With Cement App
Arguments Defined by Controllers
The power of the framework comes into play when we start talking about application controllers that streamline the process of mapping arguments and sub-commands to actions/functions as in the example (more on this later).
Ex: Arguments Defined by Controllers
Logging
level
- The level at which to start logging (INFO
,WARNING
,ERROR
,FATAL
,DEBUG
, etc).file
(path) - File path to log to.to_console
(bool) - Whether or not to log to console.rotate
(bool) - Whether or not to rotate the log file when it hitsmax_bytes
max_bytes
(int) - Maximum file size in bytes before file gets rotatedmax_files
(int) - Maximum number of log files to keep after rotating
Cement also includes the following optional extensions that provide drop-in replacements for the default log handler:
Ex: Logging Example
Output
Cement ships with several types of extensions that produce output in different forms, including the following:
Text Rendered From Template Files
Programatic Structures (JSON, Yaml, etc)
Tabulated (like MySQL, etc)
Etc
The following output handlers ship with Cement:
Multiple Output Handler Support
Ex: Mixed Template/JSON Output Example
Controllers
The most notable action of runtime dispatch is mapping arguments and sub-commands to their respective controllers and functions. For example, the default action when running $ myapp
without any arguments or sub-commands is to execute the Base._default()
function.
Ex: Application Base Controller
Nested / Embedded Controllers
nested - The arguments and commands are nested under a sub-parser whose label is that of the controller. For example, a nested controller with a label of
my-nested-controller
would be called as$ myapp my-nested-controller sub-command
.embedded - The arguments and commands are embedded within it's parent controller, therefore appearing as if they were defined by the parent itself. A sub-command under an embedded controller would be called as
$ myapp sub-command
.
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.
Controller Arguments vs Command Arguments
Both Controllers and their sub-commands can have arguments defined. Think of controllers as the primary namespace. It's arguments should be globally relevant within that namespace. A sub-command within the namespace can have it's own arguments, but are only relevant to that sub-command.
Ex: $ myapp -a my-controller -b my-sub-command -c
In the above example, -a
is relevant to the global scope of the entire application because it is defined on the base
controller. Option -b
is relevant to the scope of my-controller
and all sub-commands under it. Finally, -c
is only relevant to the my-sub-command
and has no use elsewhere.
Exposing Sub-Commands
By default, no commands are exposed to the CLI except that a _default()
function will be called if no sub-command is passed (configurable by Controller.Meta.default_func
).
To expose a function as a sub-command, you must decorate it with @ex()
. It's usage is simple, and supports the following parameters:
hide
(bool) - Whether or not to display in--help
output.arguments
(list) - Argument list of tuples in the format( [], {} )
, that are passed toArgparse.add_argument(*args, **kwargs)
.**kwargs
- Additional keyword arguments are passed directly to Argparse when creating the sub-parser for this command.
Framework Extensions
Ex: Using Framework Extensions:
Application Plugins
Cement provides an interface that automatically handles the management, configuration, and loading of Application Plugins. A Plugin is essentially the same as a Framework Extension, but is application specific where extensions are agnostic (can be used by any application).
A plugin can be anything, and provide any kind of functionality from defining runtime hooks, to extending an applications capabilities by adding nested/embedded controllers. The only thing that a plugin must provide is a load()
function that is called when the plugin is imported.
Ex: Basic Application
Using the same application, we can create a plugin to extend functionality:
Ex: Application Plugin
Hooks
Hooks provide developers the ability to tie into the framework, and applications without direct access to the runtime. For example, a plugin might need to execute some code after arguments have been parsed, but before controller sub-commands are dispatched. As a plugin developer, you don't have direct access to the applications runtime code but you can still tie into it with the builtin post_argument_parsing
hook.
Cement defines several hooks that tie in to specific points throughout the application life cycle, however application developers can also define their own hooks allowing others to tie elsewhere, when needed.
Ex: Executing Code Via Hooks
Last updated