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 |