Created
August 31, 2010 19:02
-
-
Save ryanwitt/559534 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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