Source code for mock_chroot.config.composition

#!/usr/bin/env python
"""mock_config/composition.py - Objects for composing Mock cinfiguration from
multipile objects
"""
from itertools import izip


class Composit(list):
    """Class for composing Mock configuration from multiple configuration
    objects.

    Configuration objects are objects that can either be converted to a Mock
    configuration string with 'str' or have the 'body' method which returns a
    Mock configuration string.
    The configuration object can also optionally contain the 'initialization'
    and 'finalization' methods to be called to generate additional
    configuration strings. During the composition process the 'body',
    'initialization' and 'finalization' methods will be passed the
    'composition_context' dictionary which is a shared dictionary passed to all
    confiruration objects to allow sharing of configuration state.

    The composition process is preformed upon conversion to string, it is done
    in the following stages:

    1. 'initialization' is called for all the configuration objects that have it
       in the order they were added to the Composit
    2. 'body' is called for all the configuration object that have it, in the
       order they were added to the Composit. Objects that don't have the 'body'
       methos are converted to string using 'str'
    3. 'finalization' is called for all the configuration objects that have it
       in reverse order to the one in which they were added to the Composit

    The returned values from all the method calls above are concatenated with
    newlines between them into one string which is returned.

    Objects of this class can also be passed as configuration objects to other
    objects of this class
    """
    def __init__(self, *config_objects):
        """Allow initializing with var-args for some convenience syntax for
        one-liner compositing

        :param list config_objects: Configuration objects to compose together

        Configuration object can be strings. See class doctext for more detailed
        description of configuration objects
        """
        super(Composit, self).__init__(config_objects)

    def initialization(self, composition_context):
        """Generate composit configuration initialization code"""
        return '\n'.join(
            str(conf_obj.initialization(composition_context))
            for conf_obj in self if hasattr(conf_obj, 'initialization')
        )

    def body(self, composition_context):
        """Generate composit configuration body code"""
        return '\n'.join(
            str(conf_obj.body(composition_context)) if hasattr(conf_obj, 'body')
            else str(conf_obj)
            for conf_obj in self
        )

    def finalization(self, composition_context):
        """Generate composit configuration finalization code"""
        return '\n'.join(
            str(conf_obj.finalization(composition_context))
            for conf_obj
            in reversed([
                conf_obj for conf_obj in self
                if hasattr(conf_obj, 'finalization')
            ])
        )

    def __str__(self):
        """Preform to composition process and return the composit configuration
        as described in the class doctext
        """
        composition_context = {}
        return '\n'.join(filter(None, (
            self.initialization(composition_context),
            self.body(composition_context),
            self.finalization(composition_context)
        )))


compose = Composit  # syntactic sugar


class ConfigurationObject(object):
    """A "Base Class" to make it convenient to create configuration objects as
    described by the Composit class docstring

    Contains a convenient implementation of __str__ and a quick way to create
    the various methods if they return plain strings
    """
    ALL_METHODS = ('initialization', 'body', 'finalization')

    def __init__(self, initialization=None, body=None, finalization=None):
        """Initialize the configuration object

        :param str initialization: Optional string to return from the
                                   initialization method
        :param str body: Optional string to return from the
                         body method
        :param str finalization: Optional string to return from the
                                 finalization method

        If None is passed to one of the method arguments, that method will not
        be created for the object
        """
        for method, method_val in izip(
            self.ALL_METHODS, (initialization, body, finalization)
        ):
            if method_val is not None:
                def phase_method(self, composition_context, value=method_val):
                    return value
                setattr(self, method, phase_method.__get__(self))

    def __str__(self):
        """Return a configuration string built with the methods the object has
        in a pseudo composition process
        """
        composition_context = {}
        return '\n'.join(filter(None, (
            getattr(self, method)(composition_context)
            for method in self.ALL_METHODS if hasattr(self, method)
        )))