server/hook.py
changeset 6142 8bc6eac1fac1
parent 5877 0c7b7b76a84f
child 6147 95c604ec89bf
equal deleted inserted replaced
6141:b8287e54b528 6142:8bc6eac1fac1
    59 from logilab.common.decorators import classproperty
    59 from logilab.common.decorators import classproperty
    60 from logilab.common.deprecation import deprecated
    60 from logilab.common.deprecation import deprecated
    61 from logilab.common.logging_ext import set_log_methods
    61 from logilab.common.logging_ext import set_log_methods
    62 
    62 
    63 from cubicweb import RegistryNotFound
    63 from cubicweb import RegistryNotFound
       
    64 from cubicweb.vregistry import classid
    64 from cubicweb.cwvreg import CWRegistry, VRegistry
    65 from cubicweb.cwvreg import CWRegistry, VRegistry
    65 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
    66 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
    66                                 is_instance)
    67                                 is_instance)
    67 from cubicweb.appobject import AppObject
    68 from cubicweb.appobject import AppObject
    68 from cubicweb.server.session import security_enabled
    69 from cubicweb.server.session import security_enabled
    81 class HooksRegistry(CWRegistry):
    82 class HooksRegistry(CWRegistry):
    82     def initialization_completed(self):
    83     def initialization_completed(self):
    83         for appobjects in self.values():
    84         for appobjects in self.values():
    84             for cls in appobjects:
    85             for cls in appobjects:
    85                 if not cls.enabled:
    86                 if not cls.enabled:
    86                     warn('[3.6] %s: enabled is deprecated' % cls)
    87                     warn('[3.6] %s: enabled is deprecated' % classid(cls))
    87                     self.unregister(cls)
    88                     self.unregister(cls)
    88 
    89 
    89     def register(self, obj, **kwargs):
    90     def register(self, obj, **kwargs):
    90         obj.check_events()
    91         obj.check_events()
    91         super(HooksRegistry, self).register(obj, **kwargs)
    92         super(HooksRegistry, self).register(obj, **kwargs)
   117 
   118 
   118 
   119 
   119 for event in ALL_HOOKS:
   120 for event in ALL_HOOKS:
   120     VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
   121     VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
   121 
   122 
   122 _MARKER = object()
   123 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)')
   123 def entity_oldnewvalue(entity, attr):
   124 def entity_oldnewvalue(entity, attr):
   124     """returns the couple (old attr value, new attr value)
   125     return entity.cw_edited.oldnewvalue(attr)
   125 
       
   126     NOTE: will only work in a before_update_entity hook
       
   127     """
       
   128     # get new value and remove from local dict to force a db query to
       
   129     # fetch old value
       
   130     newvalue = entity.pop(attr, _MARKER)
       
   131     oldvalue = getattr(entity, attr)
       
   132     if newvalue is not _MARKER:
       
   133         entity[attr] = newvalue
       
   134     else:
       
   135         newvalue = oldvalue
       
   136     return oldvalue, newvalue
       
   137 
   126 
   138 
   127 
   139 # some hook specific selectors #################################################
   128 # some hook specific selectors #################################################
   140 
   129 
   141 @objectify_selector
   130 @objectify_selector
   229         cls.check_events()
   218         cls.check_events()
   230         return ['%s_hooks' % ev for ev in cls.events]
   219         return ['%s_hooks' % ev for ev in cls.events]
   231 
   220 
   232     @classproperty
   221     @classproperty
   233     def __regid__(cls):
   222     def __regid__(cls):
   234         warn('[3.6] %s.%s: please specify an id for your hook'
   223         warn('[3.6] %s: please specify an id for your hook' % classid(cls),
   235              % (cls.__module__, cls.__name__), DeprecationWarning)
   224              DeprecationWarning)
   236         return str(id(cls))
   225         return str(id(cls))
   237 
   226 
   238     @classmethod
   227     @classmethod
   239     def __registered__(cls, reg):
   228     def __registered__(cls, reg):
   240         super(Hook, cls).__registered__(reg)
   229         super(Hook, cls).__registered__(reg)
   241         if getattr(cls, 'accepts', None):
   230         if getattr(cls, 'accepts', None):
   242             warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'
   231             warn('[3.6] %s: accepts is deprecated, define proper __select__'
   243                  % (cls.__module__, cls.__name__), DeprecationWarning)
   232                  % classid(cls), DeprecationWarning)
   244             rtypes = []
   233             rtypes = []
   245             for ertype in cls.accepts:
   234             for ertype in cls.accepts:
   246                 if ertype.islower():
   235                 if ertype.islower():
   247                     rtypes.append(ertype)
   236                     rtypes.append(ertype)
   248                 else:
   237                 else:
   259         super(Hook, self).__init__(req, **kwargs)
   248         super(Hook, self).__init__(req, **kwargs)
   260         self.event = event
   249         self.event = event
   261 
   250 
   262     def __call__(self):
   251     def __call__(self):
   263         if hasattr(self, 'call'):
   252         if hasattr(self, 'call'):
   264             cls = self.__class__
   253             warn('[3.6] %s: call is deprecated, implement __call__'
   265             warn('[3.6] %s.%s: call is deprecated, implement __call__'
   254                  % classid(self.__class__), DeprecationWarning)
   266                  % (cls.__module__, cls.__name__), DeprecationWarning)
       
   267             if self.event.endswith('_relation'):
   255             if self.event.endswith('_relation'):
   268                 self.call(self._cw, self.eidfrom, self.rtype, self.eidto)
   256                 self.call(self._cw, self.eidfrom, self.rtype, self.eidto)
   269             elif 'delete' in self.event:
   257             elif 'delete' in self.event:
   270                 self.call(self._cw, self.entity.eid)
   258                 self.call(self._cw, self.entity.eid)
   271             elif self.event.startswith('server_'):
   259             elif self.event.startswith('server_'):
   426             return None
   414             return None
   427         return -(i + 1)
   415         return -(i + 1)
   428 
   416 
   429     def handle_event(self, event):
   417     def handle_event(self, event):
   430         """delegate event handling to the opertaion"""
   418         """delegate event handling to the opertaion"""
       
   419         if event == 'postcommit_event' and hasattr(self, 'commit_event'):
       
   420             warn('[3.10] %s: commit_event method has been replaced by postcommit_event'
       
   421                  % classid(self.__class__), DeprecationWarning)
       
   422             self.commit_event()
   431         getattr(self, event)()
   423         getattr(self, event)()
   432 
   424 
   433     def precommit_event(self):
   425     def precommit_event(self):
   434         """the observed connections pool is preparing a commit"""
   426         """the observed connections pool is preparing a commit"""
   435 
   427 
   436     def revertprecommit_event(self):
   428     def revertprecommit_event(self):
   437         """an error went when pre-commiting this operation or a later one
   429         """an error went when pre-commiting this operation or a later one
   438 
   430 
   439         should revert pre-commit's changes but take care, they may have not
   431         should revert pre-commit's changes but take care, they may have not
   440         been all considered if it's this operation which failed
       
   441         """
       
   442 
       
   443     def commit_event(self):
       
   444         """the observed connections pool is commiting"""
       
   445 
       
   446     def revertcommit_event(self):
       
   447         """an error went when commiting this operation or a later one
       
   448 
       
   449         should revert commit's changes but take care, they may have not
       
   450         been all considered if it's this operation which failed
   432         been all considered if it's this operation which failed
   451         """
   433         """
   452 
   434 
   453     def rollback_event(self):
   435     def rollback_event(self):
   454         """the observed connections pool has been rollbacked
   436         """the observed connections pool has been rollbacked
   522         if i is None:
   504         if i is None:
   523             return None
   505             return None
   524         return -(i + 1)
   506         return -(i + 1)
   525 
   507 
   526 
   508 
   527 class SingleOperation(Operation):
   509 
   528     """special operation which should be called once"""
   510 class SingleLastOperation(Operation):
       
   511     """special operation which should be called once and after all other
       
   512     operations
       
   513     """
       
   514 
   529     def register(self, session):
   515     def register(self, session):
   530         """override register to handle cases where this operation has already
   516         """override register to handle cases where this operation has already
   531         been added
   517         been added
   532         """
   518         """
   533         operations = session.pending_operations
   519         operations = session.pending_operations
   544         for i, op in enumerate(reversed(operations)):
   530         for i, op in enumerate(reversed(operations)):
   545             if op.__class__ is self.__class__:
   531             if op.__class__ is self.__class__:
   546                 return -(i+1)
   532                 return -(i+1)
   547         return None
   533         return None
   548 
   534 
   549 
       
   550 class SingleLastOperation(SingleOperation):
       
   551     """special operation which should be called once and after all other
       
   552     operations
       
   553     """
       
   554     def insert_index(self):
   535     def insert_index(self):
   555         return None
   536         return None
   556 
   537 
   557 
   538 
   558 class SendMailOp(SingleLastOperation):
   539 class SendMailOp(SingleLastOperation):
   570     def register(self, session):
   551     def register(self, session):
   571         previous = super(SendMailOp, self).register(session)
   552         previous = super(SendMailOp, self).register(session)
   572         if previous:
   553         if previous:
   573             self.to_send = previous.to_send + self.to_send
   554             self.to_send = previous.to_send + self.to_send
   574 
   555 
   575     def commit_event(self):
   556     def postcommit_event(self):
   576         self.session.repo.threaded_task(self.sendmails)
   557         self.session.repo.threaded_task(self.sendmails)
   577 
   558 
   578     def sendmails(self):
   559     def sendmails(self):
   579         self.session.vreg.config.sendmails(self.to_send)
   560         self.session.vreg.config.sendmails(self.to_send)
   580 
   561 
   610 class CleanupDeletedEidsCacheOp(SingleLastOperation):
   591 class CleanupDeletedEidsCacheOp(SingleLastOperation):
   611     """on commit of delete query, we have to remove from repository's
   592     """on commit of delete query, we have to remove from repository's
   612     type/source cache eids of entities deleted in that transaction.
   593     type/source cache eids of entities deleted in that transaction.
   613     """
   594     """
   614 
   595 
   615     def commit_event(self):
   596     def postcommit_event(self):
   616         """the observed connections pool has been rollbacked,
   597         """the observed connections pool has been rollbacked,
   617         remove inserted eid from repository type/source cache
   598         remove inserted eid from repository type/source cache
   618         """
   599         """
   619         try:
   600         try:
   620             self.session.repo.clear_caches(
   601             self.session.repo.clear_caches(