Configuration - Part 2

The configuration manager that I spoke of in Part 1 has the task of merging configuration information from a bunch of separate sources.  In this posting, I'm going to talk about how it does the merging and then expound on some the benefits of the technique.

First, the configuration manager needs to be informed of what configuration options its going to be working with.  This is done by passing in mappings of names to Option objects.  The option objects are just definitions of name, documentation, default value and a reference to a string conversion function.

The ConfigurationManager accepts a list of these objects as the first parameter to the constructor.  In the example from the last posting, I use a Namespace object to hold a several Options.  The Namespace object is just a dict with some syntactic sugar that allows lookups using dot notation.  Any mapping will work fine, including the builtin dict.  The list of mappings is merged into a single master mapping within the ConfigurationManager instances from left to right.  If there is a conflict in the members of the list for the same name, then the right most entry wins.

import config_manager as cm

n = cm.Namespace()
n.option(
    name="datetime",
    doc="the date and time to process",
    default="2011-05-04 15:10",
    from_string_converter=cm.datetime_converter,
)
conf_man = cm.ConfigurationManager([n], application_name="sample")

The 'default' value within the Option objects provides the ConfigurationManager with the first cut of value for any given option.  From there, it overlays new values for these parameters from each of its successive config information sources.  By default, it looks to the environment, then any .conf, .ini or .json files that it knows about.  Finally, it gets values from the command line as the last place for configuration values.  Should those sources prove to be insufficient or the order isn't right, you can use an optional second parameter.  Remember the key/value pairs can be from any mapping object.

Taking the previous example as the base, let's say we've got an ini file that looks like this:

$ cat ./sample.ini
[top_level]
# name: datetime     # doc: the date and time to process     
# converter: socorro.lib.config_manager.datetime_converter     
datetime=1959-11-13 06:12

And then we invoke the program like this:

$ export datetime='1921-01-18 09:45'
$ python ./sample.py --datetime='1770-12-16 20:21'

The value for 'datetime' starts at'2011-05-04 15:10' as specified by the 'default' in the Option definition The environment (from os.env) is applied and the value becomes '1921-01-18 09:45'.  Next the ini is applied and 'datetime' changes to '1959-11-13 06:12'. Finally, the command line value is overlaid and the value becomes'1770-12-16 20:21'

It can be referenced like this:

config = conf_man.get_config()
print config.datetime

This will print the last value assigned to 'datetime'.  The conversion function will have already been applied, so the final value type will be datetime.datetime.

In the next posting on this topic, I show how to dynamically load classes using this  configuration.