diff -r 000000000000 -r b97547f5f1fa server/hooksmanager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/hooksmanager.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,254 @@ +"""Hooks management + +Hooks are called before / after any individual update of entities / relations +in the repository. + +Here is the prototype of the different hooks: + +* filtered on the entity's type: + + before_add_entity (session, entity) + after_add_entity (session, entity) + before_update_entity (session, entity) + after_update_entity (session, entity) + before_delete_entity (session, eid) + after_delete_entity (session, eid) + +* filtered on the relation's type: + + before_add_relation (session, fromeid, rtype, toeid) + after_add_relation (session, fromeid, rtype, toeid) + before_delete_relation (session, fromeid, rtype, toeid) + after_delete_relation (session, fromeid, rtype, toeid) + + +:organization: Logilab +:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +ENTITIES_HOOKS = ('before_add_entity', 'after_add_entity', + 'before_update_entity', 'after_update_entity', + 'before_delete_entity', 'after_delete_entity') +RELATIONS_HOOKS = ('before_add_relation', 'after_add_relation' , + 'before_delete_relation','after_delete_relation') +SYSTEM_HOOKS = ('server_startup', 'server_shutdown', + 'session_open', 'session_close') + +ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS) + +class HooksManager(object): + """handle hooks registration and calls + """ + verification_hooks_activated = True + + def __init__(self, schema): + self.set_schema(schema) + + def set_schema(self, schema): + self._hooks = {} + self.schema = schema + self._init_hooks(schema) + + def register_hooks(self, hooks): + """register a dictionary of hooks : + + {'event': {'entity or relation type': [callbacks list]}} + """ + for event, subevents in hooks.items(): + for subevent, callbacks in subevents.items(): + for callback in callbacks: + self.register_hook(callback, event, subevent) + + def register_hook(self, function, event, etype=''): + """register a function to call when occurs + + is an entity/relation type or an empty string. + If etype is the empty string, the function will be called at each + event, else the function will be called only when event occurs on an + entity/relation of the given type. + """ + assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS) + assert (not event in SYSTEM_HOOKS or not etype), (event, etype) + etype = etype or '' + try: + self._hooks[event][etype].append(function) + self.debug('registered hook %s on %s (%s)', event, etype or 'any', + function.func_name) + + except KeyError: + self.error('can\'t register hook %s on %s (%s)', + event, etype or 'any', function.func_name) + + def unregister_hook(self, function, event, etype=''): + """register a function to call when occurs + + is an entity/relation type or an empty string. + If etype is the empty string, the function will be called at each + event, else the function will be called only when event occurs on an + entity/relation of the given type. + """ + assert event in ALL_HOOKS, event + etype = etype or '' + self.info('unregister hook %s on %s (%s)', event, etype, + function.func_name) + self._hooks[event][etype].remove(function) + + def call_hooks(self, __event, __type='', *args, **kwargs): + """call hook matching event and optional type""" + if __type: + self.info('calling hooks for event %s (%s)', __event, __type) + else: + self.info('calling hooks for event %s', __event) + # call generic hooks first + for hook in self._hooks[__event]['']: + #print '[generic]', hook.__name__ + hook(*args, **kwargs) + if __type: + for hook in self._hooks[__event][__type]: + #print '[%s]'%__type, hook.__name__ + hook(*args, **kwargs) + + def _init_hooks(self, schema): + """initialize the hooks map""" + for hook_event in ENTITIES_HOOKS: + self._hooks[hook_event] = {'': []} + for etype in schema.entities(): + self._hooks[hook_event][etype] = [] + for hook_event in RELATIONS_HOOKS: + self._hooks[hook_event] = {'': []} + for r_type in schema.relations(): + self._hooks[hook_event][r_type] = [] + for hook_event in SYSTEM_HOOKS: + self._hooks[hook_event] = {'': []} + + def register_system_hooks(self, config): + """register system hooks according to the configuration""" + self.info('register core hooks') + from cubicweb.server.hooks import _register_metadata_hooks, _register_wf_hooks + _register_metadata_hooks(self) + self.info('register workflow hooks') + _register_wf_hooks(self) + if config.core_hooks: + from cubicweb.server.hooks import _register_core_hooks + _register_core_hooks(self) + if config.schema_hooks: + from cubicweb.server.schemahooks import _register_schema_hooks + self.info('register schema hooks') + _register_schema_hooks(self) + if config.usergroup_hooks: + from cubicweb.server.hooks import _register_usergroup_hooks + from cubicweb.server.hooks import _register_eproperty_hooks + self.info('register user/group hooks') + _register_usergroup_hooks(self) + _register_eproperty_hooks(self) + if config.security_hooks: + from cubicweb.server.securityhooks import register_security_hooks + self.info('register security hooks') + register_security_hooks(self) + if not self.verification_hooks_activated: + self.deactivate_verification_hooks() + + def deactivate_verification_hooks(self): + from cubicweb.server.hooks import (cardinalitycheck_after_add_entity, + cardinalitycheck_before_del_relation, + cstrcheck_after_add_relation, + uniquecstrcheck_before_modification) + self.warning('deactivating verification hooks') + self.verification_hooks_activated = False + self.unregister_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '') + self.unregister_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '') + self.unregister_hook(cstrcheck_after_add_relation, 'after_add_relation', '') + self.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '') + self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '') +# self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '') +# self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '') + + def reactivate_verification_hooks(self): + from cubicweb.server.hooks import (cardinalitycheck_after_add_entity, + cardinalitycheck_before_del_relation, + cstrcheck_after_add_relation, + uniquecstrcheck_before_modification) + self.warning('reactivating verification hooks') + self.verification_hooks_activated = True + self.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '') + self.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '') + self.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '') + self.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '') + self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '') +# self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '') +# self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '') + +from cubicweb.vregistry import autoselectors +from cubicweb.common.appobject import AppObject +from cubicweb.common.registerers import accepts_registerer, yes_registerer +from cubicweb.common.selectors import yes_selector + +class autoid(autoselectors): + """metaclass to create an unique 'id' attribute on the class using it""" + def __new__(mcs, name, bases, classdict): + cls = super(autoid, mcs).__new__(mcs, name, bases, classdict) + cls.id = str(id(cls)) + return cls + +class Hook(AppObject): + __metaclass__ = autoid + __registry__ = 'hooks' + __registerer__ = accepts_registerer + __selectors__ = (yes_selector,) + # set this in derivated classes + events = None + accepts = None + enabled = True + + def __init__(self, event=None): + super(Hook, self).__init__() + self.event = event + + @classmethod + def registered(cls, vreg): + super(Hook, cls).registered(vreg) + return cls() + + @classmethod + def register_to(cls): + if not cls.enabled: + cls.warning('%s hook has been disabled', cls) + return + done = set() + for event in cls.events: + for ertype in cls.accepts: + if (event, ertype) in done: + continue + yield event, ertype + done.add((event, ertype)) + try: + eschema = cls.schema.eschema(ertype) + except KeyError: + # relation schema + pass + else: + for eetype in eschema.specialized_by(): + if (event, eetype) in done: + continue + yield event, str(eetype) + done.add((event, eetype)) + + + def make_callback(self, event): + if len(self.events) == 1: + return self.call + return self.__class__(event=event).call + + def call(self): + raise NotImplementedError + +class SystemHook(Hook): + __registerer__ = yes_registerer + accepts = ('',) + +from logging import getLogger +from cubicweb import set_log_methods +set_log_methods(HooksManager, getLogger('cubicweb.hooksmanager')) +set_log_methods(Hook, getLogger('cubicweb.hooks'))