[book] fix Session.{set,free}_cnxset autodoc
They're Session methods, not bare functions.
Without this building the docs says:
reading sources... [ 39%] devrepo/repo/sessions
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.7/sphinx/ext/autodoc.py", line 326, in import_object
obj = self.get_attr(obj, part)
File "/usr/lib/pymodules/python2.7/sphinx/ext/autodoc.py", line 232, in get_attr
return safe_getattr(obj, name, *defargs)
File "/usr/lib/pymodules/python2.7/sphinx/util/inspect.py", line 70, in safe_getattr
raise AttributeError(name)
AttributeError: set_cnxset
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.7/sphinx/ext/autodoc.py", line 326, in import_object
obj = self.get_attr(obj, part)
File "/usr/lib/pymodules/python2.7/sphinx/ext/autodoc.py", line 232, in get_attr
return safe_getattr(obj, name, *defargs)
File "/usr/lib/pymodules/python2.7/sphinx/util/inspect.py", line 70, in safe_getattr
raise AttributeError(name)
AttributeError: free_cnxset
# copyright 2003-2012 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/>."""Core hooks: workflow related hooks"""__docformat__="restructuredtext en"_=unicodefromdatetimeimportdatetimefromcubicwebimportRepositoryError,validation_errorfromcubicweb.predicatesimportis_instance,adaptablefromcubicweb.serverimporthookdef_change_state(session,x,oldstate,newstate):nocheck=session.transaction_data.setdefault('skip-security',set())nocheck.add((x,'in_state',oldstate))nocheck.add((x,'in_state',newstate))# delete previous state first unless in_state isn't stored in the system# sourcefromsource=session.describe(x)[1]iffromsource=='system'or \notsession.repo.sources_by_uri[fromsource].support_relation('in_state'):session.delete_relation(x,'in_state',oldstate)session.add_relation(x,'in_state',newstate)# operations ###################################################################class_SetInitialStateOp(hook.Operation):"""make initial state be a default state"""entity=None# make pylint happydefprecommit_event(self):session=self.sessionentity=self.entityiworkflowable=entity.cw_adapt_to('IWorkflowable')# if there is an initial state and the entity's state is not set,# use the initial state as a default stateifnot(session.deleted_in_transaction(entity.eid)orentity.in_state) \andiworkflowable.current_workflow:state=iworkflowable.current_workflow.initialifstate:session.add_relation(entity.eid,'in_state',state.eid)_FireAutotransitionOp(session,entity=entity)class_FireAutotransitionOp(hook.Operation):"""try to fire auto transition after state changes"""entity=None# make pylint happydefprecommit_event(self):entity=self.entityiworkflowable=entity.cw_adapt_to('IWorkflowable')autotrs=list(iworkflowable.possible_transitions('auto'))ifautotrs:assertlen(autotrs)==1iworkflowable.fire_transition(autotrs[0])class_WorkflowChangedOp(hook.Operation):"""fix entity current state when changing its workflow"""eid=wfeid=None# make pylint happydefprecommit_event(self):# notice that enforcement that new workflow apply to the entity's type is# done by schema rule, no need to check it heresession=self.sessionpendingeids=session.transaction_data.get('pendingeids',())ifself.eidinpendingeids:returnentity=session.entity_from_eid(self.eid)iworkflowable=entity.cw_adapt_to('IWorkflowable')# check custom workflow has not been rechanged to another one in the same# transactionmainwf=iworkflowable.main_workflowifmainwf.eid==self.wfeid:deststate=mainwf.initialifnotdeststate:msg=_('workflow has no initial state')raisevalidation_error(entity,{('custom_workflow','subject'):msg})ifmainwf.state_by_eid(iworkflowable.current_state.eid):# nothing to doreturn# if there are no history, simply go to new workflow's initial stateifnotiworkflowable.workflow_history:ifiworkflowable.current_state.eid!=deststate.eid:_change_state(session,entity.eid,iworkflowable.current_state.eid,deststate.eid)_FireAutotransitionOp(session,entity=entity)returnmsg=session._('workflow changed to "%s"')msg%=session._(mainwf.name)session.transaction_data[(entity.eid,'customwf')]=self.wfeidiworkflowable.change_state(deststate,msg,u'text/plain')class_CheckTrExitPoint(hook.Operation):treid=None# make pylint happydefprecommit_event(self):tr=self.session.entity_from_eid(self.treid)outputs=set()forepintr.subworkflow_exit:ifep.subwf_state.eidinoutputs:msg=_("can't have multiple exits on the same state")raisevalidation_error(self.treid,{('subworkflow_exit','subject'):msg})outputs.add(ep.subwf_state.eid)class_SubWorkflowExitOp(hook.Operation):forentity=trinfo=None# make pylint happydefprecommit_event(self):session=self.sessionforentity=self.forentityiworkflowable=forentity.cw_adapt_to('IWorkflowable')trinfo=self.trinfo# we're in a subworkflow, check if we've reached an exit pointwftr=iworkflowable.subworkflow_input_transition()ifwftrisNone:# inconsistency detectedmsg=_("state doesn't belong to entity's current workflow")raisevalidation_error(self.trinfo,{('to_state','subject'):msg})tostate=wftr.get_exit_point(forentity,trinfo.cw_attr_cache['to_state'])iftostateisnotNone:# reached an exit pointmsg=_('exiting from subworkflow %s')msg%=session._(iworkflowable.current_workflow.name)session.transaction_data[(forentity.eid,'subwfentrytr')]=Trueiworkflowable.change_state(tostate,msg,u'text/plain',tr=wftr)# hooks ########################################################################classWorkflowHook(hook.Hook):__abstract__=Truecategory='metadata'classSetInitialStateHook(WorkflowHook):__regid__='wfsetinitial'__select__=WorkflowHook.__select__&adaptable('IWorkflowable')events=('after_add_entity',)def__call__(self):_SetInitialStateOp(self._cw,entity=self.entity)classFireTransitionHook(WorkflowHook):"""check the transition is allowed and add missing information into the TrInfo entity. Expect that: * wf_info_for inlined relation is set * by_transition or to_state (managers only) inlined relation is set Check for automatic transition to be fired at the end """__regid__='wffiretransition'__select__=WorkflowHook.__select__&is_instance('TrInfo')events=('before_add_entity',)def__call__(self):session=self._cwentity=self.entity# first retreive entity to which the state change applytry:foreid=entity.cw_attr_cache['wf_info_for']exceptKeyError:msg=_('mandatory relation')raisevalidation_error(entity,{('wf_info_for','subject'):msg})forentity=session.entity_from_eid(foreid)# see comment in the TrInfo entity definitionentity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)iworkflowable=forentity.cw_adapt_to('IWorkflowable')# then check it has a workflow set, unless we're in the process of changing# entity's workflowifsession.transaction_data.get((forentity.eid,'customwf')):wfeid=session.transaction_data[(forentity.eid,'customwf')]wf=session.entity_from_eid(wfeid)else:wf=iworkflowable.current_workflowifwfisNone:msg=_('related entity has no workflow set')raisevalidation_error(entity,{None:msg})# then check it has a state setfromstate=iworkflowable.current_stateiffromstateisNone:msg=_('related entity has no state')raisevalidation_error(entity,{None:msg})# True if we are coming back from subworkflowswtr=session.transaction_data.pop((forentity.eid,'subwfentrytr'),None)cowpowers=(session.user.is_in_group('managers')ornotsession.write_security)# no investigate the requested state change...try:treid=entity.cw_attr_cache['by_transition']exceptKeyError:# no transition set, check user is a manager and destination state# is specified (and valid)ifnotcowpowers:msg=_('mandatory relation')raisevalidation_error(entity,{('by_transition','subject'):msg})deststateeid=entity.cw_attr_cache.get('to_state')ifnotdeststateeid:msg=_('mandatory relation')raisevalidation_error(entity,{('by_transition','subject'):msg})deststate=wf.state_by_eid(deststateeid)ifdeststateisNone:msg=_("state doesn't belong to entity's workflow")raisevalidation_error(entity,{('to_state','subject'):msg})else:# check transition is valid and allowed, unless we're coming back# from subworkflowtr=session.entity_from_eid(treid)ifswtrisNone:qname=('by_transition','subject')iftrisNone:msg=_("transition doesn't belong to entity's workflow")raisevalidation_error(entity,{qname:msg})ifnottr.has_input_state(fromstate):msg=_("transition %(tr)s isn't allowed from %(st)s")raisevalidation_error(entity,{qname:msg},{'tr':tr.name,'st':fromstate.name},['tr','st'])ifnottr.may_be_fired(foreid):msg=_("transition may not be fired")raisevalidation_error(entity,{qname:msg})deststateeid=entity.cw_attr_cache.get('to_state')ifdeststateeidisnotNone:ifnotcowpowersanddeststateeid!=tr.destination(forentity).eid:msg=_("transition isn't allowed")raisevalidation_error(entity,{('by_transition','subject'):msg})ifswtrisNone:deststate=session.entity_from_eid(deststateeid)ifnotcowpowersanddeststateisNone:msg=_("state doesn't belong to entity's workflow")raisevalidation_error(entity,{('to_state','subject'):msg})else:deststateeid=tr.destination(forentity).eid# everything is ok, add missing information on the trinfo entityentity.cw_edited['from_state']=fromstate.eidentity.cw_edited['to_state']=deststateeidnocheck=session.transaction_data.setdefault('skip-security',set())nocheck.add((entity.eid,'from_state',fromstate.eid))nocheck.add((entity.eid,'to_state',deststateeid))_FireAutotransitionOp(session,entity=forentity)classFiredTransitionHook(WorkflowHook):"""change related entity state and handle exit of subworkflow"""__regid__='wffiretransition'__select__=WorkflowHook.__select__&is_instance('TrInfo')events=('after_add_entity',)def__call__(self):trinfo=self.entityrcache=trinfo.cw_attr_cache_change_state(self._cw,rcache['wf_info_for'],rcache['from_state'],rcache['to_state'])forentity=self._cw.entity_from_eid(rcache['wf_info_for'])iworkflowable=forentity.cw_adapt_to('IWorkflowable')assertiworkflowable.current_state.eid==rcache['to_state']ifiworkflowable.main_workflow.eid!=iworkflowable.current_workflow.eid:_SubWorkflowExitOp(self._cw,forentity=forentity,trinfo=trinfo)classCheckInStateChangeAllowed(WorkflowHook):"""check state apply, in case of direct in_state change using unsafe execute """__regid__='wfcheckinstate'__select__=WorkflowHook.__select__&hook.match_rtype('in_state')events=('before_add_relation',)category='integrity'def__call__(self):session=self._cwnocheck=session.transaction_data.get('skip-security',())if(self.eidfrom,'in_state',self.eidto)innocheck:# state changed through TrInfo insertion, so we already know it's okreturnentity=session.entity_from_eid(self.eidfrom)iworkflowable=entity.cw_adapt_to('IWorkflowable')mainwf=iworkflowable.main_workflowifmainwfisNone:msg=_('entity has no workflow set')raisevalidation_error(entity,{None:msg})forwfinmainwf.iter_workflows():ifwf.state_by_eid(self.eidto):breakelse:msg=_("state doesn't belong to entity's workflow. You may ""want to set a custom workflow for this entity first.")raisevalidation_error(self.eidfrom,{('in_state','subject'):msg})ifiworkflowable.current_workflowandwf.eid!=iworkflowable.current_workflow.eid:msg=_("state doesn't belong to entity's current workflow")raisevalidation_error(self.eidfrom,{('in_state','subject'):msg})classSetModificationDateOnStateChange(WorkflowHook):"""update entity's modification date after changing its state"""__regid__='wfsyncmdate'__select__=WorkflowHook.__select__&hook.match_rtype('in_state')events=('after_add_relation',)def__call__(self):ifself._cw.added_in_transaction(self.eidfrom):# new entity, not neededreturnentity=self._cw.entity_from_eid(self.eidfrom)try:entity.cw_set(modification_date=datetime.now())exceptRepositoryErrorasex:# usually occurs if entity is coming from a read-only source# (eg ldap user)self.warning('cant change modification date for %s: %s',entity,ex)classCheckWorkflowTransitionExitPoint(WorkflowHook):"""check that there is no multiple exits from the same state"""__regid__='wfcheckwftrexit'__select__=WorkflowHook.__select__&hook.match_rtype('subworkflow_exit')events=('after_add_relation',)def__call__(self):_CheckTrExitPoint(self._cw,treid=self.eidfrom)classSetCustomWorkflow(WorkflowHook):__regid__='wfsetcustom'__select__=WorkflowHook.__select__&hook.match_rtype('custom_workflow')events=('after_add_relation',)def__call__(self):_WorkflowChangedOp(self._cw,eid=self.eidfrom,wfeid=self.eidto)classDelCustomWorkflow(SetCustomWorkflow):__regid__='wfdelcustom'events=('after_delete_relation',)def__call__(self):entity=self._cw.entity_from_eid(self.eidfrom)typewf=entity.cw_adapt_to('IWorkflowable').cwetype_workflow()iftypewfisnotNone:_WorkflowChangedOp(self._cw,eid=self.eidfrom,wfeid=typewf.eid)