Validate plugins configuration

If your application use a plugin system, and you want to validate a configuration which can be different for each plugin, this trick is for you.

The idea is pretty simple, do a two-step validation: the first one will validate all the common configuration, and the second step validate each plugin’s configuration. To avoid ValidationError in the first step, you use the allow_unknown feature of Sections.

Here is a full example:

import sys
from pprint import pprint

from confiture import Confiture
from confiture.schema import ValidationError
from confiture.parser import ParsingError
from confiture.schema.containers import Section, Value, many
from confiture.schema.types import Boolean, Integer, String

# Our Confiture schemas:

class GenericPluginSchema(Section):

    # This value is common to all plugins:
    common_value = Value(Integer())

    # Enable the allow_unknown meta in order to keep unknown values
    # on the first validation:
    _meta = {'allow_unknown': True,
             'repeat': many,
             'args': Value(String())}

class MainConfigurationSchema(Section):

    global_value = Value(Integer())
    plugin = GenericPluginSchema()

# Our plugins:

class BasePlugin(object):

    name = None
    schema = None

    def __init__(self, config):
        self.config = config

    def print_config(self):
        print '%s: ' % self.__class__.__name__

class BeautifulSchema(GenericPluginSchema):

    beautiful_value = Value(String())

    # The allow_unknown meta is disable to forbid bad config:
    _meta = {'allow_unknown': False}

class BeautifulPlugin(BasePlugin):

    name = 'beautiful'
    schema = BeautifulSchema()

class UglySchema(GenericPluginSchema):

    ugly_value = Value(Boolean())
    _meta = {'allow_unknown': False}

class UglyPlugin(BasePlugin):

    name = 'ugly'
    schema = UglySchema()

# Our test config

global_value = 42

plugin 'beautiful' {
    common_value = 123
    beautiful_value = "I'm so beautiful"

plugin 'ugly' {
    common_value = 456
    ugly_value = no

# This is where the magic happen:

if __name__ == '__main__':
    my_plugins = (BeautifulPlugin, UglyPlugin)
    enabled_plugins = []

    # Parse the global configuration:
    config = Confiture(TEST_CONFIG, schema=MainConfigurationSchema())
        pconfig = config.parse()
    except (ValidationError, ParsingError) as err:
        if err.position is not None:
            print str(err.position)
        print err
        print 'Main configuration:'

    # Enable each used plugins:
    for plugin_conf in pconfig.subsections('plugin'):
        # Search the plugin:
        for plugin in my_plugins:
            if == plugin_conf.args:
            print 'Unknown plugin %r, exiting.' % plugin_conf.args
        # Check plugin configuration:
            validated_conf = plugin.schema.validate(plugin_conf)
        except ValidationError as err:
            print 'Bad plugin configuration:'
            if err.position is not None:
                print str(err.position)
            print err
            # Instanciate the plugin object:

    # Print each enabled plugin config:
    for plugin in enabled_plugins:
        print '\n' + '~' * 80 + '\n'

And the output:

Main configuration:
{'global_value': 42,
 'plugin': [{'beautiful_value': "I'm so beautiful", 'common_value': 123},
            {'common_value': 456, 'ugly_value': False}]}


{'beautiful_value': "I'm so beautiful", 'common_value': 123}


{'common_value': 456, 'ugly_value': False}