Author: jure
Date: Fri Mar 15 10:28:53 2013
New Revision: 1456865
URL: http://svn.apache.org/r1456865
Log:
#440, product environments as parametric singletons, patch
t440_1456016_product_env_singleton.diff applied (from Olemis)
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/cache.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/cache.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/cache.py?rev=1456865&r1=1456864&r2=1456865&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/cache.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/cache.py
Fri Mar 15 10:28:53 2013
@@ -3,6 +3,10 @@
# Developed by Raymond Hettinger
# (http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/)
#
+# March 13, 2013 updated by Olemis Lang
+# Added keymap arg to build custom keys out of actual args
+# March 14, 2013 updated by Olemis Lang
+# Keep cache consistency on user function failure
import collections
import functools
@@ -15,7 +19,7 @@ class Counter(dict):
def __missing__(self, key):
return 0
-def lru_cache(maxsize=100):
+def lru_cache(maxsize=100, keymap=None):
'''Least-recently-used cache decorator.
Arguments to the cached function must be hashable.
@@ -23,6 +27,8 @@ def lru_cache(maxsize=100):
Clear the cache with f.clear().
http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+ :param keymap: build custom keys out of actual arguments.
+ Its signature will be lambda (args, kwds, kwd_mark)
'''
maxqueue = maxsize * 10
def decorating_function(user_function,
@@ -40,20 +46,32 @@ def lru_cache(maxsize=100):
@functools.wraps(user_function)
def wrapper(*args, **kwds):
# cache key records both positional and keyword args
- key = args
- if kwds:
- key += (kwd_mark,) + tuple(sorted(kwds.items()))
-
- # record recent use of this key
- queue_append(key)
- refcount[key] += 1
+ if keymap is None:
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ else:
+ key = keymap(args, kwds, kwd_mark)
# get cache entry or compute if not found
try:
result = cache[key]
wrapper.hits += 1
+
+ # record recent use of this key
+ queue_append(key)
+ refcount[key] += 1
except KeyError:
- result = user_function(*args, **kwds)
+ # Explicit exception handling for readability
+ try:
+ result = user_function(*args, **kwds)
+ except:
+ raise
+ else:
+ # record recent use of this key
+ queue_append(key)
+ refcount[key] += 1
+
cache[key] = result
wrapper.misses += 1
@@ -91,7 +109,7 @@ def lru_cache(maxsize=100):
return decorating_function
-def lfu_cache(maxsize=100):
+def lfu_cache(maxsize=100, keymap=None):
'''Least-frequenty-used cache decorator.
Arguments to the cached function must be hashable.
@@ -99,6 +117,8 @@ def lfu_cache(maxsize=100):
Clear the cache with f.clear().
http://en.wikipedia.org/wiki/Least_Frequently_Used
+ :param keymap: build custom keys out of actual arguments.
+ Its signature will be lambda (args, kwds, kwd_mark)
'''
def decorating_function(user_function):
cache = {} # mapping of args to results
@@ -107,9 +127,12 @@ def lfu_cache(maxsize=100):
@functools.wraps(user_function)
def wrapper(*args, **kwds):
- key = args
- if kwds:
- key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ if keymap is None:
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ else:
+ key = keymap(args, kwds, kwd_mark)
use_count[key] += 1
# get cache entry or compute if not found
@@ -140,3 +163,12 @@ def lfu_cache(maxsize=100):
return wrapper
return decorating_function
+#----------------------
+# Helper functions
+#----------------------
+
+def default_keymap(args, kwds, kwd_mark):
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ return key
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py?rev=1456865&r1=1456864&r2=1456865&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
Fri Mar 15 10:28:53 2013
@@ -23,7 +23,8 @@ from urlparse import urlsplit
from sqlite3 import OperationalError
from trac.config import BoolOption, ConfigSection, Option
-from trac.core import Component, ComponentManager, implements, Interface,
ExtensionPoint
+from trac.core import Component, ComponentManager, ComponentMeta, \
+ ExtensionPoint, implements, Interface
from trac.db.api import TransactionContextManager, QueryContextManager,
DatabaseManager
from trac.util import get_pkginfo, lazy
from trac.util.compat import sha1
@@ -32,6 +33,7 @@ from trac.versioncontrol import Reposito
from trac.web.href import Href
from multiproduct.api import MultiProductSystem,
ISupportMultiProductEnvironment
+from multiproduct.cache import lru_cache, default_keymap
from multiproduct.config import Configuration
from multiproduct.dbcursor import ProductEnvContextManager,
BloodhoundConnectionWrapper, BloodhoundIterableCursor
from multiproduct.model import Product
@@ -319,7 +321,7 @@ class ProductEnvironment(Component, Comp
Product environments contain among other things:
- * a configuration file,
+ * configuration key-value pairs stored in the database,
* product-aware clones of the wiki and ticket attachments files,
Product environments do not have:
@@ -330,6 +332,32 @@ class ProductEnvironment(Component, Comp
See https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003
"""
+
+ class __metaclass__(ComponentMeta):
+
+ def product_env_keymap(args, kwds, kwd_mark):
+ # Remove meta-reference to self (i.e. product env class)
+ args = args[1:]
+ try:
+ product = kwds['product']
+ except KeyError:
+ # Product provided as positional argument
+ if isinstance(args[1], Product):
+ args = (args[0], args[1].prefix) + args[2:]
+ else:
+ # Product supplied as keyword argument
+ if isinstance(product, Product):
+ kwds['product'] = product.prefix
+ return default_keymap(args, kwds, kwd_mark)
+
+ @lru_cache(maxsize=100, keymap=product_env_keymap)
+ def __call__(self, *args, **kwargs):
+ """Return an existing instance of there is a hit
+ in the global LRU cache, otherwise create a new instance.
+ """
+ return ComponentMeta.__call__(self, *args, **kwargs)
+
+ del product_env_keymap
implements(trac.env.ISystemInfoProvider)
@@ -857,10 +885,4 @@ class ProductEnvironment(Component, Comp
lookup_product_env = ProductEnvironment.lookup_env
resolve_product_href = ProductEnvironment.resolve_href
-from multiproduct.cache import lru_cache
-
-@lru_cache(maxsize=100)
-def ProductEnvironmentFactory(global_env, product):
- """Product environment factory
- """
- return ProductEnvironment(global_env, product)
+ProductEnvironmentFactory = ProductEnvironment
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py?rev=1456865&r1=1456864&r2=1456865&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
Fri Mar 15 10:28:53 2013
@@ -490,6 +490,41 @@ class ProductEnvApiTestCase(Multiproduct
self.assertEquals('value1', global_config['section'].get('key'))
self.assertEquals('value2', product_config['section'].get('key'))
+ def test_parametric_singleton(self):
+ self.assertIs(self.product_env,
+ ProductEnvironment(self.env, self.default_product))
+
+ for prefix in self.PRODUCT_DATA:
+ if prefix != self.default_product:
+ self._load_product_from_data(self.env, prefix)
+
+ envgen1 = dict([prefix, ProductEnvironment(self.env, prefix)]
+ for prefix in self.PRODUCT_DATA)
+ envgen2 = dict([prefix, ProductEnvironment(self.env, prefix)]
+ for prefix in self.PRODUCT_DATA)
+
+ for prefix, env1 in envgen1.iteritems():
+ self.assertIs(env1, envgen2[prefix],
+ "Identity check (by prefix) '%s'" % (prefix,))
+
+ for prefix, env1 in envgen1.iteritems():
+ self.assertIs(env1, envgen2[prefix],
+ "Identity check (by prefix) '%s'" % (prefix,))
+
+ def load_product(prefix):
+ products = Product.select(self.env, where={'prefix' : prefix})
+ if not products:
+ raise LookupError('Missing product %s' % (prefix,))
+ else:
+ return products[0]
+
+ envgen3 = dict([prefix, ProductEnvironment(self.env,
+ load_product(prefix))]
+ for prefix in self.PRODUCT_DATA)
+
+ for prefix, env1 in envgen1.iteritems():
+ self.assertIs(env1, envgen3[prefix],
+ "Identity check (by product model) '%s'" % (prefix,))
def test_suite():
return unittest.TestSuite([