Commits

Olemis Lang  committed e52fe6f

Trac #11148 : Implemented 'extends_with_prefix' based on trac:comment:9ticket11148 by cboos.

Side effects : Realm-specific method names .

  • Participants
  • Parent commits 2fd5c1f
  • Branches trac_t11148

Comments (0)

Files changed (1)

File t11148/t11148_r11782_IEntityChangeListener_extends_with_prefix.diff

 
 diff -r 8d0c3223a818 trac/core.py
 --- a/trac/core.py	Thu Apr 18 14:30:21 2013 +0000
-+++ b/trac/core.py	Thu Apr 18 21:57:48 2013 -0500
-@@ -165,8 +165,15 @@
++++ b/trac/core.py	Fri Apr 19 00:27:22 2013 -0500
+@@ -165,8 +165,30 @@
  
          locals_.setdefault('_implements', []).extend(interfaces)
  
  
 -implements = Component.implements
 +# FIXME : Move to e.g. trac.util ?
-+def extends_with_prefix(prefix):
++# FIXME : extends in function name does not apply anymore
++def extends_with_prefix(base, prefix):
++    from functools import update_wrapper
++    from types import MethodType
++
 +    def prefix_decorator(class_):
 +        class_._extends_with_prefix = prefix
++        for name in dir(base):
++            if not name.startswith('__') and not hasattr(class_, name):
++                target = getattr(base, name)
++                if isinstance(target, MethodType):
++                    parts = name.split('_', 1)
++                    gen_name = '_'.join([prefix, parts[-1]])
++                    # TODO: Cosmetic improvements in doc et al.
++                    gen_method = update_wrapper(lambda *args, **kwargs: None,
++                                                target)
++                    gen_method.__name__ = gen_method.func_name = gen_name
++                    setattr(class_, gen_name, gen_method)
 +        return class_
 +
 +    return prefix_decorator
  
  
  class ComponentManager(object):
-@@ -236,3 +243,92 @@
+@@ -236,3 +258,82 @@
          with the given class will not be available.
          """
          return True
 +                "Notification interface can not be None. " +
 +                "Target method name is %s" % method_name)
 +
-+        if hasattr(interface, "_extends_with_prefix"):
-+            prefix = interface._extends_with_prefix
-+            if prefix:
-+                prefixed_method_name =  self._get_prefixed_method_name(
-+                    method_name, prefix)
-+
 +        for listener in self._get_listeners_for_interface(interface):
-+            if hasattr(listener, prefixed_method_name):
-+                method_to_call = prefixed_method_name
-+            else:
-+                method_to_call = method_name
-+            getattr(listener, method_to_call)(*args)
++            getattr(listener, method_name)(*args)
 +
 +        #TBD: we can also call listeners implemented IEntityChangeListener here
 +        #if community will agree that generic event listeners support is needed
 +
 diff -r 8d0c3223a818 trac/ticket/model.py
 --- a/trac/ticket/model.py	Thu Apr 18 14:30:21 2013 +0000
-+++ b/trac/ticket/model.py	Thu Apr 18 21:57:48 2013 -0500
++++ b/trac/ticket/model.py	Fri Apr 19 00:27:22 2013 -0500
 @@ -25,7 +25,8 @@
  from trac.attachment import Attachment
  from trac import core
  from trac.cache import cached
 -from trac.core import TracError
-+from trac.core import (TracError, ListenerNotifier, IEntityChangeListener,
-+                       extends_with_prefix)
++from trac.core import (extends_with_prefix, Interface, IEntityChangeListener, 
++                       ListenerNotifier, TracError)
  from trac.resource import Resource, ResourceNotFound
  from trac.ticket.api import TicketSystem
  from trac.util import embedded_numbers, partition
  
      def __init__(self, env, name=None, db=None):
          if not self.ticket_col:
-@@ -761,6 +763,10 @@
+@@ -761,6 +763,11 @@
                  except ValueError:
                      pass # Ignore cast error for this non-essential operation
              TicketSystem(self.env).reset_ticket_fields()
 +
-+        ListenerNotifier(self.env).notify(
-+            self.change_listener_interface.entity_deleted,
-+            self)
++        interface = self.change_listener_interface
++        ListenerNotifier(self.env).notify(getattr(interface, 
++                    interface._extends_with_prefix + '_deleted'),
++                    self)
          self.value = self._old_value = None
          self.name = self._old_name = None
  
-@@ -788,6 +794,9 @@
+@@ -788,6 +795,10 @@
  
          self._old_name = self.name
          self._old_value = self.value
-+        ListenerNotifier(self.env).notify(
-+            self.change_listener_interface.entity_created,
-+            self)
++        interface = self.change_listener_interface
++        ListenerNotifier(self.env).notify(getattr(interface, 
++                    interface._extends_with_prefix + '_created'),
++                    self)
  
      def update(self, db=None):
          """Update the enum value.
