server/session.py
changeset 8773 21edcb0a5ed7
parent 8772 5d10ee381e67
child 8774 608fdcab6fa1
equal deleted inserted replaced
8772:5d10ee381e67 8773:21edcb0a5ed7
   270       :attr:`_threads_in_transaction` is a set of (thread, connections set)
   270       :attr:`_threads_in_transaction` is a set of (thread, connections set)
   271       referencing threads that currently hold a connections set for the session.
   271       referencing threads that currently hold a connections set for the session.
   272     .. automethod:: cubicweb.server.session.transaction
   272     .. automethod:: cubicweb.server.session.transaction
   273 
   273 
   274     You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`,
   274     You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`,
   275     simply access transaction data transparently through the :attr:`_threaddata`
   275     simply access transaction data transparently through the :attr:`_tx`
   276     property. Also, you usually don't have to access it directly since current
   276     property. Also, you usually don't have to access it directly since current
   277     transaction's data may be accessed/modified through properties / methods:
   277     transaction's data may be accessed/modified through properties / methods:
   278 
   278 
   279       :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary
   279       :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary
   280       containing some shared data that should be cleared at the end of the
   280       containing some shared data that should be cleared at the end of the
   403             rewriter = RQLRewriter(self)
   403             rewriter = RQLRewriter(self)
   404             tx = Transaction(txid, self.default_mode, rewriter)
   404             tx = Transaction(txid, self.default_mode, rewriter)
   405             self.__threaddata.tx = self._txs[txid] = tx
   405             self.__threaddata.tx = self._txs[txid] = tx
   406 
   406 
   407     @property
   407     @property
   408     def _threaddata(self):
   408     def _tx(self):
   409         try:
   409         try:
   410             return self.__threaddata.tx
   410             return self.__threaddata.tx
   411         except AttributeError:
   411         except AttributeError:
   412             self.set_tx()
   412             self.set_tx()
   413             return self.__threaddata.tx
   413             return self.__threaddata.tx
   427 
   427 
   428 
   428 
   429     def hijack_user(self, user):
   429     def hijack_user(self, user):
   430         """return a fake request/session using specified user"""
   430         """return a fake request/session using specified user"""
   431         session = Session(user, self.repo)
   431         session = Session(user, self.repo)
   432         threaddata = session._threaddata
   432         tx = session._tx
   433         threaddata.cnxset = self.cnxset
   433         tx.cnxset = self.cnxset
   434         # we attributed a connections set, need to update ctx_count else it will be freed
   434         # we attributed a connections set, need to update ctx_count else it will be freed
   435         # while undesired
   435         # while undesired
   436         threaddata.ctx_count = 1
   436         tx.ctx_count = 1
   437         # share pending_operations, else operation added in the hi-jacked
   437         # share pending_operations, else operation added in the hi-jacked
   438         # session such as SendMailOp won't ever be processed
   438         # session such as SendMailOp won't ever be processed
   439         threaddata.pending_operations = self.pending_operations
   439         tx.pending_operations = self.pending_operations
   440         # everything in transaction_data should be copied back but the entity
   440         # everything in transaction_data should be copied back but the entity
   441         # type cache we don't want to avoid security pb
   441         # type cache we don't want to avoid security pb
   442         threaddata.transaction_data = self.transaction_data.copy()
   442         tx.transaction_data = self.transaction_data.copy()
   443         threaddata.transaction_data.pop('ecache', None)
   443         tx.transaction_data.pop('ecache', None)
   444         return session
   444         return session
   445 
   445 
   446     def add_relation(self, fromeid, rtype, toeid):
   446     def add_relation(self, fromeid, rtype, toeid):
   447         """provide direct access to the repository method to add a relation.
   447         """provide direct access to the repository method to add a relation.
   448 
   448 
   617             oldread = self.set_read_security(read)
   617             oldread = self.set_read_security(read)
   618         if write is None:
   618         if write is None:
   619             oldwrite = None
   619             oldwrite = None
   620         else:
   620         else:
   621             oldwrite = self.set_write_security(write)
   621             oldwrite = self.set_write_security(write)
   622         self._threaddata.ctx_count += 1
   622         self._tx.ctx_count += 1
   623         return oldread, oldwrite
   623         return oldread, oldwrite
   624 
   624 
   625     def reset_security(self, read, write):
   625     def reset_security(self, read, write):
   626         txstore = self._threaddata
   626         txstore = self._tx
   627         txstore.ctx_count -= 1
   627         txstore.ctx_count -= 1
   628         if txstore.ctx_count == 0:
   628         if txstore.ctx_count == 0:
   629             self._clear_thread_storage(txstore)
   629             self._clear_thread_storage(txstore)
   630         else:
   630         else:
   631             if read is not None:
   631             if read is not None:
   634                 self.set_write_security(write)
   634                 self.set_write_security(write)
   635 
   635 
   636     @property
   636     @property
   637     def read_security(self):
   637     def read_security(self):
   638         """return a boolean telling if read security is activated or not"""
   638         """return a boolean telling if read security is activated or not"""
   639         txstore = self._threaddata
   639         txstore = self._tx
   640         if txstore is None:
   640         if txstore is None:
   641             return DEFAULT_SECURITY
   641             return DEFAULT_SECURITY
   642         return txstore.read_security
   642         return txstore.read_security
   643 
   643 
   644     def set_read_security(self, activated):
   644     def set_read_security(self, activated):
   646         later restoration.
   646         later restoration.
   647 
   647 
   648         you should usually use the `security_enabled` context manager instead
   648         you should usually use the `security_enabled` context manager instead
   649         of this to change security settings.
   649         of this to change security settings.
   650         """
   650         """
   651         txstore = self._threaddata
   651         txstore = self._tx
   652         if txstore is None:
   652         if txstore is None:
   653             return DEFAULT_SECURITY
   653             return DEFAULT_SECURITY
   654         oldmode = txstore.read_security
   654         oldmode = txstore.read_security
   655         txstore.read_security = activated
   655         txstore.read_security = activated
   656         # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not
   656         # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not
   674         return oldmode
   674         return oldmode
   675 
   675 
   676     @property
   676     @property
   677     def write_security(self):
   677     def write_security(self):
   678         """return a boolean telling if write security is activated or not"""
   678         """return a boolean telling if write security is activated or not"""
   679         txstore = self._threaddata
   679         txstore = self._tx
   680         if txstore is None:
   680         if txstore is None:
   681             return DEFAULT_SECURITY
   681             return DEFAULT_SECURITY
   682         return txstore.write_security
   682         return txstore.write_security
   683 
   683 
   684     def set_write_security(self, activated):
   684     def set_write_security(self, activated):
   686         later restoration.
   686         later restoration.
   687 
   687 
   688         you should usually use the `security_enabled` context manager instead
   688         you should usually use the `security_enabled` context manager instead
   689         of this to change security settings.
   689         of this to change security settings.
   690         """
   690         """
   691         txstore = self._threaddata
   691         txstore = self._tx
   692         if txstore is None:
   692         if txstore is None:
   693             return DEFAULT_SECURITY
   693             return DEFAULT_SECURITY
   694         oldmode = txstore.write_security
   694         oldmode = txstore.write_security
   695         txstore.write_security = activated
   695         txstore.write_security = activated
   696         return oldmode
   696         return oldmode
   700         """return a boolean telling if it's triggered by a db-api query or by
   700         """return a boolean telling if it's triggered by a db-api query or by
   701         a session query.
   701         a session query.
   702 
   702 
   703         To be used in hooks, else may have a wrong value.
   703         To be used in hooks, else may have a wrong value.
   704         """
   704         """
   705         return getattr(self._threaddata, 'dbapi_query', True)
   705         return getattr(self._tx, 'dbapi_query', True)
   706 
   706 
   707     # hooks activation control #################################################
   707     # hooks activation control #################################################
   708     # all hooks should be activated during normal execution
   708     # all hooks should be activated during normal execution
   709 
   709 
   710     def allow_all_hooks_but(self, *categories):
   710     def allow_all_hooks_but(self, *categories):
   712     def deny_all_hooks_but(self, *categories):
   712     def deny_all_hooks_but(self, *categories):
   713         return hooks_control(self, HOOKS_DENY_ALL, *categories)
   713         return hooks_control(self, HOOKS_DENY_ALL, *categories)
   714 
   714 
   715     @property
   715     @property
   716     def hooks_mode(self):
   716     def hooks_mode(self):
   717         return self._threaddata.hooks_mode
   717         return self._tx.hooks_mode
   718 
   718 
   719     def set_hooks_mode(self, mode):
   719     def set_hooks_mode(self, mode):
   720         assert mode is HOOKS_ALLOW_ALL or mode is HOOKS_DENY_ALL
   720         assert mode is HOOKS_ALLOW_ALL or mode is HOOKS_DENY_ALL
   721         oldmode = self._threaddata.hooks_mode
   721         oldmode = self._tx.hooks_mode
   722         self._threaddata.hooks_mode = mode
   722         self._tx.hooks_mode = mode
   723         return oldmode
   723         return oldmode
   724 
   724 
   725     def init_hooks_mode_categories(self, mode, categories):
   725     def init_hooks_mode_categories(self, mode, categories):
   726         oldmode = self.set_hooks_mode(mode)
   726         oldmode = self.set_hooks_mode(mode)
   727         if mode is self.HOOKS_DENY_ALL:
   727         if mode is self.HOOKS_DENY_ALL:
   728             changes = self.enable_hook_categories(*categories)
   728             changes = self.enable_hook_categories(*categories)
   729         else:
   729         else:
   730             changes = self.disable_hook_categories(*categories)
   730             changes = self.disable_hook_categories(*categories)
   731         self._threaddata.ctx_count += 1
   731         self._tx.ctx_count += 1
   732         return oldmode, changes
   732         return oldmode, changes
   733 
   733 
   734     def reset_hooks_mode_categories(self, oldmode, mode, categories):
   734     def reset_hooks_mode_categories(self, oldmode, mode, categories):
   735         txstore = self._threaddata
   735         txstore = self._tx
   736         txstore.ctx_count -= 1
   736         txstore.ctx_count -= 1
   737         if txstore.ctx_count == 0:
   737         if txstore.ctx_count == 0:
   738             self._clear_thread_storage(txstore)
   738             self._clear_thread_storage(txstore)
   739         else:
   739         else:
   740             try:
   740             try:
   746             finally:
   746             finally:
   747                 self.set_hooks_mode(oldmode)
   747                 self.set_hooks_mode(oldmode)
   748 
   748 
   749     @property
   749     @property
   750     def disabled_hook_categories(self):
   750     def disabled_hook_categories(self):
   751         return self._threaddata.disabled_hook_cats
   751         return self._tx.disabled_hook_cats
   752 
   752 
   753     @property
   753     @property
   754     def enabled_hook_categories(self):
   754     def enabled_hook_categories(self):
   755         return self._threaddata.enabled_hook_cats
   755         return self._tx.enabled_hook_cats
   756 
   756 
   757     def disable_hook_categories(self, *categories):
   757     def disable_hook_categories(self, *categories):
   758         """disable the given hook categories:
   758         """disable the given hook categories:
   759 
   759 
   760         - on HOOKS_DENY_ALL mode, ensure those categories are not enabled
   760         - on HOOKS_DENY_ALL mode, ensure those categories are not enabled
   833             self.default_mode = 'transaction'
   833             self.default_mode = 'transaction'
   834         else: # mode == 'write'
   834         else: # mode == 'write'
   835             self.default_mode = 'read'
   835             self.default_mode = 'read'
   836 
   836 
   837     def get_mode(self):
   837     def get_mode(self):
   838         return self._threaddata.mode
   838         return self._tx.mode
   839     def set_mode(self, value):
   839     def set_mode(self, value):
   840         self._threaddata.mode = value
   840         self._tx.mode = value
   841     mode = property(get_mode, set_mode,
   841     mode = property(get_mode, set_mode,
   842                     doc='transaction mode (read/write/transaction), resetted to'
   842                     doc='transaction mode (read/write/transaction), resetted to'
   843                     ' default_mode on commit / rollback')
   843                     ' default_mode on commit / rollback')
   844 
   844 
   845     def get_commit_state(self):
   845     def get_commit_state(self):
   846         return self._threaddata.commit_state
   846         return self._tx.commit_state
   847     def set_commit_state(self, value):
   847     def set_commit_state(self, value):
   848         self._threaddata.commit_state = value
   848         self._tx.commit_state = value
   849     commit_state = property(get_commit_state, set_commit_state)
   849     commit_state = property(get_commit_state, set_commit_state)
   850 
   850 
   851     @property
   851     @property
   852     def cnxset(self):
   852     def cnxset(self):
   853         """connections set, set according to transaction mode for each query"""
   853         """connections set, set according to transaction mode for each query"""
   854         if self._closed:
   854         if self._closed:
   855             self.free_cnxset(True)
   855             self.free_cnxset(True)
   856             raise Exception('try to access connections set on a closed session %s' % self.id)
   856             raise Exception('try to access connections set on a closed session %s' % self.id)
   857         return getattr(self._threaddata, 'cnxset', None)
   857         return getattr(self._tx, 'cnxset', None)
   858 
   858 
   859     def set_cnxset(self):
   859     def set_cnxset(self):
   860         """the session need a connections set to execute some queries"""
   860         """the session need a connections set to execute some queries"""
   861         with self._closed_lock:
   861         with self._closed_lock:
   862             if self._closed:
   862             if self._closed:
   863                 self.free_cnxset(True)
   863                 self.free_cnxset(True)
   864                 raise Exception('try to set connections set on a closed session %s' % self.id)
   864                 raise Exception('try to set connections set on a closed session %s' % self.id)
   865             if self.cnxset is None:
   865             if self.cnxset is None:
   866                 # get connections set first to avoid race-condition
   866                 # get connections set first to avoid race-condition
   867                 self._threaddata.cnxset = cnxset = self.repo._get_cnxset()
   867                 self._tx.cnxset = cnxset = self.repo._get_cnxset()
   868                 self._threaddata.ctx_count += 1
   868                 self._tx.ctx_count += 1
   869                 try:
   869                 try:
   870                     cnxset.cnxset_set()
   870                     cnxset.cnxset_set()
   871                 except Exception:
   871                 except Exception:
   872                     self._threaddata.cnxset = None
   872                     self._tx.cnxset = None
   873                     self.repo._free_cnxset(cnxset)
   873                     self.repo._free_cnxset(cnxset)
   874                     raise
   874                     raise
   875                 self._threads_in_transaction.add(
   875                 self._threads_in_transaction.add(
   876                     (threading.currentThread(), cnxset) )
   876                     (threading.currentThread(), cnxset) )
   877             return self._threaddata.cnxset
   877             return self._tx.cnxset
   878 
   878 
   879     def _free_thread_cnxset(self, thread, cnxset, force_close=False):
   879     def _free_thread_cnxset(self, thread, cnxset, force_close=False):
   880         try:
   880         try:
   881             self._threads_in_transaction.remove( (thread, cnxset) )
   881             self._threads_in_transaction.remove( (thread, cnxset) )
   882         except KeyError:
   882         except KeyError:
   893 
   893 
   894     def free_cnxset(self, ignoremode=False):
   894     def free_cnxset(self, ignoremode=False):
   895         """the session is no longer using its connections set, at least for some time"""
   895         """the session is no longer using its connections set, at least for some time"""
   896         # cnxset may be none if no operation has been done since last commit
   896         # cnxset may be none if no operation has been done since last commit
   897         # or rollback
   897         # or rollback
   898         cnxset = getattr(self._threaddata, 'cnxset', None)
   898         cnxset = getattr(self._tx, 'cnxset', None)
   899         if cnxset is not None and (ignoremode or self.mode == 'read'):
   899         if cnxset is not None and (ignoremode or self.mode == 'read'):
   900             # even in read mode, we must release the current transaction
   900             # even in read mode, we must release the current transaction
   901             self._free_thread_cnxset(threading.currentThread(), cnxset)
   901             self._free_thread_cnxset(threading.currentThread(), cnxset)
   902             del self._threaddata.cnxset
   902             del self._tx.cnxset
   903             self._threaddata.ctx_count -= 1
   903             self._tx.ctx_count -= 1
   904 
   904 
   905     def _touch(self):
   905     def _touch(self):
   906         """update latest session usage timestamp and reset mode to read"""
   906         """update latest session usage timestamp and reset mode to read"""
   907         self.timestamp = time()
   907         self.timestamp = time()
   908         self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
   908         self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
  1122         if reset_pool is not None:
  1122         if reset_pool is not None:
  1123             warn('[3.13] use free_cnxset argument instead for reset_pool',
  1123             warn('[3.13] use free_cnxset argument instead for reset_pool',
  1124                  DeprecationWarning, stacklevel=2)
  1124                  DeprecationWarning, stacklevel=2)
  1125             free_cnxset = reset_pool
  1125             free_cnxset = reset_pool
  1126         # don't use self.cnxset, rollback may be called with _closed == True
  1126         # don't use self.cnxset, rollback may be called with _closed == True
  1127         cnxset = getattr(self._threaddata, 'cnxset', None)
  1127         cnxset = getattr(self._tx, 'cnxset', None)
  1128         if cnxset is None:
  1128         if cnxset is None:
  1129             self._clear_thread_data()
  1129             self._clear_thread_data()
  1130             self._touch()
  1130             self._touch()
  1131             self.debug('rollback session %s done (no db activity)', self.id)
  1131             self.debug('rollback session %s done (no db activity)', self.id)
  1132             return
  1132             return
  1179 
  1179 
  1180     # transaction data/operations management ##################################
  1180     # transaction data/operations management ##################################
  1181 
  1181 
  1182     @property
  1182     @property
  1183     def transaction_data(self):
  1183     def transaction_data(self):
  1184         return self._threaddata.transaction_data
  1184         return self._tx.transaction_data
  1185 
  1185 
  1186     @property
  1186     @property
  1187     def pending_operations(self):
  1187     def pending_operations(self):
  1188         return self._threaddata.pending_operations
  1188         return self._tx.pending_operations
  1189 
  1189 
  1190     @property
  1190     @property
  1191     def pruned_hooks_cache(self):
  1191     def pruned_hooks_cache(self):
  1192         return self._threaddata.pruned_hooks_cache
  1192         return self._tx.pruned_hooks_cache
  1193 
  1193 
  1194     def add_operation(self, operation, index=None):
  1194     def add_operation(self, operation, index=None):
  1195         """add an operation"""
  1195         """add an operation"""
  1196         if index is None:
  1196         if index is None:
  1197             self.pending_operations.append(operation)
  1197             self.pending_operations.append(operation)
  1221     # querier helpers #########################################################
  1221     # querier helpers #########################################################
  1222 
  1222 
  1223     @property
  1223     @property
  1224     def rql_rewriter(self):
  1224     def rql_rewriter(self):
  1225         # in thread local storage since the rewriter isn't thread safe
  1225         # in thread local storage since the rewriter isn't thread safe
  1226         return self._threaddata._rewriter
  1226         return self._tx._rewriter
  1227 
  1227 
  1228     # deprecated ###############################################################
  1228     # deprecated ###############################################################
  1229 
  1229 
  1230     @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')
  1230     @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')
  1231     def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
  1231     def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
  1298     def cnxset(self):
  1298     def cnxset(self):
  1299         """connections set, set according to transaction mode for each query"""
  1299         """connections set, set according to transaction mode for each query"""
  1300         if self.repo.shutting_down:
  1300         if self.repo.shutting_down:
  1301             self.free_cnxset(True)
  1301             self.free_cnxset(True)
  1302             raise ShuttingDown('repository is shutting down')
  1302             raise ShuttingDown('repository is shutting down')
  1303         return getattr(self._threaddata, 'cnxset', None)
  1303         return getattr(self._tx, 'cnxset', None)
  1304 
  1304 
  1305 
  1305 
  1306 class InternalManager(object):
  1306 class InternalManager(object):
  1307     """a manager user with all access rights used internally for task such as
  1307     """a manager user with all access rights used internally for task such as
  1308     bootstrapping the repository or creating regular users according to
  1308     bootstrapping the repository or creating regular users according to