[doc, adapters] drop deprecated code from examples
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""Hooks managementThis module defined the `Hook` class and registry and a set of abstract classesfor operations.Hooks are called before / after any individual update of entities / relationsin the repository and on special events such as server startup or shutdown.Operations may be registered by hooks during a transaction, which will befired when the pool is commited or rollbacked.Entity hooks (eg before_add_entity, after_add_entity, before_update_entity,after_update_entity, before_delete_entity, after_delete_entity) all have an`entity` attributeRelation (eg before_add_relation, after_add_relation, before_delete_relation,after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.Server start/maintenance/stop hooks (eg server_startup, server_maintenance,server_shutdown) have a `repo` attribute, but *their `_cw` attribute is None*.The `server_startup` is called on regular startup, while `server_maintenance`is called on cubicweb-ctl upgrade or shell commands. `server_shutdown` iscalled anyway.Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a`timestamp` attributes, but *their `_cw` attribute is None*.Session hooks (eg session_open, session_close) have no special attribute."""from__future__importwith_statement__docformat__="restructuredtext en"fromwarningsimportwarnfromloggingimportgetLoggerfromitertoolsimportchainfromlogilab.common.decoratorsimportclasspropertyfromlogilab.common.deprecationimportdeprecatedfromlogilab.common.logging_extimportset_log_methodsfromcubicwebimportRegistryNotFoundfromcubicweb.cwvregimportCWRegistry,VRegistryfromcubicweb.selectorsimport(objectify_selector,lltrace,ExpectedValueSelector,is_instance)fromcubicweb.appobjectimportAppObjectfromcubicweb.server.sessionimportsecurity_enabledENTITIES_HOOKS=set(('before_add_entity','after_add_entity','before_update_entity','after_update_entity','before_delete_entity','after_delete_entity'))RELATIONS_HOOKS=set(('before_add_relation','after_add_relation','before_delete_relation','after_delete_relation'))SYSTEM_HOOKS=set(('server_backup','server_restore','server_startup','server_maintenance','server_shutdown','session_open','session_close'))ALL_HOOKS=ENTITIES_HOOKS|RELATIONS_HOOKS|SYSTEM_HOOKSclassHooksRegistry(CWRegistry):definitialization_completed(self):forappobjectsinself.values():forclsinappobjects:ifnotcls.enabled:warn('[3.6] %s: enabled is deprecated'%cls)self.unregister(cls)defregister(self,obj,**kwargs):obj.check_events()super(HooksRegistry,self).register(obj,**kwargs)defcall_hooks(self,event,session=None,**kwargs):kwargs['event']=eventifsessionisNone:forhookinsorted(self.possible_objects(session,**kwargs),key=lambdax:x.order):hook()else:# by default, hooks are executed with security turned offwithsecurity_enabled(session,read=False):hooks=sorted(self.possible_objects(session,**kwargs),key=lambdax:x.order)withsecurity_enabled(session,write=False):forhookinhooks:hook()classHooksManager(object):def__init__(self,vreg):self.vreg=vregdefcall_hooks(self,event,session=None,**kwargs):try:self.vreg['%s_hooks'%event].call_hooks(event,session,**kwargs)exceptRegistryNotFound:pass# no hooks for this eventforeventinALL_HOOKS:VRegistry.REGISTRY_FACTORY['%s_hooks'%event]=HooksRegistry_MARKER=object()defentity_oldnewvalue(entity,attr):"""returns the couple (old attr value, new attr value) NOTE: will only work in a before_update_entity hook """# get new value and remove from local dict to force a db query to# fetch old valuenewvalue=entity.pop(attr,_MARKER)oldvalue=getattr(entity,attr)ifnewvalueisnot_MARKER:entity[attr]=newvalueelse:newvalue=oldvaluereturnoldvalue,newvalue# some hook specific selectors #################################################@objectify_selector@lltracedefenabled_category(cls,req,**kwargs):ifreqisNone:returnTrue# XXX how to deactivate server startup / shutdown eventreturnreq.is_hook_activated(cls)@objectify_selector@lltracedeffrom_dbapi_query(cls,req,**kwargs):ifreq.running_dbapi_query:return1return0classrechain(object):def__init__(self,*iterators):self.iterators=iteratorsdef__iter__(self):returniter(chain(*self.iterators))classmatch_rtype(ExpectedValueSelector):"""accept if parameters specified as initializer arguments are specified in named arguments given to the selector :param *expected: parameters (eg `basestring`) which are expected to be found in named arguments (kwargs) """def__init__(self,*expected,**more):self.expected=expectedself.frometypes=more.pop('frometypes',None)self.toetypes=more.pop('toetypes',None)@lltracedef__call__(self,cls,req,*args,**kwargs):ifkwargs.get('rtype')notinself.expected:return0ifself.frometypesisnotNoneand \req.describe(kwargs['eidfrom'])[0]notinself.frometypes:return0ifself.toetypesisnotNoneand \req.describe(kwargs['eidto'])[0]notinself.toetypes:return0return1classmatch_rtype_sets(ExpectedValueSelector):"""accept if parameters specified as initializer arguments are specified in named arguments given to the selector """def__init__(self,*expected):self.expected=expected@lltracedef__call__(self,cls,req,*args,**kwargs):forrel_setinself.expected:ifkwargs.get('rtype')inrel_set:return1return0# base class for hook ##########################################################classHook(AppObject):__select__=enabled_category()# set this in derivated classesevents=Nonecategory=Noneorder=0# XXX deprecatedenabled=True@classmethoddefcheck_events(cls):try:foreventincls.events:ifeventnotinALL_HOOKS:raiseException('bad event %s on %s.%s'%(event,cls.__module__,cls.__name__))exceptAttributeError:raiseexceptTypeError:raiseException('bad .events attribute %s on %s.%s'%(cls.events,cls.__module__,cls.__name__))@classpropertydef__registries__(cls):cls.check_events()return['%s_hooks'%evforevincls.events]@classpropertydef__regid__(cls):warn('[3.6] %s.%s: please specify an id for your hook'%(cls.__module__,cls.__name__),DeprecationWarning)returnstr(id(cls))@classmethoddef__registered__(cls,reg):super(Hook,cls).__registered__(reg)ifgetattr(cls,'accepts',None):warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'%(cls.__module__,cls.__name__),DeprecationWarning)rtypes=[]forertypeincls.accepts:ifertype.islower():rtypes.append(ertype)else:cls.__select__=cls.__select__&is_instance(ertype)ifrtypes:cls.__select__=cls.__select__&match_rtype(*rtypes)returnclsknown_args=set(('entity','rtype','eidfrom','eidto','repo','timestamp'))def__init__(self,req,event,**kwargs):forarginself.known_args:ifarginkwargs:setattr(self,arg,kwargs.pop(arg))super(Hook,self).__init__(req,**kwargs)self.event=eventdef__call__(self):ifhasattr(self,'call'):cls=self.__class__warn('[3.6] %s.%s: call is deprecated, implement __call__'%(cls.__module__,cls.__name__),DeprecationWarning)ifself.event.endswith('_relation'):self.call(self._cw,self.eidfrom,self.rtype,self.eidto)elif'delete'inself.event:self.call(self._cw,self.entity.eid)elifself.event.startswith('server_'):self.call(self.repo)elifself.event.startswith('session_'):self.call(self._cw)else:self.call(self._cw,self.entity)set_log_methods(Hook,getLogger('cubicweb.hook'))# base classes for relation propagation ########################################classPropagateSubjectRelationHook(Hook):"""propagate some `main_rtype` relation on entities linked as object of `subject_relations` or as subject of `object_relations` (the watched relations). This hook ensure that when one of the watched relation is added, the `main_rtype` relation is added to the target entity of the relation. """events=('after_add_relation',)# to set in concrete classmain_rtype=Nonesubject_relations=Noneobject_relations=Nonedef__call__(self):assertself.main_rtypeforeidin(self.eidfrom,self.eidto):etype=self._cw.describe(eid)[0]ifself.main_rtypenotinself._cw.vreg.schema.eschema(etype).subjrels:returnifself.rtypeinself.subject_relations:meid,seid=self.eidfrom,self.eidtoelse:assertself.rtypeinself.object_relationsmeid,seid=self.eidto,self.eidfromself._cw.execute('SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'%(self.main_rtype,self.main_rtype,self.main_rtype),{'x':meid,'e':seid})classPropagateSubjectRelationAddHook(Hook):"""propagate to entities at the end of watched relations when a `main_rtype` relation is added """events=('after_add_relation',)# to set in concrete classsubject_relations=Noneobject_relations=Nonedef__call__(self):eschema=self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])execute=self._cw.executeforrelinself.subject_relations:ifrelineschema.subjrels:execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''X %s R, NOT R %s P'%(self.rtype,rel,self.rtype),{'x':self.eidfrom,'p':self.eidto})forrelinself.object_relations:ifrelineschema.objrels:execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''R %s X, NOT R %s P'%(self.rtype,rel,self.rtype),{'x':self.eidfrom,'p':self.eidto})classPropagateSubjectRelationDelHook(Hook):"""propagate to entities at the end of watched relations when a `main_rtype` relation is deleted """events=('after_delete_relation',)# to set in concrete classsubject_relations=Noneobject_relations=Nonedef__call__(self):eschema=self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])execute=self._cw.executeforrelinself.subject_relations:ifrelineschema.subjrels:execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''X %s R'%(self.rtype,rel),{'x':self.eidfrom,'p':self.eidto})forrelinself.object_relations:ifrelineschema.objrels:execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''R %s X'%(self.rtype,rel),{'x':self.eidfrom,'p':self.eidto})# abstract classes for operation ###############################################classOperation(object):"""an operation is triggered on connections pool events related to commit / rollback transations. Possible events are: precommit: the pool is preparing to commit. You shouldn't do anything which has to be reverted if the commit fails at this point, but you can freely do any heavy computation or raise an exception if the commit can't go. You can add some new operations during this phase but their precommit event won't be triggered commit: the pool is preparing to commit. You should avoid to do to expensive stuff or something that may cause an exception in this event revertcommit: if an operation failed while commited, this event is triggered for all operations which had their commit event already to let them revert things (including the operation which made fail the commit) rollback: the transaction has been either rollbacked either: * intentionaly * a precommit event failed, all operations are rollbacked * a commit event failed, all operations which are not been triggered for commit are rollbacked postcommit: The transaction is over. All the ORM entities are invalid. If you need to work on the database, you need to stard a new transaction, for instance using a new internal_session, which you will need to commit (and close!). order of operations may be important, and is controlled according to the insert_index's method output """def__init__(self,session,**kwargs):self.session=sessionself.__dict__.update(kwargs)self.register(session)# execution informationself.processed=None# 'precommit', 'commit'self.failed=Falsedefregister(self,session):session.add_operation(self,self.insert_index())definsert_index(self):"""return the index of the lastest instance which is not a LateOperation instance """# faster by inspecting operation in reverse order for heavy transactionsi=Nonefori,opinenumerate(reversed(self.session.pending_operations)):ifisinstance(op,(LateOperation,SingleLastOperation)):continuereturn-iorNoneifiisNone:returnNonereturn-(i+1)defhandle_event(self,event):"""delegate event handling to the opertaion"""getattr(self,event)()defprecommit_event(self):"""the observed connections pool is preparing a commit"""defrevertprecommit_event(self):"""an error went when pre-commiting this operation or a later one should revert pre-commit's changes but take care, they may have not been all considered if it's this operation which failed """defcommit_event(self):"""the observed connections pool is commiting"""defrevertcommit_event(self):"""an error went when commiting this operation or a later one should revert commit's changes but take care, they may have not been all considered if it's this operation which failed """defrollback_event(self):"""the observed connections pool has been rollbacked do nothing by default, the operation will just be removed from the pool operation list """defpostcommit_event(self):"""the observed connections pool has committed"""@property@deprecated('[3.6] use self.session.user')defuser(self):returnself.session.user@property@deprecated('[3.6] use self.session.repo')defrepo(self):returnself.session.repo@property@deprecated('[3.6] use self.session.vreg.schema')defschema(self):returnself.session.repo.schema@property@deprecated('[3.6] use self.session.vreg.config')defconfig(self):returnself.session.repo.configset_log_methods(Operation,getLogger('cubicweb.session'))def_container_add(container,value):{set:set.add,list:list.append}[container.__class__](container,value)defset_operation(session,datakey,value,opcls,containercls=set,**opkwargs):"""Search for session.transaction_data[`datakey`] (expected to be a set): * if found, simply append `value` * else, initialize it to containercls([`value`]) and instantiate the given `opcls` operation class with additional keyword arguments. `containercls` is a set by default. Give `list` if you want to keep arrival ordering. You should use this instead of creating on operation for each `value`, since handling operations becomes coslty on massive data import. """try:_container_add(session.transaction_data[datakey],value)exceptKeyError:opcls(session,**opkwargs)session.transaction_data[datakey]=containercls()_container_add(session.transaction_data[datakey],value)classLateOperation(Operation):"""special operation which should be called after all possible (ie non late) operations """definsert_index(self):"""return the index of the lastest instance which is not a SingleLastOperation instance """# faster by inspecting operation in reverse order for heavy transactionsi=Nonefori,opinenumerate(reversed(self.session.pending_operations)):ifisinstance(op,SingleLastOperation):continuereturn-iorNoneifiisNone:returnNonereturn-(i+1)classSingleOperation(Operation):"""special operation which should be called once"""defregister(self,session):"""override register to handle cases where this operation has already been added """operations=session.pending_operationsindex=self.equivalent_index(operations)ifindexisnotNone:equivalent=operations.pop(index)else:equivalent=Nonesession.add_operation(self,self.insert_index())returnequivalentdefequivalent_index(self,operations):"""return the index of the equivalent operation if any"""fori,opinenumerate(reversed(operations)):ifop.__class__isself.__class__:return-(i+1)returnNoneclassSingleLastOperation(SingleOperation):"""special operation which should be called once and after all other operations """definsert_index(self):returnNoneclassSendMailOp(SingleLastOperation):def__init__(self,session,msg=None,recipients=None,**kwargs):# may not specify msg yet, as# `cubicweb.sobjects.supervision.SupervisionMailOp`ifmsgisnotNone:assertrecipientsself.to_send=[(msg,recipients)]else:assertrecipientsisNoneself.to_send=[]super(SendMailOp,self).__init__(session,**kwargs)defregister(self,session):previous=super(SendMailOp,self).register(session)ifprevious:self.to_send=previous.to_send+self.to_senddefcommit_event(self):self.session.repo.threaded_task(self.sendmails)defsendmails(self):self.session.vreg.config.sendmails(self.to_send)classRQLPrecommitOperation(Operation):defprecommit_event(self):execute=self.session.executeforrqlinself.rqls:execute(*rql)classCleanupNewEidsCacheOp(SingleLastOperation):"""on rollback of a insert query we have to remove from repository's type/source cache eids of entities added in that transaction. NOTE: querier's rqlst/solutions cache may have been polluted too with queries such as Any X WHERE X eid 32 if 32 has been rollbacked however generated queries are unpredictable and analysing all the cache probably too expensive. Notice that there is no pb when using args to specify eids instead of giving them into the rql string. """defrollback_event(self):"""the observed connections pool has been rollbacked, remove inserted eid from repository type/source cache """try:self.session.repo.clear_caches(self.session.transaction_data['neweids'])exceptKeyError:passclassCleanupDeletedEidsCacheOp(SingleLastOperation):"""on commit of delete query, we have to remove from repository's type/source cache eids of entities deleted in that transaction. """defcommit_event(self):"""the observed connections pool has been rollbacked, remove inserted eid from repository type/source cache """try:self.session.repo.clear_caches(self.session.transaction_data['pendingeids'])exceptKeyError:pass