aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/properties.py
diff options
context:
space:
mode:
Diffstat (limited to 'libbe/properties.py')
-rw-r--r--libbe/properties.py95
1 files changed, 77 insertions, 18 deletions
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", "<unknown>")
+ 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
._<name>_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 _<name>_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", "<unknown>")
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):