From 6a94d050bbd72b3812fd7cb05445a66484103214 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 2 Dec 2008 10:14:06 -0500 Subject: Added decorator-style properties to bugdir. Created settings_object module. settings_object.SavedSettingsObject encapsulates some of the common settings functionality in the BE BugDir, Bug, and Comment classes. It's a bit awkward due to the nature of scoping in python subclasses, but it's better than reproducing this code in each of the above classes. Now I need to move Bug and Comment over to *this* system ;). --- libbe/properties.py | 95 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 18 deletions(-) (limited to 'libbe/properties.py') diff --git a/libbe/properties.py b/libbe/properties.py index f55dc0e..176e898 100644 --- a/libbe/properties.py +++ b/libbe/properties.py @@ -134,6 +134,30 @@ def defaulting_property(default=None, null=None): return funcs return decorator +def fn_checked_property(value_allowed_fn): + """ + Define allowed values for get/set access to a property. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + fset = funcs.get("fset") + name = funcs.get("name", "") + def _fget(self): + value = fget(self) + if value_allowed_fn(value) != True: + raise ValueCheckError(name, value, value_allowed_fn) + return value + def _fset(self, value): + if value_allowed_fn(value) != True: + raise ValueCheckError(name, value, value_allowed_fn) + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + return funcs + return decorator + def checked_property(allowed=[]): """ Define allowed values for get/set access to a property. @@ -163,14 +187,22 @@ def cached_property(generator, initVal=None): Allow caching of values generated by generator(instance), where instance is the instance to which this property belongs. Uses .__cache to store a cache flag for a particular owner - instance. When the cache flag is True (or missing), the normal - value is returned. Otherwise the generator is called (and it's - output stored) for every get. The cache flag is missing on - initialization. Particular instances may override by setting - their own flag. + instance. + + When the cache flag is True or missing and the stored value is + initVal, the first fget call triggers the generator function, + whiose output is stored in __cached_value. That and + subsequent calls to fget will return this cached value. + + If the input value is no longer initVal (e.g. a value has been + loaded from disk or set with fset), that value overrides any + cached value, and this property has no effect. - If caching is True, but the stored value == initVal, the parameter - is considered 'uninitialized', and the generator is called anyway. + When the cache flag is False and the stored value is initVal, the + generator is not cached, but is called on every fget. + + The cache flag is missing on initialization. Particular instances + may override by setting their own flag. """ def decorator(funcs): if hasattr(funcs, "__call__"): @@ -180,11 +212,17 @@ def cached_property(generator, initVal=None): name = funcs.get("name", "") def _fget(self): cache = getattr(self, "_%s_cache" % name, True) + value = fget(self) if cache == True: - value = fget(self) - if cache == False or (cache == True and value == initVal): - value = generator(self) - fset(self, value) + if value == initVal: + if hasattr(self, "_%s_cached_value" % name): + value = getattr(self, "_%s_cached_value" % name) + else: + value = generator(self) + setattr(self, "_%s_cached_value" % name, value) + else: + if value == initVal: + value = generator(self) return value funcs["fget"] = _fget return funcs @@ -339,6 +377,22 @@ class DecoratorTests(unittest.TestCase): t.a = 'a' t.a = 'b' t.a = 'c' + def testFnCheckedLocalProperty(self): + class Test(object): + @Property + @fn_checked_property(lambda v : v in ['x', 'y', 'z']) + @local_property(name="CHECKED") + def x(): return {} + def __init__(self): + self._CHECKED_value = 'x' + t = Test() + self.failUnless(t.x == 'x', str(t.x)) + try: + t.x = None + e = None + except ValueCheckError, e: + pass + self.failUnless(type(e) == ValueCheckError, type(e)) def testCachedLocalProperty(self): class Gen(object): def __init__(self): @@ -359,17 +413,22 @@ class DecoratorTests(unittest.TestCase): t.x = 8 self.failUnless(t.x == 8, t.x) self.failUnless(t.x == 8, t.x) - t._CACHED_cache = False - val = t.x - self.failUnless(val == 2, val) + t._CACHED_cache = False # Caching is off, but the stored value + val = t.x # is 8, not the initVal (None), so we + self.failUnless(val == 8, val) # get 8. + t._CACHED_value = None # Now we've set the stored value to None + val = t.x # so future calls to fget (like this) + self.failUnless(val == 2, val) # will call the generator every time... val = t.x self.failUnless(val == 3, val) val = t.x self.failUnless(val == 4, val) - t._CACHED_cache = True - self.failUnless(t.x == 4, str(t.x)) - self.failUnless(t.x == 4, str(t.x)) - self.failUnless(t.x == 4, str(t.x)) + t._CACHED_cache = True # We turn caching back on, and get + self.failUnless(t.x == 1, str(t.x)) # the original cached value. + del t._CACHED_cached_value # Removing that value forces a + self.failUnless(t.x == 5, str(t.x)) # single cache-regenerating call + self.failUnless(t.x == 5, str(t.x)) # to the genenerator, after which + self.failUnless(t.x == 5, str(t.x)) # we get the new cached value. def testPrimedLocalProperty(self): class Test(object): def prime(self): -- cgit