doc/book/en/devrepo/repo/hooks.rst
changeset 10491 c67bcee93248
parent 10490 76ab3c71aff2
child 10492 68c13e0c0fc5
--- a/doc/book/en/devrepo/repo/hooks.rst	Mon Jul 06 17:39:35 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _hooks:
-
-Hooks and Operations
-====================
-
-.. autodocstring:: cubicweb.server.hook
-
-
-Example using dataflow hooks
-----------------------------
-
-We will use a very simple example to show hooks usage. Let us start with the
-following schema.
-
-.. sourcecode:: python
-
-   class Person(EntityType):
-       age = Int(required=True)
-
-We would like to add a range constraint over a person's age. Let's write an hook
-(supposing yams can not handle this nativly, which is wrong). It shall be placed
-into `mycube/hooks.py`. If this file were to grow too much, we can easily have a
-`mycube/hooks/... package` containing hooks in various modules.
-
-.. sourcecode:: python
-
-   from cubicweb import ValidationError
-   from cubicweb.predicates import is_instance
-   from cubicweb.server.hook import Hook
-
-   class PersonAgeRange(Hook):
-        __regid__ = 'person_age_range'
-        __select__ = Hook.__select__ & is_instance('Person')
-        events = ('before_add_entity', 'before_update_entity')
-
-        def __call__(self):
-	    if 'age' in self.entity.cw_edited:
-                if 0 <= self.entity.age <= 120:
-                   return
-		msg = self._cw._('age must be between 0 and 120')
-		raise ValidationError(self.entity.eid, {'age': msg})
-
-In our example the base `__select__` is augmented with an `is_instance` selector
-matching the desired entity type.
-
-The `events` tuple is used specify that our hook should be called before the
-entity is added or updated.
-
-Then in the hook's `__call__` method, we:
-
-* check if the 'age' attribute is edited
-* if so, check the value is in the range
-* if not, raise a validation error properly
-
-Now Let's augment our schema with new `Company` entity type with some relation to
-`Person` (in 'mycube/schema.py').
-
-.. sourcecode:: python
-
-   class Company(EntityType):
-        name = String(required=True)
-        boss = SubjectRelation('Person', cardinality='1*')
-        subsidiary_of = SubjectRelation('Company', cardinality='*?')
-
-
-We would like to constrain the company's bosses to have a minimum (legal)
-age. Let's write an hook for this, which will be fired when the `boss` relation
-is established (still supposing we could not specify that kind of thing in the
-schema).
-
-.. sourcecode:: python
-
-   class CompanyBossLegalAge(Hook):
-        __regid__ = 'company_boss_legal_age'
-        __select__ = Hook.__select__ & match_rtype('boss')
-        events = ('before_add_relation',)
-
-        def __call__(self):
-            boss = self._cw.entity_from_eid(self.eidto)
-            if boss.age < 18:
-                msg = self._cw._('the minimum age for a boss is 18')
-                raise ValidationError(self.eidfrom, {'boss': msg})
-
-.. Note::
-
-    We use the :class:`~cubicweb.server.hook.match_rtype` selector to select the
-    proper relation type.
-
-    The essential difference with respect to an entity hook is that there is no
-    self.entity, but `self.eidfrom` and `self.eidto` hook attributes which
-    represent the subject and object **eid** of the relation.
-
-Suppose we want to check that there is no cycle by the `subsidiary_of`
-relation. This is best achieved in an operation since all relations are likely to
-be set at commit time.
-
-.. sourcecode:: python
-
-    from cubicweb.server.hook import Hook, DataOperationMixIn, Operation, match_rtype
-
-    def check_cycle(self, session, eid, rtype, role='subject'):
-        parents = set([eid])
-        parent = session.entity_from_eid(eid)
-        while parent.related(rtype, role):
-            parent = parent.related(rtype, role)[0]
-            if parent.eid in parents:
-                msg = session._('detected %s cycle' % rtype)
-                raise ValidationError(eid, {rtype: msg})
-            parents.add(parent.eid)
-
-
-    class CheckSubsidiaryCycleOp(Operation):
-
-        def precommit_event(self):
-            check_cycle(self.session, self.eidto, 'subsidiary_of')
-
-
-    class CheckSubsidiaryCycleHook(Hook):
-        __regid__ = 'check_no_subsidiary_cycle'
-        __select__ = Hook.__select__ & match_rtype('subsidiary_of')
-        events = ('after_add_relation',)
-
-        def __call__(self):
-            CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto)
-
-
-Like in hooks, :exc:`~cubicweb.ValidationError` can be raised in operations. Other
-exceptions are usually programming errors.
-
-In the above example, our hook will instantiate an operation each time the hook
-is called, i.e. each time the `subsidiary_of` relation is set. There is an
-alternative method to schedule an operation from a hook, using the
-:func:`get_instance` class method.
-
-.. sourcecode:: python
-
-   from cubicweb.server.hook import set_operation
-
-   class CheckSubsidiaryCycleHook(Hook):
-       __regid__ = 'check_no_subsidiary_cycle'
-       events = ('after_add_relation',)
-       __select__ = Hook.__select__ & match_rtype('subsidiary_of')
-
-       def __call__(self):
-           CheckSubsidiaryCycleOp.get_instance(self._cw).add_data(self.eidto)
-
-   class CheckSubsidiaryCycleOp(DataOperationMixIn, Operation):
-
-       def precommit_event(self):
-           for eid in self.get_data():
-               check_cycle(self.session, eid, self.rtype)
-
-
-Here, we call :func:`set_operation` so that we will simply accumulate eids of
-entities to check at the end in a single `CheckSubsidiaryCycleOp`
-operation. Value are stored in a set associated to the
-'subsidiary_cycle_detection' transaction data key. The set initialization and
-operation creation are handled nicely by :func:`set_operation`.
-
-A more realistic example can be found in the advanced tutorial chapter
-:ref:`adv_tuto_security_propagation`.
-
-
-Inter-instance communication
-----------------------------
-
-If your application consists of several instances, you may need some means to
-communicate between them.  Cubicweb provides a publish/subscribe mechanism
-using ØMQ_.  In order to use it, use
-:meth:`~cubicweb.server.cwzmq.ZMQComm.add_subscription` on the
-`repo.app_instances_bus` object.  The `callback` will get the message (as a
-list).  A message can be sent by calling
-:meth:`~cubicweb.server.cwzmq.ZMQComm.publish` on `repo.app_instances_bus`.
-The first element of the message is the topic which is used for filtering and
-dispatching messages.
-
-.. _ØMQ: http://www.zeromq.org/
-
-.. sourcecode:: python
-
-  class FooHook(hook.Hook):
-      events = ('server_startup',)
-      __regid__ = 'foo_startup'
-
-      def __call__(self):
-          def callback(msg):
-              self.info('received message: %s', ' '.join(msg))
-          self.repo.app_instances_bus.add_subscription('hello', callback)
-
-.. sourcecode:: python
-
-  def do_foo(self):
-      actually_do_foo()
-      self._cw.repo.app_instances_bus.publish(['hello', 'world'])
-
-The `zmq-address-pub` configuration variable contains the address used
-by the instance for sending messages, e.g. `tcp://*:1234`.  The
-`zmq-address-sub` variable contains a comma-separated list of addresses
-to listen on, e.g. `tcp://localhost:1234, tcp://192.168.1.1:2345`.
-
-
-Hooks writing tips
-------------------
-
-Reminder
-~~~~~~~~
-
-You should never use the `entity.foo = 42` notation to update an entity. It will
-not do what you expect (updating the database). Instead, use the
-:meth:`~cubicweb.entity.Entity.cw_set` method or direct access to entity's
-:attr:`cw_edited` attribute if you're writing a hook for 'before_add_entity' or
-'before_update_entity' event.
-
-
-How to choose between a before and an after event ?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-`before_*` hooks give you access to the old attribute (or relation)
-values. You can also intercept and update edited values in the case of
-entity modification before they reach the database.
-
-Else the question is: should I need to do things before or after the actual
-modification ? If the answer is "it doesn't matter", use an 'after' event.
-
-
-Validation Errors
-~~~~~~~~~~~~~~~~~
-
-When a hook which is responsible to maintain the consistency of the
-data model detects an error, it must use a specific exception named
-:exc:`~cubicweb.ValidationError`. Raising anything but a (subclass of)
-:exc:`~cubicweb.ValidationError` is a programming error. Raising it
-entails aborting the current transaction.
-
-This exception is used to convey enough information up to the user
-interface. Hence its constructor is different from the default Exception
-constructor. It accepts, positionally:
-
-* an entity eid (**not the entity itself**),
-
-* a dict whose keys represent attribute (or relation) names and values
-  an end-user facing message (hence properly translated) relating the
-  problem.
-
-.. sourcecode:: python
-
-  raise ValidationError(earth.eid, {'sea_level': self._cw._('too high'),
-                                    'temperature': self._cw._('too hot')})
-
-
-Checking for object created/deleted in the current transaction
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In hooks, you can use the
-:meth:`~cubicweb.server.session.Session.added_in_transaction` or
-:meth:`~cubicweb.server.session.Session.deleted_in_transaction` of the session
-object to check if an eid has been created or deleted during the hook's
-transaction.
-
-This is useful to enable or disable some stuff if some entity is being added or
-deleted.
-
-.. sourcecode:: python
-
-   if self._cw.deleted_in_transaction(self.eidto):
-      return
-
-
-Peculiarities of inlined relations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Relations which are defined in the schema as `inlined` (see :ref:`RelationType`
-for details) are inserted in the database at the same time as entity attributes.
-
-This may have some side effect, for instance when creating an entity
-and setting an inlined relation in the same rql query, then at
-`before_add_relation` time, the relation will already exist in the
-database (it is otherwise not the case).