Application

The groundwork App is a container mostly for configurations, plugins/patterns and their needed objects.

Most attributes are added during runtime by patterns, which need a single instance of an object per application. For instance: A list of registered commands, a database connection object, a web application.

To initialise a groundwork application, simply do:

from groundwork import App

my_app = App()

Configuration

groundwork can load multiple configuration files during application initialisation. These values are available via my_app.config.get("MY_CONFIG_PARAMETER").

It is also possible to reload the configuration or to extend it by additional configuration files during runtime:

from groundwork import App

my_app = App(config_files=["config1.py", "config2.py"])     # Load 2 config files
my_app.config.load(["config3.py"])                          # Load a third config file

A configuration file must be python file, which defines variables on root level. Only variables with an uppercase name are used for the groundwork configuration:

# config1.py

import os

APP_NAME = "My awesome application"                 # Is used as config parameter

config_file_location = __file__                     # Is not used as config parameter
APP_PATH = os.path.dirname(config_file_location)    # Is used as config parameter

APP_PLUGINS = [ "My Plugin",                        # Is used as config parameter
                "GwPluginInfo",
                "GwCommandInfo]

After application initialisation, the configuration can be used. For instance to activate needed plugins:

from groundwork import APP

my_app = App(config_files=["config1.py"])
my_app.plugins.activate(my_app.config.get("APP_PLUGINS"))

Plugin registration

Before a plugin can be activated for a groundwork application, it must be registered.

groundwork does this registration automatically for all python packages in the current python environment. For not-packaged plugins, they must be registered by the application developer her/himself.

Packaged plugins

A packaged plugin is part of a python package, which provides a setup.py and was installed via python setup.py install or related pip/easy_install commands in the current python environment.

The package must use the entry_point groundwork.plugin and provide a class for each entry_point. Example from the groundwork package itself:

setup(
    name='groundwork',

    # A lot of other information....

    entry_points={
        'groundwork.plugin': [
            'gw_plugin_info = groundwork.plugins.gw_plugin_info:GwPluginInfo',
            'gw_signal_info = groundwork.plugins.gw_signal_info:GwSignalInfo',
            'gw_command_info = groundwork.plugins.gw_commands_info:GwCommandInfo'
        ]
    }
)

During application initialisation, groundwork registers all plugins, which are provided by this way automatically. They can be activated after app initialisation:

from groundwork import App

my_app = App()
my_app.plugins.activate(["GwPluginInfo", "GwSignalInfo"])

Registration of own plugins

If a groundwork plugin is not part of a package and not made available via entry_point, it must be registered by the application developer. This can be done during application initialisation or later:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): pass

    def deactivate(self): pass


# Registration during initialisation
my_app = App(plugins=[MyPlugin])

# Registration after initialisation
from my_module import AnotherPlugin
my_app.plugins.classes.register([AnotherPlugin])

# Activation
my_app.plugins.activate(["My Plugin", "AnotherPlugin"])

Plugin activation

Before a plugin registers its commands, signals or anything else, it must be activated.

groundwork supports two ways of activation:

  • Activation by application
  • Activation by plugin

Here is an example, which demonstrates both ways:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): pass

    def deactivate(self): pass

# Activation by application
my_app = App(plugins=[MyPlugin])                        # Registration
my_app.plugins.activate(["My Plugin"])                  # Activation

# Activation by plugin
my_plugin2 = MyPlugin(app=my_app, name="MyPlugin2")     # Registration
my_plugin2.activate()                                   # Activation

Plugin deactivation

Like for plugin activation, also the plugin deactivation supports two ways of deactivating a plugin:

# Follow up of the plugin activation example...

# Deactivation by application
my_app.deactivate(["MyPlugin"])

# Deactivation by plugin
my_plugin2.deactivate()

Handling errors

A plugin registration or activation can easily fail. Reasons may be bad code, missing dependencies, already registered classes and more.

By default groundwork logs only a warning if a registration or activation fails.

You can ask groundwork to throw also an exception, if problems occur. This behavior can be activated by setting the parameter strict=True during application initialisation:

from groundwork import App

class MyBadPlugin():
    pass

my_app = App(strict=True)
my_app.registers([MyBadPlugin])     # will throw an exception

my_app.strict = False
my_app.registers([MyBadPlugin])     # will log a warning only

Logging

A groundwork application provides its own logger object, which is available under my_app.log:

from groundwork import App

my_app = App()
my_app.log.info("Loading plugins")
my_app.log.debug("Activating Plugin X")

This logger is used by most application related objects. Plugins have their own logger, which is available under self.log inside an plugin class.

Configuration

All loggers (application and plugins) can be configured by a configuration parameter called GROUNDWORK_LOGGING inside a used configuration file.

The value of this parameter must be a dictionary. Its structure is described in the python documentation for logging.

Example of a configuration for groundwork logs:

GROUNDWORK_LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'extended': {
            'format': "%(levelname)-8s %(name)-40s - %(asctime)s - %(message)s"
        },
        'debug': {
            'format': "%(name)s - %(asctime)s - [%(levelname)s] - %(module)s:%(funcName)s(%(lineno)s) - %(message)s"
        },
    },
    'handlers': {
        'default': {
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'level': 'DEBUG'
        },
        'console_stdout': {
            'formatter': 'extended',
            'class': 'logging.StreamHandler',
            'stream': sys.stdout,
            'level': 'DEBUG'
        },
        'file': {
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "debug",
            "filename": "logs/app.log",
            "maxBytes": 1024000,
            "backupCount": 3,
            'level': 'DEBUG'
        },
        'file_my_plugin': {
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "debug",
            "filename": "logs/my_plugin.log",
            "maxBytes": 1024000,
            "backupCount": 3,
            'level': 'DEBUG'
        },
    },
    'loggers': {
        '': {
            'handlers': ['default'],
            'level': 'WARNING',
            'propagate': True
        },
        'groundwork': {
            'handlers': ['console_stdout', 'file'],
            'level': 'INFO',
            'propagate': False
        },
        'MyPlugin': {
            'handlers': ['console_stdout', 'file_my_plugin'],
            'level': 'DEBUG',
            'propagate': False
        },
    }
}