# HG changeset patch # User Aurelien Campeas # Date 1402067288 -7200 # Node ID b926ff4ef4a8ab3c647d96f8274a11099a9b9e11 # Parent ef54ea75a642dce3db198b2d29cb31407b6778cb [repoapi,session] remove all session-as-cnx backward compat The `dbapi` being gone, we now can drop the session object bw-compatibility layer. This will allow further simplifications, such as folding ClientConnection and Connection (without too much pain), and then having persistent sessions. Related to #3933480. diff -r ef54ea75a642 -r b926ff4ef4a8 devtools/testlib.py --- a/devtools/testlib.py Wed Apr 22 18:28:58 2015 +0200 +++ b/devtools/testlib.py Fri Jun 06 17:08:08 2014 +0200 @@ -358,19 +358,9 @@ @deprecated('[3.19] explicitly use RepoAccess object in test instead') def session(self): """return current server side session""" - # XXX We want to use a srv_connection instead and deprecate this - # property session = self._current_session if session is None: session = self._admin_session - # bypassing all sanity to use the same repo cnx in the session - # - # we can't call set_cnx as the Connection is not managed by the - # session. - session._Session__threaddata.cnx = self._admin_clt_cnx._cnx - else: - session._Session__threaddata.cnx = self.cnx._cnx - session.set_cnxset() return session @property @@ -542,8 +532,7 @@ self._admin_clt_cnx.close() self._admin_clt_cnx = None if self._admin_session is not None: - if not self._admin_session.closed: - self.repo.close(self._admin_session.sessionid) + self.repo.close(self._admin_session.sessionid) self._admin_session = None while self._cleanups: cleanup, args, kwargs = self._cleanups.pop(-1) diff -r ef54ea75a642 -r b926ff4ef4a8 server/migractions.py --- a/server/migractions.py Wed Apr 22 18:28:58 2015 +0200 +++ b/server/migractions.py Fri Jun 06 17:08:08 2014 +0200 @@ -97,7 +97,7 @@ self.session = cnx._session elif connect: self.repo_connect() - self.set_session() + self.set_cnx() else: self.session = None # no config on shell to a remote instance @@ -125,7 +125,7 @@ self.fs_schema = schema self._synchronized = set() - def set_session(self): + def set_cnx(self): try: login = self.repo.config.default_admin_config['login'] pwd = self.repo.config.default_admin_config['password'] @@ -149,7 +149,6 @@ print 'aborting...' sys.exit(0) self.session = self.repo._get_session(self.cnx.sessionid) - self.session.keep_cnxset_mode('transaction') # overriden from base MigrationHelper ###################################### diff -r ef54ea75a642 -r b926ff4ef4a8 server/repository.py --- a/server/repository.py Wed Apr 22 18:28:58 2015 +0200 +++ b/server/repository.py Fri Jun 06 17:08:08 2014 +0200 @@ -668,15 +668,13 @@ """raise `BadConnectionId` if the connection is no more valid, else return its latest activity timestamp. """ - return self._get_session(sessionid, setcnxset=False).timestamp + return self._get_session(sessionid).timestamp def close(self, sessionid, txid=None, checkshuttingdown=True): """close the session with the given id""" session = self._get_session(sessionid, txid=txid, checkshuttingdown=checkshuttingdown) # operation uncommited before close are rolled back before hook is called - if session._cnx._session_handled: - session._cnx.rollback(free_cnxset=False) with session.new_cnx() as cnx: self.hm.call_hooks('session_close', cnx) # commit connection at this point in case write operation has been @@ -724,8 +722,7 @@ with cnx.ensure_cnx_set: yield cnx - def _get_session(self, sessionid, setcnxset=False, txid=None, - checkshuttingdown=True): + def _get_session(self, sessionid, txid=None, checkshuttingdown=True): """return the session associated with the given session identifier""" if checkshuttingdown and self.shutting_down: raise ShuttingDown('Repository is shutting down') @@ -733,9 +730,6 @@ session = self._sessions[sessionid] except KeyError: raise BadConnectionId('No such session %s' % sessionid) - if setcnxset: - session.set_cnx(txid) # must be done before set_cnxset - session.set_cnxset() return session # data sources handling ################################################### diff -r ef54ea75a642 -r b926ff4ef4a8 server/session.py --- a/server/session.py Wed Apr 22 18:28:58 2015 +0200 +++ b/server/session.py Fri Jun 06 17:08:08 2014 +0200 @@ -439,6 +439,7 @@ """ is_request = False + mode = 'read' def __init__(self, session, cnxid=None, session_handled=False): # using super(Connection, self) confuse some test hack @@ -473,12 +474,10 @@ # other session utility self._session_timestamp = session._timestamp - #: connection handling mode - self.mode = session.default_mode #: connection set used to execute queries on sources self._cnxset = None #: CnxSetTracker used to report cnxset usage - self._cnxset_tracker = session._cnxset_tracker + self._cnxset_tracker = CnxSetTracker() #: is this connection from a client or internal to the repo self.running_dbapi_query = True # internal (root) session @@ -1226,148 +1225,37 @@ return float(self.value) -class Session(RequestSessionBase): # XXX repoapi: stop being a - # RequestSessionBase at some point +class Session(object): """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 = False def __init__(self, user, repo, cnxprops=None, _id=None): - super(Session, self).__init__(repo.vreg) self.sessionid = _id or make_uid(unormalize(user.login).encode('UTF8')) self.user = user # XXX repoapi: deprecated and store only a login. self.repo = repo + self.vreg = repo.vreg self._timestamp = Timestamp() - self.default_mode = 'read' - # short cut to querier .execute method - self._execute = repo.querier.execute - # shared data, used to communicate extra information between the client - # and the rql server self.data = {} - # i18n initialization - self.set_language(user.prefered_language()) - ### internals - # Connection of this section - self._cnxs = {} # XXX repoapi: remove this when nobody use the session - # as a Connection - # Data local to the thread - self.__threaddata = threading.local() # XXX repoapi: remove this when - # nobody use the session as a Connection - self._cnxset_tracker = CnxSetTracker() - self._closed = False - self._lock = threading.RLock() + self.closed = False + + def close(self): + self.closed = True + + def __enter__(self): + return self + + def __exit__(self, *args): + pass def __unicode__(self): return '' % ( unicode(self.user.login), self.sessionid, id(self)) + @property def timestamp(self): return float(self._timestamp) @@ -1388,55 +1276,6 @@ """ return Connection(self) - def _get_cnx(self, cnxid): - """return the connection attached to this session - - Connection is created if necessary""" - with self._lock: # no connection exist with the same id - try: - if self.closed: - raise SessionClosedError('try to access connections set on' - ' a closed session %s' % self.id) - cnx = self._cnxs[cnxid] - assert cnx._session_handled - except KeyError: - cnx = Connection(self, cnxid=cnxid, session_handled=True) - self._cnxs[cnxid] = cnx - cnx.__enter__() - return cnx - - def _close_cnx(self, cnx): - """Close a Connection related to a session""" - assert cnx._session_handled - cnx.__exit__() - self._cnxs.pop(cnx.connectionid, None) - try: - if self.__threaddata.cnx is cnx: - del self.__threaddata.cnx - except AttributeError: - pass - - def set_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 - - Connection is created if necessary""" - if cnxid is None: - cnxid = threading.currentThread().getName() - cnx = self._get_cnx(cnxid) - # New style session should not be accesed through the session. - assert cnx._session_handled - self.__threaddata.cnx = cnx - - @property - def _cnx(self): - """default connection for current session in current thread""" - try: - return self.__threaddata.cnx - except AttributeError: - self.set_cnx() - return self.__threaddata.cnx - @deprecated('[3.19] use a Connection object instead') def get_option_value(self, option, foreid=None): if foreid is not None: @@ -1444,108 +1283,6 @@ stacklevel=2) return self.repo.get_option_value(option) - @deprecated('[3.19] use a Connection object instead') - def transaction(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. - """ - return transaction(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') - def security_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') - def allow_all_hooks_but(self, *categories): - return _session_hooks_control(self, HOOKS_ALLOW_ALL, *categories) - @deprecated('[3.19] use a Connection object instead') - def deny_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') - def keep_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). - """ - assert mode in ('transaction', 'write') - if mode == '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') - def cnxset(self): - """connections set, set according to transaction mode for each query""" - if self._closed: - self.free_cnxset(True) - raise SessionClosedError('try to access connections set on a closed session %s' % self.id) - return self._cnx.cnxset - - def set_cnxset(self): - """the session need a connections set to execute some queries""" - with self._lock: # can probably be removed - if self._closed: - self.free_cnxset(True) - raise SessionClosedError('try to set connections set on a closed session %s' % self.id) - return self._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() @@ -1557,156 +1294,6 @@ assert value == {} pass - # shared data handling ################################################### - - @deprecated('[3.19] use session or transaction data') - def get_shared_data(self, key, default=None, pop=False, txdata=False): - """return value associated to `key` in session data""" - if txdata: - return self._cnx.get_shared_data(key, default, pop, txdata=True) - else: - data = self.data - if pop: - return data.pop(key, default) - else: - return data.get(key, default) - - @deprecated('[3.19] use session or transaction data') - def set_shared_data(self, key, value, txdata=False): - """set value associated to `key` in session data""" - if txdata: - return self._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') - def cursor(self): - """return a rql cursor""" - return self - - set_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') - def execute(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 = self - return rset - - def _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.cnx - except AttributeError: - pass - else: - if free_cnxset: - cnx._free_cnxset() - if cnx.ctx_count == 0: - self._close_cnx(cnx) - else: - cnx.clear() - else: - cnx.clear() - - @deprecated('[3.19] use a Connection object instead') - def commit(self, free_cnxset=True, reset_pool=None): - """commit the current session's transaction""" - cstate = self._cnx.commit_state - if cstate == 'uncommitable': - raise QueryError('transaction must be rolled back') - try: - return self._cnx.commit(free_cnxset, reset_pool) - finally: - self._clear_thread_data(free_cnxset) - - @deprecated('[3.19] use a Connection object instead') - def rollback(self, *args, **kwargs): - """rollback the current session's transaction""" - return self._rollback(*args, **kwargs) - - def _rollback(self, free_cnxset=True, **kwargs): - try: - return self._cnx.rollback(free_cnxset, **kwargs) - finally: - self._clear_thread_data(free_cnxset) - - def close(self): - # do not close connections set on session close, since they are shared now - tracker = self._cnxset_tracker - with self._lock: - self._closed = True - tracker.close() - if self._cnx._session_handled: - self._rollback() - self.debug('waiting for open connection of session: %s', self) - timeout = 10 - pendings = tracker.wait(timeout) - if pendings: - self.error('%i connection still alive after 10 seconds, will close ' - 'session anyway', len(pendings)) - for cnxid in pendings: - cnx = self._cnxs.get(cnxid) - if cnx is not None: - # drop cnx.cnxset - with tracker: - try: - cnxset = cnx.cnxset - if cnxset is None: - continue - cnx.cnxset = None - except RuntimeError: - 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 freed - cnxset.reconnect() - self.repo._free_cnxset(cnxset) - del self.__threaddata - del self._cnxs - - @property - def closed(self): - return not hasattr(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 ############################################################### @property @@ -1723,26 +1310,10 @@ def schema_rproperty(self, rtype, eidfrom, eidto, rprop): return getattr(self.rtype_eids_rdef(rtype, eidfrom, eidto), rprop) - @property - @deprecated("[3.13] use .cnxset attribute instead of .pool") - def pool(self): - return self.cnxset - - @deprecated("[3.13] use .set_cnxset() method instead of .set_pool()") - def set_pool(self): - return self.set_cnxset() - - @deprecated("[3.13] use .free_cnxset() method instead of .reset_pool()") - def reset_pool(self): - return self.free_cnxset() - # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None -Session.HOOKS_ALLOW_ALL = HOOKS_ALLOW_ALL -Session.HOOKS_DENY_ALL = HOOKS_DENY_ALL -Session.DEFAULT_SECURITY = DEFAULT_SECURITY class InternalManager(object): diff -r ef54ea75a642 -r b926ff4ef4a8 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Wed Apr 22 18:28:58 2015 +0200 +++ b/server/test/unittest_repository.py Fri Jun 06 17:08:08 2014 +0200 @@ -201,10 +201,12 @@ def test_internal_api(self): repo = self.repo cnxid = repo.connect(self.admlogin, password=self.admpassword) - session = repo._get_session(cnxid, setcnxset=True) - self.assertEqual(repo.type_and_source_from_eid(2, session), - ('CWGroup', None, 'system')) - self.assertEqual(repo.type_from_eid(2, session), 'CWGroup') + session = repo._get_session(cnxid) + with session.new_cnx() as cnx: + with cnx.ensure_cnx_set: + self.assertEqual(repo.type_and_source_from_eid(2, cnx), + ('CWGroup', None, 'system')) + self.assertEqual(repo.type_from_eid(2, cnx), 'CWGroup') repo.close(cnxid) def test_public_api(self): diff -r ef54ea75a642 -r b926ff4ef4a8 server/test/unittest_session.py --- a/server/test/unittest_session.py Wed Apr 22 18:28:58 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -# 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 . - -from cubicweb.devtools.testlib import CubicWebTC -from cubicweb.server.session import HOOKS_ALLOW_ALL, HOOKS_DENY_ALL -from cubicweb.server import hook -from cubicweb.predicates import is_instance - -class SessionTC(CubicWebTC): - - def test_hooks_control(self): - session = self.session - # this test check the "old" behavior of session with automatic connection management - # close the default cnx, we do nto want it to interfer with the test - self.cnx.close() - # open a dedicated one - session.set_cnx('Some-random-cnx-unrelated-to-the-default-one') - # go test go - self.assertEqual(HOOKS_ALLOW_ALL, session.hooks_mode) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(), session.enabled_hook_categories) - self.assertEqual(1, len(session._cnxs)) - with session.deny_all_hooks_but('metadata'): - self.assertEqual(HOOKS_DENY_ALL, session.hooks_mode) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(('metadata',)), session.enabled_hook_categories) - session.commit() - self.assertEqual(HOOKS_DENY_ALL, session.hooks_mode) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(('metadata',)), session.enabled_hook_categories) - session.rollback() - self.assertEqual(HOOKS_DENY_ALL, session.hooks_mode) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(('metadata',)), session.enabled_hook_categories) - with session.allow_all_hooks_but('integrity'): - self.assertEqual(HOOKS_ALLOW_ALL, session.hooks_mode) - self.assertEqual(set(('integrity',)), session.disabled_hook_categories) - self.assertEqual(set(('metadata',)), session.enabled_hook_categories) # not changed in such case - self.assertEqual(HOOKS_DENY_ALL, session.hooks_mode) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(('metadata',)), session.enabled_hook_categories) - # leaving context manager with no transaction running should reset the - # transaction local storage (and associated cnxset) - self.assertEqual({}, session._cnxs) - self.assertEqual(None, session.cnxset) - self.assertEqual(HOOKS_ALLOW_ALL, session.hooks_mode, session.HOOKS_ALLOW_ALL) - self.assertEqual(set(), session.disabled_hook_categories) - self.assertEqual(set(), session.enabled_hook_categories) - - def test_explicit_connection(self): - with self.session.new_cnx() as cnx: - rset = cnx.execute('Any X LIMIT 1 WHERE X is CWUser') - self.assertEqual(1, len(rset)) - user = rset.get_entity(0, 0) - user.cw_delete() - cnx.rollback() - new_user = cnx.entity_from_eid(user.eid) - self.assertIsNotNone(new_user.login) - self.assertFalse(cnx._open) - - def test_internal_cnx(self): - with self.repo.internal_cnx() as cnx: - rset = cnx.execute('Any X LIMIT 1 WHERE X is CWUser') - self.assertEqual(1, len(rset)) - user = rset.get_entity(0, 0) - user.cw_delete() - cnx.rollback() - new_user = cnx.entity_from_eid(user.eid) - self.assertIsNotNone(new_user.login) - self.assertFalse(cnx._open) - - def test_connection_exit(self): - """exiting a connection should roll back the transaction, including any - pending operations""" - self.rollbacked = False - class RollbackOp(hook.Operation): - _test = self - def rollback_event(self): - self._test.rollbacked = True - class RollbackHook(hook.Hook): - __regid__ = 'rollback' - events = ('after_update_entity',) - __select__ = hook.Hook.__select__ & is_instance('CWGroup') - def __call__(self): - RollbackOp(self._cw) - with self.temporary_appobjects(RollbackHook): - with self.admin_access.client_cnx() as cnx: - cnx.execute('SET G name "foo" WHERE G is CWGroup, G name "managers"') - self.assertTrue(self.rollbacked) - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main()