# a class for modules that need outside objects to parameterize their behavior (because what are generics?)
# Modules inherit from this class and use their self['expected_configured_property']
import traceback
[docs]class ConfigValue(object):
""" A value to be configured.
Elements of a :class:`Configuration` are, in fact, :class:`ConfigValue` objects. They
can be resolved an arbitrary time after the :class:`Configuration` object is created
by calling :meth:`get`.
"""
[docs] def get(self):
""" Override this method to return a value when a configuration variable is accessed"""
raise NotImplementedError
class _C(ConfigValue):
""" A helper class that simply stores a value and can report it back with the get method.
Subclasses ConfigValue and implements the get method.
"""
def __init__(self, v):
self.v = v
def get(self):
return self.v
def __str__(self):
return str(self.v)
def __repr__(self):
return str(self.v)
[docs]class BadConf(Exception):
""" Special exception subclass for alerting the user to a bad configuration
"""
pass
class _link(ConfigValue):
""" Helper class that groups values within a Configuration
"""
def __init__(self,members,c):
self.members = members
self.conf = c
def get(self):
return self.conf[self.members[0]]
[docs]class Configuration(object):
""" A simple configuration object. Enables setting and getting key-value pairs"""
# conf: is a configure instance to base this one on
# dependencies are required for this class to be initialized (TODO)
def __init__(self, **kwargs):
for x in kwargs:
if not isinstance(kwargs[x],ConfigValue):
kwargs[x] = _C(kwargs[x])
self._properties = kwargs
def __setitem__(self, pname, value):
if not isinstance(value, ConfigValue):
value = _C(value)
if (pname in self._properties) and isinstance(self._properties[pname], _link):
for x in self._properties[pname].members:
self._properties[x] = value
else:
self._properties[pname] = value
def __getitem__(self, pname):
return self._properties[pname].get()
def __iter__(self):
return iter(self._properties)
[docs] def link(self,*names):
""" Call link() with the names of configuration values that should
always be the same to link them together
"""
l = _link(names,self)
for n in names:
self._properties[n] = l
def __contains__(self, thing):
return (thing in self._properties)
def __str__(self):
return "\n".join("%s = %s" %(k,self._properties[k]) for k in self._properties)
def __len__(self):
return len(self._properties)
@classmethod
[docs] def open(cls,file_name):
""" Open a configuration file and read it to build the internal state.
Parameters
----------
file_name : str
The name of a configuration file encoded as JSON
Returns
-------
Configuration
a Configuration object with the configuration taken from the JSON file
"""
import json
with open(file_name) as f:
c = Configuration()
d = json.load(f)
for k in d:
c[k] = _C(d[k])
c['configure.file_location'] = file_name
return c
[docs] def copy(self, other):
""" Copy configuration values from a different object.
Parameters
----------
other : dict or Configuration
A dict or Configuration object to copy the configuration from
Returns
-------
self
"""
if isinstance(other,Configuration):
self._properties = dict(other._properties)
elif isinstance(other,dict):
for x in other:
self[x] = other[x]
return self
[docs] def get(self, pname, default=None):
""" Retrieve a configuration value.
Parameters
----------
pname : str
The key of the value to return.
default : object
The value to return if there is no value corresponding to the given key
Returns
-------
object
The value corresponding to the key in pname or `default` if none is
available and a default is provided.
Raises
------
KeyError
If the given key has no associated value and no default is provided
"""
if pname in self._properties:
return self._properties[pname].get()
elif (default is not None):
return default
else:
raise KeyError(pname)