[connection] enforce that a connection must be open to be used
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Thu, 27 Jun 2013 12:02:38 +0200
changeset 9112 c859c7cef346
parent 9111 9c867d852067
child 9113 af6efc15fc90
[connection] enforce that a connection must be open to be used The same than for ClientConnection, we ensure the connection is used inside a its context. .. note:: We may rely on that for ClientConnection and remove de dedicated code in Client Connection but I prefer the current explicite and duplicated version for now.
server/session.py
--- a/server/session.py	Wed Jun 26 15:11:22 2013 +0200
+++ b/server/session.py	Thu Jun 27 12:02:38 2013 +0200
@@ -31,7 +31,7 @@
 from logilab.common.textutils import unormalize
 from logilab.common.registry import objectify_predicate
 
-from cubicweb import UnknownEid, QueryError, schema, server
+from cubicweb import UnknownEid, QueryError, schema, server, ProgrammingError
 from cubicweb.req import RequestSessionBase
 from cubicweb.utils import make_uid
 from cubicweb.rqlrewrite import RQLRewriter
@@ -371,6 +371,15 @@
             return func(cnx, *args, **kwargs)
     return wrapper
 
+def _open_only(func):
+    """decorator for Connection method that check it is open"""
+    @functools.wraps(func)
+    def check_open(cnx, *args, **kwargs):
+        if not cnx._open:
+            raise ProgrammingError('Closed Connection: %s'
+                                    % cnx.connectionid)
+        return func(cnx, *args, **kwargs)
+    return check_open
 
 class Connection(RequestSessionBase):
     """Repository Connection
@@ -535,6 +544,7 @@
     def rql_rewriter(self):
         return self._rewriter
 
+    @_open_only
     def get_shared_data(self, key, default=None, pop=False, txdata=False):
         """return value associated to `key` in session data"""
         if txdata:
@@ -546,6 +556,7 @@
         else:
             return data.get(key, default)
 
+    @_open_only
     def set_shared_data(self, key, value, txdata=False):
         """set value associated to `key` in session data"""
         if txdata:
@@ -566,10 +577,12 @@
 
     # Connection Set Management ###############################################
     @property
+    @_open_only
     def cnxset(self):
         return self._cnxset
 
     @cnxset.setter
+    @_open_only
     def cnxset(self, new_cnxset):
         with self._cnxset_tracker:
             old_cnxset = self._cnxset
@@ -584,6 +597,7 @@
                 self._cnxset = new_cnxset
                 self.ctx_count += 1
 
+    @_open_only
     def _set_cnxset(self):
         """the connection need a connections set to execute some queries"""
         if self.cnxset is None:
@@ -600,6 +614,7 @@
                 raise
         return self.cnxset
 
+    @_open_only
     def _free_cnxset(self, ignoremode=False):
         """the connection is no longer using its connections set, at least for some time"""
         # cnxset may be none if no operation has been done since last commit
@@ -623,6 +638,7 @@
 
     @property
     @contextmanager
+    @_open_only
     def ensure_cnx_set(self):
         assert self._cnxset_count >= 0
         if self._cnxset_count == 0:
@@ -648,17 +664,25 @@
 
     def set_entity_cache(self, entity):
         """Add `entity` to the connection entity cache"""
+        #XXX not using _open_only because before at creation time. _set_user
+        # call this function to cache the Connection user.
+        if entity.cw_etype != 'CWUser' and not self._open:
+            raise ProgrammingError('Closed Connection: %s'
+                                    % self.connectionid)
         ecache = self.transaction_data.setdefault('ecache', {})
         ecache.setdefault(entity.eid, entity)
 
+    @_open_only
     def entity_cache(self, eid):
         """get cache entity for `eid`"""
         return self.transaction_data['ecache'][eid]
 
+    @_open_only
     def cached_entities(self):
         """return the whole entity cache"""
         return self.transaction_data.get('ecache', {}).values()
 
+    @_open_only
     def drop_entity_cache(self, eid=None):
         """drop entity from the cache
 
@@ -670,6 +694,7 @@
 
     # relations handling #######################################################
 
+    @_open_only
     def add_relation(self, fromeid, rtype, toeid):
         """provide direct access to the repository method to add a relation.
 
@@ -683,6 +708,7 @@
         """
         self.add_relations([(rtype, [(fromeid,  toeid)])])
 
