move hooks activation control on session object, so we can have a per transaction control. Added a new `hooks_control` context manager for usual modification of hooks activation.
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 08 Mar 2010 19:02:35 +0100
changeset 4834 b718626a0e60
parent 4833 41a78fb4107c
child 4835 13b0b96d7982
move hooks activation control on session object, so we can have a per transaction control. Added a new `hooks_control` context manager for usual modification of hooks activation.
misc/migration/bootstrapmigration_repository.py
schema.py
server/__init__.py
server/checkintegrity.py
server/hook.py
server/migractions.py
server/repository.py
server/serverconfig.py
server/session.py
server/test/unittest_hook.py
--- a/misc/migration/bootstrapmigration_repository.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Mar 08 19:02:35 2010 +0100
@@ -32,7 +32,7 @@
     session.execute = session.unsafe_execute
     permsdict = ss.deserialize_ertype_permissions(session)
 
-    config.disabled_hooks_categories.add('integrity')
+    changes = session.disable_hooks_category.add('integrity')
     for rschema in repo.schema.relations():
         rpermsdict = permsdict.get(rschema.eid, {})
         for rdef in rschema.rdefs.values():
@@ -72,7 +72,8 @@
     for action in ('read', 'add', 'delete'):
         drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
         drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
-    config.disabled_hooks_categories.remove('integrity')
+    if changes:
+        session.enable_hooks_category.add(*changes)
 
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
 
--- a/schema.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/schema.py	Mon Mar 08 19:02:35 2010 +0100
@@ -1087,7 +1087,7 @@
         if hasattr(cw, 'is_super_session'):
             # cw is a server session
             hasperm = cw.is_super_session or \
-                      not cw.vreg.config.is_hook_category_activated('integrity') or \
+                      not cw.is_hooks_category_activated('integrity') or \
                       cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
         else:
             hasperm = cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
--- a/server/__init__.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/__init__.py	Mon Mar 08 19:02:35 2010 +0100
@@ -8,6 +8,8 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -209,29 +211,26 @@
 
 def initialize_schema(config, schema, mhandler, event='create'):
     from cubicweb.server.schemaserial import serialize_schema
-    # deactivate every hooks but those responsible to set metadata
-    # so, NO INTEGRITY CHECKS are done, to have quicker db creation
-    oldmode = config.set_hooks_mode(config.DENY_ALL)
-    changes = config.enable_hook_category('metadata')
+    from cubicweb.server.session import hooks_control
+    session = mhandler.session
     paths = [p for p in config.cubes_path() + [config.apphome]
              if exists(join(p, 'migration'))]
-    # execute cubicweb's pre<event> script
-    mhandler.exec_event_script('pre%s' % event)
-    # execute cubes pre<event> script if any
-    for path in reversed(paths):
-        mhandler.exec_event_script('pre%s' % event, path)
-    # enter instance'schema into the database
-    mhandler.session.set_pool()
-    serialize_schema(mhandler.session, schema)
-    # execute cubicweb's post<event> script
-    mhandler.exec_event_script('post%s' % event)
-    # execute cubes'post<event> script if any
-    for path in reversed(paths):
-        mhandler.exec_event_script('post%s' % event, path)
-    # restore hooks config
-    if changes:
-        config.disable_hook_category(changes)
-    config.set_hooks_mode(oldmode)
+    # deactivate every hooks but those responsible to set metadata
+    # so, NO INTEGRITY CHECKS are done, to have quicker db creation
+    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+        # execute cubicweb's pre<event> script
+        mhandler.exec_event_script('pre%s' % event)
+        # execute cubes pre<event> script if any
+        for path in reversed(paths):
+            mhandler.exec_event_script('pre%s' % event, path)
+        # enter instance'schema into the database
+        session.set_pool()
+        serialize_schema(session, schema)
+        # execute cubicweb's post<event> script
+        mhandler.exec_event_script('post%s' % event)
+        # execute cubes'post<event> script if any
+        for path in reversed(paths):
+            mhandler.exec_event_script('post%s' % event, path)
 
 
 # sqlite'stored procedures have to be registered at connection opening time
