--- a/cwconfig.py Mon Jun 21 15:32:58 2010 +0200
+++ b/cwconfig.py Mon Jun 21 15:34:46 2010 +0200
@@ -668,6 +668,7 @@
self.load_defaults()
# will be properly initialized later by _gettext_init
self.translations = {'en': (unicode, lambda ctx, msgid: unicode(msgid) )}
+ self._site_loaded = set()
# don't register ReStructured Text directives by simple import, avoid pb
# with eg sphinx.
# XXX should be done properly with a function from cw.uicfg
@@ -696,7 +697,7 @@
init_log(self.debugmode, syslog, logthreshold, logfile, self.log_format,
rotation_parameters={'when': 'W6', # every sunday
'interval': 1,
- 'backupCount': 52,})
+ 'backupCount': 52})
else:
init_log(self.debugmode, syslog, logthreshold, logfile, self.log_format)
# configure simpleTal logger
@@ -708,6 +709,34 @@
"""
return []
+ apphome = None
+
+ def load_site_cubicweb(self, paths=None):
+ """load instance's specific site_cubicweb file"""
+ if paths is None:
+ paths = self.cubes_path()
+ if self.apphome is not None:
+ paths = [self.apphome] + paths
+ for path in reversed(paths):
+ sitefile = join(path, 'site_cubicweb.py')
+ if exists(sitefile) and not sitefile in self._site_loaded:
+ self._load_site_cubicweb(sitefile)
+ self._site_loaded.add(sitefile)
+ else:
+ sitefile = join(path, 'site_erudi.py')
+ if exists(sitefile) and not sitefile in self._site_loaded:
+ self._load_site_cubicweb(sitefile)
+ self._site_loaded.add(sitefile)
+ self.warning('[3.5] site_erudi.py is deprecated, should be '
+ 'renamed to site_cubicweb.py')
+
+ def _load_site_cubicweb(self, sitefile):
+ # XXX extrapath argument to load_module_from_file only in lgc > 0.50.2
+ from logilab.common.modutils import load_module_from_modpath, modpath_from_file
+ module = load_module_from_modpath(modpath_from_file(sitefile, self.extrapath))
+ self.info('%s loaded', sitefile)
+ return module
+
def eproperty_definitions(self):
cfg = self.persistent_options_configuration()
for section, options in cfg.options_by_section():
@@ -889,7 +918,6 @@
self.appid = appid
CubicWebNoAppConfiguration.__init__(self, debugmode)
self._cubes = None
- self._site_loaded = set()
self.load_file_configuration(self.main_config_file())
def adjust_sys_path(self):
@@ -964,37 +992,6 @@
infos.append('%s-%s' % (pkg, version))
return md5.new(';'.join(infos)).hexdigest()
- def load_site_cubicweb(self):
- """load instance's specific site_cubicweb file"""
- paths = self.cubes_path()
- if self.apphome is not None:
- paths = [self.apphome] + paths
- for path in reversed(paths):
- sitefile = join(path, 'site_cubicweb.py')
- if exists(sitefile) and not sitefile in self._site_loaded:
- self._load_site_cubicweb(sitefile)
- self._site_loaded.add(sitefile)
- else:
- sitefile = join(path, 'site_erudi.py')
- if exists(sitefile) and not sitefile in self._site_loaded:
- self._load_site_cubicweb(sitefile)
- self._site_loaded.add(sitefile)
- self.warning('[3.5] site_erudi.py is deprecated, should be '
- 'renamed to site_cubicweb.py')
-
- def _load_site_cubicweb(self, sitefile):
- # XXX extrapath argument to load_module_from_file only in lgc > 0.46
- from logilab.common.modutils import load_module_from_modpath, modpath_from_file
- def load_module_from_file(filepath, path=None, use_sys=1, extrapath=None):
- return load_module_from_modpath(modpath_from_file(filepath, extrapath),
- path, use_sys)
- module = load_module_from_file(sitefile, extrapath=self.extrapath)
- self.info('%s loaded', sitefile)
- # cube specific options
- if getattr(module, 'options', None):
- self.register_options(module.options)
- self.load_defaults()
-
def load_configuration(self):
"""load instance's configuration files"""
super(CubicWebConfiguration, self).load_configuration()
@@ -1002,6 +999,13 @@
# init gettext
self._gettext_init()
+ def _load_site_cubicweb(self, sitefile):
+ # overriden to register cube specific options
+ mod = super(CubicWebConfiguration, self)._load_site_cubicweb(sitefile)
+ if getattr(mod, 'options', None):
+ self.register_options(module.options)
+ self.load_defaults()
+
def init_log(self, logthreshold=None, force=False):
"""init the log service"""
if not force and hasattr(self, '_logging_initialized'):
@@ -1096,7 +1100,8 @@
SMTP_LOCK.release()
return True
-set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
+set_log_methods(CubicWebNoAppConfiguration,
+ logging.getLogger('cubicweb.configuration'))
# alias to get a configuration instance from an instance id
instance_configuration = CubicWebConfiguration.config_for
--- a/dbapi.py Mon Jun 21 15:32:58 2010 +0200
+++ b/dbapi.py Mon Jun 21 15:34:46 2010 +0200
@@ -24,6 +24,7 @@
__docformat__ = "restructuredtext en"
+from threading import currentThread
from logging import getLogger
from time import time, clock
from itertools import count
@@ -400,6 +401,9 @@
"""no effect"""
pass
+ def _txid(self):
+ return self.connection._txid(self)
+
def execute(self, rql, args=None, eid_key=None, build_descr=True):
"""execute a rql query, return resulting rows and their description in
a :class:`~cubicweb.rset.ResultSet` object
@@ -435,7 +439,8 @@
warn('[3.8] eid_key is deprecated, you can safely remove this argument',
DeprecationWarning, stacklevel=2)
# XXX use named argument for build_descr in case repo is < 3.8
- rset = self._repo.execute(self._sessid, rql, args, build_descr=build_descr)
+ rset = self._repo.execute(self._sessid, rql, args,
+ build_descr=build_descr, txid=self._txid())
rset.req = self.req
return rset
@@ -490,6 +495,9 @@
self.rollback()
return False #propagate the exception
+ def _txid(self, cursor=None): # XXX could now handle various isolation level!
+ return currentThread().getName()
+
def request(self):
return DBAPIRequest(self.vreg, DBAPISession(self))
@@ -551,18 +559,12 @@
esubpath = list(subpath)
esubpath.remove('views')
esubpath.append(join('web', 'views'))
- cubes = reversed([config.cube_dir(p) for p in cubes])
- vpath = config.build_vregistry_path(cubes, evobjpath=esubpath,
+ cubespath = [config.cube_dir(p) for p in cubes]
+ config.load_site_cubicweb(cubespath)
+ vpath = config.build_vregistry_path(reversed(cubespath),
+ evobjpath=esubpath,
tvobjpath=subpath)
self.vreg.register_objects(vpath)
- if self._cnxtype == 'inmemory':
- # should reinit hooks manager as well
- hm, config = self._repo.hm, self._repo.config
- hm.set_schema(hm.schema) # reset structure
- hm.register_system_hooks(config)
- # instance specific hooks
- if self._repo.config.instance_hooks:
- hm.register_hooks(config.load_hooks(self.vreg))
def use_web_compatible_requests(self, baseurl, sitetitle=None):
"""monkey patch DBAPIRequest to fake a cw.web.request, so you should
@@ -632,7 +634,7 @@
def describe(self, eid):
if self._closed is not None:
raise ProgrammingError('Closed connection')
- return self._repo.describe(self.sessionid, eid)
+ return self._repo.describe(self.sessionid, eid, txid=self._txid())
def close(self):
"""Close the connection now (rather than whenever __del__ is called).
@@ -645,7 +647,7 @@
"""
if self._closed:
raise ProgrammingError('Connection is already closed')
- self._repo.close(self.sessionid)
+ self._repo.close(self.sessionid, txid=self._txid())
del self._repo # necessary for proper garbage collection
self._closed = 1
@@ -659,7 +661,7 @@
"""
if not self._closed is None:
raise ProgrammingError('Connection is already closed')
- return self._repo.commit(self.sessionid)
+ return self._repo.commit(self.sessionid, txid=self._txid())
def rollback(self):
"""This method is optional since not all databases provide transaction
@@ -672,7 +674,7 @@
"""
if not self._closed is None:
raise ProgrammingError('Connection is already closed')
- self._repo.rollback(self.sessionid)
+ self._repo.rollback(self.sessionid, txid=self._txid())
def cursor(self, req=None):
"""Return a new Cursor Object using the connection.
@@ -713,6 +715,7 @@
and set to false.
"""
txinfos = self._repo.undoable_transactions(self.sessionid, ueid,
+ txid=self._txid(),
**actionfilters)
if req is None:
req = self.request()
@@ -727,7 +730,8 @@
allowed (eg not in managers group and the transaction doesn't belong to
him).
"""
- txinfo = self._repo.transaction_info(self.sessionid, txuuid)
+ txinfo = self._repo.transaction_info(self.sessionid, txuuid,
+ txid=self._txid())
if req is None:
req = self.request()
txinfo.req = req
@@ -743,7 +747,8 @@
session's user is not allowed (eg not in managers group and the
transaction doesn't belong to him).
"""
- return self._repo.transaction_actions(self.sessionid, txuuid, public)
+ return self._repo.transaction_actions(self.sessionid, txuuid, public,
+ txid=self._txid())
def undo_transaction(self, txuuid):
"""Undo the given transaction. Return potential restoration errors.
@@ -752,4 +757,5 @@
allowed (eg not in managers group and the transaction doesn't belong to
him).
"""
- return self._repo.undo_transaction(self.sessionid, txuuid)
+ return self._repo.undo_transaction(self.sessionid, txuuid,
+ txid=self._txid())
--- a/hooks/security.py Mon Jun 21 15:32:58 2010 +0200
+++ b/hooks/security.py Mon Jun 21 15:34:46 2010 +0200
@@ -18,6 +18,7 @@
"""Security hooks: check permissions to add/delete/update entities according to
the user connected to a session
"""
+
__docformat__ = "restructuredtext en"
from cubicweb import Unauthorized
--- a/server/repository.py Mon Jun 21 15:32:58 2010 +0200
+++ b/server/repository.py Mon Jun 21 15:34:46 2010 +0200
@@ -575,7 +575,8 @@
session.commit()
return session.id
- def execute(self, sessionid, rqlstring, args=None, build_descr=True):
+ def execute(self, sessionid, rqlstring, args=None, build_descr=True,
+ txid=None):
"""execute a RQL query
* rqlstring should be an unicode string or a plain ascii string
@@ -583,7 +584,7 @@
* build_descr is a flag indicating if the description should be
built on select queries
"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
try:
rset = self.querier.execute(session, rqlstring, args,
@@ -611,9 +612,9 @@
finally:
session.reset_pool()
- def describe(self, sessionid, eid):
+ def describe(self, sessionid, eid, txid=None):
"""return a tuple (type, source, extid) for the entity with id <eid>"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
return self.type_and_source_from_eid(eid, session)
finally:
@@ -639,32 +640,36 @@
session = self._get_session(sessionid, setpool=False)
session.set_shared_data(key, value, querydata)
- def commit(self, sessionid):
+ def commit(self, sessionid, txid=None):
"""commit transaction for the session with the given id"""
self.debug('begin commit for session %s', sessionid)
try:
- return self._get_session(sessionid).commit()
+ session = self._get_session(sessionid)
+ session.set_tx_data(txid)
+ return session.commit()
except (ValidationError, Unauthorized):
raise
except:
self.exception('unexpected error')
raise
- def rollback(self, sessionid):
+ def rollback(self, sessionid, txid=None):
"""commit transaction for the session with the given id"""
self.debug('begin rollback for session %s', sessionid)
try:
- self._get_session(sessionid).rollback()
+ session = self._get_session(sessionid)
+ session.set_tx_data(txid)
+ session.rollback()
except:
self.exception('unexpected error')
raise
- def close(self, sessionid, checkshuttingdown=True):
+ def close(self, sessionid, txid=None, checkshuttingdown=True):
"""close the session with the given id"""
- session = self._get_session(sessionid, setpool=True,
+ session = self._get_session(sessionid, setpool=True, txid=txid,
checkshuttingdown=checkshuttingdown)
# operation uncommited before close are rollbacked before hook is called
- session.rollback()
+ session.rollback(reset_pool=False)
self.hm.call_hooks('session_close', session)
# commit session at this point in case write operation has been done
# during `session_close` hooks
@@ -695,34 +700,35 @@
for prop, value in props.items():
session.change_property(prop, value)
- def undoable_transactions(self, sessionid, ueid=None, **actionfilters):
+ def undoable_transactions(self, sessionid, ueid=None, txid=None,
+ **actionfilters):
"""See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
return self.system_source.undoable_transactions(session, ueid,
**actionfilters)
finally:
session.reset_pool()
- def transaction_info(self, sessionid, txuuid):
+ def transaction_info(self, sessionid, txuuid, txid=None):
"""See :class:`cubicweb.dbapi.Connection.transaction_info`"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
return self.system_source.tx_info(session, txuuid)
finally:
session.reset_pool()
- def transaction_actions(self, sessionid, txuuid, public=True):
+ def transaction_actions(self, sessionid, txuuid, public=True, txid=None):
"""See :class:`cubicweb.dbapi.Connection.transaction_actions`"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
return self.system_source.tx_actions(session, txuuid, public)
finally:
session.reset_pool()
- def undo_transaction(self, sessionid, txuuid):
+ def undo_transaction(self, sessionid, txuuid, txid=None):
"""See :class:`cubicweb.dbapi.Connection.undo_transaction`"""
- session = self._get_session(sessionid, setpool=True)
+ session = self._get_session(sessionid, setpool=True, txid=txid)
try:
return self.system_source.undo_transaction(session, txuuid)
finally:
@@ -785,7 +791,8 @@
session.set_pool()
return session
- def _get_session(self, sessionid, setpool=False, checkshuttingdown=True):
+ def _get_session(self, sessionid, setpool=False, txid=None,
+ checkshuttingdown=True):
"""return the user associated to the given session identifier"""
if checkshuttingdown and self._shutting_down:
raise Exception('Repository is shutting down')
@@ -794,6 +801,7 @@
except KeyError:
raise BadConnectionId('No such session %s' % sessionid)
if setpool:
+ session.set_tx_data(txid) # must be done before set_pool
session.set_pool()
return session
--- a/server/session.py Mon Jun 21 15:32:58 2010 +0200
+++ b/server/session.py Mon Jun 21 15:34:46 2010 +0200
@@ -117,6 +117,9 @@
# print INDENT + 'reset write to', self.oldwrite
+class TransactionData(object):
+ def __init__(self, txid):
+ self.transactionid = txid
class Session(RequestSessionBase):
"""tie session id, user, connections pool and other session data all
@@ -148,7 +151,8 @@
# i18n initialization
self.set_language(cnxprops.lang)
# internals
- self._threaddata = threading.local()
+ self._tx_data = {}
+ self.__threaddata = threading.local()
self._threads_in_transaction = set()
self._closed = False
@@ -156,6 +160,23 @@
return '<%ssession %s (%s 0x%x)>' % (
self.cnxtype, unicode(self.user.login), self.id, id(self))
+ def set_tx_data(self, txid=None):
+ if txid is None:
+ txid = threading.currentThread().getName()
+ try:
+ self.__threaddata.txdata = self._tx_data[txid]
+ except KeyError:
+ self.__threaddata.txdata = self._tx_data[txid] = TransactionData(txid)
+
+ @property
+ def _threaddata(self):
+ try:
+ return self.__threaddata.txdata
+ except AttributeError:
+ self.set_tx_data()
+ return self.__threaddata.txdata
+
+
def hijack_user(self, user):
"""return a fake request/session using specified user"""
session = Session(user, self.repo)
@@ -339,11 +360,14 @@
@property
def read_security(self):
"""return a boolean telling if read security is activated or not"""
+ txstore = self._threaddata
+ if txstore is None:
+ return self.DEFAULT_SECURITY
try:
- return self._threaddata.read_security
+ return txstore.read_security
except AttributeError:
- self._threaddata.read_security = self.DEFAULT_SECURITY
- return self._threaddata.read_security
+ txstore.read_security = self.DEFAULT_SECURITY
+ return txstore.read_security
def set_read_security(self, activated):
"""[de]activate read security, returning the previous value set for
@@ -352,8 +376,11 @@
you should usually use the `security_enabled` context manager instead
of this to change security settings.
"""
- oldmode = self.read_security
- self._threaddata.read_security = activated
+ txstore = self._threaddata
+ if txstore is None:
+ return self.DEFAULT_SECURITY
+ oldmode = getattr(txstore, 'read_security', self.DEFAULT_SECURITY)
+ txstore.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:
@@ -370,18 +397,21 @@
# else (False actually) is not perfect but should be enough
#
# also reset dbapi_query to true when we go back to DEFAULT_SECURITY
- self._threaddata.dbapi_query = (oldmode is self.DEFAULT_SECURITY
- or activated is self.DEFAULT_SECURITY)
+ txstore.dbapi_query = (oldmode is self.DEFAULT_SECURITY
+ or activated is self.DEFAULT_SECURITY)
return oldmode
@property
def write_security(self):
"""return a boolean telling if write security is activated or not"""
+ txstore = self._threaddata
+ if txstore is None:
+ return self.DEFAULT_SECURITY
try:
- return self._threaddata.write_security
+ return txstore.write_security
except:
- self._threaddata.write_security = self.DEFAULT_SECURITY
- return self._threaddata.write_security
+ txstore.write_security = self.DEFAULT_SECURITY
+ return txstore.write_security
def set_write_security(self, activated):
"""[de]activate write security, returning the previous value set for
@@ -390,8 +420,11 @@
you should usually use the `security_enabled` context manager instead
of this to change security settings.
"""
- oldmode = self.write_security
- self._threaddata.write_security = activated
+ txstore = self._threaddata
+ if txstore is None:
+ return self.DEFAULT_SECURITY
+ oldmode = getattr(txstore, 'write_security', self.DEFAULT_SECURITY)
+ txstore.write_security = activated
return oldmode
@property
@@ -568,7 +601,6 @@
"""update latest session usage timestamp and reset mode to read"""
self.timestamp = time()
self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
- self._threaddata.mode = self.default_mode
# shared data handling ###################################################
@@ -648,18 +680,29 @@
rset.req = self
return rset
- def _clear_thread_data(self):
+ def _clear_thread_data(self, reset_pool=True):
"""remove everything from the thread local storage, except pool
which is explicitly removed by reset_pool, and mode which is set anyway
by _touch
"""
- store = self._threaddata
- for name in ('commit_state', 'transaction_data', 'pending_operations',
- '_rewriter'):
- try:
- delattr(store, name)
- except AttributeError:
- pass
+ try:
+ txstore = self.__threaddata.txdata
+ except AttributeError:
+ pass
+ else:
+ if reset_pool:
+ self._tx_data.pop(txstore.transactionid, None)
+ try:
+ del self.__threaddata.txdata
+ except AttributeError:
+ pass
+ else:
+ for name in ('commit_state', 'transaction_data',
+ 'pending_operations', '_rewriter'):
+ try:
+ delattr(txstore, name)
+ except AttributeError:
+ continue
def commit(self, reset_pool=True):
"""commit the current session's transaction"""
@@ -671,13 +714,13 @@
return
if self.commit_state:
return
- # by default, operations are executed with security turned off
- with security_enabled(self, False, False):
- # on rollback, an operation should have the following state
- # information:
- # - processed by the precommit/commit event or not
- # - if processed, is it the failed operation
- try:
+ # on rollback, an operation should have the following state
+ # information:
+ # - processed by the precommit/commit event or not
+ # - if processed, is it the failed operation
+ try:
+ # by default, operations are executed with security turned off
+ with security_enabled(self, False, False):
for trstate in ('precommit', 'commit'):
processed = []
self.commit_state = trstate
@@ -721,23 +764,22 @@
exc_info=sys.exc_info())
self.info('%s session %s done', trstate, self.id)
return self.transaction_uuid(set=False)
- finally:
- self._clear_thread_data()
- self._touch()
- if reset_pool:
- self.reset_pool(ignoremode=True)
+ finally:
+ self._touch()
+ if reset_pool:
+ self.reset_pool(ignoremode=True)
+ self._clear_thread_data(reset_pool)
def rollback(self, reset_pool=True):
"""rollback the current session's transaction"""
if self.pool is None:
- assert not self.pending_operations
self._clear_thread_data()
self._touch()
self.debug('rollback session %s done (no db activity)', self.id)
return
- # by default, operations are executed with security turned off
- with security_enabled(self, False, False):
- try:
+ try:
+ # by default, operations are executed with security turned off
+ with security_enabled(self, False, False):
while self.pending_operations:
try:
operation = self.pending_operations.pop(0)
@@ -747,11 +789,11 @@
continue
self.pool.rollback()
self.debug('rollback for session %s done', self.id)
- finally:
- self._clear_thread_data()
- self._touch()
- if reset_pool:
- self.reset_pool(ignoremode=True)
+ finally:
+ self._touch()
+ if reset_pool:
+ self.reset_pool(ignoremode=True)
+ self._clear_thread_data(reset_pool)
def close(self):
"""do not close pool on session close, since they are shared now"""
@@ -773,7 +815,8 @@
self.error('thread %s still alive after 10 seconds, will close '
'session anyway', thread)
self.rollback()
- del self._threaddata
+ del self.__threaddata
+ del self._tx_data
# transaction data/operations management ##################################