CacheValidation: simplify the setup of a cached value
 - added documentation for the decorators
 - infers the cache key from the retrieval method's module and name

diff --git a/trac/cache.py b/trac/cache.py
--- a/trac/cache.py
+++ b/trac/cache.py
@@ -25,6 +25,36 @@
 __all__ = ["CacheManager", "cached", "cached_value"]
 
 
+def cached_value(meth):
+    """Method decorator creating a cached attribute from a data retrieval
+    method.
+
+    Accessing the cached attribute gives back the cached value. The data
+    retrieval method will be called as needed by the CacheManager.
+    Invalidating the cache for this value is done by `del`eting the attribute.
+    
+    Note that the cache validity is maintained using a table in the database.
+    Most notably, a cache invalidation will trigger a commit, so don't do this
+    while another database operation is in progress.
+
+    If more control over the transaction is needed, see the `cached` decorator.
+    """
+    return CachedValue(meth.__module__ + '.' + meth.__name__, meth)
+
+def cached(meth):
+    """Method decorator creating a cached attribute from a data retrieval
+    method.
+
+    In contrast with cached attributes created by the `cached_value` decorator,
+    accessing a cached attribute created with `cached` will not directly give 
+    back the cached value.  Instead, this will return a proxy object with `get`
+    and `invalidate` methods, both accepting a `db` connection.
+    After calling `invalidate(db)`, doing a `commit` is the responsibility 
+    of the caller.
+    """
+    return Cached(meth.__module__ + '.' + meth.__name__, meth)
+
+
 class CachedValue(object):
     """Descriptor for a cached attribute value."""
     def __init__(self, key, retriever):
@@ -36,23 +66,18 @@
         if instance is None:
             return self
         return CacheManager(instance.env).get(
-            self.key, partial(self.retriever, instance))
+                self.key, partial(self.retriever, instance))
         
     def __delete__(self, instance):
         CacheManager(instance.env).invalidate(self.key)
 
-
-def cached_value(key):
-    """Method decorator creating a cached attribute value from a data
-    retrieval method.
-    
-    This decorator should be used when data retrieval and cache
-    invalidation can be done in a separate transaction.
-    """
-    def thunk(retriever):
-        return CachedValue(key, retriever)
-    return thunk
-
+class Cached(CachedValue):
+    """Descriptor for a cached attribute value with transaction control."""
+    def __get__(self, instance, owner):
+        if instance is None:
+            return self
+        return CacheProxy(self.key, partial(self.retriever, instance), 
+                          instance.env)
 
 class CacheProxy(object):
     """Cached attribute proxy."""
@@ -70,29 +95,6 @@
         CacheManager(self.env).invalidate(self.key, db)
 
 
-class Cached(CachedValue):
-    """Descriptor for a cached attribute."""
-    __slots__ = []
-    
-    def __get__(self, instance, owner):
-        if instance is None:
-            return self
-        return CacheProxy(self.key, partial(self.retriever, instance),
-                          instance.env)
-
-
-def cached(key):
-    """Method decorator creating a cached attribute from a data retrieval
-    method.
-    
-    This decorator can be used when data retrieval and cache invalidation
-    must be done in a given transaction.
-    """
-    def thunk(retriever):
-        return Cached(key, retriever)
-    return thunk
-
-
 class CacheManager(Component):
     """Cache manager component."""
     
diff --git a/trac/ticket/api.py b/trac/ticket/api.py
--- a/trac/ticket/api.py
+++ b/trac/ticket/api.py
@@ -191,7 +191,7 @@
         """Invalidate ticket field cache."""
         self.fields.invalidate(db)
 
-    @cached('ticket.TicketSystem.fields')
+    @cached
     def fields(self, db):
         """Return the list of fields available for tickets."""
         from trac.ticket import model
@@ -273,7 +273,7 @@
     def get_custom_fields(self):
         return [f.copy() for f in self.custom_fields]
 
-    @cached_value('ticket.TicketSystem.custom_fields')
+    @cached_value
     def custom_fields(self, db):
         """Return the list of custom ticket fields available for tickets."""
         fields = []
diff --git a/trac/wiki/api.py b/trac/wiki/api.py
--- a/trac/wiki/api.py
+++ b/trac/wiki/api.py
@@ -176,7 +176,7 @@
         For public sites where anonymous users can edit the wiki it is
         recommended to leave this option disabled (which is the default).""")
 
-    @cached_value('wiki.WikiSystem.pages')
+    @cached_value
     def pages(self, db):
         """Return the names of all existing wiki pages."""
         cursor = db.cursor()
diff --git a/trac/wiki/interwiki.py b/trac/wiki/interwiki.py
--- a/trac/wiki/interwiki.py
+++ b/trac/wiki/interwiki.py
@@ -96,7 +96,7 @@
         if page.name == InterWikiMap._page_name:
             del self.interwiki_map
 
-    @cached_value('wiki.interwiki.map')
+    @cached_value
     def interwiki_map(self, db):
         """Map from upper-cased namespaces to (namespace, prefix, title) 
         values.