-@@ -811,8 +820,15 @@
+@@ -811,8 +822,17 @@
                     (self.name, self._old_name))
              TicketSystem(self.env).reset_ticket_fields()
  
 +            old_values["value"] = self._old_value
          self._old_name = self.name
          self._old_value = self.value
-+        ListenerNotifier(self.env).notify(
-+            self.change_listener_interface.entity_changed, self, old_values)
++        interface = self.change_listener_interface
++        ListenerNotifier(self.env).notify(getattr(interface, 
++                    interface._extends_with_prefix + '_changed'),
++                    self, old_values)
  
      @classmethod
      def select(cls, env, db=None):
-@@ -831,9 +847,15 @@
+@@ -831,9 +851,15 @@
                  yield obj
  
  
-+@extends_with_prefix("type")
-+class ITypeChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "type")
++class ITypeChangeListener(Interface):
 +    pass
 +
 +
  
  
  class Status(object):
-@@ -848,16 +870,39 @@
+@@ -848,16 +874,39 @@
              yield status
  
  
-+@extends_with_prefix("resolution")
-+class IResolutionChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "resolution")
++class IResolutionChangeListener(Interface):
 +    pass
 +
 +
 +    change_listener_interface = IResolutionChangeListener
 +
 +
-+@extends_with_prefix("priority")
-+class IPriorityChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "priority")
++class IPriorityChangeListener(Interface):
 +    pass
  
  
 +    change_listener_interface = IPriorityChangeListener
 +
 +
-+@extends_with_prefix("severity")
-+class ISeverityChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "severity")
++class ISeverityChangeListener(Interface):
 +    pass
  
  
 +    change_listener_interface = ISeverityChangeListener
 +
 +
-+@extends_with_prefix("component")
-+class IComponentChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "component")
++class IComponentChangeListener(Interface):
 +   pass 
  
  
  class Component(object):
-@@ -893,9 +938,13 @@
+@@ -893,9 +942,13 @@
          with self.env.db_transaction as db:
              self.env.log.info("Deleting component %s", self.name)
              db("DELETE FROM component WHERE name=%s", (self.name,))
              TicketSystem(self.env).reset_ticket_fields()
  
 +        ListenerNotifier(self.env).notify(
-+            IComponentChangeListener.entity_deleted, self)
++                    IComponentChangeListener.component_deleted, self)
 +
 +        self.name = self._old_name = None
 +
      def insert(self, db=None):
          """Insert a new component.
  
-@@ -915,6 +964,9 @@
+@@ -915,6 +968,9 @@
              self._old_name = self.name
              TicketSystem(self.env).reset_ticket_fields()
  
 +        ListenerNotifier(self.env).notify(
-+            IComponentChangeListener.entity_created, self)
++                    IComponentChangeListener.component_created, self)
 +
      def update(self, db=None):
          """Update the component.
  
