home

Language-level Support for Software Configuration

A while back, I wrote a small load testing application where each test declared what about itself could be configured, and when the tests were loaded, a central manager tracked those declarations and ensured that they were satisfied at runtime. This seems like an approach that would be useful if it were baked in at the language level. I think such a feature should have the following characteristics:

First, the language should provide syntactic support for configuration. For instance, if your language declared variables “var foo = ‘bar’”, you could instead say “conf foo = ‘bar’” (where ‘bar’ would be interpreted as a default value). If you hate this example, please don’t run away yet, I’m not advocating any ‘one true syntax’ for this. The important bit is that it should be as convenient as possible for the programmer to declare directly in the source code what’s available for end-user configuration.

Second, the configuration surface of a program should be discoverable. Users shouldn’t have to pore through the code to find all the little bits they can tweak, and they shouldn’t have to fight with possibly out-of-date documentation. The language system should be able to reveal every little knob that can be fiddled with for a given body of software.

Third, the language should not specify the end-user configuration format. Instead, it should provide a swappable configuration input system, so that the end user can switch between command-line parameters, different file formats, remote files, databases, etc. (this implies some bootstrapping – how do you configure which configuration system you’re going to use?).

Finally, configured variables should be safe. This could mean several things: that visibility for configured variables is limited to the module that declared that variable. That the system ensures that variables without a default are actually configured or else throws an error. That type or value constraints for variables are enforced. That configured values can be ‘locked down’ externally if they need to be. Again, it really depends on what’s appropriate for the language.

I’ve thrown together a starting approximation of how this might work for Python. Please don’t take this as the full expression of the idea, it’s just a nudge in that direction, to see what’s possible. Thanks are due to Thermal Noise for getting me pointed in the “right” direction for the bytecode stuff.

#!/usr/bin/python
"""
A first pass proof-of-concept at a quasi-language-level 
configuration system for Python.  
 
This defines the conf function; any time you want a variable that 
can be configured externally, simply assign it the output value 
of conf(), which will look up the external configuration for the 
variable, fallback to the default value given as an argument to 
conf(), or throw an error.
 
There are some serious limitations to this.  First, there's no way 
to externally define configuration, there's only the hint of future 
mechanisms for this.  Second, there's no way to expose what 
can be configured until the conf() function is evaluated for an 
assignment (this could be addressed if conf were more integrated
into the core language). Third, there's no care taken to mitigate 
misuse of conf().  I'm sure there are many more.
 
For the sake of simplicity, this is limited to module-level 
variables.
"""
import inspect
import dis
 
_undef = []
external_config = {}
def conf(default=_undef, process=None):
    confkey = makeconfkey(inspect.currentframe().f_back)
    if confkey in external_config:
        confval = external_config[confkey]
        return process(confval) if process is not None else confval
    elif default is not _undef:
        return default
    else:
        raise Exception('No config value provided')
 
def makeconfkey(frame):
    varname = findvarname(frame)
    varmodule = inspect.getmodulename(inspect.getfile(frame))
    return '%s.%s' % (varmodule, varname)
 
def findvarname(frame):
    """Look forward from the frame's current op to find the name 
    of the variable we're assigning to.  I'm sure this is horribly 
    broken in some or many ways."""
    varname = None
    bytecode = frame.f_code.co_code
    for i in xrange(frame.f_lasti, len(bytecode)):
        op = dis.opname[ord(bytecode[i])]
        if op == 'STORE_NAME':
            varname = frame.f_code.co_names[ord(bytecode[i+1])]
            break
    return varname
 
##EXAMPLES
#regular assignment with a default
foo = conf(1)
print foo
 
#overriding default with configured value
external_config['conf.bar'] = '2'
bar = conf(1.0, float)
print bar
 
#oops, no default, no config
try:
    baz = conf()
except:
    print 'No config for baz'