# HG changeset patch # User Pierre-Yves David # Date 1372327358 -7200 # Node ID c859c7cef346c536f389f1d769ebb5622eab399f # Parent 9c867d852067512ebded52e9e262b323bc385a00 [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. diff -r 9c867d852067 -r c859c7cef346 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 """ 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 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)