--- a/server/checkintegrity.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/checkintegrity.py	Mon Mar 08 19:02:35 2010 +0100
@@ -73,8 +73,6 @@
     if not repo.system_source.dbhelper.has_fti_table(cursor):
         print 'no text index table'
         dbhelper.init_fti(cursor)
-    repo.config.disabled_hooks_categories.add('metadata')
-    repo.config.disabled_hooks_categories.add('integrity')
     repo.system_source.do_fti = True  # ensure full-text indexation is activated
     etypes = set()
     for eschema in schema.entities():
@@ -90,9 +88,6 @@
     if withpb:
         pb = ProgressBar(len(etypes) + 1)
     # first monkey patch Entity.check to disable validation
-    from cubicweb.entity import Entity
-    _check = Entity.check
-    Entity.check = lambda self, creation=False: True
     # clear fti table first
     session.system_sql('DELETE FROM %s' % session.repo.system_source.dbhelper.fti_table)
     if withpb:
@@ -102,14 +97,9 @@
     source = repo.system_source
     for eschema in etypes:
         for entity in session.execute('Any X WHERE X is %s' % eschema).entities():
-            source.fti_unindex_entity(session, entity.eid)
             source.fti_index_entity(session, entity)
         if withpb:
             pb.update()
-    # restore Entity.check
-    Entity.check = _check
-    repo.config.disabled_hooks_categories.remove('metadata')
-    repo.config.disabled_hooks_categories.remove('integrity')
 
 
 def check_schema(schema, session, eids, fix=1):
--- a/server/hook.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/hook.py	Mon Mar 08 19:02:35 2010 +0100
@@ -113,11 +113,8 @@
 @lltrace
 def enabled_category(cls, req, **kwargs):
     if req is None:
-        # server startup / shutdown event
-        config = kwargs['repo'].config
-    else:
-        config = req.vreg.config
-    return config.is_hook_activated(cls)
+        return True # XXX how to deactivate server startup / shutdown event
+    return req.is_hook_activated(cls)
 
 @objectify_selector
 @lltrace
--- a/server/migractions.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/migractions.py	Mon Mar 08 19:02:35 2010 +0100
@@ -1223,12 +1223,6 @@
     def rqliter(self, rql, kwargs=None, ask_confirm=True):
         return ForRqlIterator(self, rql, None, ask_confirm)
 
-    def cmd_deactivate_verification_hooks(self):
-        self.config.disabled_hooks_categories.add('integrity')
-
-    def cmd_reactivate_verification_hooks(self):
-        self.config.disabled_hooks_categories.remove('integrity')
-
     # broken db commands ######################################################
 
     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
@@ -1279,6 +1273,14 @@
         if commit:
             self.commit()
 
+    @deprecated("[3.7] use session.disable_hooks_category('integrity')")
+    def cmd_deactivate_verification_hooks(self):
+        self.session.disable_hooks_category('integrity')
+
+    @deprecated("[3.7] use session.enable_hooks_category('integrity')")
+    def cmd_reactivate_verification_hooks(self):
+        self.session.enable_hooks_category('integrity')
+
 
 class ForRqlIterator:
     """specific rql iterator to make the loop skipable"""
--- a/server/repository.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/repository.py	Mon Mar 08 19:02:35 2010 +0100
@@ -85,7 +85,7 @@
     #     sessions). Also we should imo rely on the orm to first fetch existing
     #     entity if any then delete it.
     if session.is_internal_session \
-           or not session.vreg.config.is_hook_category_activated('integrity'):
+           or not session.is_hooks_category_activated('integrity'):
         return
     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
@@ -988,7 +988,8 @@
             if not rschema.final: # inlined relation
                 relations.append((attr, entity[attr]))
         entity.set_defaults()
-        entity.check(creation=True)
+        if session.is_hooks_category_activated('integrity'):
+            entity.check(creation=True)
         source.add_entity(session, entity)
         if source.uri != 'system':
             extid = source.get_extid(entity)