-@@ -926,6 +978,7 @@
+@@ -926,6 +982,7 @@
          if not self.name:
              raise TracError(_("Invalid component name."))
  
          with self.env.db_transaction as db:
              self.env.log.info("Updating component '%s'", self.name)
              db("""UPDATE component SET name=%s,owner=%s, description=%s
-@@ -939,6 +992,13 @@
+@@ -939,6 +996,13 @@
                  self._old_name = self.name
              TicketSystem(self.env).reset_ticket_fields()
  
 +        if self.name != old_name:
 +            old_values["name"] = old_name
 +        ListenerNotifier(self.env).notify(
-+            IComponentChangeListener.entity_changed, self, old_values)
++                    IComponentChangeListener.component_changed, self, old_values)
 +
      @classmethod
      def select(cls, env, db=None):
          """
-@@ -1170,6 +1230,11 @@
+@@ -1170,6 +1234,11 @@
      return groups
  
  
-+@extends_with_prefix("version")
-+class IVersionChangeListener(IEntityChangeListener):
++@extends_with_prefix(IEntityChangeListener, "version")
++class IVersionChangeListener(Interface):
 +    pass
 +
 +
  class Version(object):
      def __init__(self, env, name=None, db=None):
          self.env = env
-@@ -1199,9 +1264,12 @@
+@@ -1199,9 +1268,12 @@
          with self.env.db_transaction as db:
              self.env.log.info("Deleting version %s", self.name)
              db("DELETE FROM version WHERE name=%s", (self.name,))
              TicketSystem(self.env).reset_ticket_fields()
  
 +        ListenerNotifier(self.env).notify(
-+            IVersionChangeListener.entity_deleted, self)
++            IVersionChangeListener.version_deleted, self)
 +        self.name = self._old_name = None
 +
      def insert(self, db=None):
          """Insert a new version.
  
-@@ -1220,6 +1288,9 @@
+@@ -1220,6 +1292,9 @@
              self._old_name = self.name
              TicketSystem(self.env).reset_ticket_fields()
  
 +        ListenerNotifier(self.env).notify(
-+            IVersionChangeListener.entity_created, self)
++            IVersionChangeListener.version_created, self)
 +
      def update(self, db=None):
          """Update the version.
  
