[migration] mark all version prior 3.7 as non-migrable
API possible used by such old migration script are not supported in recent
Cubiweb.
Closes #2772958
# 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/>."""Repository users' and internal' sessions."""__docformat__="restructuredtext en"importsysimportthreadingfromtimeimporttimefromuuidimportuuid4fromwarningsimportwarnfromlogilab.common.deprecationimportdeprecatedfromlogilab.common.textutilsimportunormalizefromlogilab.common.registryimportobjectify_predicatefromcubicwebimportUnknownEid,QueryError,schema,serverfromcubicweb.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 rollbacked 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)classhooks_control(object):"""context manager to control activated hooks categories. If mode is session.`HOOKS_DENY_ALL`, given hooks categories will be enabled. If mode is session.`HOOKS_ALLOW_ALL`, given hooks categories will be disabled. .. sourcecode:: python with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, 'integrity'): # ... do stuff with all but 'integrity' hooks activated with hooks_control(self.session, self.session.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.Session.deny_all_hooks_but` or :meth:`~cubicweb.server.session.Session.allow_all_hooks_but` session methods. """def__init__(self,session,mode,*categories):self.session=sessionself.mode=modeself.categories=categoriesdef__enter__(self):self.oldmode,self.changes=self.session.init_hooks_mode_categories(self.mode,self.categories)def__exit__(self,exctype,exc,traceback):self.session.reset_hooks_mode_categories(self.oldmode,self.mode,self.changes)classsecurity_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,session,read=None,write=None):self.session=sessionself.read=readself.write=writedef__enter__(self):self.oldread,self.oldwrite=self.session.init_security(self.read,self.write)def__exit__(self,exctype,exc,traceback):self.session.reset_security(self.oldread,self.oldwrite)HOOKS_ALLOW_ALL=object()HOOKS_DENY_ALL=object()DEFAULT_SECURITY=object()# evaluated to true by designclassTransaction(object):"""Repository Transaction Holds all transaction related data Database connections resource: :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 query. This allows multiple transaction with a reasonable low connection set pool size. control mechanism is detailed below :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 rollbacked). 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. """def__init__(self,txid,mode,rewriter):#: transaction unique idself.transactionid=txid#: reentrance handlingself.ctx_count=0#: connection handling modeself.mode=mode#: connection set used to execute queries on sourcesself.cnxset=None#: dict containing arbitrary data cleared at the end of the transactionself.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_SECURITYself.write_security=DEFAULT_SECURITY# RQLRewriter are not thread safeself._rewriter=rewriter@propertydeftransaction_data(self):returnself.datadefclear(self):"""reset internal data"""self.data={}#: ordered list of operations to be processed on commit/rollbackself.pending_operations=[]#: (None, 'precommit', 'postcommit', 'uncommitable')self.commit_state=Noneself.pruned_hooks_cache={}# Entity cache management ################################################### The transaction entity cache as held in tx.data it is removed at end the# end of the transaction (commit and rollback)## XXX transaction 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 transaction entity cache"""ecache=self.data.setdefault('ecache',{})ecache.setdefault(entity.eid,entity)defentity_cache(self,eid):"""get cache entity for `eid`"""returnself.data['ecache'][eid]defcached_entities(self):"""return the whole entity cache"""returnself.data.get('ecache',{}).values()defdrop_entity_cache(self,eid=None):"""drop entity from the cache If eid is None, the whole cache is dropped"""ifeidisNone:self.data.pop('ecache',None)else:delself.data['ecache'][eid]# Tracking of entity added of removed in the transaction #################### Those are function to allows cheap call from client in other process.defdeleted_in_transaction(self,eid):"""return True if the entity of the given eid is being deleted in the current transaction """returneidinself.data.get('pendingeids',())defadded_in_transaction(self,eid):"""return True if the entity of the given eid is being created in the current transaction """returneidinself.data.get('neweids',())# Operation management ####################################################defadd_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 ###########################################################defdisable_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)defenable_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)defis_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_catsdefis_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)deftx_attr(attr_name,writable=False):"""return a property to forward attribute access to transaction. This is to be used by session"""args={}defattr_from_tx(session):returngetattr(session._tx,attr_name)args['fget']=attr_from_txifwritable:defwrite_attr(session,value):returnsetattr(session._tx,attr_name,value)args['fset']=write_attrreturnproperty(**args)deftx_meth(meth_name):"""return a function forwarding calls to transaction. This is to be used by session"""defmeth_from_tx(session,*args,**kwargs):returngetattr(session._tx,meth_name)(*args,**kwargs)returnmeth_from_txclassSession(RequestSessionBase):"""Repository user session This tie 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:`_txs` is a dictionary of :class:`TransactionData` instance, one for each running transaction. The key is the transaction id. By default the transaction 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 `tx` attribute refers to the proper instance of :class:`Transaction` according to the transaction. :attr:`_threads_in_transaction` is a set of (thread, connections set) referencing threads that currently hold a connections set for the session. .. automethod:: cubicweb.server.session.transaction You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`, simply access transaction data transparently through the :attr:`_tx` property. Also, you usually don't have to access it directly since current transaction's data may be accessed/modified through properties / methods: :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary containing some shared data that should be 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. .. 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 Transaction 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.set_cnxset .. automethod:: cubicweb.server.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 rollbacked). .. 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.set_write_security .. automethod:: cubicweb.server.session.Session.set_read_security .. automethod:: cubicweb.server.session.Session.init_security .. automethod:: cubicweb.server.session.Session.reset_security .. 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.id=_idormake_uid(unormalize(user.login).encode('UTF8'))self.user=userself.repo=repoself.timestamp=time()self.default_mode='read'# undo supportifrepo.config.creatingorrepo.config.repairingorself.is_internal_session:self.undo_actions=Falseelse:self.undo_actions=repo.config['undo-enabled']# 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# Transaction of this sectionself._txs={}# Data local to the threadself.__threaddata=threading.local()self._threads_in_transaction=set()self._closed=Falseself._lock=threading.RLock()def__unicode__(self):return'<session %s (%s 0x%x)>'%(unicode(self.user.login),self.id,id(self))defget_tx(self,txid):"""return the <txid> transaction attached to this session Transaction is created if necessary"""withself._lock:# no transaction exist with the same idtry:tx=self._txs[txid]exceptKeyError:rewriter=RQLRewriter(self)tx=Transaction(txid,self.default_mode,rewriter)self._txs[txid]=txreturntxdefset_tx(self,txid=None):"""set the default transaction of the current thread to <txid> Transaction is created if necessary"""iftxidisNone:txid=threading.currentThread().getName()self.__threaddata.tx=self.get_tx(txid)@propertydef_tx(self):"""default transaction for current session in current thread"""try:returnself.__threaddata.txexceptAttributeError:self.set_tx()returnself.__threaddata.txdefget_option_value(self,option,foreid=None):returnself.repo.get_option_value(option,foreid)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 wether the connections set should be freed or not. """returntransaction(self,free_cnxset)defhijack_user(self,user):"""return a fake request/session using specified user"""session=Session(user,self.repo)tx=session._txtx.cnxset=self.cnxset# we attributed a connections set, need to update ctx_count else it will be freed# while undesiredtx.ctx_count=1# share pending_operations, else operation added in the hi-jacked# session such as SendMailOp won't ever be processedtx.pending_operations=self.pending_operations# everything in tx.data should be copied back but the entity# type cache we don't want to avoid security pbtx.data=self._tx.data.copy()tx.data.pop('ecache',None)returnsessiondefadd_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)])])defadd_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={}withsecurity_enabled(self,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)defdelete_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. """withsecurity_enabled(self,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 #################################################defupdate_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)defupdate_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)def_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.describe(targeteid)[0]])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))def_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))# resource accessors ######################################################defsystem_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.cnxset.source('system')try: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(source)returnsource.doexec(self,sql,args,rollback=rollback_on_failure)deleted_in_transaction=tx_meth('deleted_in_transaction')added_in_transaction=tx_meth('added_in_transaction')defrtype_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)]# security control #########################################################defsecurity_enabled(self,read=None,write=None):returnsecurity_enabled(self,read=read,write=write)definit_security(self,read,write):ifreadisNone:oldread=Noneelse:oldread=self.set_read_security(read)ifwriteisNone:oldwrite=Noneelse:oldwrite=self.set_write_security(write)self._tx.ctx_count+=1returnoldread,oldwritedefreset_security(self,read,write):tx=self._txtx.ctx_count-=1iftx.ctx_count==0:self._clear_thread_storage(tx)else:ifreadisnotNone:self.set_read_security(read)ifwriteisnotNone:self.set_write_security(write)read_security=tx_attr('read_security')defset_read_security(self,activated):"""[de]activate read security, returning the previous value set for later restoration. you should usually use the `security_enabled` context manager instead of this to change security settings. """tx=self._txoldmode=tx.read_securitytx.read_security=activated# 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 dbapi_query to true when we go back to DEFAULT_SECURITYtx.dbapi_query=(oldmodeisDEFAULT_SECURITYoractivatedisDEFAULT_SECURITY)returnoldmodewrite_security=tx_attr('write_security')defset_write_security(self,activated):"""[de]activate write security, returning the previous value set for later restoration. you should usually use the `security_enabled` context manager instead of this to change security settings. """tx=self._txoldmode=tx.write_securitytx.write_security=activatedreturnoldmode@propertydefrunning_dbapi_query(self):"""return a boolean telling if it's triggered by a db-api query or by a session query. To be used in hooks, else may have a wrong value. """returngetattr(self._tx,'dbapi_query',True)# hooks activation control ################################################## all hooks should be activated during normal executiondefallow_all_hooks_but(self,*categories):returnhooks_control(self,HOOKS_ALLOW_ALL,*categories)defdeny_all_hooks_but(self,*categories):returnhooks_control(self,HOOKS_DENY_ALL,*categories)hooks_mode=tx_attr('hooks_mode')defset_hooks_mode(self,mode):assertmodeisHOOKS_ALLOW_ALLormodeisHOOKS_DENY_ALLoldmode=self._tx.hooks_modeself._tx.hooks_mode=modereturnoldmodedefinit_hooks_mode_categories(self,mode,categories):oldmode=self.set_hooks_mode(mode)ifmodeisself.HOOKS_DENY_ALL:changes=self.enable_hook_categories(*categories)else:changes=self.disable_hook_categories(*categories)self._tx.ctx_count+=1returnoldmode,changesdefreset_hooks_mode_categories(self,oldmode,mode,categories):tx=self._txtx.ctx_count-=1iftx.ctx_count==0:self._clear_thread_storage(tx)else:try:ifcategories:ifmodeisself.HOOKS_DENY_ALL:returnself.disable_hook_categories(*categories)else:returnself.enable_hook_categories(*categories)finally:self.set_hooks_mode(oldmode)disabled_hook_categories=tx_attr('disabled_hook_cats')enabled_hook_categories=tx_attr('enabled_hook_cats')disable_hook_categories=tx_meth('disable_hook_categories')enable_hook_categories=tx_meth('enable_hook_categories')is_hook_category_activated=tx_meth('is_hook_category_activated')is_hook_activated=tx_meth('is_hook_activated')# connection management ###################################################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 ready 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'defget_mode(self):returnself._tx.modedefset_mode(self,value):self._tx.mode=valuemode=property(get_mode,set_mode,doc='transaction mode (read/write/transaction), resetted to'' default_mode on commit / rollback')commit_state=tx_attr('commit_state',writable=True)@propertydefcnxset(self):"""connections set, set according to transaction mode for each query"""ifself._closed:self.free_cnxset(True)raiseException('try to access connections set on a closed session %s'%self.id)returngetattr(self._tx,'cnxset',None)defset_cnxset(self):"""the session need a connections set to execute some queries"""withself._lock:ifself._closed:self.free_cnxset(True)raiseException('try to set connections set on a closed session %s'%self.id)ifself.cnxsetisNone:# get connections set first to avoid race-conditionself._tx.cnxset=cnxset=self.repo._get_cnxset()self._tx.ctx_count+=1try:cnxset.cnxset_set()exceptException:self._tx.cnxset=Noneself.repo._free_cnxset(cnxset)raiseself._threads_in_transaction.add((threading.currentThread(),cnxset))returnself._tx.cnxsetdef_free_thread_cnxset(self,thread,cnxset,force_close=False):try:self._threads_in_transaction.remove((thread,cnxset))exceptKeyError:# race condition on cnxset freeing (freed by commit or rollback vs# close)passelse:ifforce_close:cnxset.reconnect()else:cnxset.cnxset_freed()# free cnxset once everything is done to avoid race-conditionself.repo._free_cnxset(cnxset)deffree_cnxset(self,ignoremode=False):"""the session 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=getattr(self._tx,'cnxset',None)ifcnxsetisnotNoneand(ignoremodeorself.mode=='read'):# even in read mode, we must release the current transactionself._free_thread_cnxset(threading.currentThread(),cnxset)delself._tx.cnxsetself._tx.ctx_count-=1def_touch(self):"""update latest session usage timestamp and reset mode to read"""self.timestamp=time()self.local_perm_cache.clear()# XXX simply move in tx.data, no?# shared data handling ###################################################defget_shared_data(self,key,default=None,pop=False,txdata=False):"""return value associated to `key` in session data"""iftxdata:data=self._tx.dataelse:data=self.dataifpop:returndata.pop(key,default)else:returndata.get(key,default)defset_shared_data(self,key,value,txdata=False):"""set value associated to `key` in session data"""iftxdata:self._tx.data[key]=valueelse:self.data[key]=value# server-side service call #################################################defcall_service(self,regid,async=False,**kwargs):returnself.repo.call_service(self.id,regid,async,**kwargs)# request interface #######################################################@propertydefcursor(self):"""return a rql cursor"""returnselfset_entity_cache=tx_meth('set_entity_cache')entity_cache=tx_meth('entity_cache')cache_entities=tx_meth('cached_entities')drop_entity_cache=tx_meth('drop_entity_cache')deffrom_controller(self):"""return the id (string) of the controller issuing the request (no sense here, always return 'view') """return'view'defsource_defs(self):returnself.repo.source_defs()defdescribe(self,eid,asdict=False):"""return a tuple (type, sourceuri, extid) for the entity with id <eid>"""metas=self.repo.type_and_source_from_eid(eid,self)ifasdict:returndict(zip(('type','source','extid','asource'),metas))# XXX :-1 for cw compat, use asdict=True for full informationreturnmetas[:-1]# db-api like interface ###################################################defsource_from_eid(self,eid):"""return the source where the entity with id <eid> is located"""returnself.repo.source_from_eid(eid,self)defexecute(self,rql,kwargs=None,eid_key=None,build_descr=True):"""db-api like method directly linked to the querier execute method. See :meth:`cubicweb.dbapi.Cursor.execute` documentation. """ifeid_keyisnotNone:warn('[3.8] eid_key is deprecated, you can safely remove this argument',DeprecationWarning,stacklevel=2)self.timestamp=time()# update timestamprset=self._execute(self,rql,kwargs,build_descr)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:tx=self.__threaddata.txexceptAttributeError:passelse:iffree_cnxset:self.free_cnxset()iftx.ctx_count==0:self._clear_thread_storage(tx)else:self._clear_tx_storage(tx)else:self._clear_tx_storage(tx)def_clear_thread_storage(self,tx):self._txs.pop(tx.transactionid,None)try:delself.__threaddata.txexceptAttributeError:passdef_clear_tx_storage(self,tx):tx.clear()tx._rewriter=RQLRewriter(self)defcommit(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_thread_data()self._touch()self.debug('commit session %s done (no db activity)',self.id)returncstate=self.commit_stateifcstate=='uncommitable':raiseQueryError('transaction must be rollbacked')ifcstateisnotNone:return# 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 offwithsecurity_enabled(self,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 session %s done',self.id)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 session %s done',self.id)returnself.transaction_uuid(set=False)finally:self._touch()iffree_cnxset:self.free_cnxset(ignoremode=True)self._clear_thread_data(free_cnxset)defrollback(self,free_cnxset=True,reset_pool=None):"""rollback the current session's transaction"""ifreset_poolisnotNone:warn('[3.13] use free_cnxset argument instead for reset_pool',DeprecationWarning,stacklevel=2)free_cnxset=reset_pool# don't use self.cnxset, rollback may be called with _closed == Truecnxset=getattr(self._tx,'cnxset',None)ifcnxsetisNone:self._clear_thread_data()self._touch()self.debug('rollback session %s done (no db activity)',self.id)returntry:# by default, operations are executed with security turned offwithsecurity_enabled(self,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 session %s done',self.id)finally:self._touch()iffree_cnxset:self.free_cnxset(ignoremode=True)self._clear_thread_data(free_cnxset)defclose(self):"""do not close connections set on session close, since they are shared now"""withself._lock:self._closed=True# copy since _threads_in_transaction maybe modified while waitingforthread,cnxsetinself._threads_in_transaction.copy():ifthreadisthreading.currentThread():continueself.info('waiting for thread %s',thread)# do this loop/break instead of a simple join(10) in case thread is# the main thread (in which case it will be removed from# self._threads_in_transaction but still be alive...)foriinxrange(10):thread.join(1)ifnot(thread.isAlive()and(thread,cnxset)inself._threads_in_transaction):breakelse:self.error('thread %s still alive after 10 seconds, will close ''session anyway',thread)self._free_thread_cnxset(thread,cnxset,force_close=True)self.rollback()delself.__threaddatadelself._txs@propertydefclosed(self):returnnothasattr(self,'_txs')# transaction data/operations management ##################################transaction_data=tx_attr('data')pending_operations=tx_attr('pending_operations')pruned_hooks_cache=tx_attr('pruned_hooks_cache')add_operation=tx_meth('add_operation')# undo support ############################################################defertype_supports_undo(self,ertype):returnself.undo_actionsandertypenotinNO_UNDO_TYPESdeftransaction_uuid(self,set=True):try:returnself._tx.data['tx_uuid']exceptKeyError:ifnotset:returnself._tx.data['tx_uuid']=uuid=uuid4().hexself.repo.system_source.start_undoable_transaction(self,uuid)returnuuiddeftransaction_inc_action_counter(self):num=self._tx.data.setdefault('tx_action_count',0)+1self._tx.data['tx_action_count']=numreturnnum# querier helpers #########################################################rql_rewriter=tx_attr('_rewriter')# deprecated ###############################################################@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 internaly 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 goneifnotsafe:self.disable_hook_categories('integrity')def__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')returngetattr(self._tx,'cnxset',None)classInternalManager(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):self.eid=-1self.login=u'__internal_manager__'self.properties={}defmatching_groups(self,groups):return1defis_in_group(self,group):returnTruedefowns(self,eid):returnTruedefproperty_value(self,key):ifkey=='ui.language':return'en'returnNonedefprefered_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'))