@@ -1035,7 +1036,8 @@
             print 'UPDATE entity', etype, entity.eid, \
                   dict(entity), edited_attributes
         entity.edited_attributes = edited_attributes
-        entity.check()
+        if session.is_hooks_category_activated('integrity'):
+            entity.check()
         eschema = entity.e_schema
         session.set_entity_cache(entity)
         only_inline_rels, need_fti_update = True, False
--- a/server/serverconfig.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/serverconfig.py	Mon Mar 08 19:02:35 2010 +0100
@@ -185,63 +185,6 @@
     # check user's state at login time
     consider_user_state = True
 
-    # XXX hooks control stuff should probably be on the session, not on the config
-
-    # hooks activation configuration
-    # all hooks should be activated during normal execution
-    disabled_hooks_categories = set()
-    enabled_hooks_categories = set()
-    ALLOW_ALL = object()
-    DENY_ALL = object()
-    hooks_mode = ALLOW_ALL
-
-    @classmethod
-    def set_hooks_mode(cls, mode):
-        assert mode is cls.ALLOW_ALL or mode is cls.DENY_ALL
-        oldmode = cls.hooks_mode
-        cls.hooks_mode = mode
-        return oldmode
-
-    @classmethod
-    def disable_hook_category(cls, *categories):
-        changes = set()
-        if cls.hooks_mode is cls.DENY_ALL:
-            for category in categories:
-                if category in cls.enabled_hooks_categories:
-                    cls.enabled_hooks_categories.remove(category)
-                    changes.add(category)
-        else:
-            for category in categories:
-                if category not in cls.disabled_hooks_categories:
-                    cls.disabled_hooks_categories.add(category)
-                    changes.add(category)
-        return changes
-
-    @classmethod
-    def enable_hook_category(cls, *categories):
-        changes = set()
-        if cls.hooks_mode is cls.DENY_ALL:
-            for category in categories:
-                if category not in cls.enabled_hooks_categories:
-                    cls.enabled_hooks_categories.add(category)
-                    changes.add(category)
-        else:
-            for category in categories:
-                if category in cls.disabled_hooks_categories:
-                    cls.disabled_hooks_categories.remove(category)
-                    changes.add(category)
-        return changes
-
-    @classmethod
-    def is_hook_activated(cls, hook):
-        return cls.is_hook_category_activated(hook.category)
-
-    @classmethod
-    def is_hook_category_activated(cls, category):
-        if cls.hooks_mode is cls.DENY_ALL:
-            return category in cls.enabled_hooks_categories
-        return category not in cls.disabled_hooks_categories
-
     # should some hooks be deactivated during [pre|post]create script execution
     free_wheel = False
 
--- a/server/session.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/session.py	Mon Mar 08 19:02:35 2010 +0100
@@ -42,6 +42,37 @@
     return description
 
 
+class hooks_control(object):
+    """context manager to control activated hooks categories.
+
+    If mode is session.`HOOKS_DENY_ALL`, given hooks categories will
+    be enabled.
+
+    If mode is session.`HOOKS_ALLOW_ALL`, given hooks categories will
+    be disabled.
+    """
+    def __init__(self, session, mode, *categories):
+        self.session = session
+        self.mode = mode
+        self.categories = categories
+
+    def __enter__(self):
+        self.oldmode = self.session.set_hooks_mode(self.mode)
+        if self.mode is self.session.HOOKS_DENY_ALL:
+            self.changes = self.session.enable_hooks_category(*self.categories)
+        else:
+            self.changes = self.session.disable_hooks_category(*self.categories)
+
+    def __exit__(self, exctype, exc, traceback):
+        if self.changes:
+            if self.mode is self.session.HOOKS_DENY_ALL:
+                self.session.disable_hooks_category(*self.changes)
+            else:
+                self.session.enable_hooks_category(*self.changes)
+        self.session.set_hooks_mode(self.oldmode)
+
+
+
 class Session(RequestSessionBase):
     """tie session id, user, connections pool and other session data all
     together
@@ -245,6 +276,78 @@
         rdef = rschema.rdef(subjtype, objtype)
         return rdef.get(rprop)
 
+    # hooks activation control #################################################
+    # all hooks should be activated during normal execution
+
+    HOOKS_ALLOW_ALL = object()
+    HOOKS_DENY_ALL = object()
+
+    @property
+    def hooks_mode(self):
+        return getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL)
+
+    def set_hooks_mode(self, mode):
+        assert mode is self.HOOKS_ALLOW_ALL or mode is self.HOOKS_DENY_ALL
+        oldmode = getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL)
+        self._threaddata.hooks_mode = mode
+        return oldmode
+
+    @property
+    def disabled_hooks_categories(self):
+        try:
+            return getattr(self._threaddata, 'disabled_hooks_cats')
+        except AttributeError:
+            cats = self._threaddata.disabled_hooks_cats = set()
+            return cats
+
+    @property
+    def enabled_hooks_categories(self):
+        try:
+            return getattr(self._threaddata, 'enabled_hooks_cats')
+        except AttributeError:
+            cats = self._threaddata.enabled_hooks_cats = set()
+            return cats
+
+    def disable_hooks_category(self, *categories):
+        changes = set()
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            enablecats = self.enabled_hooks_categories
+            for category in categories:
+                if category in enablecats:
+                    enablecats.remove(category)
+                    changes.add(category)
+        else:
+            disablecats = self.disabled_hooks_categories
+            for category in categories:
+                if category not in disablecats:
+                    disablecats.add(category)
+                    changes.add(category)
+        return tuple(changes)
+
+    def enable_hooks_category(self, *categories):
+        changes = set()
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            enablecats = self.enabled_hooks_categories
+            for category in categories:
+                if category not in enablecats:
+                    enablecats.add(category)
+                    changes.add(category)
+        else:
+            disablecats = self.disabled_hooks_categories
+            for category in categories:
+                if category in self.disabled_hooks_categories:
+                    disablecats.remove(category)
+                    changes.add(category)
+        return tuple(changes)
+
+    def is_hooks_category_activated(self, category):
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            return category in self.enabled_hooks_categories
+        return category not in self.disabled_hooks_categories
+
+    def is_hook_activated(self, hook):
+        return self.is_hooks_category_activated(hook.category)
+
     # connection management ###################################################
 
     def keep_pool_mode(self, mode):
@@ -716,6 +819,21 @@
     def super_session(self):
         return self
 
+    @property
+    def hooks_mode(self):
+        return self.parent_session.hooks_mode
+    def set_hooks_mode(self, mode):
+        return self.parent_session.set_hooks_mode(mode)
+
+    @property
+    def disabled_hooks_categories(self):
+        return self.parent_session.disabled_hooks_categories
+
+    @property
+    def enabled_hooks_categories(self):
+        return self.parent_session.enabled_hooks_categories
+
+
     def get_mode(self):
         return self.parent_session.mode
     def set_mode(self, value):
--- a/server/test/unittest_hook.py	Mon Mar 08 18:31:36 2010 +0100
+++ b/server/test/unittest_hook.py	Mon Mar 08 19:02:35 2010 +0100
@@ -108,13 +108,17 @@
 
     def test_call_hook(self):
         self.o.register(AddAnyHook)
-        cw = mock_object(vreg=self.vreg)
-        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+        dis = set()
+        cw = mock_object(vreg=self.vreg,
+                         is_hook_activated=lambda x, cls: cls.category not in dis)
+        self.assertRaises(HookCalled,
+                          self.o.call_hooks, 'before_add_entity', cw)
         self.o.call_hooks('before_delete_entity', cw) # nothing to call
-        config.disabled_hooks_categories.add('cat1')
+        dis.add('cat1')
         self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
-        config.disabled_hooks_categories.remove('cat1')
-        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+        dis.remove('cat1')
+        self.assertRaises(HookCalled,
+                          self.o.call_hooks, 'before_add_entity', cw)
         self.o.unregister(AddAnyHook)
         self.o.call_hooks('before_add_entity', cw) # nothing to call