[repoapi] fold ClientConnection into Connection
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Tue, 10 Jun 2014 16:01:49 +0200
changeset 10354 635cfac73d28
parent 10353 d9a1e7939ee6
child 10355 60b8204fcca3
[repoapi] fold ClientConnection into Connection Connection replaces ClientConnection everywhere. Some notes: * testlib: .client_cnx and .repo_cnx become aliases of .cnx (we might not want to tell people to update their tests again for just no real benefit, so we'll live with these aliases for a while) * entity.as_rset must not be cached because we risk caching result sets having a Connection object as .req (helps unittest_breadcrumbs) * entity._cw_dont_cache_attributes loses its repo/request special paths and only keeps its storage/bfss user (this helps unittest_wfobjs) * moreover, entity.cw_instantiate and .cw_set stop overriding the attributes cache *after* the before_*_entity hooks have run, because there is no need to (it is now actually harmful to do it and unittest_hooks.test_html_tidy* tests remain green because of this) * rset._build_entity sticks its .req onto the entity just fetched from the cache, because otherwise it might carry a _cw that is a Connection object where a Request is expected (helps unittest_views_actions) * we get overall better cache usages (entity caches were split over Request + ClientConnection and Connection), hence the changes unittest_entity and unittest_wfobjs * void the ecache when providing the cnx to a request object Having the entity cache pre-filled when we bind it to the request object hurts because these entities are bound to Connection objects, that lack e.g. `.form` or `.add_js` and crash the views subsystem. Thus, the unittest_testlib.test_error_raised test will are kept green. Closes #3837233
devtools/__init__.py
devtools/testlib.py
entities/test/unittest_wfobjs.py
entity.py
repoapi.py
rset.py
server/__init__.py
server/edition.py
server/repository.py
server/serverctl.py
server/sources/storages.py
server/test/unittest_querier.py
test/unittest_entity.py
test/unittest_repoapi.py
web/application.py
web/request.py
web/views/sessions.py
--- a/devtools/__init__.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/devtools/__init__.py	Tue Jun 10 16:01:49 2014 +0200
@@ -494,7 +494,7 @@
             repo = self.get_repo(startup=True)
             cnx = self.get_cnx()
             with cnx:
-                pre_setup_func(cnx._cnx, self.config)
+                pre_setup_func(cnx, self.config)
                 cnx.commit()
         self.backup_database(test_db_id)
 
--- a/devtools/testlib.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/devtools/testlib.py	Tue Jun 10 16:01:49 2014 +0200
@@ -189,8 +189,7 @@
 
     A repo access can create three type of object:
 
-    .. automethod:: cubicweb.testlib.RepoAccess.repo_cnx
-    .. automethod:: cubicweb.testlib.RepoAccess.client_cnx
+    .. automethod:: cubicweb.testlib.RepoAccess.cnx
     .. automethod:: cubicweb.testlib.RepoAccess.web_request
 
     The RepoAccess need to be closed to destroy the associated Session.
@@ -225,16 +224,13 @@
         return session
 
     @contextmanager
-    def repo_cnx(self):
+    def cnx(self):
         """Context manager returning a server side connection for the user"""
         with self._session.new_cnx() as cnx:
             yield cnx
 
-    @contextmanager
-    def client_cnx(self):
-        """Context manager returning a client side connection for the user"""
-        with repoapi.ClientConnection(self._session) as cnx:
-            yield cnx
+    # aliases for bw compat
+    client_cnx = repo_cnx = cnx
 
     @contextmanager
     def web_request(self, url=None, headers={}, method='GET', **kwargs):
@@ -247,9 +243,10 @@
         """
         req = self.requestcls(self._repo.vreg, url=url, headers=headers,
                               method=method, form=kwargs)
-        clt_cnx = repoapi.ClientConnection(self._session)
-        req.set_cnx(clt_cnx)
-        with clt_cnx:
+        with self._session.new_cnx() as cnx:
+            if 'ecache' in cnx.transaction_data:
+                del cnx.transaction_data['ecache']
+            req.set_cnx(cnx)
             yield req
 
     def close(self):
@@ -349,9 +346,7 @@
     def _close_cnx(self):
         """ensure that all cnx used by a test have been closed"""
         for cnx in list(self._cnxs):
-            if cnx._open and not cnx._session.closed:
-                cnx.rollback()
-                cnx.close()
+            cnx.rollback()
             self._cnxs.remove(cnx)
 
     @property
@@ -416,7 +411,7 @@
         login = unicode(db_handler.config.default_admin_config['login'])
         self.admin_access = self.new_access(login)
         self._admin_session = self.admin_access._session
-        self._admin_clt_cnx = repoapi.ClientConnection(self._admin_session)
+        self._admin_clt_cnx = repoapi.Connection(self._admin_session)
         self._cnxs.add(self._admin_clt_cnx)
         self._admin_clt_cnx.__enter__()
         self.config.repository = lambda x=None: self.repo
@@ -528,8 +523,6 @@
     def tearDown(self):
         # XXX hack until logilab.common.testlib is fixed
         if self._admin_clt_cnx is not None:
-            if self._admin_clt_cnx._open:
-                self._admin_clt_cnx.close()
             self._admin_clt_cnx = None
         if self._admin_session is not None:
             self.repo.close(self._admin_session.sessionid)
@@ -976,7 +969,7 @@
     def assertAuthSuccess(self, req, origsession, nbsessions=1):
         sh = self.app.session_handler
         session = self.app.get_session(req)
-        clt_cnx = repoapi.ClientConnection(session)
+        clt_cnx = repoapi.Connection(session)
         req.set_cnx(clt_cnx)
         self.assertEqual(len(self.open_sessions), nbsessions, self.open_sessions)
         self.assertEqual(session.login, origsession.login)
@@ -1263,7 +1256,7 @@
         """this method populates the database with `how_many` entities
         of each possible type. It also inserts random relations between them
         """
-        with self.admin_access.repo_cnx() as cnx:
+        with self.admin_access.cnx() as cnx:
             with cnx.security_enabled(read=False, write=False):
                 self._auto_populate(cnx, how_many)
                 cnx.commit()
--- a/entities/test/unittest_wfobjs.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/entities/test/unittest_wfobjs.py	Tue Jun 10 16:01:49 2014 +0200
@@ -569,8 +569,9 @@
         with self.admin_access.web_request() as req:
             user = self.create_user(req, 'member', surname=u'toto')
             req.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
-                     {'wf': wf.eid, 'x': user.eid})
+                        {'wf': wf.eid, 'x': user.eid})
             req.cnx.commit()
+            user.cw_clear_all_caches()
             iworkflowable = user.cw_adapt_to('IWorkflowable')
             self.assertEqual(iworkflowable.state, 'dead')
 
--- a/entity.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/entity.py	Tue Jun 10 16:01:49 2014 +0200
@@ -514,7 +514,7 @@
         prefixing the relation name by 'reverse_'. Also, relation values may be
         an entity or eid, a list of entities or eids.
         """
-        rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+        rql, qargs, pendingrels, _attrcache = cls._cw_build_entity_query(kwargs)
         if rql:
             rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
         else:
@@ -524,7 +524,6 @@
         except IndexError:
             raise Exception('could not create a %r with %r (%r)' %
                             (cls.__regid__, rql, qargs))
-        created._cw_update_attr_cache(attrcache)
         cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
         return created
 
@@ -556,41 +555,26 @@
         return super(Entity, self).__hash__()
 
     def _cw_update_attr_cache(self, attrcache):
-        # if context is a repository session, don't consider dont-cache-attrs as
-        # the instance already holds modified values and loosing them could
-        # introduce severe problems
         trdata = self._cw.transaction_data
         uncached_attrs = trdata.get('%s.storage-special-process-attrs' % self.eid, set())
-        if self._cw.is_request:
-            uncached_attrs.update(trdata.get('%s.dont-cache-attrs' % self.eid, set()))
         for attr in uncached_attrs:
             attrcache.pop(attr, None)
             self.cw_attr_cache.pop(attr, None)
         self.cw_attr_cache.update(attrcache)
 
     def _cw_dont_cache_attribute(self, attr, repo_side=False):
-        """Repository side method called when some attribute has been
-        transformed by a hook, hence original value should not be cached by
-        the client.
-
-        If repo_side is True, this means that the attribute has been
-        transformed by a *storage*, hence the original value should
-        not be cached **by anyone**.
-
-        This only applies to a storage special case where the value
-        specified in creation or update is **not** the value that will
-        be transparently exposed later.
+        """Called when some attribute has been transformed by a *storage*,
+        hence the original value should not be cached **by anyone**.
 
         For example we have a special "fs_importing" mode in BFSS
         where a file path is given as attribute value and stored as is
         in the data base. Later access to the attribute will provide
         the content of the file at the specified path. We do not want
         the "filepath" value to be cached.
+
         """
-        self._cw.transaction_data.setdefault('%s.dont-cache-attrs' % self.eid, set()).add(attr)
-        if repo_side:
-            trdata = self._cw.transaction_data
-            trdata.setdefault('%s.storage-special-process-attrs' % self.eid, set()).add(attr)
+        trdata = self._cw.transaction_data
+        trdata.setdefault('%s.storage-special-process-attrs' % self.eid, set()).add(attr)
 
     def __json_encode__(self):
         """custom json dumps hook to dump the entity's eid
@@ -836,7 +820,6 @@
 
     # data fetching methods ###################################################
 
-    @cached
     def as_rset(self): # XXX .cw_as_rset
         """returns a resultset containing `self` information"""
         rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
@@ -1329,10 +1312,6 @@
             else:
                 rql += ' WHERE X eid %(x)s'
             self._cw.execute(rql, qargs)
-        # update current local object _after_ the rql query to avoid
-        # interferences between the query execution itself and the cw_edited /
-        # skip_security machinery
-        self._cw_update_attr_cache(attrcache)
         self._cw_handle_pending_relations(self.eid, pendingrels, self._cw.execute)
         # XXX update relation cache
 
--- a/repoapi.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/repoapi.py	Tue Jun 10 16:01:49 2014 +0200
@@ -17,14 +17,12 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Official API to access the content of a repository
 """
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import class_deprecated
 
 from cubicweb.utils import parse_repo_uri
-from cubicweb import ConnectionError, ProgrammingError, AuthenticationError
-from uuid import uuid4
-from contextlib import contextmanager
-from cubicweb.req import RequestSessionBase
-from functools import wraps
+from cubicweb import ConnectionError, AuthenticationError
+from cubicweb.server.session import Connection
+
 
 ### private function for specific method ############################
 
@@ -65,7 +63,7 @@
     session = repo._get_session(sessionid)
     # XXX the autoclose_session should probably be handle on the session directly
     # this is something to consider once we have proper server side Connection.
-    return ClientConnection(session, autoclose_session=True)
+    return Connection(session)
 
 def anonymous_cnx(repo):
     """return a ClientConnection for Anonymous user.
@@ -82,292 +80,7 @@
     # use vreg's repository cache
     return connect(repo, anon_login, password=anon_password)
 
-def _srv_cnx_func(name):
-    """Decorate ClientConnection method blindly forward to Connection
-    THIS TRANSITIONAL PURPOSE
 
-    will be dropped when we have standalone connection"""
-    def proxy(clt_cnx, *args, **kwargs):
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        if not clt_cnx._open:
-            raise ProgrammingError('Closed client connection')
-        return getattr(clt_cnx._cnx, name)(*args, **kwargs)
-    return proxy
-
-def _open_only(func):
-    """decorator for ClientConnection method that check it is open"""
-    @wraps(func)
-    def check_open(clt_cnx, *args, **kwargs):
-        if not clt_cnx._open:
-            raise ProgrammingError('Closed client connection')
-        return func(clt_cnx, *args, **kwargs)
-    return check_open
-
-
-class ClientConnection(RequestSessionBase):
-    """A Connection object to be used Client side.
-
-    This object is aimed to be used client side (so potential communication
-    with the repo through RPC) and aims to offer some compatibility with the
-    cubicweb.dbapi.Connection interface.
-
-    The autoclose_session parameter informs the connection that this session
-    has been opened explicitly and only for this client connection. The
-    connection will close the session on exit.
-    """
-    # make exceptions available through the connection object
-    ProgrammingError = ProgrammingError
-    # attributes that may be overriden per connection instance
-    anonymous_connection = False # XXX really needed ?
-    is_repo_in_memory = True # BC, always true
-
-    def __init__(self, session, autoclose_session=False):
-        super(ClientConnection, self).__init__(session.vreg)
-        self._session = session # XXX there is no real reason to keep the
-                                # session around function still using it should
-                                # be rewritten and migrated.
-        self._cnx = None
-        self._open = None
-        self._web_request = False
-        #: cache entities built during the connection
-        self._eid_cache = {}
-        self._set_user(session.user)
-        self._autoclose_session = autoclose_session
-
-    def __enter__(self):
-        assert self._open is None
-        self._open = True
-        self._cnx = self._session.new_cnx()
-        self._cnx.__enter__()
-        self._cnx.ctx_count += 1
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self._open = False
-        self._cnx.ctx_count -= 1
-        self._cnx.__exit__(exc_type, exc_val, exc_tb)
-        self._cnx = None
-        if self._autoclose_session:
-            # we have to call repo.close to ensure the repo properly forgets the
-            # session; calling session.close() is not enough :-(
-            self._session.repo.close(self._session.sessionid)
-
-
-    # begin silly BC
-    @property
-    def _closed(self):
-        return not self._open
-
-    def close(self):
-        if self._open:
-            self.__exit__(None, None, None)
-
-    def __repr__(self):
-        # XXX we probably want to reference the user of the session here
-        if self._open is None:
-            return '<ClientConnection (not open yet)>'
-        elif not self._open:
-            return '<ClientConnection (closed)>'
-        elif self.anonymous_connection:
-            return '<ClientConnection %s (anonymous)>' % self._cnx.connectionid
-        else:
-            return '<ClientConnection %s>' % self._cnx.connectionid
-    # end silly BC
-
-    # Main Connection purpose in life #########################################
-
-    call_service = _srv_cnx_func('call_service')
-
-    @_open_only
-    def execute(self, *args, **kwargs):
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        rset = self._cnx.execute(*args, **kwargs)
-        rset.req = self
-        return rset
-
-    @_open_only
-    def commit(self, *args, **kwargs):
-        try:
-            return self._cnx.commit(*args, **kwargs)
-        finally:
-            self.drop_entity_cache()
-
-    @_open_only
-    def rollback(self, *args, **kwargs):
-        try:
-            return self._cnx.rollback(*args, **kwargs)
-        finally:
-            self.drop_entity_cache()
-
-    # security #################################################################
-
-    allow_all_hooks_but = _srv_cnx_func('allow_all_hooks_but')
-    deny_all_hooks_but = _srv_cnx_func('deny_all_hooks_but')
-    security_enabled = _srv_cnx_func('security_enabled')
-
-    # direct sql ###############################################################
-
-    system_sql = _srv_cnx_func('system_sql')
-
-    # session data methods #####################################################
-
-    get_shared_data = _srv_cnx_func('get_shared_data')
-    set_shared_data = _srv_cnx_func('set_shared_data')
-
-    @property
-    def transaction_data(self):
-        return self._cnx.transaction_data
-
-    # meta-data accessors ######################################################
-
-    @_open_only
-    def source_defs(self):
-        """Return the definition of sources used by the repository."""
-        return self._session.repo.source_defs()
-
-    @_open_only
-    def get_schema(self):
-        """Return the schema currently used by the repository."""
-        return self._session.repo.source_defs()
-
-    @_open_only
-    def get_option_value(self, option):
-        """Return the value for `option` in the configuration."""
-        return self._session.repo.get_option_value(option)
-
-    entity_metas = _srv_cnx_func('entity_metas')
-    describe = _srv_cnx_func('describe') # XXX deprecated in 3.19
-
-    # undo support ############################################################
-
-    @_open_only
-    def undoable_transactions(self, ueid=None, req=None, **actionfilters):
-        """Return a list of undoable transaction objects by the connection's
-        user, ordered by descendant transaction time.
-
-        Managers may filter according to user (eid) who has done the transaction
-        using the `ueid` argument. Others will only see their own transactions.
-
-        Additional filtering capabilities is provided by using the following
-        named arguments:
-
-        * `etype` to get only transactions creating/updating/deleting entities
-          of the given type
-
-        * `eid` to get only transactions applied to entity of the given eid
-
-        * `action` to get only transactions doing the given action (action in
-          'C', 'U', 'D', 'A', 'R'). If `etype`, action can only be 'C', 'U' or
-          'D'.
-
-        * `public`: when additional filtering is provided, their are by default
-          only searched in 'public' actions, unless a `public` argument is given
-          and set to false.
-        """
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        source = self._cnx.repo.system_source
-        txinfos = source.undoable_transactions(self._cnx, ueid, **actionfilters)
-        for txinfo in txinfos:
-            txinfo.req = req or self  # XXX mostly wrong
-        return txinfos
-
-    @_open_only
-    def transaction_info(self, txuuid, req=None):
-        """Return transaction object for the given uid.
-
-        raise `NoSuchTransaction` if not found or if session's user is not
-        allowed (eg not in managers group and the transaction doesn't belong to
-        him).
-        """
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        txinfo = self._cnx.repo.system_source.tx_info(self._cnx, txuuid)
-        if req:
-            txinfo.req = req
-        else:
-            txinfo.cnx = self
-        return txinfo
-
-    @_open_only
-    def transaction_actions(self, txuuid, public=True):
-        """Return an ordered list of action effectued during that transaction.
-
-        If public is true, return only 'public' actions, eg not ones triggered
-        under the cover by hooks, else return all actions.
-
-        raise `NoSuchTransaction` if the transaction is not found or if
-        session's user is not allowed (eg not in managers group and the
-        transaction doesn't belong to him).
-        """
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        return self._cnx.repo.system_source.tx_actions(self._cnx, txuuid, public)
-
-    @_open_only
-    def undo_transaction(self, txuuid):
-        """Undo the given transaction. Return potential restoration errors.
-
-        raise `NoSuchTransaction` if not found or if session's user is not
-        allowed (eg not in managers group and the transaction doesn't belong to
-        him).
-        """
-        # the ``with`` dance is transitional. We do not have Standalone
-        # Connection yet so we use this trick to unsure the session have the
-        # proper cnx loaded. This can be simplified one we have Standalone
-        # Connection object
-        return self._cnx.repo.system_source.undo_transaction(self._cnx, txuuid)
-
-    # cache management
-
-    def entity_cache(self, eid):
-        return self._eid_cache[eid]
-
-    def set_entity_cache(self, entity):
-        self._eid_cache[entity.eid] = entity
-
-    def cached_entities(self):
-        return self._eid_cache.values()
-
-    def drop_entity_cache(self, eid=None):
-        if eid is None:
-            self._eid_cache = {}
-        else:
-            del self._eid_cache[eid]
-
-    # deprecated stuff
-
-    @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
-    def request(self):
-        return self
-
-    @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
-    def cursor(self):
-        return self
-
-    @property
-    @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
-    def sessionid(self):
-        return self._session.sessionid
-
-    @property
-    @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
-    def connection(self):
-        return self
-
-    @property
-    @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
-    def _repo(self):
-        return self._session.repo
+class ClientConnection(Connection):
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.20] %(cls)s is deprecated, use Connection instead'
--- a/rset.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/rset.py	Tue Jun 10 16:01:49 2014 +0200
@@ -483,6 +483,7 @@
         #     new attributes found in this resultset ?
         try:
             entity = req.entity_cache(eid)
