Skip to content

Instantly share code, notes, and snippets.

@ryanwitt
Created August 31, 2010 19:02
Show Gist options
  • Save ryanwitt/559534 to your computer and use it in GitHub Desktop.
Save ryanwitt/559534 to your computer and use it in GitHub Desktop.
"""
Classes for a dynamic, templated settings environment.
By "environment" we just mean a dictionary where keys can be accessed as
object attributes. The _AttributeDict class comes from Fabric.
The _RecursiveAttributeDict class takes this idea one step further, and lets
you refer to other keys in the dictionary using new-style python formatting
syntax (PEP 3101). See the class docstring for examples.
"""
class _AttributeDict(dict):
"""
Dictionary subclass enabling attribute lookup/assignment of keys/values.
For example::
>>> m = _AttributeDict({'foo': 'bar'})
>>> m.foo
'bar'
>>> m.foo = 'not bar'
>>> m['foo']
'not bar'
``_AttributeDict`` objects also provide ``.first()`` which acts like
``.get()`` but accepts multiple keys as arguments, and returns the value of
the first hit, e.g.::
>>> m = _AttributeDict({'foo': 'bar', 'biz': 'baz'})
>>> m.first('wrong', 'incorrect', 'foo', 'biz')
'bar'
"""
def __getattr__(self, key):
try:
return self[key]
except KeyError:
# to conform with __getattr__ spec
raise AttributeError(key)
def __setattr__(self, key, value):
self[key] = value
def first(self, *names):
for name in names:
value = self.get(name)
if value:
return value
class CircularReference(KeyError):
"""
Exception for circular references. Each time it is raised, it
adds the current key to the cycle list and outputs the list in
the end.
The ``cycle`` property gives you they keys involved in the
cycle, from shallow to deep.
"""
def __init__(self, key, previous=None, **kwargs):
if previous:
self.cycle = [key] + previous.cycle
else:
self.cycle = [key]
super(CircularReference, self).__init__(str(self), **kwargs)
def __str__(self):
return ' -> '.join(self.cycle)
import string
class _RecursiveAttributeDict(_AttributeDict):
"""
Whenever an attribute is accessed, this dictionary evaluates
any PEP 3101 style format strings it finds. It does this
recursively so your env can have complex interdependencies.
For example::
>>> env = _RecursiveAttributeDict()
>>> env.one = 'one depends on two, {two}'
>>> env.two = 'two stands alone'
>>> env.one
'one depends on two, two stands alone'
Circular references will trigger an exception that lists the
cycle::
>>> env = _RecursiveAttributeDict()
>>> env.foo = 'foo depends on {bar}'
>>> env.bar = 'but bar depends on {foo}, oh noes!'
>>> env.foo
Traceback (most recent call last):
...
CircularReference: foo -> bar -> foo
"""
def __init__(self, *args, **kwargs):
dict.__setattr__(self, '_formatter', string.Formatter())
super(_RecursiveAttributeDict, self).__init__(*args, **kwargs)
def evaluate_item(self, key, keys_so_far=None):
"""
Recursively evaluates a single dictionary item.
"""
if keys_so_far is None:
keys_so_far = set()
if key in keys_so_far:
raise CircularReference(key)
keys_so_far.add(key)
raw_value = dict.__getitem__(self, key)
if not isinstance(raw_value, basestring):
return raw_value
subkeys = dict(
(key,None) for t,key,f,c in
self._formatter.parse(raw_value)
if key
)
# Recursively evaluate subkeys
try:
for subkey in subkeys:
subkeys[subkey] = self.evaluate_item(subkey, keys_so_far)
except CircularReference as e:
raise CircularReference(key, e)
return self._formatter.format(raw_value, **subkeys)
def __getitem__(self, key):
return self.evaluate_item(key)
__getattr__ = __getitem__
if __name__ == '__main__':
env = _RecursiveAttributeDict()
env.first = 'first depends on {second}'
env.second = 'second depends on {third}'
env.third = 'third stands alone'
env.fourth = 'fourth depends on {first} and {ninth}'
env.fifth = 'fifth depends on {fourth}'
env.sixth = 'sixth stands alone'
env.seventh = 'seventh depends on {sixth}'
env.eighth = 'eighth depends on {seventh}'
env.ninth = 'ninth depends on {eighth}'
print '%(first)s vs %(fifth)s' % env
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment