doc/book/en/devrepo/repo/hooks.rst
changeset 6147 95c604ec89bf
parent 5394 105011657405
child 6162 76bd320c5ace
equal deleted inserted replaced
6146:f3d82f25ab61 6147:95c604ec89bf
     1 .. -*- coding: utf-8 -*-
     1 .. -*- coding: utf-8 -*-
     2 
       
     3 .. _hooks:
     2 .. _hooks:
     4 
     3 
     5 Hooks and Operations
     4 Hooks and Operations
     6 ====================
     5 ====================
     7 
     6 
     8 Generalities
     7 .. autodocstring:: cubicweb.server.hook
     9 ------------
     8 
    10 
     9 Example using dataflow hooks
    11 Paraphrasing the `emacs`_ documentation, let us say that hooks are an
    10 ----------------------------
    12 important mechanism for customizing an application. A hook is
    11 
    13 basically a list of functions to be called on some well-defined
    12 We will use a very simple example to show hooks usage. Let us start with the
    14 occasion (this is called `running the hook`).
    13 following schema.
    15 
       
    16 .. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
       
    17 
       
    18 In CubicWeb, hooks are subclasses of the Hook class in
       
    19 `server/hook.py`, implementing their own `call` method, and selected
       
    20 over a set of pre-defined `events` (and possibly more conditions,
       
    21 hooks being selectable AppObjects like views and components).
       
    22 
       
    23 There are two families of events: data events and server events. In a
       
    24 typical application, most of the Hooks are defined over data
       
    25 events.
       
    26 
       
    27 The purpose of data hooks is to complement the data model as defined
       
    28 in the schema.py, which is static by nature, with dynamic or value
       
    29 driven behaviours. It is functionally equivalent to a `database
       
    30 trigger`_, except that database triggers definition languages are not
       
    31 standardized, hence not portable (for instance, PL/SQL works with
       
    32 Oracle and PostgreSQL but not SqlServer nor Sqlite).
       
    33 
       
    34 .. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
       
    35 
       
    36 Data hooks can serve the following purposes:
       
    37 
       
    38 * enforcing constraints that the static schema cannot express
       
    39   (spanning several entities/relations, exotic value ranges and
       
    40   cardinalities, etc.)
       
    41 
       
    42 * implement computed attributes
       
    43 
       
    44 Operations are Hook-like objects that may be created by Hooks and
       
    45 scheduled to happen just before (or after) the `commit` event. Hooks
       
    46 being fired immediately on data operations, it is sometime necessary
       
    47 to delay the actual work down to a time where all other Hooks have
       
    48 run, for instance a validation check which needs that all relations be
       
    49 already set on an entity. Also while the order of execution of Hooks
       
    50 is data dependant (and thus hard to predict), it is possible to force
       
    51 an order on Operations.
       
    52 
       
    53 Operations also may be used to process various side effects associated
       
    54 with a transaction such as filesystem udpates, mail notifications,
       
    55 etc.
       
    56 
       
    57 Operations are subclasses of the Operation class in `server/hook.py`,
       
    58 implementing `precommit_event` and other standard methods (wholly
       
    59 described in :ref:`operations_api`).
       
    60 
       
    61 Events
       
    62 ------
       
    63 
       
    64 Hooks are mostly defined and used to handle `dataflow`_ operations. It
       
    65 means as data gets in (entities added, updated, relations set or
       
    66 unset), specific events are issued and the Hooks matching these events
       
    67 are called.
       
    68 
       
    69 .. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
       
    70 
       
    71 Below comes a list of the dataflow events related to entities operations:
       
    72 
       
    73 * before_add_entity
       
    74 
       
    75 * before_update_entity
       
    76 
       
    77 * before_delete_entity
       
    78 
       
    79 * after_add_entity
       
    80 
       
    81 * after_update_entity
       
    82 
       
    83 * after_delete_entity
       
    84 
       
    85 These define ENTTIES HOOKS. RELATIONS HOOKS are defined
       
    86 over the following events:
       
    87 
       
    88 * after_add_relation
       
    89 
       
    90 * after_delete_relation
       
    91 
       
    92 * before_add_relation
       
    93 
       
    94 * before_delete_relation
       
    95 
       
    96 This is an occasion to remind us that relations support the add/delete
       
    97 operation, but no update.
       
    98 
       
    99 Non data events also exist. These are called SYSTEM HOOKS.
       
   100 
       
   101 * server_startup
       
   102 
       
   103 * server_shutdown
       
   104 
       
   105 * server_maintenance
       
   106 
       
   107 * server_backup
       
   108 
       
   109 * server_restore
       
   110 
       
   111 * session_open
       
   112 
       
   113 * session_close
       
   114 
       
   115 
       
   116 Using dataflow Hooks
       
   117 --------------------
       
   118 
       
   119 Dataflow hooks either automate data operations or maintain the
       
   120 consistency of the data model. In the later case, we must use a
       
   121 specific exception named ValidationError
       
   122 
       
   123 Validation Errors
       
   124 ~~~~~~~~~~~~~~~~~
       
   125 
       
   126 When a condition is not met in a Hook/Operation, it must raise a
       
   127 `ValidationError`. Raising anything but a (subclass of)
       
   128 ValidationError is a programming error. Raising a ValidationError
       
   129 entails aborting the current transaction.
       
   130 
       
   131 The ValidationError exception is used to convey enough information up
       
   132 to the user interface. Hence its constructor is different from the
       
   133 default Exception constructor. It accepts, positionally:
       
   134 
       
   135 * an entity eid,
       
   136 
       
   137 * a dict whose keys represent attribute (or relation) names and values
       
   138   an end-user facing message (hence properly translated) relating the
       
   139   problem.
       
   140 
       
   141 An entity hook
       
   142 ~~~~~~~~~~~~~~
       
   143 
       
   144 We will use a very simple example to show hooks usage. Let us start
       
   145 with the following schema.
       
   146 
    14 
   147 .. sourcecode:: python
    15 .. sourcecode:: python
   148 
    16 
   149    class Person(EntityType):
    17    class Person(EntityType):
   150        age = Int(required=True)
    18        age = Int(required=True)
   151 
    19 
   152 We would like to add a range constraint over a person's age. Let's
    20 We would like to add a range constraint over a person's age. Let's write an hook
   153 write an hook. It shall be placed into mycube/hooks.py. If this file
    21 (supposing yams can not handle this nativly, which is wrong). It shall be placed
   154 were to grow too much, we can easily have a mycube/hooks/... package
    22 into `mycube/hooks.py`. If this file were to grow too much, we can easily have a
   155 containing hooks in various modules.
    23 `mycube/hooks/... package` containing hooks in various modules.
   156 
    24 
   157 .. sourcecode:: python
    25 .. sourcecode:: python
   158 
    26 
   159    from cubicweb import ValidationError
    27    from cubicweb import ValidationError
   160    from cubicweb.selectors import implements
    28    from cubicweb.selectors import implements
   164         __regid__ = 'person_age_range'
    32         __regid__ = 'person_age_range'
   165         events = ('before_add_entity', 'before_update_entity')
    33         events = ('before_add_entity', 'before_update_entity')
   166         __select__ = Hook.__select__ & implements('Person')
    34         __select__ = Hook.__select__ & implements('Person')
   167 
    35 
   168         def __call__(self):
    36         def __call__(self):
   169             if 0 >= self.entity.age <= 120:
    37 	    if 'age' in self.entity.cw_edited:
   170                return
    38 		if 0 >= self.entity.age <= 120:
   171             msg = self._cw._('age must be between 0 and 120')
    39 		   return
   172             raise ValidationError(self.entity.eid, {'age': msg})
    40 		msg = self._cw._('age must be between 0 and 120')
   173 
    41 		raise ValidationError(self.entity.eid, {'age': msg})
   174 Hooks being AppObjects like views, they have a __regid__ and a
    42 
   175 __select__ class attribute. The base __select__ is augmented with an
    43 In our example the base `__select__` is augmented with an `implements` selector
   176 `implements` selector matching the desired entity type. The `events`
    44 matching the desired entity type.
   177 tuple is used by the Hook.__select__ base selector to dispatch the
    45 
   178 hook on the right events. In an entity hook, it is possible to
    46 The `events` tuple is used specify that our hook should be called before the
   179 dispatch on any entity event (e.g. 'before_add_entity',
    47 entity is added or updated.
   180 'before_update_entity') at once if needed.
    48 
   181 
    49 Then in the hook's `__call__` method, we:
   182 Like all appobjects, hooks have the `self._cw` attribute which
    50 
   183 represents the current session. In entity hooks, a `self.entity`
    51 * check if the 'age' attribute is edited
   184 attribute is also present.
    52 * if so, check the value is in the range
   185 
    53 * if not, raise a validation error properly
   186 
    54 
   187 A relation hook
    55 Now Let's augment our schema with new `Company` entity type with some relation to
   188 ~~~~~~~~~~~~~~~
    56 `Person` (in 'mycube/schema.py').
   189 
       
   190 Let us add another entity type with a relation to person (in
       
   191 mycube/schema.py).
       
   192 
    57 
   193 .. sourcecode:: python
    58 .. sourcecode:: python
   194 
    59 
   195    class Company(EntityType):
    60    class Company(EntityType):
   196         name = String(required=True)
    61         name = String(required=True)
   197         boss = SubjectRelation('Person', cardinality='1*')
    62         boss = SubjectRelation('Person', cardinality='1*')
   198 
    63         subsidiary_of = SubjectRelation('Company', cardinality='*?')
   199 We would like to constrain the company's bosses to have a minimum
    64 
   200 (legal) age. Let's write an hook for this, which will be fired when
    65 
   201 the `boss` relation is established.
    66 We would like to constrain the company's bosses to have a minimum (legal)
       
    67 age. Let's write an hook for this, which will be fired when the `boss` relation
       
    68 is established (still supposing we could not specify that kind of thing in the
       
    69 schema).
   202 
    70 
   203 .. sourcecode:: python
    71 .. sourcecode:: python
   204 
    72 
   205    class CompanyBossLegalAge(Hook):
    73    class CompanyBossLegalAge(Hook):
   206         __regid__ = 'company_boss_legal_age'
    74         __regid__ = 'company_boss_legal_age'
       
    75         __select__ = Hook.__select__ & match_rtype('boss')
   207         events = ('before_add_relation',)
    76         events = ('before_add_relation',)
   208         __select__ = Hook.__select__ & match_rtype('boss')
       
   209 
    77 
   210         def __call__(self):
    78         def __call__(self):
   211             boss = self._cw.entity_from_eid(self.eidto)
    79             boss = self._cw.entity_from_eid(self.eidto)
   212             if boss.age < 18:
    80             if boss.age < 18:
   213                 msg = self._cw._('the minimum age for a boss is 18')
    81                 msg = self._cw._('the minimum age for a boss is 18')
   214                 raise ValidationError(self.eidfrom, {'boss': msg})
    82                 raise ValidationError(self.eidfrom, {'boss': msg})
   215 
    83 
   216 We use the `match_rtype` selector to select the proper relation type.
    84 .. Note::
   217 
    85 
   218 The essential difference with respect to an entity hook is that there
    86     We use the :class:`~cubicweb.server.hook.match_rtype` selector to select the
   219 is no self.entity, but `self.eidfrom` and `self.eidto` hook attributes
    87     proper relation type.
   220 which represent the subject and object eid of the relation.
    88 
   221 
    89     The essential difference with respect to an entity hook is that there is no
   222 
    90     self.entity, but `self.eidfrom` and `self.eidto` hook attributes which
   223 Using Operations
    91     represent the subject and object **eid** of the relation.
   224 ----------------
    92 
   225 
    93 Suppose we want to check that there is no cycle by the `subsidiary_of`
   226 Let's augment our example with a new `subsidiary_of` relation on Company.
    94 relation. This is best achieved in an operation since all relations are likely to
   227 
    95 be set at commit time.
   228 .. sourcecode:: python
       
   229 
       
   230    class Company(EntityType):
       
   231         name = String(required=True)
       
   232         boss = SubjectRelation('Person', cardinality='1*')
       
   233         subsidiary_of = SubjectRelation('Company', cardinality='*?')
       
   234 
       
   235 Base example
       
   236 ~~~~~~~~~~~~
       
   237 
       
   238 We would like to check that there is no cycle by the `subsidiary_of`
       
   239 relation. This is best achieved in an Operation since all relations
       
   240 are likely to be set at commit time.
       
   241 
    96 
   242 .. sourcecode:: python
    97 .. sourcecode:: python
   243 
    98 
   244     def check_cycle(self, session, eid, rtype, role='subject'):
    99     def check_cycle(self, session, eid, rtype, role='subject'):
   245         parents = set([eid])
   100         parents = set([eid])
   249             if parent.eid in parents:
   104             if parent.eid in parents:
   250                 msg = session._('detected %s cycle' % rtype)
   105                 msg = session._('detected %s cycle' % rtype)
   251                 raise ValidationError(eid, {rtype: msg})
   106                 raise ValidationError(eid, {rtype: msg})
   252             parents.add(parent.eid)
   107             parents.add(parent.eid)
   253 
   108 
       
   109 
   254     class CheckSubsidiaryCycleOp(Operation):
   110     class CheckSubsidiaryCycleOp(Operation):
   255 
   111 
   256         def precommit_event(self):
   112         def precommit_event(self):
   257             check_cycle(self.session, self.eidto, 'subsidiary_of')
   113             check_cycle(self.session, self.eidto, 'subsidiary_of')
   258 
   114 
   259 
   115 
   260     class CheckSubsidiaryCycleHook(Hook):
   116     class CheckSubsidiaryCycleHook(Hook):
   261         __regid__ = 'check_no_subsidiary_cycle'
   117         __regid__ = 'check_no_subsidiary_cycle'
       
   118         __select__ = Hook.__select__ & match_rtype('subsidiary_of')
   262         events = ('after_add_relation',)
   119         events = ('after_add_relation',)
   263         __select__ = Hook.__select__ & match_rtype('subsidiary_of')
       
   264 
   120 
   265         def __call__(self):
   121         def __call__(self):
   266             CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto)
   122             CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto)
   267 
   123 
   268 The operation is instantiated in the Hook.__call__ method.
   124 
   269 
   125 Like in hooks, :exc:`~cubicweb.ValidationError` can be raised in operations. Other
   270 An operation always takes a session object as first argument
   126 exceptions are usually programming errors.
   271 (accessible as `.session` from the operation instance), and optionally
   127 
   272 all keyword arguments needed by the operation. These keyword arguments
   128 In the above example, our hook will instantiate an operation each time the hook
   273 will be accessible as attributes from the operation instance.
   129 is called, i.e. each time the `subsidiary_of` relation is set. There is an
   274 
   130 alternative method to schedule an operation from a hook, using the
   275 Like in Hooks, ValidationError can be raised in Operations. Other
   131 :func:`set_operation` function.
   276 exceptions are programming errors.
       
   277 
       
   278 Notice how our hook will instantiate an operation each time the Hook
       
   279 is called, i.e. each time the `subsidiary_of` relation is set.
       
   280 
       
   281 Using set_operation
       
   282 ~~~~~~~~~~~~~~~~~~~
       
   283 
       
   284 There is an alternative method to schedule an Operation from a Hook,
       
   285 using the `set_operation` function.
       
   286 
   132 
   287 .. sourcecode:: python
   133 .. sourcecode:: python
   288 
   134 
   289    from cubicweb.server.hook import set_operation
   135    from cubicweb.server.hook import set_operation
   290 
   136 
   293        events = ('after_add_relation',)
   139        events = ('after_add_relation',)
   294        __select__ = Hook.__select__ & match_rtype('subsidiary_of')
   140        __select__ = Hook.__select__ & match_rtype('subsidiary_of')
   295 
   141 
   296        def __call__(self):
   142        def __call__(self):
   297            set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto,
   143            set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto,
   298                          CheckSubsidiaryCycleOp, rtype=self.rtype)
   144                          CheckSubsidiaryCycleOp)
   299 
   145 
   300    class CheckSubsidiaryCycleOp(Operation):
   146    class CheckSubsidiaryCycleOp(Operation):
   301 
   147 
   302        def precommit_event(self):
   148        def precommit_event(self):
   303            for eid in self._cw.transaction_data['subsidiary_cycle_detection']:
   149            for eid in self._cw.transaction_data.pop('subsidiary_cycle_detection'):
   304                check_cycle(self.session, eid, self.rtype)
   150                check_cycle(self.session, eid, 'subsidiary_of')
   305 
   151 
   306 Here, we call set_operation with a session object, a specially forged
   152 
   307 key, a value that is the actual payload of an individual operation (in
   153 Here, we call :func:`set_operation` so that we will simply accumulate eids of
   308 our case, the object of the subsidiary_of relation) , the class of the
   154 entities to check at the end in a single CheckSubsidiaryCycleOp operation.  Value
   309 Operation, and more optional parameters to give to the operation (here
   155 are stored in a set associated to the 'subsidiary_cycle_detection' transaction
   310 the rtype which do not vary accross operations).
   156 data key. The set initialization and operation creation are handled nicely by
   311 
   157 :func:set_operation.
   312 The body of the operation must then iterate over the values that have
   158 
   313 been mapped in the transaction_data dictionary to the forged key.
   159 A more realistic example can be found in the advanced tutorial chapter
   314 
   160 :ref:`adv_tuto_security_propagation`.
   315 This mechanism is especially useful on two occasions (not shown in our
   161 
   316 example):
   162 
   317 
   163 Hooks writing tips
   318 * massive data import (reduced memory consumption within a large
   164 ------------------
   319   transaction)
   165 
   320 
   166 Reminder
   321 * when several hooks need to instantiate the same operation (e.g. an
   167 ~~~~~~~~
   322   entity and a relation hook).
   168 
   323 
   169 Never, ever use the `entity.foo = 42` notation to update an entity. It will not
   324 .. note::
   170 work.To updating an entity attribute or relation, uses :meth:`set_attributes` and
   325 
   171 :meth:`set_relations` methods.
   326   A more realistic example can be found in the advanced tutorial
   172 
   327   chapter :ref:`adv_tuto_security_propagation`.
       
   328 
       
   329 .. _operations_api:
       
   330 
       
   331 Operation: a small API overview
       
   332 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   333 
       
   334 .. autoclass:: cubicweb.server.hook.Operation
       
   335 .. autoclass:: cubicweb.server.hook.LateOperation
       
   336 .. autofunction:: cubicweb.server.hook.set_operation
       
   337 
       
   338 Hooks writing rules
       
   339 -------------------
       
   340 
       
   341 Remainder
       
   342 ~~~~~~~~~
       
   343 
       
   344 Never, ever use the `entity.foo = 42` notation to update an entity. It
       
   345 will not work.
       
   346 
   173 
   347 How to choose between a before and an after event ?
   174 How to choose between a before and an after event ?
   348 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   175 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   349 
   176 
   350 Before hooks give you access to the old attribute (or relation)
   177 'before_*' hooks give you access to the old attribute (or relation)
   351 values. By definition the database is not yet updated in a before
   178 values. You can also hi-jack actually edited stuff in the case of entity
   352 hook.
   179 modification. Needing one of this will definitly guide your choice.
   353 
   180 
   354 To access old and new values in an before_update_entity hook, one can
   181 Else the question is: should I need to do things before or after the actual
   355 use the `server.hook.entity_oldnewvalue` function which returns a
   182 modification. If the answer is "it doesn't matter", use an 'after' event.
   356 tuple of the old and new values. This function takes an entity and an
   183 
   357 attribute name as parameters.
   184 
   358 
   185 Validation Errors
   359 In a 'before_add|update_entity' hook the self.entity contains the new
   186 ~~~~~~~~~~~~~~~~~
   360 values. One is allowed to further modify them before database
   187 
   361 operations, using the dictionary notation.
   188 When a hook is responsible to maintain the consistency of the data model detect
   362 
   189 an error, it must use a specific exception named
   363 .. sourcecode:: python
   190 :exc:`~cubicweb.ValidationError`. Raising anything but a (subclass of)
   364 
   191 :exc:`~cubicweb.ValidationError` is a programming error. Raising a it entails
   365    self.entity['age'] = 42
   192 aborting the current transaction.
   366 
   193 
   367 This is because using self.entity.set_attributes(age=42) will
   194 This exception is used to convey enough information up to the user
   368 immediately update the database (which does not make sense in a
   195 interface. Hence its constructor is different from the default Exception
   369 pre-database hook), and will trigger any existing
   196 constructor. It accepts, positionally:
   370 before_add|update_entity hook, thus leading to infinite hook loops or
   197 
   371 such awkward situations.
   198 * an entity eid,
   372 
   199 
   373 Beyond these specific cases, updating an entity attribute or relation
   200 * a dict whose keys represent attribute (or relation) names and values
   374 must *always* be done using `set_attributes` and `set_relations`
   201   an end-user facing message (hence properly translated) relating the
   375 methods.
   202   problem.
   376 
   203 
   377 (Of course, ValidationError will always abort the current transaction,
   204 
   378 whetever the event).
   205 Checking for object created/deleted in the current transaction
       
   206 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   207 
       
   208 In hooks, you can use the
       
   209 :meth:`~cubicweb.server.session.Session.added_in_transaction` or
       
   210 :meth:`~cubicweb.server.session.Session.deleted_in_transaction` of the session
       
   211 object to check if an eid has been created or deleted during the hook's
       
   212 transaction.
       
   213 
       
   214 This is useful to enable or disable some stuff if some entity is being added or
       
   215 deleted.
       
   216 
       
   217 .. sourcecode:: python
       
   218 
       
   219    if self._cw.deleted_in_transaction(self.eidto):
       
   220       return
       
   221 
   379 
   222 
   380 Peculiarities of inlined relations
   223 Peculiarities of inlined relations
   381 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   224 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   382 
   225 
   383 Some relations are defined in the schema as `inlined` (see
   226 Relations which are defined in the schema as `inlined` (see :ref:`RelationType`
   384 :ref:`RelationType` for details). In this case, they are inserted in
   227 for details) are inserted in the database at the same time as entity attributes.
   385 the database at the same time as entity attributes.
   228 This may have some side effect, for instance when creating entity and setting an
   386 
   229 inlined relation in the same rql query, when 'before_add_relation' for that
   387 Hence in the case of before_add_relation, such relations already exist
   230 relation will be run, the relation will already exist in the database (it's
   388 in the database.
   231 usually not the case).
   389 
       
   390 Edited attributes
       
   391 ~~~~~~~~~~~~~~~~~
       
   392 
       
   393 On udpates, it is possible to ask the `entity.edited_attributes`
       
   394 variable whether one attribute has been updated.
       
   395 
       
   396 .. sourcecode:: python
       
   397 
       
   398   if 'age' not in entity.edited_attribute:
       
   399       return
       
   400 
       
   401 Deleted in transaction
       
   402 ~~~~~~~~~~~~~~~~~~~~~~
       
   403 
       
   404 The session object has a deleted_in_transaction method, which can help
       
   405 writing deletion Hooks.
       
   406 
       
   407 .. sourcecode:: python
       
   408 
       
   409    if self._cw.deleted_in_transaction(self.eidto):
       
   410       return
       
   411 
       
   412 Given this predicate, we can avoid scheduling an operation.
       
   413 
       
   414 Disabling hooks
       
   415 ~~~~~~~~~~~~~~~
       
   416 
       
   417 It is sometimes convenient to disable some hooks. For instance to
       
   418 avoid infinite Hook loops. One uses the `hooks_control` context
       
   419 manager.
       
   420 
       
   421 This can be controlled more finely through the `category` Hook class
       
   422 attribute, which is a string.
       
   423 
       
   424 .. sourcecode:: python
       
   425 
       
   426    with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, <category>):
       
   427        # ... do stuff
       
   428 
       
   429 .. autoclass:: cubicweb.server.session.hooks_control
       
   430 
       
   431 The existing categories are: ``email``, ``syncsession``,
       
   432 ``syncschema``, ``bookmark``, ``security``, ``worfklow``,
       
   433 ``metadata``, ``notification``, ``integrity``, ``activeintegrity``.
       
   434 
       
   435 Nothing precludes one to invent new categories and use the
       
   436 hooks_control context manager to filter them (in or out).