+            entity._cw = req
         except KeyError:
             pass
         else:
--- a/server/__init__.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/__init__.py	Tue Jun 10 16:01:49 2014 +0200
@@ -289,7 +289,6 @@
     config._cubes = None # avoid assertion error
     repo = get_repository(config=config)
     with connect(repo, login, password=pwd) as cnx:
-        cnx = cnx._cnx # client connection -> repo connection
         with cnx.security_enabled(False, False):
             repo.system_source.eid = ssource.eid # redo this manually
             handler = config.migration_handler(schema, interactive=False,
--- a/server/edition.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/edition.py	Tue Jun 10 16:01:49 2014 +0200
@@ -103,8 +103,6 @@
         assert not self.saved, 'too late to modify edited attributes'
         super(EditedEntity, self).__setitem__(attr, value)
         self.entity.cw_attr_cache[attr] = value
-        # mark attribute as needing purge by the client
-        self.entity._cw_dont_cache_attribute(attr)
 
     def oldnewvalue(self, attr):
         """returns the couple (old attr value, new attr value)
--- a/server/repository.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/repository.py	Tue Jun 10 16:01:49 2014 +0200
@@ -812,11 +812,6 @@
             return self._extid_cache[extid]
         except KeyError:
             pass
-        try:
-            # bw compat: cnx may be a session, get at the Connection
-            cnx = cnx._cnx
-        except AttributeError:
-            pass
         with cnx.ensure_cnx_set:
             eid = self.system_source.extid2eid(cnx, extid)
         if eid is not None:
--- a/server/serverctl.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/serverctl.py	Tue Jun 10 16:01:49 2014 +0200
@@ -957,7 +957,7 @@
         config = ServerConfiguration.config_for(appid)
         repo, cnx = repo_cnx(config)
         with cnx:
-            reindex_entities(repo.schema, cnx._cnx, etypes=etypes)
+            reindex_entities(repo.schema, cnx, etypes=etypes)
             cnx.commit()
 
 
--- a/server/sources/storages.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/sources/storages.py	Tue Jun 10 16:01:49 2014 +0200
@@ -152,7 +152,7 @@
         """an entity using this storage for attr has been added"""
         if entity._cw.transaction_data.get('fs_importing'):
             binary = Binary.from_file(entity.cw_edited[attr].getvalue())
-            entity._cw_dont_cache_attribute(attr, repo_side=True)
+            entity._cw_dont_cache_attribute(attr)
         else:
             binary = entity.cw_edited.pop(attr)
             fpath = self.new_fs_path(entity, attr)
@@ -171,7 +171,7 @@
             # We do not need to create it but we need to fetch the content of
             # the file as the actual content of the attribute
             fpath = entity.cw_edited[attr].getvalue()
-            entity._cw_dont_cache_attribute(attr, repo_side=True)
+            entity._cw_dont_cache_attribute(attr)
             assert fpath is not None
             binary = Binary.from_file(fpath)
         else:
--- a/server/test/unittest_querier.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/server/test/unittest_querier.py	Tue Jun 10 16:01:49 2014 +0200
@@ -69,7 +69,6 @@
 
 def tearDownClass(cls, *args):
     global repo, cnx
-    cnx.close()
     repo.shutdown()
     del repo, cnx
 
--- a/test/unittest_entity.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/test/unittest_entity.py	Tue Jun 10 16:01:49 2014 +0200
@@ -140,13 +140,24 @@
         with self.admin_access.web_request() as req:
             user = req.execute('Any X WHERE X eid %(x)s', {'x':req.user.eid}).get_entity(0, 0)
             adeleid = req.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
+            self.assertEqual({}, user._cw_related_cache)
             req.cnx.commit()
-            self.assertEqual(user._cw_related_cache, {})
+            self.assertEqual(['primary_email_subject', 'use_email_subject', 'wf_info_for_object'],
+                             sorted(user._cw_related_cache))
             email = user.primary_email[0]
-            self.assertEqual(sorted(user._cw_related_cache), ['primary_email_subject'])
-            self.assertEqual(list(email._cw_related_cache), ['primary_email_object'])
+            self.assertEqual(u'toto@logilab.org', email.address)
+            self.assertEqual(['created_by_subject',
+                              'cw_source_subject',
+                              'is_instance_of_subject',
+                              'is_subject',
+                              'owned_by_subject',
+                              'prefered_form_object',
+                              'prefered_form_subject',
+                              'primary_email_object',
+                              'use_email_object'],
+                             sorted(email._cw_related_cache))
+            self.assertEqual('admin', email._cw_related_cache['primary_email_object'][1][0].login)
             groups = user.in_group
-            self.assertEqual(sorted(user._cw_related_cache), ['in_group_subject', 'primary_email_subject'])
             for group in groups:
                 self.assertNotIn('in_group_subject', group._cw_related_cache)
             user.cw_clear_all_caches()
--- a/test/unittest_repoapi.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/test/unittest_repoapi.py	Tue Jun 10 16:01:49 2014 +0200
@@ -21,7 +21,7 @@
 from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb import ProgrammingError
-from cubicweb.repoapi import ClientConnection, connect, anonymous_cnx
+from cubicweb.repoapi import Connection, connect, anonymous_cnx
 
 
 class REPOAPITC(CubicWebTC):
@@ -56,7 +56,7 @@
         """Check that ClientConnection requires explicit open and close
         """
         access = self.admin_access
-        cltcnx = ClientConnection(access._session)
+        cltcnx = Connection(access._session)
         # connection not open yet
         with self.assertRaises(ProgrammingError):
             cltcnx.execute('Any X WHERE X is CWUser')
--- a/web/application.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/web/application.py	Tue Jun 10 16:01:49 2014 +0200
@@ -350,7 +350,7 @@
             try:
                 session = self.get_session(req)
                 from  cubicweb import repoapi
-                cnx = repoapi.ClientConnection(session)
+                cnx = repoapi.Connection(session)
                 req.set_cnx(cnx)
             except AuthenticationError:
                 # Keep the dummy session set at initialisation.
--- a/web/request.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/web/request.py	Tue Jun 10 16:01:49 2014 +0200
@@ -1002,6 +1002,8 @@
         return self.cnx.transaction_data
 
     def set_cnx(self, cnx):
+        if 'ecache' in cnx.transaction_data:
+            del cnx.transaction_data['ecache']
         self.cnx = cnx
         self.session = cnx._session
         self._set_user(cnx.user)
--- a/web/views/sessions.py	Fri Jun 13 13:54:28 2014 +0200
+++ b/web/views/sessions.py	Tue Jun 10 16:01:49 2014 +0200
@@ -96,7 +96,7 @@
         # XXX should properly detect missing permission / non writeable source
         # and avoid "except (RepositoryError, Unauthorized)" below
         try:
-            cnx = repoapi.ClientConnection(session)
+            cnx = repoapi.Connection(session)
             with cnx:
                 cnx.execute('SET X last_login_time NOW WHERE X eid %(x)s',
                            {'x' : session.user.eid})