-@@ -1231,6 +1302,7 @@
+@@ -1231,6 +1306,7 @@
          if not self.name:
              raise TracError(_("Invalid version name."))
  
          with self.env.db_transaction as db:
              self.env.log.info("Updating version '%s'", self.name)
              db("""UPDATE version
-@@ -1244,6 +1316,14 @@
+@@ -1244,6 +1320,14 @@
                  self._old_name = self.name
              TicketSystem(self.env).reset_ticket_fields()
  
 +            old_values["name"] = old_name
 +
 +        ListenerNotifier(self.env).notify(
-+            IVersionChangeListener.entity_changed, self, old_values)
++            IVersionChangeListener.version_changed, self, old_values)
 +
      @classmethod
      def select(cls, env, db=None):
          """
 diff -r 8d0c3223a818 trac/ticket/tests/model.py
 --- a/trac/ticket/tests/model.py	Thu Apr 18 14:30:21 2013 +0000
-+++ b/trac/ticket/tests/model.py	Thu Apr 18 21:57:48 2013 -0500
++++ b/trac/ticket/tests/model.py	Fri Apr 19 00:27:22 2013 -0500
 @@ -12,8 +12,8 @@
  from trac.core import TracError, implements
  from trac.resource import ResourceNotFound
  from trac.ticket.api import (
      IMilestoneChangeListener, ITicketChangeListener, TicketSystem
  )
-@@ -1097,6 +1097,169 @@
+@@ -1097,6 +1097,228 @@
          self.assertEqual([('Test', 0, 'Some text')], self.env.db_query(
              "SELECT name, time, description FROM version WHERE name='Test'"))
  
 +    def callback(self, action, entity, changeinfo, old_values = None):
 +        pass
 +
-+    def entity_created(self, entity, changeinfo = None):
++    def _entity_created(self, entity, changeinfo = None):
 +        self.action = "created"
 +        self.entity = entity
 +        self.changeinfo = changeinfo
 +        self.callback(self.action, entity, changeinfo)
 +
-+    def entity_changed(self, entity, old_values, changeinfo = None):
++    def _entity_changed(self, entity, old_values, changeinfo = None):
 +        self.action = "changed"
 +        self.entity = entity
 +        self.old_values = old_values
 +        self.callback(
 +            self.action, entity, changeinfo, old_values=self.old_values)
 +
-+    def entity_deleted(self, entity, changeinfo = None):
++    def _entity_deleted(self, entity, changeinfo = None):
 +        self.action = "deleted"
 +        self.entity = entity
 +        self.changeinfo = changeinfo
 +        self.callback(self.action, entity, changeinfo)
 +
-+    def entity_reparented(self, entity, changeinfo = None):
++    def _entity_reparented(self, entity, changeinfo = None):
 +        self.action = "reparented"
 +        self.entity = entity
 +        self.changeinfo = changeinfo
 +    def listener_callback(self, action, entity, changeinfo, old_values = None):
 +        self.entity_name = self._get_entity_name(entity)
 +
++
 +class ComponentChangeListenerMock(EntityChangeListenerMock):
 +    implements(IComponentChangeListener)
 +
++    def component_created(self, entity, changeinfo = None):
++        return self._entity_created(entity, changeinfo)
++
++    def component_changed(self, entity, old_values, changeinfo = None):
++        return self._entity_changed(entity, old_values, changeinfo)
++
++    def component_deleted(self, entity, changeinfo = None):
++        return self._entity_deleted(entity, changeinfo)
++
++    def component_reparented(self, entity, changeinfo = None):
++        return self._entity_reparented(entity, changeinfo)
++
++
 +class ComponentEntityChangeListenerTestCase(
 +    BaseEntityChangeListenerTestCase):
 +    entity_type = Component
 +    entityChangeListener = ComponentChangeListenerMock
 +
++    def test_all_methods(self):
++        self.assertTrue(hasattr(IComponentChangeListener, 'component_created'))
++        self.assertTrue(hasattr(IComponentChangeListener, 'component_changed'))
++        self.assertTrue(hasattr(IComponentChangeListener, 'component_deleted'))
++        self.assertTrue(hasattr(IComponentChangeListener, 'component_reparented'))
++
++
 +class VersionChangeListenerMock(EntityChangeListenerMock):
 +    implements(IVersionChangeListener)
 +    entity_type = Component
 +
++    def version_created(self, entity, changeinfo = None):
++        return self._entity_created(entity, changeinfo)
++
++    def version_changed(self, entity, old_values, changeinfo = None):
++        return self._entity_changed(entity, old_values, changeinfo)
++
++    def version_deleted(self, entity, changeinfo = None):
++        return self._entity_deleted(entity, changeinfo)
++
++    def version_reparented(self, entity, changeinfo = None):
++        return self._entity_reparented(entity, changeinfo)
++
++
 +class VersionEntityChangeListenerTestCase(
 +    BaseEntityChangeListenerTestCase):
 +    entity_type = Version
 +    entityChangeListener = VersionChangeListenerMock
 +
++    def test_all_methods(self):
++        self.assertTrue(hasattr(IVersionChangeListener, 'version_created'))
++        self.assertTrue(hasattr(IVersionChangeListener, 'version_changed'))
++        self.assertTrue(hasattr(IVersionChangeListener, 'version_deleted'))
++        self.assertTrue(hasattr(IVersionChangeListener, 'version_reparented'))
++
 +class PriorityChangeListenerMock(EntityChangeListenerMock):
 +    implements(IPriorityChangeListener)
 +
++    def priority_created(self, entity, changeinfo = None):
++        return self._entity_created(entity, changeinfo)
++
++    def priority_changed(self, entity, old_values, changeinfo = None):
++        return self._entity_changed(entity, old_values, changeinfo)
++
++    def priority_deleted(self, entity, changeinfo = None):
++        return self._entity_deleted(entity, changeinfo)
++
++    def priority_reparented(self, entity, changeinfo = None):
++        return self._entity_reparented(entity, changeinfo)
++
++
 +class PriorityEntityChangeListenerTestCase(
 +    BaseEntityChangeListenerTestCase):
 +    entity_type = Priority
 +    entityChangeListener = PriorityChangeListenerMock
 +
++    def test_all_methods(self):
++        self.assertTrue(hasattr(IPriorityChangeListener, 'priority_created'))
++        self.assertTrue(hasattr(IPriorityChangeListener, 'priority_changed'))
++        self.assertTrue(hasattr(IPriorityChangeListener, 'priority_deleted'))
++        self.assertTrue(hasattr(IPriorityChangeListener, 'priority_reparented'))
++
 +class MultipleEntitiesChangeListenerMock(core.Component):
 +    implements(IComponentChangeListener, IVersionChangeListener)
 +
  
  def suite():
      suite = unittest.TestSuite()
-@@ -1107,6 +1270,14 @@
+@@ -1107,6 +1329,14 @@
      suite.addTest(unittest.makeSuite(MilestoneTestCase, 'test'))
      suite.addTest(unittest.makeSuite(ComponentTestCase, 'test'))
      suite.addTest(unittest.makeSuite(VersionTestCase, 'test'))