repoapi.py
changeset 9052 4cba5f2cd57b
parent 9045 5378a738f333
child 9053 862040061173
--- a/repoapi.py	Tue Jun 25 17:25:47 2013 +0200
+++ b/repoapi.py	Tue Jun 25 11:06:57 2013 +0200
@@ -18,7 +18,10 @@
 """Official API to access the content of a repository
 """
 from cubicweb.utils import parse_repo_uri
-from cubicweb import ConnectionError
+from cubicweb import ConnectionError, ProgrammingError
+from uuid import uuid4
+from contextlib import contextmanager
+from cubicweb.req import RequestSessionBase
 
 ### private function for specific method ############################
 
@@ -71,3 +74,216 @@
     else:
         raise ConnectionError('unknown protocol: `%s`' % protocol)
 
+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
+        with clt_cnx._srv_cnx as cnx:
+            return getattr(cnx, name)(*args, **kwargs)
+    return proxy
+
+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 RTC) and aims to offer some compatibility with the
+    cubicweb.dbapi.Connection interface.
+    """
+    # 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):
+        self._session = session
+        self._cnxid = None
+        self._open = None
+        self._web_request = False
+        self.vreg = session.vreg
+        self._set_user(session.user)
+
+    def __enter__(self):
+        assert self._open is None
+        self._open = True
+        self._cnxid = '%s-%s' % (self._session.id, uuid4().hex)
+        self._session.set_cnx(self._cnxid)
+        self._session._cnx.ctx_count += 1
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._open = False
+        cnxid = self._cnxid
+        self._cnxid = None
+        self._session._cnx.ctx_count -= 1
+        self._session.close_cnx(cnxid)
+
+
+    # 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):
+        if self.anonymous_connection:
+            return '<Connection %s (anonymous)>' % self._cnxid
+        return '<Connection %s>' % self._cnxid
+    # end silly BC
+
+    @property
+    @contextmanager
+    def _srv_cnx(self):
+        """ensure that the session is locked to the right transaction
+
+        TRANSITIONAL PURPOSE, This will be dropped once we use standalone
+        session object"""
+        if not self._open:
+            raise ProgrammingError('Closed connection %s' % self._cnxid)
+        session = self._session
+        old_cnx = session._current_cnx_id
+        try:
+            session.set_cnx(self._cnxid)
+            session.set_cnxset()
+            try:
+                yield session
+            finally:
+                session.free_cnxset()
+        finally:
+            if old_cnx is not None:
+                session.set_cnx(old_cnx)
+
+    # Main Connection purpose in life #########################################
+
+    call_service = _srv_cnx_func('call_service')
+
+    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
+        with self._srv_cnx as cnx:
+            rset = cnx.execute(*args, **kwargs)
+        rset.req = self
+        return rset
+
+    commit = _srv_cnx_func('commit')
+    rollback = _srv_cnx_func('rollback')
+
+    # session data methods #####################################################
+
+    get_shared_data = _srv_cnx_func('get_shared_data')
+    set_shared_data = _srv_cnx_func('set_shared_data')
+
+    # meta-data accessors ######################################################
+
+    def source_defs(self):
+        """Return the definition of sources used by the repository."""
+        return self._session.repo.source_defs()
+
+    def get_schema(self):
+        """Return the schema currently used by the repository."""
+        return self._session.repo.source_defs()
+
+    def get_option_value(self, option, foreid=None):
+        """Return the value for `option` in the configuration. If `foreid` is
+        specified, the actual repository to which this entity belongs is
+        dereferenced and the option value retrieved from it.
+        """
+        return self._session.repo.get_option_value(option, foreid)
+
+    describe = _srv_cnx_func('describe')
+
+    # undo support ############################################################
+
+    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
+        with self._srv_cnx as cnx:
+            source = cnx.repo.system_source
+            txinfos = source.undoable_transactions(cnx, ueid, **actionfilters)
+        for txinfo in txinfos:
+            txinfo.req = req or self  # XXX mostly wrong
+        return txinfos
+
+    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
+        with self._srv_cnx as cnx:
+            txinfo = cnx.repo.system_source.tx_info(cnx, txuuid)
+        if req:
+            txinfo.req = req
+        else:
+            txinfo.cnx = self
+        return txinfo
+
+    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
+        with self._srv_cnx as cnx:
+            return cnx.repo.system_source.tx_actions(cnx, txuuid, public)
+
+    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
+        with self._srv_cnx as cnx:
+            return cnx.repo.system_source.undo_transaction(cnx, txuuid)