Source code for caput.config

"""
Configure class attributes using values from a dictionary.

This module to defines strictly typed attributes of a class, that can be loaded
from an input dictionary. This is particularly useful for loading a class from
a YAML document.

Classes
=======

.. autosummary::
   :toctree: generated/

   Property
   Reader

Examples
========

In this example we set up a class to store information about a person.

>>> class Person(Reader):
...
...     name = Property(default='Bill', proptype=str)
...     age = Property(default=26, proptype=float, key='ageinyears')

We then extend it to store information about a person with a pet. The
configuration will be successfully inherited.

>>> class PersonWithPet(Person):
... 
...     petname = Property(default='Molly', proptype=str)

Let's create a couple of objects from these classes.

>>> person1 = Person()
>>> person2 = PersonWithPet()

And a dictionary of replacement parameters.

>>> testdict = { 'name' : 'Richard', 'ageinyears' : 40, 'petname' : 'Sooty'}

First let's check what the default parameters are:

>>> print person1.name, person1.age
Bill 26.0
>>> print person2.name, person2.age, person2.petname
Bill 26.0 Molly

Now let's load the configuration from a dictionary:

>>> person1.read_config(testdict)
>>> person2.read_config(testdict)
    
Then we'll print the output to see the updated configuration:

>>> print person1.name, person1.age
Richard 40.0
>>> print person2.name, person2.age, person2.petname
Richard 40.0 Sooty

"""


[docs]class Property(object): """Custom property descriptor that can load values from a given dict. """
[docs] def __init__(self, default=None, proptype=None, key=None): """Make a new property type. Parameters ---------- default : object The initial value for the property. proptype : function The type of the property. In reality this is just a function which gets called whenever we update the value: `val = proptype(newval)` key : string The name of the dictionary key that we can fetch this value from. If None (default), attempt to use the attribute name from the class. """ self.proptype = (lambda x: x) if proptype is None else proptype self.default = default self.key = key self.propname = None
[docs] def __get__(self, obj, objtype): ## Object getter. if obj is None: return None self._set_propname(obj) if self.propname not in obj.__dict__: return self.proptype(self.default) if self.default is not None else None else: return obj.__dict__[self.propname]
[docs] def __set__(self, obj, val): ## Object setter. if obj is None: return None self._set_propname(obj) obj.__dict__[self.propname] = self.proptype(val)
[docs] def _from_config(self, obj, config): """Load the configuration from the supplied dictionary. Parameters ---------- obj : object The parent object of the Property that we want to update. config : dict Dictionary of configuration values. """ self._set_propname(obj) if self.key is None: self.key = self.propname if self.key in config: val = self.proptype(config[self.key]) obj.__dict__[self.propname] = val #print "Setting attribute %s to %s." % (self.key, val)
[docs] def _set_propname(self, obj): import inspect if self.propname is None: for basecls in inspect.getmro(type(obj))[::-1]: for propname, clsprop in basecls.__dict__.items(): if isinstance(clsprop, Property) and clsprop == self: self.propname = propname
[docs]class Reader(object): """A class that allows the values of Properties to be assigned from a dictionary. """ @classmethod
[docs] def from_config(cls, config, *args, **kwargs): """Create a new instance with values loaded from config. Parameters ---------- config : dict Dictionary of configuration values. """ c = cls(*args, **kwargs) c.read_config(config) return c
[docs] def read_config(self, config): """Set all properties in this class from the supplied config. Parameters ---------- config : dict Dictionary of configuration values. """ import inspect for basecls in inspect.getmro(type(self))[::-1]: for propname, clsprop in basecls.__dict__.items(): if isinstance(clsprop, Property): clsprop._from_config(self, config) self._finalise_config()
[docs] def _finalise_config(self): """Finish up the configuration. To be overriden in subclasses if we need to perform some processing post configutation. """ pass
if __name__ == "__main__": import doctest doctest.testmod()