59 from logilab.common.decorators import classproperty |
59 from logilab.common.decorators import classproperty |
60 from logilab.common.deprecation import deprecated |
60 from logilab.common.deprecation import deprecated |
61 from logilab.common.logging_ext import set_log_methods |
61 from logilab.common.logging_ext import set_log_methods |
62 |
62 |
63 from cubicweb import RegistryNotFound |
63 from cubicweb import RegistryNotFound |
|
64 from cubicweb.vregistry import classid |
64 from cubicweb.cwvreg import CWRegistry, VRegistry |
65 from cubicweb.cwvreg import CWRegistry, VRegistry |
65 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector, |
66 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector, |
66 is_instance) |
67 is_instance) |
67 from cubicweb.appobject import AppObject |
68 from cubicweb.appobject import AppObject |
68 from cubicweb.server.session import security_enabled |
69 from cubicweb.server.session import security_enabled |
81 class HooksRegistry(CWRegistry): |
82 class HooksRegistry(CWRegistry): |
82 def initialization_completed(self): |
83 def initialization_completed(self): |
83 for appobjects in self.values(): |
84 for appobjects in self.values(): |
84 for cls in appobjects: |
85 for cls in appobjects: |
85 if not cls.enabled: |
86 if not cls.enabled: |
86 warn('[3.6] %s: enabled is deprecated' % cls) |
87 warn('[3.6] %s: enabled is deprecated' % classid(cls)) |
87 self.unregister(cls) |
88 self.unregister(cls) |
88 |
89 |
89 def register(self, obj, **kwargs): |
90 def register(self, obj, **kwargs): |
90 obj.check_events() |
91 obj.check_events() |
91 super(HooksRegistry, self).register(obj, **kwargs) |
92 super(HooksRegistry, self).register(obj, **kwargs) |
117 |
118 |
118 |
119 |
119 for event in ALL_HOOKS: |
120 for event in ALL_HOOKS: |
120 VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry |
121 VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry |
121 |
122 |
122 _MARKER = object() |
123 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)') |
123 def entity_oldnewvalue(entity, attr): |
124 def entity_oldnewvalue(entity, attr): |
124 """returns the couple (old attr value, new attr value) |
125 return entity.cw_edited.oldnewvalue(attr) |
125 |
|
126 NOTE: will only work in a before_update_entity hook |
|
127 """ |
|
128 # get new value and remove from local dict to force a db query to |
|
129 # fetch old value |
|
130 newvalue = entity.pop(attr, _MARKER) |
|
131 oldvalue = getattr(entity, attr) |
|
132 if newvalue is not _MARKER: |
|
133 entity[attr] = newvalue |
|
134 else: |
|
135 newvalue = oldvalue |
|
136 return oldvalue, newvalue |
|
137 |
126 |
138 |
127 |
139 # some hook specific selectors ################################################# |
128 # some hook specific selectors ################################################# |
140 |
129 |
141 @objectify_selector |
130 @objectify_selector |
229 cls.check_events() |
218 cls.check_events() |
230 return ['%s_hooks' % ev for ev in cls.events] |
219 return ['%s_hooks' % ev for ev in cls.events] |
231 |
220 |
232 @classproperty |
221 @classproperty |
233 def __regid__(cls): |
222 def __regid__(cls): |
234 warn('[3.6] %s.%s: please specify an id for your hook' |
223 warn('[3.6] %s: please specify an id for your hook' % classid(cls), |
235 % (cls.__module__, cls.__name__), DeprecationWarning) |
224 DeprecationWarning) |
236 return str(id(cls)) |
225 return str(id(cls)) |
237 |
226 |
238 @classmethod |
227 @classmethod |
239 def __registered__(cls, reg): |
228 def __registered__(cls, reg): |
240 super(Hook, cls).__registered__(reg) |
229 super(Hook, cls).__registered__(reg) |
241 if getattr(cls, 'accepts', None): |
230 if getattr(cls, 'accepts', None): |
242 warn('[3.6] %s.%s: accepts is deprecated, define proper __select__' |
231 warn('[3.6] %s: accepts is deprecated, define proper __select__' |
243 % (cls.__module__, cls.__name__), DeprecationWarning) |
232 % classid(cls), DeprecationWarning) |
244 rtypes = [] |
233 rtypes = [] |
245 for ertype in cls.accepts: |
234 for ertype in cls.accepts: |
246 if ertype.islower(): |
235 if ertype.islower(): |
247 rtypes.append(ertype) |
236 rtypes.append(ertype) |
248 else: |
237 else: |
259 super(Hook, self).__init__(req, **kwargs) |
248 super(Hook, self).__init__(req, **kwargs) |
260 self.event = event |
249 self.event = event |
261 |
250 |
262 def __call__(self): |
251 def __call__(self): |
263 if hasattr(self, 'call'): |
252 if hasattr(self, 'call'): |
264 cls = self.__class__ |
253 warn('[3.6] %s: call is deprecated, implement __call__' |
265 warn('[3.6] %s.%s: call is deprecated, implement __call__' |
254 % classid(self.__class__), DeprecationWarning) |
266 % (cls.__module__, cls.__name__), DeprecationWarning) |
|
267 if self.event.endswith('_relation'): |
255 if self.event.endswith('_relation'): |
268 self.call(self._cw, self.eidfrom, self.rtype, self.eidto) |
256 self.call(self._cw, self.eidfrom, self.rtype, self.eidto) |
269 elif 'delete' in self.event: |
257 elif 'delete' in self.event: |
270 self.call(self._cw, self.entity.eid) |
258 self.call(self._cw, self.entity.eid) |
271 elif self.event.startswith('server_'): |
259 elif self.event.startswith('server_'): |
426 return None |
414 return None |
427 return -(i + 1) |
415 return -(i + 1) |
428 |
416 |
429 def handle_event(self, event): |
417 def handle_event(self, event): |
430 """delegate event handling to the opertaion""" |
418 """delegate event handling to the opertaion""" |
|
419 if event == 'postcommit_event' and hasattr(self, 'commit_event'): |
|
420 warn('[3.10] %s: commit_event method has been replaced by postcommit_event' |
|
421 % classid(self.__class__), DeprecationWarning) |
|
422 self.commit_event() |
431 getattr(self, event)() |
423 getattr(self, event)() |
432 |
424 |
433 def precommit_event(self): |
425 def precommit_event(self): |
434 """the observed connections pool is preparing a commit""" |
426 """the observed connections pool is preparing a commit""" |
435 |
427 |
436 def revertprecommit_event(self): |
428 def revertprecommit_event(self): |
437 """an error went when pre-commiting this operation or a later one |
429 """an error went when pre-commiting this operation or a later one |
438 |
430 |
439 should revert pre-commit's changes but take care, they may have not |
431 should revert pre-commit's changes but take care, they may have not |
440 been all considered if it's this operation which failed |
|
441 """ |
|
442 |
|
443 def commit_event(self): |
|
444 """the observed connections pool is commiting""" |
|
445 |
|
446 def revertcommit_event(self): |
|
447 """an error went when commiting this operation or a later one |
|
448 |
|
449 should revert commit's changes but take care, they may have not |
|
450 been all considered if it's this operation which failed |
432 been all considered if it's this operation which failed |
451 """ |
433 """ |
452 |
434 |
453 def rollback_event(self): |
435 def rollback_event(self): |
454 """the observed connections pool has been rollbacked |
436 """the observed connections pool has been rollbacked |
522 if i is None: |
504 if i is None: |
523 return None |
505 return None |
524 return -(i + 1) |
506 return -(i + 1) |
525 |
507 |
526 |
508 |
527 class SingleOperation(Operation): |
509 |
528 """special operation which should be called once""" |
510 class SingleLastOperation(Operation): |
|
511 """special operation which should be called once and after all other |
|
512 operations |
|
513 """ |
|
514 |
529 def register(self, session): |
515 def register(self, session): |
530 """override register to handle cases where this operation has already |
516 """override register to handle cases where this operation has already |
531 been added |
517 been added |
532 """ |
518 """ |
533 operations = session.pending_operations |
519 operations = session.pending_operations |
544 for i, op in enumerate(reversed(operations)): |
530 for i, op in enumerate(reversed(operations)): |
545 if op.__class__ is self.__class__: |
531 if op.__class__ is self.__class__: |
546 return -(i+1) |
532 return -(i+1) |
547 return None |
533 return None |
548 |
534 |
549 |
|
550 class SingleLastOperation(SingleOperation): |
|
551 """special operation which should be called once and after all other |
|
552 operations |
|
553 """ |
|
554 def insert_index(self): |
535 def insert_index(self): |
555 return None |
536 return None |
556 |
537 |
557 |
538 |
558 class SendMailOp(SingleLastOperation): |
539 class SendMailOp(SingleLastOperation): |
570 def register(self, session): |
551 def register(self, session): |
571 previous = super(SendMailOp, self).register(session) |
552 previous = super(SendMailOp, self).register(session) |
572 if previous: |
553 if previous: |
573 self.to_send = previous.to_send + self.to_send |
554 self.to_send = previous.to_send + self.to_send |
574 |
555 |
575 def commit_event(self): |
556 def postcommit_event(self): |
576 self.session.repo.threaded_task(self.sendmails) |
557 self.session.repo.threaded_task(self.sendmails) |
577 |
558 |
578 def sendmails(self): |
559 def sendmails(self): |
579 self.session.vreg.config.sendmails(self.to_send) |
560 self.session.vreg.config.sendmails(self.to_send) |
580 |
561 |
610 class CleanupDeletedEidsCacheOp(SingleLastOperation): |
591 class CleanupDeletedEidsCacheOp(SingleLastOperation): |
611 """on commit of delete query, we have to remove from repository's |
592 """on commit of delete query, we have to remove from repository's |
612 type/source cache eids of entities deleted in that transaction. |
593 type/source cache eids of entities deleted in that transaction. |
613 """ |
594 """ |
614 |
595 |
615 def commit_event(self): |
596 def postcommit_event(self): |
616 """the observed connections pool has been rollbacked, |
597 """the observed connections pool has been rollbacked, |
617 remove inserted eid from repository type/source cache |
598 remove inserted eid from repository type/source cache |
618 """ |
599 """ |
619 try: |
600 try: |
620 self.session.repo.clear_caches( |
601 self.session.repo.clear_caches( |