doc/book/en/development/devrepo/hooks.rst
branchstable
changeset 5202 4a77da652759
parent 5191 6d182c7d4392
child 5220 42f854b6083d
equal deleted inserted replaced
5194:395f076512a1 5202:4a77da652759
     3 .. _hooks:
     3 .. _hooks:
     4 
     4 
     5 Hooks and Operations
     5 Hooks and Operations
     6 ====================
     6 ====================
     7 
     7 
     8 Principles
     8 Generalities
     9 ----------
     9 ------------
    10 
    10 
    11 Paraphrasing the `emacs`_ documentation, let us say that hooks are an
    11 Paraphrasing the `emacs`_ documentation, let us say that hooks are an
    12 important mechanism for customizing an application. A hook is
    12 important mechanism for customizing an application. A hook is
    13 basically a list of functions to be called on some well-defined
    13 basically a list of functions to be called on some well-defined
    14 occasion (This is called `running the hook`).
    14 occasion (this is called `running the hook`).
    15 
    15 
    16 .. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
    16 .. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
    17 
    17 
    18 In CubicWeb, hooks are classes subclassing the Hook class in
    18 In CubicWeb, hooks are subclasses of the Hook class in
    19 `server/hook.py`, implementing their own `call` method, and defined
    19 `server/hook.py`, implementing their own `call` method, and selected
    20 over pre-defined `events`.
    20 over a set of pre-defined `events` (and possibly more conditions,
       
    21 hooks being selectable AppObjects like views and components).
    21 
    22 
    22 There are two families of events: data events and server events. In a
    23 There are two families of events: data events and server events. In a
    23 typical application, most of the Hooks are defined over data
    24 typical application, most of the Hooks are defined over data
    24 events. There can be a lot of them.
    25 events.
    25 
    26 
    26 The purpose of data hooks is to complement the data model as defined
    27 The purpose of data hooks is to complement the data model as defined
    27 in the schema.py, which is static by nature, with dynamic or value
    28 in the schema.py, which is static by nature, with dynamic or value
    28 driven behaviours. It is functionally equivalent to a `database
    29 driven behaviours. It is functionally equivalent to a `database
    29 trigger`_, except that database triggers definitions languages are not
    30 trigger`_, except that database triggers definition languages are not
    30 standardized, hence not portable (for instance, PL/SQL works with
    31 standardized, hence not portable (for instance, PL/SQL works with
    31 Oracle and PostgreSQL but not SqlServer nor Sqlite).
    32 Oracle and PostgreSQL but not SqlServer nor Sqlite).
    32 
    33 
    33 .. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
    34 .. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
    34 
    35 
    35 Data hooks can serve the following purposes:
    36 Data hooks can serve the following purposes:
    36 
    37 
    37 * enforcing constraints that the static schema cannot express
    38 * enforcing constraints that the static schema cannot express
    38   (spanning several entities/relations, exotic cardinalities, etc.)
    39   (spanning several entities/relations, specific value ranges, exotic
       
    40   cardinalities, etc.)
    39 
    41 
    40 * implement computed attributes (an example could be the maintenance
    42 * implement computed attributes (an example could be the maintenance
    41   of a relation representing the transitive closure of another relation)
    43   of a relation representing the transitive closure of another relation)
    42 
    44 
    43 Operations are Hook-like objects that are created by Hooks and
    45 Operations are Hook-like objects that are created by Hooks and
    46 to delay the actual work down to a time where all other Hooks have run
    48 to delay the actual work down to a time where all other Hooks have run
    47 and the application state converges towards consistency. Also while
    49 and the application state converges towards consistency. Also while
    48 the order of execution of Hooks is data dependant (and thus hard to
    50 the order of execution of Hooks is data dependant (and thus hard to
    49 predict), it is possible to force an order on Operations.
    51 predict), it is possible to force an order on Operations.
    50 
    52 
       
    53 Operations are subclasses of the Operation class in `server/hook.py`,
       
    54 implementing `precommit_event` and other standard methods (wholly
       
    55 described later in this chapter).
       
    56 
    51 Events
    57 Events
    52 ------
    58 ------
    53 
    59 
    54 Hooks are mostly defined and used to handle `dataflow`_ operations. It
    60 Hooks are mostly defined and used to handle `dataflow`_ operations. It
    55 means as data gets in (mostly), specific events are issued and the
    61 means as data gets in (entities added, updated, relations set or
    56 Hooks matching these events are called.
    62 unset), specific events are issued and the Hooks matching these events
       
    63 are called.
    57 
    64 
    58 .. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
    65 .. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
    59 
    66 
    60 Below comes a list of the dataflow events related to entities operations:
    67 Below comes a list of the dataflow events related to entities operations:
    61 
    68 
   100 * session_open
   107 * session_open
   101 
   108 
   102 * session_close
   109 * session_close
   103 
   110 
   104 
   111 
       
   112 Using Hooks
       
   113 -----------
       
   114 
       
   115 We will use a very simple example to show hooks usage. Let us start
       
   116 with the following schema.
       
   117 
       
   118 .. sourcecode:: python
       
   119 
       
   120    class Person(EntityType):
       
   121        age = Int(required=True)
       
   122 
       
   123 An entity hook
       
   124 ~~~~~~~~~~~~~~
       
   125 
       
   126 We would like to add a range constraint over a person's age. Let's
       
   127 write an hook. It shall be placed into mycube/hooks.py. If this file
       
   128 were to grow too much, we can easily have a mycube/hooks/... package
       
   129 containing hooks in various modules.
       
   130 
       
   131 .. sourcecode:: python
       
   132 
       
   133    from cubicweb import ValidationError
       
   134    from cubicweb.selectors import implements
       
   135    from cubicweb.server.hook import Hook
       
   136 
       
   137    class PersonAgeRange(Hook):
       
   138         __regid__ = 'person_age_range'
       
   139         events = ('before_add_entity', 'before_update_entity')
       
   140         __select__ = Hook.__select__ & implements('Person')
       
   141 
       
   142         def __call__(self):
       
   143             if 0 >= self.entity.age <= 120:
       
   144                return
       
   145             msg = self._cw._('age must be between 0 and 120')
       
   146             raise ValidationError(self.entity.eid, {'age': msg})
       
   147 
       
   148 Hooks being AppObjects like views, they have a __regid__ and a
       
   149 __select__ class attribute. The base __select__ is augmented with an
       
   150 `implements` selector matching the desired entity type. The `events`
       
   151 tuple is used by the Hook.__select__ base selector to dispatch the
       
   152 hook on the right events. In an entity hook, it is possible to
       
   153 dispatch on any entity event at once if needed.
       
   154 
       
   155 Like all appobjects, hooks have the self._cw attribute which
       
   156 represents the current session. In entity hooks, a self.entity
       
   157 attribute is also present.
       
   158 
       
   159 When a condition is not met in a Hook, it must raise a
       
   160 ValidationError. Raising anything but a (subclass of) ValidationError
       
   161 is a programming error.
       
   162 
       
   163 The ValidationError exception is used to convey enough information up
       
   164 to the user interface. Hence its constructor is different from the
       
   165 default Exception constructor.It accepts, positionally:
       
   166 
       
   167 * an entity eid,
       
   168 
       
   169 * a dict whose keys represent attributes and values a message relating
       
   170   the problem; such a message will be presented to the end-users;
       
   171   hence it must be properly translated.
       
   172 
       
   173 A relation hook
       
   174 ~~~~~~~~~~~~~~~
       
   175 
       
   176 Let us add another entity type with a relation to person (in
       
   177 mycube/schema.py).
       
   178 
       
   179 .. sourcecode:: python
       
   180 
       
   181    class Company(EntityType):
       
   182         name = String(required=True)
       
   183         boss = SubjectRelation('Person', cardinality='1*')
       
   184 
       
   185 We would like to constrain the company's bosses to have a minimum
       
   186 (legal) age. Let's write an hook for this, which will be fired when
       
   187 the `boss` relation is established.
       
   188 
       
   189 .. sourcecode:: python
       
   190 
       
   191    class CompanyBossLegalAge(Hook):
       
   192         __regid__ = 'company_boss_legal_age'
       
   193         events = ('before_add_relation',)
       
   194         __select__ = Hook.__select__ & match_rtype('boss')
       
   195 
       
   196         def __call__(self):
       
   197             boss = self._cw.entity_from_eid(self.eidto)
       
   198             if boss.age < 18:
       
   199                 msg = self._cw._('the minimum age for a boss is 18')
       
   200                 raise ValidationError(self.eidfrom, {'boss': msg})
       
   201 
       
   202 We use the `match_rtype` selector to select the proper relation type.
       
   203 
       
   204 The essential difference with respect to an entity hook is that there
       
   205 is no self.entity, but `self.eidfrom` and `self.eidto` hook attributes
       
   206 which represent the subject and object eid of the relation.
       
   207 
       
   208 
       
   209 # XXX talk about
       
   210 
       
   211 dict access to entities in before_[add|update]
       
   212 set_operation