+    @_open_only
     def add_relations(self, relations):
         '''set many relation using a shortcut similar to the one in add_relation
 
@@ -710,6 +736,7 @@
                 self.repo.glob_update_entity(self, edited)
 
 
+    @_open_only
     def delete_relation(self, fromeid, rtype, toeid):
         """provide direct access to the repository method to delete a relation.
 
@@ -731,6 +758,7 @@
 
     # relations cache handling #################################################
 
+    @_open_only
     def update_rel_cache_add(self, subject, rtype, object, symmetric=False):
         self._update_entity_rel_cache_add(subject, rtype, 'subject', object)
         if symmetric:
@@ -738,6 +766,7 @@
         else:
             self._update_entity_rel_cache_add(object, rtype, 'object', subject)
 
+    @_open_only
     def update_rel_cache_del(self, subject, rtype, object, symmetric=False):
         self._update_entity_rel_cache_del(subject, rtype, 'subject', object)
         if symmetric:
@@ -745,6 +774,7 @@
         else:
             self._update_entity_rel_cache_del(object, rtype, 'object', subject)
 
+    @_open_only
     def _update_entity_rel_cache_add(self, eid, rtype, role, targeteid):
         try:
             entity = self.entity_cache(eid)
@@ -769,6 +799,7 @@
             entity._cw_related_cache['%s_%s' % (rtype, role)] = (
                 rset, tuple(entities))
 
+    @_open_only
     def _update_entity_rel_cache_del(self, eid, rtype, role, targeteid):
         try:
             entity = self.entity_cache(eid)
@@ -800,12 +831,14 @@
     #
     # Those are function to  allows cheap call from client in other process.
 
+    @_open_only
     def deleted_in_transaction(self, eid):
         """return True if the entity of the given eid is being deleted in the
         current transaction
         """
         return eid in self.transaction_data.get('pendingeids', ())
 
+    @_open_only
     def added_in_transaction(self, eid):
         """return True if the entity of the given eid is being created in the
         current transaction
@@ -814,6 +847,7 @@
 
     # Operation management ####################################################
 
+    @_open_only
     def add_operation(self, operation, index=None):
         """add an operation to be executed at the end of the transaction"""
         if index is None:
@@ -823,12 +857,15 @@
 
     # Hooks control ###########################################################
 
+    @_open_only
     def allow_all_hooks_but(self, *categories):
         return _hooks_control(self, HOOKS_ALLOW_ALL, *categories)
 
+    @_open_only
     def deny_all_hooks_but(self, *categories):
         return _hooks_control(self, HOOKS_DENY_ALL, *categories)
 
+    @_open_only
     def disable_hook_categories(self, *categories):
         """disable the given hook categories:
 
@@ -848,6 +885,7 @@
             disabledcats |= changes # changes is small hence faster
         return tuple(changes)
 
+    @_open_only
     def enable_hook_categories(self, *categories):
         """enable the given hook categories:
 
@@ -867,6 +905,7 @@
             disabledcats -= changes # changes is small hence faster
         return tuple(changes)
 
+    @_open_only
     def is_hook_category_activated(self, category):
         """return a boolean telling if the given category is currently activated
         or not
@@ -875,6 +914,7 @@
             return category in self.enabled_hook_cats
         return category not in self.disabled_hook_cats
 
+    @_open_only
     def is_hook_activated(self, hook):
         """return a boolean telling if the given hook class is currently
         activated or not
@@ -883,14 +923,17 @@
 
     # Security management #####################################################
 
+    @_open_only
     def security_enabled(self, read=None, write=None):
         return _security_enabled(self, read=read, write=write)
 
     @property
+    @_open_only
     def read_security(self):
         return self._read_security
 
     @read_security.setter
+    @_open_only
     def read_security(self, activated):
         oldmode = self._read_security
         self._read_security = activated
@@ -916,9 +959,11 @@
 
     # undo support ############################################################
 
+    @_open_only
     def ertype_supports_undo(self, ertype):
         return self.undo_actions and ertype not in NO_UNDO_TYPES
 
+    @_open_only
     def transaction_uuid(self, set=True):
         uuid = self.transaction_data.get('tx_uuid')
         if set and uuid is None:
@@ -926,16 +971,19 @@
             self.repo.system_source.start_undoable_transaction(self, uuid)
         return uuid
 
+    @_open_only
     def transaction_inc_action_counter(self):
         num = self.transaction_data.setdefault('tx_action_count', 0) + 1
         self.transaction_data['tx_action_count'] = num
         return num
     # db-api like interface ###################################################
 
+    @_open_only
     def source_defs(self):
         return self.repo.source_defs()
 
     @_with_cnx_set
+    @_open_only
     def describe(self, eid, asdict=False):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         metas = self.repo.type_and_source_from_eid(eid, self)
@@ -945,6 +993,7 @@
         return metas[:-1]
 
     @_with_cnx_set
+    @_open_only
     def source_from_eid(self, eid):
         """return the source where the entity with id <eid> is located"""
         return self.repo.source_from_eid(eid, self)
@@ -952,6 +1001,7 @@
     # core method #############################################################
 
     @_with_cnx_set
+    @_open_only
     def execute(self, rql, kwargs=None, eid_key=None, build_descr=True):
         """db-api like method directly linked to the querier execute method.
 
@@ -966,6 +1016,7 @@
         self._session_timestamp.touch()
         return rset
 
+    @_open_only
     def rollback(self, free_cnxset=True, reset_pool=None):
         """rollback the current transaction"""
         if reset_pool is not None:
@@ -996,6 +1047,7 @@
                 self.free_cnxset(ignoremode=True)
             self.clear()
 
+    @_open_only
     def commit(self, free_cnxset=True, reset_pool=None):
         """commit the current session's transaction"""
         if reset_pool is not None:
@@ -1087,6 +1139,7 @@
     # resource accessors ######################################################
 
     @_with_cnx_set
+    @_open_only
     def call_service(self, regid, **kwargs):
         json.dumps(kwargs) # This line ensure that people use serialisable
                            # argument for call service. this is very important
@@ -1102,6 +1155,7 @@
         return result
 
     @_with_cnx_set
+    @_open_only
     def system_sql(self, sql, args=None, rollback_on_failure=True):
         """return a sql cursor on the system database"""
         if sql.split(None, 1)[0].upper() != 'SELECT':
@@ -1116,6 +1170,7 @@
             self.cnxset.reconnect(source)
             return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
+    @_open_only
     def rtype_eids_rdef(self, rtype, eidfrom, eidto):
         # use type_and_source_from_eid instead of type_from_eid for optimization
         # (avoid two extra methods call)