[devtools] call turn_repo_off in tearDown (closes #4673491)
lets it catch leaked sessions even when running a single test
# copyright 2003-2014 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/>."""Repository users' and internal' sessions."""__docformat__="restructuredtext en"importsysimportthreadingfromtimeimporttimefromuuidimportuuid4fromwarningsimportwarnimportfunctoolsfromcontextlibimportcontextmanagerfromlogilab.common.deprecationimportdeprecatedfromlogilab.common.textutilsimportunormalizefromlogilab.common.registryimportobjectify_predicatefromcubicwebimportQueryError,schema,server,ProgrammingErrorfromcubicweb.reqimportRequestSessionBasefromcubicweb.utilsimportmake_uidfromcubicweb.rqlrewriteimportRQLRewriterfromcubicweb.serverimportShuttingDownfromcubicweb.server.editionimportEditedEntityNO_UNDO_TYPES=schema.SCHEMA_TYPES.copy()NO_UNDO_TYPES.add('CWCache')# is / is_instance_of are usually added by sql hooks except when using# dataimport.NoHookRQLObjectStore, and we don't want to record them# anyway in the later caseNO_UNDO_TYPES.add('is')NO_UNDO_TYPES.add('is_instance_of')NO_UNDO_TYPES.add('cw_source')# XXX rememberme,forgotpwd,apycot,vcsfile@objectify_predicatedefis_user_session(cls,req,**kwargs):"""return 1 when session is not internal. This predicate can only be used repository side only. """returnnotreq.is_internal_session@objectify_predicatedefis_internal_session(cls,req,**kwargs):"""return 1 when session is not internal. This predicate can only be used repository side only. """returnreq.is_internal_session@objectify_predicatedefrepairing(cls,req,**kwargs):"""return 1 when repository is running in repair mode"""returnreq.vreg.config.repairingclasstransaction(object):"""Ensure that the transaction is either commited or rolled back at exit Context manager to enter a transaction for a session: when exiting the `with` block on exception, call `session.rollback()`, else call `session.commit()` on normal exit """def__init__(self,session,free_cnxset=True):self.session=sessionself.free_cnxset=free_cnxsetdef__enter__(self):# ensure session has a cnxsetself.session.set_cnxset()def__exit__(self,exctype,exc,traceback):ifexctype:self.session.rollback(free_cnxset=self.free_cnxset)else:self.session.commit(free_cnxset=self.free_cnxset)@deprecated('[3.17] use <object>.allow/deny_all_hooks_but instead')defhooks_control(obj,mode,*categories):assertmodein(HOOKS_ALLOW_ALL,HOOKS_DENY_ALL)ifmode==HOOKS_ALLOW_ALL:returnobj.allow_all_hooks_but(*categories)elifmode==HOOKS_DENY_ALL:returnobj.deny_all_hooks_but(*categories)class_hooks_control(object):# XXX repoapi: remove me when# session stop being connection"""context manager to control activated hooks categories. If mode is `HOOKS_DENY_ALL`, given hooks categories will be enabled. If mode is `HOOKS_ALLOW_ALL`, given hooks categories will be disabled. .. sourcecode:: python with _hooks_control(cnx, HOOKS_ALLOW_ALL, 'integrity'): # ... do stuff with all but 'integrity' hooks activated with _hooks_control(cnx, HOOKS_DENY_ALL, 'integrity'): # ... do stuff with none but 'integrity' hooks activated This is an internal API, you should rather use :meth:`~cubicweb.server.session.Connection.deny_all_hooks_but` or :meth:`~cubicweb.server.session.Connection.allow_all_hooks_but` Connection methods. """def__init__(self,cnx,mode,*categories):assertmodein(HOOKS_ALLOW_ALL,HOOKS_DENY_ALL)self.cnx=cnxself.mode=modeself.categories=categoriesself.oldmode=Noneself.changes=()def__enter__(self):self.oldmode=self.cnx.hooks_modeself.cnx.hooks_mode=self.modeifself.modeisHOOKS_DENY_ALL:self.changes=self.cnx.enable_hook_categories(*self.categories)else:self.changes=self.cnx.disable_hook_categories(*self.categories)self.cnx.ctx_count+=1def__exit__(self,exctype,exc,traceback):self.cnx.ctx_count-=1try:ifself.categories:ifself.modeisHOOKS_DENY_ALL:self.cnx.disable_hook_categories(*self.categories)else:self.cnx.enable_hook_categories(*self.categories)finally:self.cnx.hooks_mode=self.oldmodeclass_session_hooks_control(_hooks_control):# XXX repoapi: remove me when# session stop being connection"""hook control context manager for session Necessary to handle some unholy transaction scope logic."""def__init__(self,session,mode,*categories):self.session=sessionsuper_init=super(_session_hooks_control,self).__init__super_init(session._cnx,mode,*categories)def__exit__(self,exctype,exc,traceback):super_exit=super(_session_hooks_control,self).__exit__ret=super_exit(exctype,exc,traceback)ifself.cnx.ctx_count==0:self.session._close_cnx(self.cnx)returnret@deprecated('[3.17] use <object>.security_enabled instead')defsecurity_enabled(obj,*args,**kwargs):returnobj.security_enabled(*args,**kwargs)class_security_enabled(object):"""context manager to control security w/ session.execute, By default security is disabled on queries executed on the repository side. """def__init__(self,cnx,read=None,write=None):self.cnx=cnxself.read=readself.write=writeself.oldread=Noneself.oldwrite=Nonedef__enter__(self):ifself.readisNone:self.oldread=Noneelse:self.oldread=self.cnx.read_securityself.cnx.read_security=self.readifself.writeisNone:self.oldwrite=Noneelse:self.oldwrite=self.cnx.write_securityself.cnx.write_security=self.writeself.cnx.ctx_count+=1def__exit__(self,exctype,exc,traceback):self.cnx.ctx_count-=1ifself.oldreadisnotNone:self.cnx.read_security=self.oldreadifself.oldwriteisnotNone:self.cnx.write_security=self.oldwriteclass_session_security_enabled(_security_enabled):"""hook security context manager for session Necessary To handle some unholy transaction scope logic."""def__init__(self,session,read=None,write=None):self.session=sessionsuper_init=super(_session_security_enabled,self).__init__super_init(session._cnx,read=read,write=write)def__exit__(self,exctype,exc,traceback):super_exit=super(_session_security_enabled,self).__exit__ret=super_exit(exctype,exc,traceback)ifself.cnx.ctx_count==0:self.session._close_cnx(self.cnx)returnretHOOKS_ALLOW_ALL=object()HOOKS_DENY_ALL=object()DEFAULT_SECURITY=object()# evaluated to true by designclassSessionClosedError(RuntimeError):passclassCnxSetTracker(object):"""Keep track of which connection use which cnxset. There should be one of these objects per session (including internal sessions). Session objects are responsible for creating their CnxSetTracker object. Connections should use the :meth:`record` and :meth:`forget` to inform the tracker of cnxsets they have acquired. .. automethod:: cubicweb.server.session.CnxSetTracker.record .. automethod:: cubicweb.server.session.CnxSetTracker.forget Sessions use the :meth:`close` and :meth:`wait` methods when closing. .. automethod:: cubicweb.server.session.CnxSetTracker.close .. automethod:: cubicweb.server.session.CnxSetTracker.wait This object itself is threadsafe. It also requires caller to acquired its lock in some situation. """def__init__(self):self._active=Trueself._condition=threading.Condition()self._record={}def__enter__(self):returnself._condition.__enter__()def__exit__(self,*args):returnself._condition.__exit__(*args)defrecord(self,cnxid,cnxset):"""Inform the tracker that a cnxid has acquired a cnxset This method is to be used by Connection objects. This method fails when: - The cnxid already has a recorded cnxset. - The tracker is not active anymore. Notes about the caller: (1) It is responsible for retrieving a cnxset. (2) It must be prepared to release the cnxset if the `cnxsettracker.forget` call fails. (3) It should acquire the tracker lock until the very end of the operation. (4) However it must only lock the CnxSetTracker object after having retrieved the cnxset to prevent deadlock. A typical usage look like:: cnxset = repo._get_cnxset() # (1) try: with cnxset_tracker: # (3) and (4) cnxset_tracker.record(caller.id, cnxset) # (3') operation ends when caller is in expected state only caller.cnxset = cnxset except Exception: repo._free_cnxset(cnxset) # (2) raise """# dubious since the caller is supposed to have acquired it anyway.withself._condition:ifnotself._active:raiseSessionClosedError('Closed')old=self._record.get(cnxid)ifoldisnotNone:raiseValueError('connection "%s" already has a cnx_set (%r)'%(cnxid,old))self._record[cnxid]=cnxsetdefforget(self,cnxid,cnxset):"""Inform the tracker that a cnxid have release a cnxset This methode is to be used by Connection object. This method fails when: - The cnxset for the cnxid does not match the recorded one. Notes about the caller: (1) It is responsible for releasing the cnxset. (2) It should acquire the tracker lock during the operation to ensure the internal tracker state is always accurate regarding its own state. A typical usage look like:: cnxset = caller.cnxset try: with cnxset_tracker: # (2) you can not have caller.cnxset out of sync with # cnxset_tracker state while unlocked caller.cnxset = None cnxset_tracker.forget(caller.id, cnxset) finally: cnxset = repo._free_cnxset(cnxset) # (1) """withself._condition:old=self._record.get(cnxid,None)ifoldisnotcnxset:raiseValueError('recorded cnxset for "%s" mismatch: %r != %r'%(cnxid,old,cnxset))self._record.pop(cnxid)self._condition.notify_all()defclose(self):"""Marks the tracker as inactive. This method is to be used by Session objects. An inactive tracker does not accept new records anymore. """withself._condition:self._active=Falsedefwait(self,timeout=10):"""Wait for all recorded cnxsets to be released This method is to be used by Session objects. Returns a tuple of connection ids that remain open. """withself._condition:ifself._active:raiseRuntimeError('Cannot wait on active tracker.'' Call tracker.close() first')whileself._recordandtimeout>0:start=time()self._condition.wait(timeout)timeout-=time()-startreturntuple(self._record)def_with_cnx_set(func):"""decorator for Connection method that ensure they run with a cnxset """@functools.wraps(func)defwrapper(cnx,*args,**kwargs):withcnx.ensure_cnx_set:returnfunc(cnx,*args,**kwargs)returnwrapperdef_open_only(func):"""decorator for Connection method that check it is open"""@functools.wraps(func)defcheck_open(cnx,*args,**kwargs):ifnotcnx._open:raiseProgrammingError('Closed Connection: %s'%cnx.connectionid)returnfunc(cnx,*args,**kwargs)returncheck_openclassConnection(RequestSessionBase):"""Repository Connection Holds all connection related data Database connection resources: :attr:`running_dbapi_query`, boolean flag telling if the executing query is coming from a dbapi connection or is a query from within the repository :attr:`cnxset`, the connections set to use to execute queries on sources. If the transaction is read only, the connection set may be freed between actual queries. This allows multiple connections with a reasonably low connection set pool size. Control mechanism is detailed below. .. automethod:: cubicweb.server.session.Connection.set_cnxset .. automethod:: cubicweb.server.session.Connection.free_cnxset :attr:`mode`, string telling the connections set handling mode, may be one of 'read' (connections set may be freed), 'write' (some write was done in the connections set, it can't be freed before end of the transaction), 'transaction' (we want to keep the connections set during all the transaction, with or without writing) Internal transaction data: :attr:`data` is a dictionary containing some shared data cleared at the end of the transaction. Hooks and operations may put arbitrary data in there, and this may also be used as a communication channel between the client and the repository. :attr:`pending_operations`, ordered list of operations to be processed on commit/rollback :attr:`commit_state`, describing the transaction commit state, may be one of None (not yet committing), 'precommit' (calling precommit event on operations), 'postcommit' (calling postcommit event on operations), 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error has been raised during the transaction and so it must be rolled back). Hooks controls: :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`. :attr:`enabled_hook_cats`, when :attr:`hooks_mode` is `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled. :attr:`disabled_hook_cats`, when :attr:`hooks_mode` is `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled. Security level Management: :attr:`read_security` and :attr:`write_security`, boolean flags telling if read/write security is currently activated. """is_request=Falsedef__init__(self,session,cnxid=None,session_handled=False):# using super(Connection, self) confuse some test hackRequestSessionBase.__init__(self,session.vreg)# only the session provide expliciteifcnxidisnotNone:assertsession_handled# only session profive explicite cnxid#: connection unique idself._open=NoneifcnxidisNone:cnxid='%s-%s'%(session.sessionid,uuid4().hex)self.connectionid=cnxidself.sessionid=session.sessionid#: self._session_handled#: are the life cycle of this Connection automatically controlled by the#: Session This is the old backward compatibility modeself._session_handled=session_handled#: reentrance handlingself.ctx_count=0#: count the number of entry in a context needing a cnxsetself._cnxset_count=0#: Boolean for compat with the older explicite set_cnxset/free_cnx API#: When a call set_cnxset is done, no automatic freeing will be done#: until free_cnx is called.self._auto_free_cnx_set=True#: server.Repository objectself.repo=session.repoself.vreg=self.repo.vregself._execute=self.repo.querier.execute# other session utilityself._session_timestamp=session._timestamp#: connection handling modeself.mode=session.default_mode#: connection set used to execute queries on sourcesself._cnxset=None#: CnxSetTracker used to report cnxset usageself._cnxset_tracker=session._cnxset_tracker#: is this connection from a client or internal to the repoself.running_dbapi_query=True# internal (root) sessionself.is_internal_session=session.is_internal_session#: dict containing arbitrary data cleared at the end of the transactionself.transaction_data={}self._session_data=session.data#: ordered list of operations to be processed on commit/rollbackself.pending_operations=[]#: (None, 'precommit', 'postcommit', 'uncommitable')self.commit_state=None### hook control attributeself.hooks_mode=HOOKS_ALLOW_ALLself.disabled_hook_cats=set()self.enabled_hook_cats=set()self.pruned_hooks_cache={}### security control attributesself._read_security=DEFAULT_SECURITY# handled by a propertyself.write_security=DEFAULT_SECURITY# undo controlconfig=session.repo.configifconfig.creatingorconfig.repairingorsession.is_internal_session:self.undo_actions=Falseelse:self.undo_actions=config['undo-enabled']# RQLRewriter are not thread safeself._rewriter=RQLRewriter(self)# other session utilityifsession.user.login=='__internal_manager__':self.user=session.userself.set_language(self.user.prefered_language())else:self._set_user(session.user)# live cycle handling ####################################################def__enter__(self):assertself._openisNone# first openingself._open=Truereturnselfdef__exit__(self,exctype=None,excvalue=None,tb=None):assertself._open# actually already openassertself._cnxset_count==0self.rollback()self._open=False# shared data handling ###################################################@propertydefdata(self):returnself._session_data@propertydefrql_rewriter(self):returnself._rewriter@_open_only@deprecated('[3.19] use session or transaction data')defget_shared_data(self,key,default=None,pop=False,txdata=False):"""return value associated to `key` in session data"""iftxdata:data=self.transaction_dataelse:data=self._session_dataifpop:returndata.pop(key,default)else:returndata.get(key,default)@_open_only@deprecated('[3.19] use session or transaction data')defset_shared_data(self,key,value,txdata=False):"""set value associated to `key` in session data"""iftxdata:self.transaction_data[key]=valueelse:self._session_data[key]=valuedefclear(self):"""reset internal data"""self.transaction_data={}#: ordered list of operations to be processed on commit/rollbackself.pending_operations=[]#: (None, 'precommit', 'postcommit', 'uncommitable')self.commit_state=Noneself.pruned_hooks_cache={}self.local_perm_cache.clear()self.rewriter=RQLRewriter(self)# Connection Set Management ###############################################@property@_open_onlydefcnxset(self):returnself._cnxset@cnxset.setter@_open_onlydefcnxset(self,new_cnxset):withself._cnxset_tracker:old_cnxset=self._cnxsetifnew_cnxsetisold_cnxset:return#nothing to doifold_cnxsetisnotNone:old_cnxset.rollback()self._cnxset=Noneself.ctx_count-=1self._cnxset_tracker.forget(self.connectionid,old_cnxset)ifnew_cnxsetisnotNone:self._cnxset_tracker.record(self.connectionid,new_cnxset)self._cnxset=new_cnxsetself.ctx_count+=1@_open_onlydef_set_cnxset(self):"""the connection need a connections set to execute some queries"""ifself.cnxsetisNone:cnxset=self.repo._get_cnxset()try:self.cnxset=cnxsetexcept:self.repo._free_cnxset(cnxset)raisereturnself.cnxset@_open_onlydef_free_cnxset(self,ignoremode=False):"""the connection is no longer using its connections set, at least for some time"""# cnxset may be none if no operation has been done since last commit# or rollbackcnxset=self.cnxsetifcnxsetisnotNoneand(ignoremodeorself.mode=='read'):assertself._cnxset_count==0try:self.cnxset=Nonefinally:cnxset.cnxset_freed()self.repo._free_cnxset(cnxset)@deprecated('[3.19] cnxset are automatically managed now.'' stop using explicit set and free.')defset_cnxset(self):self._auto_free_cnx_set=Falsereturnself._set_cnxset()@deprecated('[3.19] cnxset are automatically managed now.'' stop using explicit set and free.')deffree_cnxset(self,ignoremode=False):self._auto_free_cnx_set=Truereturnself._free_cnxset(ignoremode=ignoremode)@property@contextmanager@_open_onlydefensure_cnx_set(self):assertself._cnxset_count>=0ifself._cnxset_count==0:self._set_cnxset()try:self._cnxset_count+=1yieldfinally:self._cnxset_count=max(self._cnxset_count-1,0)ifself._cnxset_count==0andself._auto_free_cnx_set:self._free_cnxset()# Entity cache management ################################################### The connection entity cache as held in cnx.transaction_data is removed at the# end of the connection (commit and rollback)## XXX connection level caching may be a pb with multiple repository# instances, but 1. this is probably not the only one :$ and 2. it may be# an acceptable risk. Anyway we could activate it or not according to a# configuration optiondefset_entity_cache(self,entity):"""Add `entity` to the connection entity cache"""# XXX not using _open_only because before at creation time. _set_user# call this function to cache the Connection user.ifentity.cw_etype!='CWUser'andnotself._open:raiseProgrammingError('Closed Connection: %s'%self.connectionid)ecache=self.transaction_data.setdefault('ecache',{})ecache.setdefault(entity.eid,entity)@_open_onlydefentity_cache(self,eid):"""get cache entity for `eid`"""returnself.transaction_data['ecache'][eid]@_open_onlydefcached_entities(self):"""return the whole entity cache"""returnself.transaction_data.get('ecache',{}).values()@_open_onlydefdrop_entity_cache(self,eid=None):"""drop entity from the cache If eid is None, the whole cache is dropped"""ifeidisNone:self.transaction_data.pop('ecache',None)else:delself.transaction_data['ecache'][eid]# relations handling #######################################################@_open_onlydefadd_relation(self,fromeid,rtype,toeid):"""provide direct access to the repository method to add a relation. This is equivalent to the following rql query: SET X rtype Y WHERE X eid fromeid, T eid toeid without read security check but also all the burden of rql execution. You may use this in hooks when you know both eids of the relation you want to add. """self.add_relations([(rtype,[(fromeid,toeid)])])@_open_onlydefadd_relations(self,relations):'''set many relation using a shortcut similar to the one in add_relation relations is a list of 2-uples, the first element of each 2-uple is the rtype, and the second is a list of (fromeid, toeid) tuples '''edited_entities={}relations_dict={}withself.security_enabled(False,False):forrtype,eidsinrelations:ifself.vreg.schema[rtype].inlined:forfromeid,toeidineids:iffromeidnotinedited_entities:entity=self.entity_from_eid(fromeid)edited=EditedEntity(entity)edited_entities[fromeid]=editedelse:edited=edited_entities[fromeid]edited.edited_attribute(rtype,toeid)else:relations_dict[rtype]=eidsself.repo.glob_add_relations(self,relations_dict)foreditedinedited_entities.itervalues():self.repo.glob_update_entity(self,edited)@_open_onlydefdelete_relation(self,fromeid,rtype,toeid):"""provide direct access to the repository method to delete a relation. This is equivalent to the following rql query: DELETE X rtype Y WHERE X eid fromeid, T eid toeid without read security check but also all the burden of rql execution. You may use this in hooks when you know both eids of the relation you want to delete. """withself.security_enabled(False,False):ifself.vreg.schema[rtype].inlined:entity=self.entity_from_eid(fromeid)entity.cw_attr_cache[rtype]=Noneself.repo.glob_update_entity(self,entity,set((rtype,)))else:self.repo.glob_delete_relation(self,fromeid,rtype,toeid)# relations cache handling #################################################@_open_onlydefupdate_rel_cache_add(self,subject,rtype,object,symmetric=False):self._update_entity_rel_cache_add(subject,rtype,'subject',object)ifsymmetric:self._update_entity_rel_cache_add(object,rtype,'subject',subject)else:self._update_entity_rel_cache_add(object,rtype,'object',subject)@_open_onlydefupdate_rel_cache_del(self,subject,rtype,object,symmetric=False):self._update_entity_rel_cache_del(subject,rtype,'subject',object)ifsymmetric:self._update_entity_rel_cache_del(object,rtype,'object',object)else:self._update_entity_rel_cache_del(object,rtype,'object',subject)@_open_onlydef_update_entity_rel_cache_add(self,eid,rtype,role,targeteid):try:entity=self.entity_cache(eid)exceptKeyError:returnrcache=entity.cw_relation_cached(rtype,role)ifrcacheisnotNone:rset,entities=rcacherset=rset.copy()entities=list(entities)rset.rows.append([targeteid])ifnotisinstance(rset.description,list):# else description not setrset.description=list(rset.description)rset.description.append([self.entity_metas(targeteid)['type']])targetentity=self.entity_from_eid(targeteid)iftargetentity.cw_rsetisNone:targetentity.cw_rset=rsettargetentity.cw_row=rset.rowcounttargetentity.cw_col=0rset.rowcount+=1entities.append(targetentity)entity._cw_related_cache['%s_%s'%(rtype,role)]=(rset,tuple(entities))@_open_onlydef_update_entity_rel_cache_del(self,eid,rtype,role,targeteid):try:entity=self.entity_cache(eid)exceptKeyError:returnrcache=entity.cw_relation_cached(rtype,role)ifrcacheisnotNone:rset,entities=rcacheforidx,rowinenumerate(rset.rows):ifrow[0]==targeteid:breakelse:# this may occurs if the cache has been filed by a hook# after the database updateself.debug('cache inconsistency for %s%s%s%s',eid,rtype,role,targeteid)returnrset=rset.copy()entities=list(entities)delrset.rows[idx]ifisinstance(rset.description,list):# else description not setdelrset.description[idx]delentities[idx]rset.rowcount-=1entity._cw_related_cache['%s_%s'%(rtype,role)]=(rset,tuple(entities))# Tracking of entities added of removed in the transaction ##################@_open_onlydefdeleted_in_transaction(self,eid):"""return True if the entity of the given eid is being deleted in the current transaction """returneidinself.transaction_data.get('pendingeids',())@_open_onlydefadded_in_transaction(self,eid):"""return True if the entity of the given eid is being created in the current transaction """returneidinself.transaction_data.get('neweids',())# Operation management ####################################################@_open_onlydefadd_operation(self,operation,index=None):"""add an operation to be executed at the end of the transaction"""ifindexisNone:self.pending_operations.append(operation)else:self.pending_operations.insert(index,operation)# Hooks control ###########################################################@_open_onlydefallow_all_hooks_but(self,*categories):return_hooks_control(self,HOOKS_ALLOW_ALL,*categories)@_open_onlydefdeny_all_hooks_but(self,*categories):return_hooks_control(self,HOOKS_DENY_ALL,*categories)@_open_onlydefdisable_hook_categories(self,*categories):"""disable the given hook categories: - on HOOKS_DENY_ALL mode, ensure those categories are not enabled - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled """changes=set()self.pruned_hooks_cache.clear()categories=set(categories)ifself.hooks_modeisHOOKS_DENY_ALL:enabledcats=self.enabled_hook_catschanges=enabledcats&categoriesenabledcats-=changes# changes is small hence fasterelse:disabledcats=self.disabled_hook_catschanges=categories-disabledcatsdisabledcats|=changes# changes is small hence fasterreturntuple(changes)@_open_onlydefenable_hook_categories(self,*categories):"""enable the given hook categories: - on HOOKS_DENY_ALL mode, ensure those categories are enabled - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled """changes=set()self.pruned_hooks_cache.clear()categories=set(categories)ifself.hooks_modeisHOOKS_DENY_ALL:enabledcats=self.enabled_hook_catschanges=categories-enabledcatsenabledcats|=changes# changes is small hence fasterelse:disabledcats=self.disabled_hook_catschanges=disabledcats&categoriesdisabledcats-=changes# changes is small hence fasterreturntuple(changes)@_open_onlydefis_hook_category_activated(self,category):"""return a boolean telling if the given category is currently activated or not """ifself.hooks_modeisHOOKS_DENY_ALL:returncategoryinself.enabled_hook_catsreturncategorynotinself.disabled_hook_cats@_open_onlydefis_hook_activated(self,hook):"""return a boolean telling if the given hook class is currently activated or not """returnself.is_hook_category_activated(hook.category)# Security management #####################################################@_open_onlydefsecurity_enabled(self,read=None,write=None):return_security_enabled(self,read=read,write=write)@property@_open_onlydefread_security(self):returnself._read_security@read_security.setter@_open_onlydefread_security(self,activated):oldmode=self._read_securityself._read_security=activated# running_dbapi_query used to detect hooks triggered by a 'dbapi' query# (eg not issued on the session). This is tricky since we the execution# model of a (write) user query is:## repository.execute (security enabled)# \-> querier.execute# \-> repo.glob_xxx (add/update/delete entity/relation)# \-> deactivate security before calling hooks# \-> WE WANT TO CHECK QUERY NATURE HERE# \-> potentially, other calls to querier.execute## so we can't rely on simply checking session.read_security, but# recalling the first transition from DEFAULT_SECURITY to something# else (False actually) is not perfect but should be enough## also reset running_dbapi_query to true when we go back to# DEFAULT_SECURITYself.running_dbapi_query=(oldmodeisDEFAULT_SECURITYoractivatedisDEFAULT_SECURITY)# undo support ############################################################@_open_onlydefertype_supports_undo(self,ertype):returnself.undo_actionsandertypenotinNO_UNDO_TYPES@_open_onlydeftransaction_uuid(self,set=True):uuid=self.transaction_data.get('tx_uuid')ifsetanduuidisNone:self.transaction_data['tx_uuid']=uuid=uuid4().hexself.repo.system_source.start_undoable_transaction(self,uuid)returnuuid@_open_onlydeftransaction_inc_action_counter(self):num=self.transaction_data.setdefault('tx_action_count',0)+1self.transaction_data['tx_action_count']=numreturnnum# db-api like interface ###################################################@_open_onlydefsource_defs(self):returnself.repo.source_defs()@deprecated('[3.19] use .entity_metas(eid) instead')@_with_cnx_set@_open_onlydefdescribe(self,eid,asdict=False):"""return a tuple (type, sourceuri, extid) for the entity with id <eid>"""etype,extid,source=self.repo.type_and_source_from_eid(eid,self)metas={'type':etype,'source':source,'extid':extid}ifasdict:metas['asource']=meta['source']# XXX pre 3.19 client compatreturnmetareturnetype,source,extid@_with_cnx_set@_open_onlydefentity_metas(self,eid):"""return a tuple (type, sourceuri, extid) for the entity with id <eid>"""etype,extid,source=self.repo.type_and_source_from_eid(eid,self)return{'type':etype,'source':source,'extid':extid}# core method #############################################################@_with_cnx_set@_open_onlydefexecute(self,rql,kwargs=None,build_descr=True):"""db-api like method directly linked to the querier execute method. See :meth:`cubicweb.dbapi.Cursor.execute` documentation. """self._session_timestamp.touch()rset=self._execute(self,rql,kwargs,build_descr)rset.req=selfself._session_timestamp.touch()returnrset@_open_onlydefrollback(self,free_cnxset=True,reset_pool=None):"""rollback the current transaction"""ifreset_poolisnotNone:warn('[3.13] use free_cnxset argument instead for reset_pool',DeprecationWarning,stacklevel=2)free_cnxset=reset_poolifself._cnxset_count!=0:# we are inside ensure_cnx_set, don't lose itfree_cnxset=Falsecnxset=self.cnxsetifcnxsetisNone:self.clear()self._session_timestamp.touch()self.debug('rollback transaction %s done (no db activity)',self.connectionid)returntry:# by default, operations are executed with security turned offwithself.security_enabled(False,False):whileself.pending_operations:try:operation=self.pending_operations.pop(0)operation.handle_event('rollback_event')exceptBaseException:self.critical('rollback error',exc_info=sys.exc_info())continuecnxset.rollback()self.debug('rollback for transaction %s done',self.connectionid)finally:self._session_timestamp.touch()iffree_cnxset:self._free_cnxset(ignoremode=True)self.clear()@_open_onlydefcommit(self,free_cnxset=True,reset_pool=None):"""commit the current session's transaction"""ifreset_poolisnotNone:warn('[3.13] use free_cnxset argument instead for reset_pool',DeprecationWarning,stacklevel=2)free_cnxset=reset_poolifself.cnxsetisNone:assertnotself.pending_operationsself.clear()self._session_timestamp.touch()self.debug('commit transaction %s done (no db activity)',self.connectionid)returnifself._cnxset_count!=0:# we are inside ensure_cnx_set, don't lose itfree_cnxset=Falsecstate=self.commit_stateifcstate=='uncommitable':raiseQueryError('transaction must be rolled back')ifcstate=='precommit':self.warn('calling commit in precommit makes no sense; ignoring commit')returnifcstate=='postcommit':self.critical('postcommit phase is not allowed to write to the db; ignoring commit')returnassertcstateisNone# on rollback, an operation should have the following state# information:# - processed by the precommit/commit event or not# - if processed, is it the failed operationdebug=server.DEBUG&server.DBG_OPStry:# by default, operations are executed with security turned offwithself.security_enabled(False,False):processed=[]self.commit_state='precommit'ifdebug:printself.commit_state,'*'*20try:whileself.pending_operations:operation=self.pending_operations.pop(0)operation.processed='precommit'processed.append(operation)ifdebug:printoperationoperation.handle_event('precommit_event')self.pending_operations[:]=processedself.debug('precommit transaction %s done',self.connectionid)exceptBaseException:# if error on [pre]commit:## * set .failed = True on the operation causing the failure# * call revert<event>_event on processed operations# * call rollback_event on *all* operations## that seems more natural than not calling rollback_event# for processed operations, and allow generic rollback# instead of having to implements rollback, revertprecommit# and revertcommit, that will be enough in mont case.operation.failed=Trueifdebug:printself.commit_state,'*'*20foroperationinreversed(processed):ifdebug:printoperationtry:operation.handle_event('revertprecommit_event')exceptBaseException:self.critical('error while reverting precommit',exc_info=True)# XXX use slice notation since self.pending_operations is a# read-only property.self.pending_operations[:]=processed+self.pending_operationsself.rollback(free_cnxset)raiseself.cnxset.commit()self.commit_state='postcommit'ifdebug:printself.commit_state,'*'*20whileself.pending_operations:operation=self.pending_operations.pop(0)ifdebug:printoperationoperation.processed='postcommit'try:operation.handle_event('postcommit_event')exceptBaseException:self.critical('error while postcommit',exc_info=sys.exc_info())self.debug('postcommit transaction %s done',self.connectionid)returnself.transaction_uuid(set=False)finally:self._session_timestamp.touch()iffree_cnxset:self._free_cnxset(ignoremode=True)self.clear()# resource accessors ######################################################@_with_cnx_set@_open_onlydefcall_service(self,regid,**kwargs):self.debug('calling service %s',regid)service=self.vreg['services'].select(regid,self,**kwargs)returnservice.call(**kwargs)@_with_cnx_set@_open_onlydefsystem_sql(self,sql,args=None,rollback_on_failure=True):"""return a sql cursor on the system database"""ifsql.split(None,1)[0].upper()!='SELECT':self.mode='write'source=self.repo.system_sourcetry:returnsource.doexec(self,sql,args,rollback=rollback_on_failure)except(source.OperationalError,source.InterfaceError):ifnotrollback_on_failure:raisesource.warning("trying to reconnect")self.cnxset.reconnect()returnsource.doexec(self,sql,args,rollback=rollback_on_failure)@_open_onlydefrtype_eids_rdef(self,rtype,eidfrom,eidto):# use type_and_source_from_eid instead of type_from_eid for optimization# (avoid two extra methods call)subjtype=self.repo.type_and_source_from_eid(eidfrom,self)[0]objtype=self.repo.type_and_source_from_eid(eidto,self)[0]returnself.vreg.schema.rschema(rtype).rdefs[(subjtype,objtype)]defcnx_attr(attr_name,writable=False):"""return a property to forward attribute access to connection. This is to be used by session"""args={}@deprecated('[3.19] use a Connection object instead')defattr_from_cnx(session):returngetattr(session._cnx,attr_name)args['fget']=attr_from_cnxifwritable:@deprecated('[3.19] use a Connection object instead')defwrite_attr(session,value):returnsetattr(session._cnx,attr_name,value)args['fset']=write_attrreturnproperty(**args)defcnx_meth(meth_name):"""return a function forwarding calls to connection. This is to be used by session"""@deprecated('[3.19] use a Connection object instead')defmeth_from_cnx(session,*args,**kwargs):result=getattr(session._cnx,meth_name)(*args,**kwargs)ifgetattr(result,'_cw',None)isnotNone:result._cw=sessionreturnresultmeth_from_cnx.__doc__=getattr(Connection,meth_name).__doc__returnmeth_from_cnxclassTimestamp(object):def__init__(self):self.value=time()deftouch(self):self.value=time()def__float__(self):returnfloat(self.value)classSession(RequestSessionBase):# XXX repoapi: stop being a# RequestSessionBase at some point"""Repository user session This ties all together: * session id, * user, * connections set, * other session data. **About session storage / transactions** Here is a description of internal session attributes. Besides :attr:`data` and :attr:`transaction_data`, you should not have to use attributes described here but higher level APIs. :attr:`data` is a dictionary containing shared data, used to communicate extra information between the client and the repository :attr:`_cnxs` is a dictionary of :class:`Connection` instance, one for each running connection. The key is the connection id. By default the connection id is the thread name but it can be otherwise (per dbapi cursor for instance, or per thread name *from another process*). :attr:`__threaddata` is a thread local storage whose `cnx` attribute refers to the proper instance of :class:`Connection` according to the connection. You should not have to use neither :attr:`_cnx` nor :attr:`__threaddata`, simply access connection data transparently through the :attr:`_cnx` property. Also, you usually don't have to access it directly since current connection's data may be accessed/modified through properties / methods: :attr:`connection_data`, similarly to :attr:`data`, is a dictionary containing some shared data that should be cleared at the end of the connection. Hooks and operations may put arbitrary data in there, and this may also be used as a communication channel between the client and the repository. .. automethod:: cubicweb.server.session.Session.get_shared_data .. automethod:: cubicweb.server.session.Session.set_shared_data .. automethod:: cubicweb.server.session.Session.added_in_transaction .. automethod:: cubicweb.server.session.Session.deleted_in_transaction Connection state information: :attr:`running_dbapi_query`, boolean flag telling if the executing query is coming from a dbapi connection or is a query from within the repository :attr:`cnxset`, the connections set to use to execute queries on sources. During a transaction, the connection set may be freed so that is may be used by another session as long as no writing is done. This means we can have multiple sessions with a reasonably low connections set pool size. .. automethod:: cubicweb.server.session.Session.set_cnxset .. automethod:: cubicweb.server.session.Session.free_cnxset :attr:`mode`, string telling the connections set handling mode, may be one of 'read' (connections set may be freed), 'write' (some write was done in the connections set, it can't be freed before end of the transaction), 'transaction' (we want to keep the connections set during all the transaction, with or without writing) :attr:`pending_operations`, ordered list of operations to be processed on commit/rollback :attr:`commit_state`, describing the transaction commit state, may be one of None (not yet committing), 'precommit' (calling precommit event on operations), 'postcommit' (calling postcommit event on operations), 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error has been raised during the transaction and so it must be rolled back). .. automethod:: cubicweb.server.session.Session.commit .. automethod:: cubicweb.server.session.Session.rollback .. automethod:: cubicweb.server.session.Session.close .. automethod:: cubicweb.server.session.Session.closed Security level Management: :attr:`read_security` and :attr:`write_security`, boolean flags telling if read/write security is currently activated. .. automethod:: cubicweb.server.session.Session.security_enabled Hooks Management: :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`. :attr:`enabled_hook_categories`, when :attr:`hooks_mode` is `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled. :attr:`disabled_hook_categories`, when :attr:`hooks_mode` is `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled. .. automethod:: cubicweb.server.session.Session.deny_all_hooks_but .. automethod:: cubicweb.server.session.Session.allow_all_hooks_but .. automethod:: cubicweb.server.session.Session.is_hook_category_activated .. automethod:: cubicweb.server.session.Session.is_hook_activated Data manipulation: .. automethod:: cubicweb.server.session.Session.add_relation .. automethod:: cubicweb.server.session.Session.add_relations .. automethod:: cubicweb.server.session.Session.delete_relation Other: .. automethod:: cubicweb.server.session.Session.call_service """is_request=Falseis_internal_session=Falsedef__init__(self,user,repo,cnxprops=None,_id=None):super(Session,self).__init__(repo.vreg)self.sessionid=_idormake_uid(unormalize(user.login).encode('UTF8'))self.user=user# XXX repoapi: deprecated and store only a login.self.repo=repoself._timestamp=Timestamp()self.default_mode='read'# short cut to querier .execute methodself._execute=repo.querier.execute# shared data, used to communicate extra information between the client# and the rql serverself.data={}# i18n initializationself.set_language(user.prefered_language())### internals# Connection of this sectionself._cnxs={}# XXX repoapi: remove this when nobody use the session# as a Connection# Data local to the threadself.__threaddata=threading.local()# XXX repoapi: remove this when# nobody use the session as a Connectionself._cnxset_tracker=CnxSetTracker()self._closed=Falseself._lock=threading.RLock()def__unicode__(self):return'<session %s (%s 0x%x)>'%(unicode(self.user.login),self.sessionid,id(self))@propertydeftimestamp(self):returnfloat(self._timestamp)@property@deprecated('[3.19] session.id is deprecated, use session.sessionid')defid(self):returnself.sessionid@propertydeflogin(self):returnself.user.logindefnew_cnx(self):"""Return a new Connection object linked to the session The returned Connection will *not* be managed by the Session. """returnConnection(self)def_get_cnx(self,cnxid):"""return the <cnxid> connection attached to this session Connection is created if necessary"""withself._lock:# no connection exist with the same idtry:ifself.closed:raiseSessionClosedError('try to access connections set on'' a closed session %s'%self.id)cnx=self._cnxs[cnxid]assertcnx._session_handledexceptKeyError:cnx=Connection(self,cnxid=cnxid,session_handled=True)self._cnxs[cnxid]=cnxcnx.__enter__()returncnxdef_close_cnx(self,cnx):"""Close a Connection related to a session"""assertcnx._session_handledcnx.__exit__()self._cnxs.pop(cnx.connectionid,None)try:ifself.__threaddata.cnxiscnx:delself.__threaddata.cnxexceptAttributeError:passdefset_cnx(self,cnxid=None):# XXX repoapi: remove this when nobody use the session as a Connection"""set the default connection of the current thread to <cnxid> Connection is created if necessary"""ifcnxidisNone:cnxid=threading.currentThread().getName()cnx=self._get_cnx(cnxid)# New style session should not be accesed through the session.assertcnx._session_handledself.__threaddata.cnx=cnx@propertydef_cnx(self):"""default connection for current session in current thread"""try:returnself.__threaddata.cnxexceptAttributeError:self.set_cnx()returnself.__threaddata.cnx@deprecated('[3.19] use a Connection object instead')defget_option_value(self,option,foreid=None):ifforeidisnotNone:warn('[3.19] foreid argument is deprecated',DeprecationWarning,stacklevel=2)returnself.repo.get_option_value(option)@deprecated('[3.19] use a Connection object instead')deftransaction(self,free_cnxset=True):"""return context manager to enter a transaction for the session: when exiting the `with` block on exception, call `session.rollback()`, else call `session.commit()` on normal exit. The `free_cnxset` will be given to rollback/commit methods to indicate whether the connections set should be freed or not. """returntransaction(self,free_cnxset)add_relation=cnx_meth('add_relation')add_relations=cnx_meth('add_relations')delete_relation=cnx_meth('delete_relation')# relations cache handling #################################################update_rel_cache_add=cnx_meth('update_rel_cache_add')update_rel_cache_del=cnx_meth('update_rel_cache_del')# resource accessors ######################################################system_sql=cnx_meth('system_sql')deleted_in_transaction=cnx_meth('deleted_in_transaction')added_in_transaction=cnx_meth('added_in_transaction')rtype_eids_rdef=cnx_meth('rtype_eids_rdef')# security control #########################################################@deprecated('[3.19] use a Connection object instead')defsecurity_enabled(self,read=None,write=None):return_session_security_enabled(self,read=read,write=write)read_security=cnx_attr('read_security',writable=True)write_security=cnx_attr('write_security',writable=True)running_dbapi_query=cnx_attr('running_dbapi_query')# hooks activation control ################################################## all hooks should be activated during normal execution@deprecated('[3.19] use a Connection object instead')defallow_all_hooks_but(self,*categories):return_session_hooks_control(self,HOOKS_ALLOW_ALL,*categories)@deprecated('[3.19] use a Connection object instead')defdeny_all_hooks_but(self,*categories):return_session_hooks_control(self,HOOKS_DENY_ALL,*categories)hooks_mode=cnx_attr('hooks_mode')disabled_hook_categories=cnx_attr('disabled_hook_cats')enabled_hook_categories=cnx_attr('enabled_hook_cats')disable_hook_categories=cnx_meth('disable_hook_categories')enable_hook_categories=cnx_meth('enable_hook_categories')is_hook_category_activated=cnx_meth('is_hook_category_activated')is_hook_activated=cnx_meth('is_hook_activated')# connection management ###################################################@deprecated('[3.19] use a Connection object instead')defkeep_cnxset_mode(self,mode):"""set `mode`, e.g. how the session will keep its connections set: * if mode == 'write', the connections set is freed after each read query, but kept until the transaction's end (eg commit or rollback) when a write query is detected (eg INSERT/SET/DELETE queries) * if mode == 'transaction', the connections set is only freed after the transaction's end notice that a repository has a limited set of connections sets, and a session has to wait for a free connections set to run any rql query (unless it already has one set). """assertmodein('transaction','write')ifmode=='transaction':self.default_mode='transaction'else:# mode == 'write'self.default_mode='read'mode=cnx_attr('mode',writable=True)commit_state=cnx_attr('commit_state',writable=True)@property@deprecated('[3.19] use a Connection object instead')defcnxset(self):"""connections set, set according to transaction mode for each query"""ifself._closed:self.free_cnxset(True)raiseSessionClosedError('try to access connections set on a closed session %s'%self.id)returnself._cnx.cnxsetdefset_cnxset(self):"""the session need a connections set to execute some queries"""withself._lock:# can probably be removedifself._closed:self.free_cnxset(True)raiseSessionClosedError('try to set connections set on a closed session %s'%self.id)returnself._cnx.set_cnxset()free_cnxset=cnx_meth('free_cnxset')ensure_cnx_set=cnx_attr('ensure_cnx_set')def_touch(self):"""update latest session usage timestamp and reset mode to read"""self._timestamp.touch()local_perm_cache=cnx_attr('local_perm_cache')@local_perm_cache.setterdeflocal_perm_cache(self,value):#base class assign an empty dict:-(assertvalue=={}pass# shared data handling ###################################################@deprecated('[3.19] use session or transaction data')defget_shared_data(self,key,default=None,pop=False,txdata=False):"""return value associated to `key` in session data"""iftxdata:returnself._cnx.get_shared_data(key,default,pop,txdata=True)else:data=self.dataifpop:returndata.pop(key,default)else:returndata.get(key,default)@deprecated('[3.19] use session or transaction data')defset_shared_data(self,key,value,txdata=False):"""set value associated to `key` in session data"""iftxdata:returnself._cnx.set_shared_data(key,value,txdata=True)else:self.data[key]=value# server-side service call #################################################call_service=cnx_meth('call_service')# request interface #######################################################@property@deprecated('[3.19] use a Connection object instead')defcursor(self):"""return a rql cursor"""returnselfset_entity_cache=cnx_meth('set_entity_cache')entity_cache=cnx_meth('entity_cache')cache_entities=cnx_meth('cached_entities')drop_entity_cache=cnx_meth('drop_entity_cache')source_defs=cnx_meth('source_defs')entity_metas=cnx_meth('entity_metas')describe=cnx_meth('describe')# XXX deprecated in 3.19@deprecated('[3.19] use a Connection object instead')defexecute(self,*args,**kwargs):"""db-api like method directly linked to the querier execute method. See :meth:`cubicweb.dbapi.Cursor.execute` documentation. """rset=self._cnx.execute(*args,**kwargs)rset.req=selfreturnrsetdef_clear_thread_data(self,free_cnxset=True):"""remove everything from the thread local storage, except connections set which is explicitly removed by free_cnxset, and mode which is set anyway by _touch """try:cnx=self.__threaddata.cnxexceptAttributeError:passelse:iffree_cnxset:cnx._free_cnxset()ifcnx.ctx_count==0:self._close_cnx(cnx)else:cnx.clear()else:cnx.clear()@deprecated('[3.19] use a Connection object instead')defcommit(self,free_cnxset=True,reset_pool=None):"""commit the current session's transaction"""cstate=self._cnx.commit_stateifcstate=='uncommitable':raiseQueryError('transaction must be rolled back')try:returnself._cnx.commit(free_cnxset,reset_pool)finally:self._clear_thread_data(free_cnxset)@deprecated('[3.19] use a Connection object instead')defrollback(self,*args,**kwargs):"""rollback the current session's transaction"""returnself._rollback(*args,**kwargs)def_rollback(self,free_cnxset=True,**kwargs):try:returnself._cnx.rollback(free_cnxset,**kwargs)finally:self._clear_thread_data(free_cnxset)defclose(self):# do not close connections set on session close, since they are shared nowtracker=self._cnxset_trackerwithself._lock:self._closed=Truetracker.close()ifself._cnx._session_handled:self._rollback()self.debug('waiting for open connection of session: %s',self)timeout=10pendings=tracker.wait(timeout)ifpendings:self.error('%i connection still alive after 10 seconds, will close ''session anyway',len(pendings))forcnxidinpendings:cnx=self._cnxs.get(cnxid)ifcnxisnotNone:# drop cnx.cnxsetwithtracker:try:cnxset=cnx.cnxsetifcnxsetisNone:continuecnx.cnxset=NoneexceptRuntimeError:msg='issue while force free of cnxset in %s'self.error(msg,cnx)# cnxset.reconnect() do an hard reset of the cnxset# it force it to be freedcnxset.reconnect()self.repo._free_cnxset(cnxset)delself.__threaddatadelself._cnxs@propertydefclosed(self):returnnothasattr(self,'_cnxs')# transaction data/operations management ##################################transaction_data=cnx_attr('transaction_data')pending_operations=cnx_attr('pending_operations')pruned_hooks_cache=cnx_attr('pruned_hooks_cache')add_operation=cnx_meth('add_operation')# undo support ############################################################ertype_supports_undo=cnx_meth('ertype_supports_undo')transaction_inc_action_counter=cnx_meth('transaction_inc_action_counter')transaction_uuid=cnx_meth('transaction_uuid')# querier helpers #########################################################rql_rewriter=cnx_attr('_rewriter')# deprecated ###############################################################@propertydefanonymous_session(self):# XXX for now, anonymous_user only exists in webconfig (and testconfig).# It will only be present inside all-in-one instance.# there is plan to move it down to global config.ifnothasattr(self.repo.config,'anonymous_user'):# not a web or test config, no anonymous userreturnFalsereturnself.user.login==self.repo.config.anonymous_user()[0]@deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')defschema_rproperty(self,rtype,eidfrom,eidto,rprop):returngetattr(self.rtype_eids_rdef(rtype,eidfrom,eidto),rprop)@property@deprecated("[3.13] use .cnxset attribute instead of .pool")defpool(self):returnself.cnxset@deprecated("[3.13] use .set_cnxset() method instead of .set_pool()")defset_pool(self):returnself.set_cnxset()@deprecated("[3.13] use .free_cnxset() method instead of .reset_pool()")defreset_pool(self):returnself.free_cnxset()# these are overridden by set_log_methods below# only defining here to prevent pylint from complaininginfo=warning=error=critical=exception=debug=lambdamsg,*a,**kw:NoneSession.HOOKS_ALLOW_ALL=HOOKS_ALLOW_ALLSession.HOOKS_DENY_ALL=HOOKS_DENY_ALLSession.DEFAULT_SECURITY=DEFAULT_SECURITYclassInternalSession(Session):"""special session created internally by the repository"""is_internal_session=Truerunning_dbapi_query=Falsedef__init__(self,repo,cnxprops=None,safe=False):super(InternalSession,self).__init__(InternalManager(),repo,cnxprops,_id='internal')self.user._cw=self# XXX remove when "vreg = user._cw.vreg" hack in entity.py is gonedef__enter__(self):returnselfdef__exit__(self,exctype,excvalue,tb):self.close()@propertydefcnxset(self):"""connections set, set according to transaction mode for each query"""ifself.repo.shutting_down:self.free_cnxset(True)raiseShuttingDown('repository is shutting down')returnself._cnx.cnxsetclassInternalManager(object):"""a manager user with all access rights used internally for task such as bootstrapping the repository or creating regular users according to repository content """def__init__(self,lang='en'):self.eid=-1self.login=u'__internal_manager__'self.properties={}self.groups=set(['managers'])self.lang=langdefmatching_groups(self,groups):return1defis_in_group(self,group):returnTruedefowns(self,eid):returnTruedefproperty_value(self,key):ifkey=='ui.language':returnself.langreturnNonedefprefered_language(self,language=None):# mock CWUser.prefered_language, mainly for testing purposereturnself.property_value('ui.language')# CWUser compat for notification ###########################################defname(self):return'cubicweb'class_IEmailable:@staticmethoddefget_email():return''defcw_adapt_to(self,iface):ififace=='IEmailable':returnself._IEmailablereturnNonefromloggingimportgetLoggerfromcubicwebimportset_log_methodsset_log_methods(Session,getLogger('cubicweb.session'))set_log_methods(Connection,getLogger('cubicweb.session'))