119 methods. |
119 methods. |
120 """ |
120 """ |
121 def __init__(self, session, mode, *categories): |
121 def __init__(self, session, mode, *categories): |
122 assert mode in (HOOKS_ALLOW_ALL, HOOKS_DENY_ALL) |
122 assert mode in (HOOKS_ALLOW_ALL, HOOKS_DENY_ALL) |
123 self.session = session |
123 self.session = session |
124 self.tx = session._tx |
124 self.cnx = session._cnx |
125 self.mode = mode |
125 self.mode = mode |
126 self.categories = categories |
126 self.categories = categories |
127 self.oldmode = None |
127 self.oldmode = None |
128 self.changes = () |
128 self.changes = () |
129 |
129 |
130 def __enter__(self): |
130 def __enter__(self): |
131 self.oldmode = self.tx.hooks_mode |
131 self.oldmode = self.cnx.hooks_mode |
132 self.tx.hooks_mode = self.mode |
132 self.cnx.hooks_mode = self.mode |
133 if self.mode is HOOKS_DENY_ALL: |
133 if self.mode is HOOKS_DENY_ALL: |
134 self.changes = self.tx.enable_hook_categories(*self.categories) |
134 self.changes = self.cnx.enable_hook_categories(*self.categories) |
135 else: |
135 else: |
136 self.changes = self.tx.disable_hook_categories(*self.categories) |
136 self.changes = self.cnx.disable_hook_categories(*self.categories) |
137 self.tx.ctx_count += 1 |
137 self.cnx.ctx_count += 1 |
138 |
138 |
139 def __exit__(self, exctype, exc, traceback): |
139 def __exit__(self, exctype, exc, traceback): |
140 self.tx.ctx_count -= 1 |
140 self.cnx.ctx_count -= 1 |
141 if self.tx.ctx_count == 0: |
141 if self.cnx.ctx_count == 0: |
142 self.session._clear_thread_storage(self.tx) |
142 self.session._clear_thread_storage(self.cnx) |
143 else: |
143 else: |
144 try: |
144 try: |
145 if self.categories: |
145 if self.categories: |
146 if self.mode is HOOKS_DENY_ALL: |
146 if self.mode is HOOKS_DENY_ALL: |
147 self.tx.disable_hook_categories(*self.categories) |
147 self.cnx.disable_hook_categories(*self.categories) |
148 else: |
148 else: |
149 self.tx.enable_hook_categories(*self.categories) |
149 self.cnx.enable_hook_categories(*self.categories) |
150 finally: |
150 finally: |
151 self.tx.hooks_mode = self.oldmode |
151 self.cnx.hooks_mode = self.oldmode |
152 |
152 |
153 @deprecated('[3.17] use <object>.security_enabled instead') |
153 @deprecated('[3.17] use <object>.security_enabled instead') |
154 def security_enabled(obj, *args, **kwargs): |
154 def security_enabled(obj, *args, **kwargs): |
155 return obj.security_enabled(*args, **kwargs) |
155 return obj.security_enabled(*args, **kwargs) |
156 |
156 |
160 By default security is disabled on queries executed on the repository |
160 By default security is disabled on queries executed on the repository |
161 side. |
161 side. |
162 """ |
162 """ |
163 def __init__(self, session, read=None, write=None): |
163 def __init__(self, session, read=None, write=None): |
164 self.session = session |
164 self.session = session |
165 self.tx = session._tx |
165 self.cnx = session._cnx |
166 self.read = read |
166 self.read = read |
167 self.write = write |
167 self.write = write |
168 self.oldread = None |
168 self.oldread = None |
169 self.oldwrite = None |
169 self.oldwrite = None |
170 |
170 |
171 def __enter__(self): |
171 def __enter__(self): |
172 if self.read is None: |
172 if self.read is None: |
173 self.oldread = None |
173 self.oldread = None |
174 else: |
174 else: |
175 self.oldread = self.tx.read_security |
175 self.oldread = self.cnx.read_security |
176 self.tx.read_security = self.read |
176 self.cnx.read_security = self.read |
177 if self.write is None: |
177 if self.write is None: |
178 self.oldwrite = None |
178 self.oldwrite = None |
179 else: |
179 else: |
180 self.oldwrite = self.tx.write_security |
180 self.oldwrite = self.cnx.write_security |
181 self.tx.write_security = self.write |
181 self.cnx.write_security = self.write |
182 self.tx.ctx_count += 1 |
182 self.cnx.ctx_count += 1 |
183 |
183 |
184 def __exit__(self, exctype, exc, traceback): |
184 def __exit__(self, exctype, exc, traceback): |
185 self.tx.ctx_count -= 1 |
185 self.cnx.ctx_count -= 1 |
186 if self.tx.ctx_count == 0: |
186 if self.cnx.ctx_count == 0: |
187 self.session._clear_thread_storage(self.tx) |
187 self.session._clear_thread_storage(self.cnx) |
188 else: |
188 else: |
189 if self.oldread is not None: |
189 if self.oldread is not None: |
190 self.tx.read_security = self.oldread |
190 self.cnx.read_security = self.oldread |
191 if self.oldwrite is not None: |
191 if self.oldwrite is not None: |
192 self.tx.write_security = self.oldwrite |
192 self.cnx.write_security = self.oldwrite |
193 |
193 |
194 HOOKS_ALLOW_ALL = object() |
194 HOOKS_ALLOW_ALL = object() |
195 HOOKS_DENY_ALL = object() |
195 HOOKS_DENY_ALL = object() |
196 DEFAULT_SECURITY = object() # evaluated to true by design |
196 DEFAULT_SECURITY = object() # evaluated to true by design |
197 |
197 |
198 class SessionClosedError(RuntimeError): |
198 class SessionClosedError(RuntimeError): |
199 pass |
199 pass |
200 |
200 |
201 class CnxSetTracker(object): |
201 class CnxSetTracker(object): |
202 """Keep track of which transaction use which cnxset. |
202 """Keep track of which connection use which cnxset. |
203 |
203 |
204 There should be one of this object per session plus one another for |
204 There should be one of this object per session plus one another for |
205 internal session. |
205 internal session. |
206 |
206 |
207 Session object are responsible of creating their CnxSetTracker object. |
207 Session object are responsible of creating their CnxSetTracker object. |
208 |
208 |
209 Transaction should use the :meth:`record` and :meth:`forget` to inform the |
209 Connection should use the :meth:`record` and :meth:`forget` to inform the |
210 tracker of cnxset they have acquired. |
210 tracker of cnxset they have acquired. |
211 |
211 |
212 .. automethod:: cubicweb.server.session.CnxSetTracker.record |
212 .. automethod:: cubicweb.server.session.CnxSetTracker.record |
213 .. automethod:: cubicweb.server.session.CnxSetTracker.forget |
213 .. automethod:: cubicweb.server.session.CnxSetTracker.forget |
214 |
214 |
230 self._condition.__enter__() |
230 self._condition.__enter__() |
231 |
231 |
232 def __exit__(self, *args): |
232 def __exit__(self, *args): |
233 self._condition.__exit__(*args) |
233 self._condition.__exit__(*args) |
234 |
234 |
235 def record(self, txid, cnxset): |
235 def record(self, cnxid, cnxset): |
236 """Inform the tracker that a txid have acquired a cnxset |
236 """Inform the tracker that a cnxid have acquired a cnxset |
237 |
237 |
238 This methode is to be used by Transaction object. |
238 This methode is to be used by Connection object. |
239 |
239 |
240 This method fails when: |
240 This method fails when: |
241 - The txid already have a recorded cnxset. |
241 - The cnxid already have a recorded cnxset. |
242 - The tracker is not active anymore. |
242 - The tracker is not active anymore. |
243 |
243 |
244 Notes about the caller: |
244 Notes about the caller: |
245 (1) It is responsible for retrieving a cnxset. |
245 (1) It is responsible for retrieving a cnxset. |
246 (2) It must be prepared to release the cnxset if the |
246 (2) It must be prepared to release the cnxset if the |
263 """ |
263 """ |
264 # dubious since the caller is suppose to have acquired it anyway. |
264 # dubious since the caller is suppose to have acquired it anyway. |
265 with self._condition: |
265 with self._condition: |
266 if not self._active: |
266 if not self._active: |
267 raise SessionClosedError('Closed') |
267 raise SessionClosedError('Closed') |
268 old = self._record.get(txid) |
268 old = self._record.get(cnxid) |
269 if old is not None: |
269 if old is not None: |
270 raise ValueError('"%s" already have a cnx_set (%r)' |
270 raise ValueError('"%s" already have a cnx_set (%r)' |
271 % (txid, old)) |
271 % (cnxid, old)) |
272 self._record[txid] = cnxset |
272 self._record[cnxid] = cnxset |
273 |
273 |
274 def forget(self, txid, cnxset): |
274 def forget(self, cnxid, cnxset): |
275 """Inform the tracker that a txid have release a cnxset |
275 """Inform the tracker that a cnxid have release a cnxset |
276 |
276 |
277 This methode is to be used by Transaction object. |
277 This methode is to be used by Connection object. |
278 |
278 |
279 This method fails when: |
279 This method fails when: |
280 - The cnxset for the txid does not match the recorded one. |
280 - The cnxset for the cnxid does not match the recorded one. |
281 |
281 |
282 Notes about the caller: |
282 Notes about the caller: |
283 (1) It is responsible for releasing the cnxset. |
283 (1) It is responsible for releasing the cnxset. |
284 (2) It should acquire the tracker lock during the operation to ensure |
284 (2) It should acquire the tracker lock during the operation to ensure |
285 the internal tracker state is always accurate regarding its own state. |
285 the internal tracker state is always accurate regarding its own state. |
295 cnxset_tracker.forget(caller.id, cnxset) |
295 cnxset_tracker.forget(caller.id, cnxset) |
296 finally: |
296 finally: |
297 cnxset = repo._free_cnxset(cnxset) # (1) |
297 cnxset = repo._free_cnxset(cnxset) # (1) |
298 """ |
298 """ |
299 with self._condition: |
299 with self._condition: |
300 old = self._record.get(txid, None) |
300 old = self._record.get(cnxid, None) |
301 if old is not cnxset: |
301 if old is not cnxset: |
302 raise ValueError('recorded cnxset for "%s" mismatch: %r != %r' |
302 raise ValueError('recorded cnxset for "%s" mismatch: %r != %r' |
303 % (txid, old, cnxset)) |
303 % (cnxid, old, cnxset)) |
304 self._record.pop(txid) |
304 self._record.pop(cnxid) |
305 self._condition.notify_all() |
305 self._condition.notify_all() |
306 |
306 |
307 def close(self): |
307 def close(self): |
308 """Marks the tracker as inactive. |
308 """Marks the tracker as inactive. |
309 |
309 |
329 start = time() |
329 start = time() |
330 self._condition.wait(timeout) |
330 self._condition.wait(timeout) |
331 timeout -= time() - start |
331 timeout -= time() - start |
332 return tuple(self._record) |
332 return tuple(self._record) |
333 |
333 |
334 class Transaction(object): |
334 class Connection(object): |
335 """Repository Transaction |
335 """Repository Connection |
336 |
336 |
337 Holds all transaction related data |
337 Holds all connection related data |
338 |
338 |
339 Database connections resource: |
339 Database connections resource: |
340 |
340 |
341 :attr:`running_dbapi_query`, boolean flag telling if the executing query |
341 :attr:`running_dbapi_query`, boolean flag telling if the executing query |
342 is coming from a dbapi connection or is a query from within the repository |
342 is coming from a dbapi connection or is a query from within the repository |
343 |
343 |
344 :attr:`cnxset`, the connections set to use to execute queries on sources. |
344 :attr:`cnxset`, the connections set to use to execute queries on sources. |
345 If the transaction is read only, the connection set may be freed between |
345 If the transaction is read only, the connection set may be freed between |
346 actual query. This allows multiple transaction with a reasonable low |
346 actual query. This allows multiple connection with a reasonable low |
347 connection set pool size. control mechanism is detailed below |
347 connection set pool size. control mechanism is detailed below |
348 |
348 |
349 .. automethod:: cubicweb.server.session.Transaction.set_cnxset |
349 .. automethod:: cubicweb.server.session.Connection.set_cnxset |
350 .. automethod:: cubicweb.server.session.Transaction.free_cnxset |
350 .. automethod:: cubicweb.server.session.Connection.free_cnxset |
351 |
351 |
352 :attr:`mode`, string telling the connections set handling mode, may be one |
352 :attr:`mode`, string telling the connections set handling mode, may be one |
353 of 'read' (connections set may be freed), 'write' (some write was done in |
353 of 'read' (connections set may be freed), 'write' (some write was done in |
354 the connections set, it can't be freed before end of the transaction), |
354 the connections set, it can't be freed before end of the transaction), |
355 'transaction' (we want to keep the connections set during all the |
355 'transaction' (we want to keep the connections set during all the |
386 :attr:`read_security` and :attr:`write_security`, boolean flags telling if |
386 :attr:`read_security` and :attr:`write_security`, boolean flags telling if |
387 read/write security is currently activated. |
387 read/write security is currently activated. |
388 |
388 |
389 """ |
389 """ |
390 |
390 |
391 def __init__(self, txid, session, rewriter): |
391 def __init__(self, cnxid, session, rewriter): |
392 #: transaction unique id |
392 #: connection unique id |
393 self.transactionid = txid |
393 self.connectionid = cnxid |
394 #: reentrance handling |
394 #: reentrance handling |
395 self.ctx_count = 0 |
395 self.ctx_count = 0 |
396 |
396 |
397 #: server.Repository object |
397 #: server.Repository object |
398 self.repo = session.repo |
398 self.repo = session.repo |
402 self.mode = session.default_mode |
402 self.mode = session.default_mode |
403 #: connection set used to execute queries on sources |
403 #: connection set used to execute queries on sources |
404 self._cnxset = None |
404 self._cnxset = None |
405 #: CnxSetTracker used to report cnxset usage |
405 #: CnxSetTracker used to report cnxset usage |
406 self._cnxset_tracker = session._cnxset_tracker |
406 self._cnxset_tracker = session._cnxset_tracker |
407 #: is this transaction from a client or internal to the repo |
407 #: is this connection from a client or internal to the repo |
408 self.running_dbapi_query = True |
408 self.running_dbapi_query = True |
409 |
409 |
410 #: dict containing arbitrary data cleared at the end of the transaction |
410 #: dict containing arbitrary data cleared at the end of the transaction |
411 self.data = {} |
411 self.data = {} |
412 #: ordered list of operations to be processed on commit/rollback |
412 #: ordered list of operations to be processed on commit/rollback |
460 if new_cnxset is old_cnxset: |
460 if new_cnxset is old_cnxset: |
461 return #nothing to do |
461 return #nothing to do |
462 if old_cnxset is not None: |
462 if old_cnxset is not None: |
463 self._cnxset = None |
463 self._cnxset = None |
464 self.ctx_count -= 1 |
464 self.ctx_count -= 1 |
465 self._cnxset_tracker.forget(self.transactionid, old_cnxset) |
465 self._cnxset_tracker.forget(self.connectionid, old_cnxset) |
466 if new_cnxset is not None: |
466 if new_cnxset is not None: |
467 self._cnxset_tracker.record(self.transactionid, new_cnxset) |
467 self._cnxset_tracker.record(self.connectionid, new_cnxset) |
468 self._cnxset = new_cnxset |
468 self._cnxset = new_cnxset |
469 self.ctx_count += 1 |
469 self.ctx_count += 1 |
470 |
470 |
471 def set_cnxset(self): |
471 def set_cnxset(self): |
472 """the transaction need a connections set to execute some queries""" |
472 """the connection need a connections set to execute some queries""" |
473 if self.cnxset is None: |
473 if self.cnxset is None: |
474 cnxset = self.repo._get_cnxset() |
474 cnxset = self.repo._get_cnxset() |
475 try: |
475 try: |
476 self.cnxset = cnxset |
476 self.cnxset = cnxset |
477 try: |
477 try: |
497 self.repo._free_cnxset(cnxset) |
497 self.repo._free_cnxset(cnxset) |
498 |
498 |
499 |
499 |
500 # Entity cache management ################################################# |
500 # Entity cache management ################################################# |
501 # |
501 # |
502 # The transaction entity cache as held in tx.data it is removed at end the |
502 # The connection entity cache as held in cnx.data it is removed at end the |
503 # end of the transaction (commit and rollback) |
503 # end of the connection (commit and rollback) |
504 # |
504 # |
505 # XXX transaction level caching may be a pb with multiple repository |
505 # XXX connection level caching may be a pb with multiple repository |
506 # instances, but 1. this is probably not the only one :$ and 2. it may be |
506 # instances, but 1. this is probably not the only one :$ and 2. it may be |
507 # an acceptable risk. Anyway we could activate it or not according to a |
507 # an acceptable risk. Anyway we could activate it or not according to a |
508 # configuration option |
508 # configuration option |
509 |
509 |
510 def set_entity_cache(self, entity): |
510 def set_entity_cache(self, entity): |
511 """Add `entity` to the transaction entity cache""" |
511 """Add `entity` to the connection entity cache""" |
512 ecache = self.data.setdefault('ecache', {}) |
512 ecache = self.data.setdefault('ecache', {}) |
513 ecache.setdefault(entity.eid, entity) |
513 ecache.setdefault(entity.eid, entity) |
514 |
514 |
515 def entity_cache(self, eid): |
515 def entity_cache(self, eid): |
516 """get cache entity for `eid`""" |
516 """get cache entity for `eid`""" |
692 subjtype = self.repo.type_and_source_from_eid(eidfrom, self)[0] |
692 subjtype = self.repo.type_and_source_from_eid(eidfrom, self)[0] |
693 objtype = self.repo.type_and_source_from_eid(eidto, self)[0] |
693 objtype = self.repo.type_and_source_from_eid(eidto, self)[0] |
694 return self.vreg.schema.rschema(rtype).rdefs[(subjtype, objtype)] |
694 return self.vreg.schema.rschema(rtype).rdefs[(subjtype, objtype)] |
695 |
695 |
696 |
696 |
697 def tx_attr(attr_name, writable=False): |
697 def cnx_attr(attr_name, writable=False): |
698 """return a property to forward attribute access to transaction. |
698 """return a property to forward attribute access to connection. |
699 |
699 |
700 This is to be used by session""" |
700 This is to be used by session""" |
701 args = {} |
701 args = {} |
702 def attr_from_tx(session): |
702 def attr_from_cnx(session): |
703 return getattr(session._tx, attr_name) |
703 return getattr(session._cnx, attr_name) |
704 args['fget'] = attr_from_tx |
704 args['fget'] = attr_from_cnx |
705 if writable: |
705 if writable: |
706 def write_attr(session, value): |
706 def write_attr(session, value): |
707 return setattr(session._tx, attr_name, value) |
707 return setattr(session._cnx, attr_name, value) |
708 args['fset'] = write_attr |
708 args['fset'] = write_attr |
709 return property(**args) |
709 return property(**args) |
710 |
710 |
711 def tx_meth(meth_name): |
711 def cnx_meth(meth_name): |
712 """return a function forwarding calls to transaction. |
712 """return a function forwarding calls to connection. |
713 |
713 |
714 This is to be used by session""" |
714 This is to be used by session""" |
715 def meth_from_tx(session, *args, **kwargs): |
715 def meth_from_cnx(session, *args, **kwargs): |
716 return getattr(session._tx, meth_name)(*args, **kwargs) |
716 return getattr(session._cnx, meth_name)(*args, **kwargs) |
717 return meth_from_tx |
717 return meth_from_cnx |
718 |
718 |
719 |
719 |
720 class Session(RequestSessionBase): |
720 class Session(RequestSessionBase): |
721 """Repository user session |
721 """Repository user session |
722 |
722 |
734 described here but higher level APIs. |
734 described here but higher level APIs. |
735 |
735 |
736 :attr:`data` is a dictionary containing shared data, used to communicate |
736 :attr:`data` is a dictionary containing shared data, used to communicate |
737 extra information between the client and the repository |
737 extra information between the client and the repository |
738 |
738 |
739 :attr:`_txs` is a dictionary of :class:`TransactionData` instance, one |
739 :attr:`_cnxs` is a dictionary of :class:`Connection` instance, one |
740 for each running transaction. The key is the transaction id. By default |
740 for each running connection. The key is the connection id. By default |
741 the transaction id is the thread name but it can be otherwise (per dbapi |
741 the connection id is the thread name but it can be otherwise (per dbapi |
742 cursor for instance, or per thread name *from another process*). |
742 cursor for instance, or per thread name *from another process*). |
743 |
743 |
744 :attr:`__threaddata` is a thread local storage whose `tx` attribute |
744 :attr:`__threaddata` is a thread local storage whose `cnx` attribute |
745 refers to the proper instance of :class:`Transaction` according to the |
745 refers to the proper instance of :class:`Connection` according to the |
746 transaction. |
746 connection. |
747 |
747 |
748 You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`, |
748 You should not have to use neither :attr:`_cnx` nor :attr:`__threaddata`, |
749 simply access transaction data transparently through the :attr:`_tx` |
749 simply access connection data transparently through the :attr:`_cnx` |
750 property. Also, you usually don't have to access it directly since current |
750 property. Also, you usually don't have to access it directly since current |
751 transaction's data may be accessed/modified through properties / methods: |
751 connection's data may be accessed/modified through properties / methods: |
752 |
752 |
753 :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary |
753 :attr:`connection_data`, similarly to :attr:`data`, is a dictionary |
754 containing some shared data that should be cleared at the end of the |
754 containing some shared data that should be cleared at the end of the |
755 transaction. Hooks and operations may put arbitrary data in there, and |
755 connection. Hooks and operations may put arbitrary data in there, and |
756 this may also be used as a communication channel between the client and |
756 this may also be used as a communication channel between the client and |
757 the repository. |
757 the repository. |
758 |
758 |
759 .. automethod:: cubicweb.server.session.Session.get_shared_data |
759 .. automethod:: cubicweb.server.session.Session.get_shared_data |
760 .. automethod:: cubicweb.server.session.Session.set_shared_data |
760 .. automethod:: cubicweb.server.session.Session.set_shared_data |
761 .. automethod:: cubicweb.server.session.Session.added_in_transaction |
761 .. automethod:: cubicweb.server.session.Session.added_in_transaction |
762 .. automethod:: cubicweb.server.session.Session.deleted_in_transaction |
762 .. automethod:: cubicweb.server.session.Session.deleted_in_transaction |
763 |
763 |
764 Transaction state information: |
764 Connection state information: |
765 |
765 |
766 :attr:`running_dbapi_query`, boolean flag telling if the executing query |
766 :attr:`running_dbapi_query`, boolean flag telling if the executing query |
767 is coming from a dbapi connection or is a query from within the repository |
767 is coming from a dbapi connection or is a query from within the repository |
768 |
768 |
769 :attr:`cnxset`, the connections set to use to execute queries on sources. |
769 :attr:`cnxset`, the connections set to use to execute queries on sources. |
845 # and the rql server |
845 # and the rql server |
846 self.data = {} |
846 self.data = {} |
847 # i18n initialization |
847 # i18n initialization |
848 self.set_language(user.prefered_language()) |
848 self.set_language(user.prefered_language()) |
849 ### internals |
849 ### internals |
850 # Transaction of this section |
850 # Connection of this section |
851 self._txs = {} |
851 self._cnxs = {} |
852 # Data local to the thread |
852 # Data local to the thread |
853 self.__threaddata = threading.local() |
853 self.__threaddata = threading.local() |
854 self._cnxset_tracker = CnxSetTracker() |
854 self._cnxset_tracker = CnxSetTracker() |
855 self._closed = False |
855 self._closed = False |
856 self._lock = threading.RLock() |
856 self._lock = threading.RLock() |
857 |
857 |
858 def __unicode__(self): |
858 def __unicode__(self): |
859 return '<session %s (%s 0x%x)>' % ( |
859 return '<session %s (%s 0x%x)>' % ( |
860 unicode(self.user.login), self.id, id(self)) |
860 unicode(self.user.login), self.id, id(self)) |
861 |
861 |
862 def get_tx(self, txid): |
862 def get_cnx(self, cnxid): |
863 """return the <txid> transaction attached to this session |
863 """return the <cnxid> connection attached to this session |
864 |
864 |
865 Transaction is created if necessary""" |
865 Connection is created if necessary""" |
866 with self._lock: # no transaction exist with the same id |
866 with self._lock: # no connection exist with the same id |
867 try: |
867 try: |
868 if self.closed: |
868 if self.closed: |
869 raise SessionClosedError('try to access connections set on a closed session %s' % self.id) |
869 raise SessionClosedError('try to access connections set on a closed session %s' % self.id) |
870 tx = self._txs[txid] |
870 cnx = self._cnxs[cnxid] |
871 except KeyError: |
871 except KeyError: |
872 rewriter = RQLRewriter(self) |
872 rewriter = RQLRewriter(self) |
873 tx = Transaction(txid, self, rewriter) |
873 cnx = Connection(cnxid, self, rewriter) |
874 self._txs[txid] = tx |
874 self._cnxs[cnxid] = cnx |
875 return tx |
875 return cnx |
876 |
876 |
877 def set_tx(self, txid=None): |
877 def set_cnx(self, cnxid=None): |
878 """set the default transaction of the current thread to <txid> |
878 """set the default connection of the current thread to <cnxid> |
879 |
879 |
880 Transaction is created if necessary""" |
880 Connection is created if necessary""" |
881 if txid is None: |
881 if cnxid is None: |
882 txid = threading.currentThread().getName() |
882 cnxid = threading.currentThread().getName() |
883 self.__threaddata.tx = self.get_tx(txid) |
883 self.__threaddata.cnx = self.get_cnx(cnxid) |
884 |
884 |
885 @property |
885 @property |
886 def _tx(self): |
886 def _cnx(self): |
887 """default transaction for current session in current thread""" |
887 """default connection for current session in current thread""" |
888 try: |
888 try: |
889 return self.__threaddata.tx |
889 return self.__threaddata.cnx |
890 except AttributeError: |
890 except AttributeError: |
891 self.set_tx() |
891 self.set_cnx() |
892 return self.__threaddata.tx |
892 return self.__threaddata.cnx |
893 |
893 |
894 def get_option_value(self, option, foreid=None): |
894 def get_option_value(self, option, foreid=None): |
895 return self.repo.get_option_value(option, foreid) |
895 return self.repo.get_option_value(option, foreid) |
896 |
896 |
897 def transaction(self, free_cnxset=True): |
897 def transaction(self, free_cnxset=True): |
1030 entity._cw_related_cache['%s_%s' % (rtype, role)] = ( |
1030 entity._cw_related_cache['%s_%s' % (rtype, role)] = ( |
1031 rset, tuple(entities)) |
1031 rset, tuple(entities)) |
1032 |
1032 |
1033 # resource accessors ###################################################### |
1033 # resource accessors ###################################################### |
1034 |
1034 |
1035 system_sql = tx_meth('system_sql') |
1035 system_sql = cnx_meth('system_sql') |
1036 deleted_in_transaction = tx_meth('deleted_in_transaction') |
1036 deleted_in_transaction = cnx_meth('deleted_in_transaction') |
1037 added_in_transaction = tx_meth('added_in_transaction') |
1037 added_in_transaction = cnx_meth('added_in_transaction') |
1038 rtype_eids_rdef = tx_meth('rtype_eids_rdef') |
1038 rtype_eids_rdef = cnx_meth('rtype_eids_rdef') |
1039 |
1039 |
1040 # security control ######################################################### |
1040 # security control ######################################################### |
1041 |
1041 |
1042 |
1042 |
1043 def security_enabled(self, read=None, write=None): |
1043 def security_enabled(self, read=None, write=None): |
1044 return _security_enabled(self, read=read, write=write) |
1044 return _security_enabled(self, read=read, write=write) |
1045 |
1045 |
1046 read_security = tx_attr('read_security', writable=True) |
1046 read_security = cnx_attr('read_security', writable=True) |
1047 write_security = tx_attr('write_security', writable=True) |
1047 write_security = cnx_attr('write_security', writable=True) |
1048 running_dbapi_query = tx_attr('running_dbapi_query') |
1048 running_dbapi_query = cnx_attr('running_dbapi_query') |
1049 |
1049 |
1050 # hooks activation control ################################################# |
1050 # hooks activation control ################################################# |
1051 # all hooks should be activated during normal execution |
1051 # all hooks should be activated during normal execution |
1052 |
1052 |
1053 def allow_all_hooks_but(self, *categories): |
1053 def allow_all_hooks_but(self, *categories): |
1054 return _hooks_control(self, HOOKS_ALLOW_ALL, *categories) |
1054 return _hooks_control(self, HOOKS_ALLOW_ALL, *categories) |
1055 def deny_all_hooks_but(self, *categories): |
1055 def deny_all_hooks_but(self, *categories): |
1056 return _hooks_control(self, HOOKS_DENY_ALL, *categories) |
1056 return _hooks_control(self, HOOKS_DENY_ALL, *categories) |
1057 |
1057 |
1058 hooks_mode = tx_attr('hooks_mode') |
1058 hooks_mode = cnx_attr('hooks_mode') |
1059 |
1059 |
1060 disabled_hook_categories = tx_attr('disabled_hook_cats') |
1060 disabled_hook_categories = cnx_attr('disabled_hook_cats') |
1061 enabled_hook_categories = tx_attr('enabled_hook_cats') |
1061 enabled_hook_categories = cnx_attr('enabled_hook_cats') |
1062 disable_hook_categories = tx_meth('disable_hook_categories') |
1062 disable_hook_categories = cnx_meth('disable_hook_categories') |
1063 enable_hook_categories = tx_meth('enable_hook_categories') |
1063 enable_hook_categories = cnx_meth('enable_hook_categories') |
1064 is_hook_category_activated = tx_meth('is_hook_category_activated') |
1064 is_hook_category_activated = cnx_meth('is_hook_category_activated') |
1065 is_hook_activated = tx_meth('is_hook_activated') |
1065 is_hook_activated = cnx_meth('is_hook_activated') |
1066 |
1066 |
1067 # connection management ################################################### |
1067 # connection management ################################################### |
1068 |
1068 |
1069 def keep_cnxset_mode(self, mode): |
1069 def keep_cnxset_mode(self, mode): |
1070 """set `mode`, e.g. how the session will keep its connections set: |
1070 """set `mode`, e.g. how the session will keep its connections set: |
1084 if mode == 'transaction': |
1084 if mode == 'transaction': |
1085 self.default_mode = 'transaction' |
1085 self.default_mode = 'transaction' |
1086 else: # mode == 'write' |
1086 else: # mode == 'write' |
1087 self.default_mode = 'read' |
1087 self.default_mode = 'read' |
1088 |
1088 |
1089 mode = tx_attr('mode', writable=True) |
1089 mode = cnx_attr('mode', writable=True) |
1090 commit_state = tx_attr('commit_state', writable=True) |
1090 commit_state = cnx_attr('commit_state', writable=True) |
1091 |
1091 |
1092 @property |
1092 @property |
1093 def cnxset(self): |
1093 def cnxset(self): |
1094 """connections set, set according to transaction mode for each query""" |
1094 """connections set, set according to transaction mode for each query""" |
1095 if self._closed: |
1095 if self._closed: |
1096 self.free_cnxset(True) |
1096 self.free_cnxset(True) |
1097 raise SessionClosedError('try to access connections set on a closed session %s' % self.id) |
1097 raise SessionClosedError('try to access connections set on a closed session %s' % self.id) |
1098 return self._tx.cnxset |
1098 return self._cnx.cnxset |
1099 |
1099 |
1100 def set_cnxset(self): |
1100 def set_cnxset(self): |
1101 """the session need a connections set to execute some queries""" |
1101 """the session need a connections set to execute some queries""" |
1102 with self._lock: # can probably be removed |
1102 with self._lock: # can probably be removed |
1103 if self._closed: |
1103 if self._closed: |
1104 self.free_cnxset(True) |
1104 self.free_cnxset(True) |
1105 raise SessionClosedError('try to set connections set on a closed session %s' % self.id) |
1105 raise SessionClosedError('try to set connections set on a closed session %s' % self.id) |
1106 return self._tx.set_cnxset() |
1106 return self._cnx.set_cnxset() |
1107 free_cnxset = tx_meth('free_cnxset') |
1107 free_cnxset = cnx_meth('free_cnxset') |
1108 |
1108 |
1109 def _touch(self): |
1109 def _touch(self): |
1110 """update latest session usage timestamp and reset mode to read""" |
1110 """update latest session usage timestamp and reset mode to read""" |
1111 self.timestamp = time() |
1111 self.timestamp = time() |
1112 self.local_perm_cache.clear() # XXX simply move in tx.data, no? |
1112 self.local_perm_cache.clear() # XXX simply move in cnx.data, no? |
1113 |
1113 |
1114 # shared data handling ################################################### |
1114 # shared data handling ################################################### |
1115 |
1115 |
1116 def get_shared_data(self, key, default=None, pop=False, txdata=False): |
1116 def get_shared_data(self, key, default=None, pop=False, txdata=False): |
1117 """return value associated to `key` in session data""" |
1117 """return value associated to `key` in session data""" |
1118 if txdata: |
1118 if txdata: |
1119 data = self._tx.data |
1119 data = self._cnx.data |
1120 else: |
1120 else: |
1121 data = self.data |
1121 data = self.data |
1122 if pop: |
1122 if pop: |
1123 return data.pop(key, default) |
1123 return data.pop(key, default) |
1124 else: |
1124 else: |
1125 return data.get(key, default) |
1125 return data.get(key, default) |
1126 |
1126 |
1127 def set_shared_data(self, key, value, txdata=False): |
1127 def set_shared_data(self, key, value, txdata=False): |
1128 """set value associated to `key` in session data""" |
1128 """set value associated to `key` in session data""" |
1129 if txdata: |
1129 if txdata: |
1130 self._tx.data[key] = value |
1130 self._cnx.data[key] = value |
1131 else: |
1131 else: |
1132 self.data[key] = value |
1132 self.data[key] = value |
1133 |
1133 |
1134 # server-side service call ################################################# |
1134 # server-side service call ################################################# |
1135 |
1135 |
1143 @property |
1143 @property |
1144 def cursor(self): |
1144 def cursor(self): |
1145 """return a rql cursor""" |
1145 """return a rql cursor""" |
1146 return self |
1146 return self |
1147 |
1147 |
1148 set_entity_cache = tx_meth('set_entity_cache') |
1148 set_entity_cache = cnx_meth('set_entity_cache') |
1149 entity_cache = tx_meth('entity_cache') |
1149 entity_cache = cnx_meth('entity_cache') |
1150 cache_entities = tx_meth('cached_entities') |
1150 cache_entities = cnx_meth('cached_entities') |
1151 drop_entity_cache = tx_meth('drop_entity_cache') |
1151 drop_entity_cache = cnx_meth('drop_entity_cache') |
1152 |
1152 |
1153 def from_controller(self): |
1153 def from_controller(self): |
1154 """return the id (string) of the controller issuing the request (no |
1154 """return the id (string) of the controller issuing the request (no |
1155 sense here, always return 'view') |
1155 sense here, always return 'view') |
1156 """ |
1156 """ |
1157 return 'view' |
1157 return 'view' |
1158 |
1158 |
1159 source_defs = tx_meth('source_defs') |
1159 source_defs = cnx_meth('source_defs') |
1160 describe = tx_meth('describe') |
1160 describe = cnx_meth('describe') |
1161 source_from_eid = tx_meth('source_from_eid') |
1161 source_from_eid = cnx_meth('source_from_eid') |
1162 |
1162 |
1163 |
1163 |
1164 def execute(self, rql, kwargs=None, eid_key=None, build_descr=True): |
1164 def execute(self, rql, kwargs=None, eid_key=None, build_descr=True): |
1165 """db-api like method directly linked to the querier execute method. |
1165 """db-api like method directly linked to the querier execute method. |
1166 |
1166 |
1178 """remove everything from the thread local storage, except connections set |
1178 """remove everything from the thread local storage, except connections set |
1179 which is explicitly removed by free_cnxset, and mode which is set anyway |
1179 which is explicitly removed by free_cnxset, and mode which is set anyway |
1180 by _touch |
1180 by _touch |
1181 """ |
1181 """ |
1182 try: |
1182 try: |
1183 tx = self.__threaddata.tx |
1183 cnx = self.__threaddata.cnx |
1184 except AttributeError: |
1184 except AttributeError: |
1185 pass |
1185 pass |
1186 else: |
1186 else: |
1187 if free_cnxset: |
1187 if free_cnxset: |
1188 self.free_cnxset() |
1188 self.free_cnxset() |
1189 if tx.ctx_count == 0: |
1189 if cnx.ctx_count == 0: |
1190 self._clear_thread_storage(tx) |
1190 self._clear_thread_storage(cnx) |
1191 else: |
1191 else: |
1192 self._clear_tx_storage(tx) |
1192 self._clear_cnx_storage(cnx) |
1193 else: |
1193 else: |
1194 self._clear_tx_storage(tx) |
1194 self._clear_cnx_storage(cnx) |
1195 |
1195 |
1196 def _clear_thread_storage(self, tx): |
1196 def _clear_thread_storage(self, cnx): |
1197 self._txs.pop(tx.transactionid, None) |
1197 self._cnxs.pop(cnx.connectionid, None) |
1198 try: |
1198 try: |
1199 del self.__threaddata.tx |
1199 del self.__threaddata.cnx |
1200 except AttributeError: |
1200 except AttributeError: |
1201 pass |
1201 pass |
1202 |
1202 |
1203 def _clear_tx_storage(self, tx): |
1203 def _clear_cnx_storage(self, cnx): |
1204 tx.clear() |
1204 cnx.clear() |
1205 tx._rewriter = RQLRewriter(self) |
1205 cnx._rewriter = RQLRewriter(self) |
1206 |
1206 |
1207 def commit(self, free_cnxset=True, reset_pool=None): |
1207 def commit(self, free_cnxset=True, reset_pool=None): |
1208 """commit the current session's transaction""" |
1208 """commit the current session's transaction""" |
1209 if reset_pool is not None: |
1209 if reset_pool is not None: |
1210 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1210 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1297 if reset_pool is not None: |
1297 if reset_pool is not None: |
1298 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1298 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1299 DeprecationWarning, stacklevel=2) |
1299 DeprecationWarning, stacklevel=2) |
1300 free_cnxset = reset_pool |
1300 free_cnxset = reset_pool |
1301 # don't use self.cnxset, rollback may be called with _closed == True |
1301 # don't use self.cnxset, rollback may be called with _closed == True |
1302 cnxset = self._tx.cnxset |
1302 cnxset = self._cnx.cnxset |
1303 if cnxset is None: |
1303 if cnxset is None: |
1304 self._clear_thread_data() |
1304 self._clear_thread_data() |
1305 self._touch() |
1305 self._touch() |
1306 self.debug('rollback session %s done (no db activity)', self.id) |
1306 self.debug('rollback session %s done (no db activity)', self.id) |
1307 return |
1307 return |
1328 tracker = self._cnxset_tracker |
1328 tracker = self._cnxset_tracker |
1329 with self._lock: |
1329 with self._lock: |
1330 self._closed = True |
1330 self._closed = True |
1331 tracker.close() |
1331 tracker.close() |
1332 self.rollback() |
1332 self.rollback() |
1333 self.info('waiting for open transaction of session: %s', self) |
1333 self.info('waiting for open connection of session: %s', self) |
1334 timeout = 10 |
1334 timeout = 10 |
1335 pendings = tracker.wait(timeout) |
1335 pendings = tracker.wait(timeout) |
1336 if pendings: |
1336 if pendings: |
1337 self.error('%i transaction still alive after 10 seconds, will close ' |
1337 self.error('%i connection still alive after 10 seconds, will close ' |
1338 'session anyway', len(pendings)) |
1338 'session anyway', len(pendings)) |
1339 for txid in pendings: |
1339 for cnxid in pendings: |
1340 tx = self._txs.get(txid) |
1340 cnx = self._cnxs.get(cnxid) |
1341 if tx is not None: |
1341 if cnx is not None: |
1342 # drop tx.cnxset |
1342 # drop cnx.cnxset |
1343 with tracker: |
1343 with tracker: |
1344 try: |
1344 try: |
1345 cnxset = tx.cnxset |
1345 cnxset = cnx.cnxset |
1346 if cnxset is None: |
1346 if cnxset is None: |
1347 continue |
1347 continue |
1348 tx.cnxset = None |
1348 cnx.cnxset = None |
1349 except RuntimeError: |
1349 except RuntimeError: |
1350 msg = 'issue while force free of cnxset in %s' |
1350 msg = 'issue while force free of cnxset in %s' |
1351 self.error(msg, tx) |
1351 self.error(msg, cnx) |
1352 # cnxset.reconnect() do an hard reset of the cnxset |
1352 # cnxset.reconnect() do an hard reset of the cnxset |
1353 # it force it to be freed |
1353 # it force it to be freed |
1354 cnxset.reconnect() |
1354 cnxset.reconnect() |
1355 self.repo._free_cnxset(cnxset) |
1355 self.repo._free_cnxset(cnxset) |
1356 del self.__threaddata |
1356 del self.__threaddata |
1357 del self._txs |
1357 del self._cnxs |
1358 |
1358 |
1359 @property |
1359 @property |
1360 def closed(self): |
1360 def closed(self): |
1361 return not hasattr(self, '_txs') |
1361 return not hasattr(self, '_cnxs') |
1362 |
1362 |
1363 # transaction data/operations management ################################## |
1363 # transaction data/operations management ################################## |
1364 |
1364 |
1365 transaction_data = tx_attr('data') |
1365 transaction_data = cnx_attr('data') |
1366 pending_operations = tx_attr('pending_operations') |
1366 pending_operations = cnx_attr('pending_operations') |
1367 pruned_hooks_cache = tx_attr('pruned_hooks_cache') |
1367 pruned_hooks_cache = cnx_attr('pruned_hooks_cache') |
1368 add_operation = tx_meth('add_operation') |
1368 add_operation = cnx_meth('add_operation') |
1369 |
1369 |
1370 # undo support ############################################################ |
1370 # undo support ############################################################ |
1371 |
1371 |
1372 ertype_supports_undo = tx_meth('ertype_supports_undo') |
1372 ertype_supports_undo = cnx_meth('ertype_supports_undo') |
1373 transaction_inc_action_counter = tx_meth('transaction_inc_action_counter') |
1373 transaction_inc_action_counter = cnx_meth('transaction_inc_action_counter') |
1374 |
1374 |
1375 def transaction_uuid(self, set=True): |
1375 def transaction_uuid(self, set=True): |
1376 try: |
1376 try: |
1377 return self._tx.transaction_uuid(set=set) |
1377 return self._cnx.transaction_uuid(set=set) |
1378 except KeyError: |
1378 except KeyError: |
1379 self._tx.data['tx_uuid'] = uuid = uuid4().hex |
1379 self._cnx.data['tx_uuid'] = uuid = uuid4().hex |
1380 self.repo.system_source.start_undoable_transaction(self, uuid) |
1380 self.repo.system_source.start_undoable_transaction(self, uuid) |
1381 return uuid |
1381 return uuid |
1382 |
1382 |
1383 # querier helpers ######################################################### |
1383 # querier helpers ######################################################### |
1384 |
1384 |
1385 rql_rewriter = tx_attr('_rewriter') |
1385 rql_rewriter = cnx_attr('_rewriter') |
1386 |
1386 |
1387 # deprecated ############################################################### |
1387 # deprecated ############################################################### |
1388 |
1388 |
1389 @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)') |
1389 @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)') |
1390 def schema_rproperty(self, rtype, eidfrom, eidto, rprop): |
1390 def schema_rproperty(self, rtype, eidfrom, eidto, rprop): |
1435 def cnxset(self): |
1435 def cnxset(self): |
1436 """connections set, set according to transaction mode for each query""" |
1436 """connections set, set according to transaction mode for each query""" |
1437 if self.repo.shutting_down: |
1437 if self.repo.shutting_down: |
1438 self.free_cnxset(True) |
1438 self.free_cnxset(True) |
1439 raise ShuttingDown('repository is shutting down') |
1439 raise ShuttingDown('repository is shutting down') |
1440 return self._tx.cnxset |
1440 return self._cnx.cnxset |
1441 |
1441 |
1442 |
1442 |
1443 class InternalManager(object): |
1443 class InternalManager(object): |
1444 """a manager user with all access rights used internally for task such as |
1444 """a manager user with all access rights used internally for task such as |
1445 bootstrapping the repository or creating regular users according to |
1445 bootstrapping the repository or creating regular users according to |