server/hook.py
changeset 8190 2a3c1b787688
parent 8032 bcb87336c7d2
child 8211 543e1579ba0d
equal deleted inserted replaced
8189:2ee0ef069fa7 8190:2a3c1b787688
     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
   231 :class:`~cubicweb.server.session.hooks_control` context manager to
   231 :class:`~cubicweb.server.session.hooks_control` context manager to
   232 filter them in or out. Note that ending the transaction with commit()
   232 filter them in or out. Note that ending the transaction with commit()
   233 or rollback() will restore the hooks.
   233 or rollback() will restore the hooks.
   234 
   234 
   235 
   235 
   236 Hooks specific selector
   236 Hooks specific predicate
   237 ~~~~~~~~~~~~~~~~~~~~~~~
   237 ~~~~~~~~~~~~~~~~~~~~~~~
   238 .. autoclass:: cubicweb.server.hook.match_rtype
   238 .. autoclass:: cubicweb.server.hook.match_rtype
   239 .. autoclass:: cubicweb.server.hook.match_rtype_sets
   239 .. autoclass:: cubicweb.server.hook.match_rtype_sets
   240 
   240 
   241 
   241 
   256 from itertools import chain
   256 from itertools import chain
   257 
   257 
   258 from logilab.common.decorators import classproperty, cached
   258 from logilab.common.decorators import classproperty, cached
   259 from logilab.common.deprecation import deprecated, class_renamed
   259 from logilab.common.deprecation import deprecated, class_renamed
   260 from logilab.common.logging_ext import set_log_methods
   260 from logilab.common.logging_ext import set_log_methods
       
   261 from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
       
   262                                      classid, objectify_predicate, yes)
   261 
   263 
   262 from cubicweb import RegistryNotFound
   264 from cubicweb import RegistryNotFound
   263 from cubicweb.vregistry import classid
   265 from cubicweb.cwvreg import CWRegistry, CWRegistryStore
   264 from cubicweb.cwvreg import CWRegistry, VRegistry
   266 from cubicweb.predicates import ExpectedValuePredicate, is_instance
   265 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
   267 from cubicweb.appobject import AppObject
   266                                 is_instance)
       
   267 from cubicweb.appobject import AppObject, NotSelector, OrSelector
       
   268 from cubicweb.server.session import security_enabled
   268 from cubicweb.server.session import security_enabled
   269 
   269 
   270 ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
   270 ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
   271                       'before_update_entity', 'after_update_entity',
   271                       'before_update_entity', 'after_update_entity',
   272                       'before_delete_entity', 'after_delete_entity'))
   272                       'before_delete_entity', 'after_delete_entity'))
   336         the idea is to make a first pass over all the hooks in the
   336         the idea is to make a first pass over all the hooks in the
   337         registry and to mark put some of them in a pruned list. The
   337         registry and to mark put some of them in a pruned list. The
   338         pruned hooks are the one which:
   338         pruned hooks are the one which:
   339 
   339 
   340         * are disabled at the session level
   340         * are disabled at the session level
   341         * have a match_rtype or an is_instance selector which does not
   341 
   342           match the rtype / etype of the relations / entities for
   342         * have a selector containing a :class:`match_rtype` or an
   343           which we are calling the hooks. This works because the
   343           :class:`is_instance` predicate which does not match the rtype / etype
   344           repository calls the hooks grouped by rtype or by etype when
   344           of the relations / entities for which we are calling the hooks. This
   345           using the entities or eids_to_from keyword arguments
   345           works because the repository calls the hooks grouped by rtype or by
   346 
   346           etype when using the entities or eids_to_from keyword arguments
   347         Only hooks with a simple selector or an AndSelector of simple
   347 
   348         selectors are considered for disabling.
   348         Only hooks with a simple predicate or an AndPredicate of simple
       
   349         predicates are considered for disabling.
   349 
   350 
   350         """
   351         """
   351         if 'entity' in kwargs:
   352         if 'entity' in kwargs:
   352             entities = [kwargs['entity']]
   353             entities = [kwargs['entity']]
   353         if len(entities):
   354         if len(entities):
   408             return # no hooks for this event
   409             return # no hooks for this event
   409         registry.call_hooks(event, session, **kwargs)
   410         registry.call_hooks(event, session, **kwargs)
   410 
   411 
   411 
   412 
   412 for event in ALL_HOOKS:
   413 for event in ALL_HOOKS:
   413     VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
   414     CWRegistryStore.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
   414 
   415 
   415 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)')
   416 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)')
   416 def entity_oldnewvalue(entity, attr):
   417 def entity_oldnewvalue(entity, attr):
   417     return entity.cw_edited.oldnewvalue(attr)
   418     return entity.cw_edited.oldnewvalue(attr)
   418 
   419 
   419 
   420 
   420 # some hook specific selectors #################################################
   421 # some hook specific predicates #################################################
   421 
   422 
   422 @objectify_selector
   423 @objectify_predicate
   423 @lltrace
       
   424 def enabled_category(cls, req, **kwargs):
   424 def enabled_category(cls, req, **kwargs):
   425     if req is None:
   425     if req is None:
   426         return True # XXX how to deactivate server startup / shutdown event
   426         return True # XXX how to deactivate server startup / shutdown event
   427     return req.is_hook_activated(cls)
   427     return req.is_hook_activated(cls)
   428 
   428 
   429 @objectify_selector
   429 @objectify_predicate
   430 @lltrace
       
   431 def from_dbapi_query(cls, req, **kwargs):
   430 def from_dbapi_query(cls, req, **kwargs):
   432     if req.running_dbapi_query:
   431     if req.running_dbapi_query:
   433         return 1
   432         return 1
   434     return 0
   433     return 0
   435 
   434 
   438         self.iterators = iterators
   437         self.iterators = iterators
   439     def __iter__(self):
   438     def __iter__(self):
   440         return iter(chain(*self.iterators))
   439         return iter(chain(*self.iterators))
   441 
   440 
   442 
   441 
   443 class match_rtype(ExpectedValueSelector):
   442 class match_rtype(ExpectedValuePredicate):
   444     """accept if parameters specified as initializer arguments are specified
   443     """accept if parameters specified as initializer arguments are specified
   445     in named arguments given to the selector
   444     in named arguments given to the predicate
   446 
   445 
   447     :param \*expected: parameters (eg `basestring`) which are expected to be
   446     :param \*expected: parameters (eg `basestring`) which are expected to be
   448                        found in named arguments (kwargs)
   447                        found in named arguments (kwargs)
   449     """
   448     """
   450     def __init__(self, *expected, **more):
   449     def __init__(self, *expected, **more):
   451         self.expected = expected
   450         self.expected = expected
   452         self.frometypes = more.pop('frometypes', None)
   451         self.frometypes = more.pop('frometypes', None)
   453         self.toetypes = more.pop('toetypes', None)
   452         self.toetypes = more.pop('toetypes', None)
   454         assert not more, "unexpected kwargs in match_rtype: %s" % more
   453         assert not more, "unexpected kwargs in match_rtype: %s" % more
   455 
   454 
   456     @lltrace
       
   457     def __call__(self, cls, req, *args, **kwargs):
   455     def __call__(self, cls, req, *args, **kwargs):
   458         if kwargs.get('rtype') not in self.expected:
   456         if kwargs.get('rtype') not in self.expected:
   459             return 0
   457             return 0
   460         if self.frometypes is not None and \
   458         if self.frometypes is not None and \
   461                req.describe(kwargs['eidfrom'])[0] not in self.frometypes:
   459                req.describe(kwargs['eidfrom'])[0] not in self.frometypes:
   464                req.describe(kwargs['eidto'])[0] not in self.toetypes:
   462                req.describe(kwargs['eidto'])[0] not in self.toetypes:
   465             return 0
   463             return 0
   466         return 1
   464         return 1
   467 
   465 
   468 
   466 
   469 class match_rtype_sets(ExpectedValueSelector):
   467 class match_rtype_sets(ExpectedValuePredicate):
   470     """accept if the relation type is in one of the sets given as initializer
   468     """accept if the relation type is in one of the sets given as initializer
   471     argument. The goal of this selector is that it keeps reference to original sets,
   469     argument. The goal of this predicate is that it keeps reference to original sets,
   472     so modification to thoses sets are considered by the selector. For instance
   470     so modification to thoses sets are considered by the predicate. For instance
   473 
   471 
   474     MYSET = set()
   472     MYSET = set()
   475 
   473 
   476     class Hook1(Hook):
   474     class Hook1(Hook):
   477         __regid__ = 'hook1'
   475         __regid__ = 'hook1'
   487     """
   485     """
   488 
   486 
   489     def __init__(self, *expected):
   487     def __init__(self, *expected):
   490         self.expected = expected
   488         self.expected = expected
   491 
   489 
   492     @lltrace
       
   493     def __call__(self, cls, req, *args, **kwargs):
   490     def __call__(self, cls, req, *args, **kwargs):
   494         for rel_set in self.expected:
   491         for rel_set in self.expected:
   495             if kwargs.get('rtype') in rel_set:
   492             if kwargs.get('rtype') in rel_set:
   496                 return 1
   493                 return 1
   497         return 0
   494         return 0
   533 
   530 
   534     @classmethod
   531     @classmethod
   535     @cached
   532     @cached
   536     def filterable_selectors(cls):
   533     def filterable_selectors(cls):
   537         search = cls.__select__.search_selector
   534         search = cls.__select__.search_selector
   538         if search((NotSelector, OrSelector)):
   535         if search((NotPredicate, OrPredicate)):
   539             return None, None
   536             return None, None
   540         enabled_cat = search(enabled_category)
   537         enabled_cat = search(enabled_category)
   541         main_filter = search((is_instance, match_rtype))
   538         main_filter = search((is_instance, match_rtype))
   542         return enabled_cat, main_filter
   539         return enabled_cat, main_filter
   543 
   540 
   581     This hook ensure that when one of the watched relation is added, the
   578     This hook ensure that when one of the watched relation is added, the
   582     `main_rtype` relation is added to the target entity of the relation.
   579     `main_rtype` relation is added to the target entity of the relation.
   583     Notice there are no default behaviour defined when a watched relation is
   580     Notice there are no default behaviour defined when a watched relation is
   584     deleted, you'll have to handle this by yourself.
   581     deleted, you'll have to handle this by yourself.
   585 
   582 
   586     You usually want to use the :class:`match_rtype_sets` selector on concrete
   583     You usually want to use the :class:`match_rtype_sets` predicate on concrete
   587     classes.
   584     classes.
   588     """
   585     """
   589     events = ('after_add_relation',)
   586     events = ('after_add_relation',)
   590 
   587 
   591     # to set in concrete class
   588     # to set in concrete class