This package contains a generic transaction implementation for Python. It is mainly used by the ZODB.
See http://transaction.readthedocs.org/en/latest for narrative documentation on its usage.
Generic transaction implementation for Python.
License: Other
This package contains a generic transaction implementation for Python. It is mainly used by the ZODB.
See http://transaction.readthedocs.org/en/latest for narrative documentation on its usage.
Transaction 1.4.3 is currently the latest release in PyPI, but the changelog doesn't mention that it has been released, nor what changes it contains.
Perhaps it was released mistakenly?
On building my packages I'm using sphinx-build
command with -n
switch which shows warmings about missing references. These are not critical issues.
Here is the output with warnings:
+ /usr/bin/sphinx-build -n -T -b man docs build/sphinx/man
Running Sphinx v5.0.1
making output directory... done
loading intersphinx inventory from https://docs.python.org/objects.inv...
loading intersphinx inventory from http://www.zodb.org/en/latest/objects.inv...
intersphinx inventory has moved: https://docs.python.org/objects.inv -> https://docs.python.org/3/objects.inv
intersphinx inventory has moved: http://www.zodb.org/en/latest/objects.inv -> https://zodb.org/en/latest/objects.inv
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: [new config] 11 added, 0 changed, 0 removed
reading sources... [100%] sqlalchemy
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
writing... python-transaction.3 { changes convenience doom savepoint hooks datamanager integrations sqlalchemy api developer } /home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/docs/hooks.rst:8: WARNING: unknown document: resourcemanager
/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/docs/hooks.rst:18: WARNING: py:meth reference target not found: addBeforeCommitHook
/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/docs/hooks.rst:184: WARNING: py:meth reference target not found: addAfterCommitHook
/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/src/transaction/interfaces.py:docstring of transaction.interfaces.ITransactionManager:5: WARNING: py:obj reference target not found: __enter__
/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/src/transaction/interfaces.py:docstring of transaction.interfaces.ITransactionManager:5: WARNING: py:obj reference target not found: __exit__
docstring of transaction.interfaces.ITransactionManager.registeredSynchs:1: WARNING: py:obj reference target not found: ISynchronizers
docstring of transaction.interfaces.ITransaction.setExtendedInfo:: WARNING: py:class reference target not found: text
docstring of transaction.interfaces.ITransaction.setExtendedInfo:15: WARNING: py:obj reference target not found: <name, value>
docstring of transaction.interfaces.ITransaction.addBeforeCommitHook:: WARNING: py:class reference target not found: sequence
docstring of transaction.interfaces.IDataManager.sortKey:1: WARNING: py:obj reference target not found: IDataManagers
/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/src/transaction/_manager.py:docstring of transaction._manager.ThreadTransactionManager:1: WARNING: py:class reference target not found: _thread._local
done
build succeeded, 11 warnings.
You can peak on fixes that kind of issues in other projects
latchset/jwcrypto#289
click-contrib/sphinx-click@abc31069
latchset/jwcrypto#289
RDFLib/rdflib-sqlalchemy#95
sissaschool/elementpath@bf869d9e
jaraco/cssutils#21
While working on the mail package, I noticed that a resource could join a transaction multiple times.
I'm not sure this if this is a bug or intended behavior. I couldn't think of any situation where a resource should join a transaction multiple times, so I wanted to report it. If this happens, seemingly random errors appear in the two-phase commit, as the object will have tpc_begin
called for each join.
if there's no reason for multiple joins, it might make sense to raise an exception if something is joined a multiple time -- though this would also require tracking of the raw and wrapped resources ( line 162 ).
https://github.com/zopefoundation/transaction/blob/master/transaction/_transaction.py#L145-L179
I submitted this to the python-transaction mailing list but it didn't get picked up by anyone so I'm posting it here.
tm = transaction.TransactionManager(explicit=True)
with tm:
tm.commit()
The tm.__exit__
raises a NoTransaction
exception.
I think this use case should be considered valid and should not raise a NoTransaction
exception. Specifically the __exit__
should be implemented to squash a NoTransaction
exception or properly check if there is a transaction before calling commit()
or abort()
.
In a web context we want to wrap as much of the request as possible, but we also want to render errors. This causes a circular dependency between the transaction manager and the system that renders errors.
In pyramid_tm the goal is to expose to the underlying code whether it is processing a retryable error and allow it to handle such errors differently if it wants. If we choose to handle the retryable error then we are effectively squashing the exception and thus the transaction.run()
will see it as valid. I'd like to allow pyramid_tm to detect the retryable errors itself and raise exceptions if necessary. This depends on manager._retryable
right now, but I'd be happier about it if the API itself were public.
A public api would make sense as well as it's intended to be an abstraction around retryable errors... without it each error handler in an app needs to check for arbitrary exceptions on a case-by-case basis instead of just checking that they are retryable.
Would manager.is_retryable(error)
work? The error_type
seems redundant to me. I'm happy to make the PR myself.
From: http://jenkins.pylonsproject.org/job/transaction/58/console
Coverage gap:
coverage inst-nodeps: /home/agendaless/jenkins/pylonsproject/jobs/transaction/workspace/.tox/dist/transaction-1.7.0.zip
coverage installed: coverage==4.2,funcsigs==1.0.2,mock==2.0.0,nose==1.3.7,nosexcover==1.0.10,pbr==1.10.0,six==1.10.0,transaction==1.7.0,zope.interface==4.3.2
coverage runtests: PYTHONHASHSEED='1290084312'
coverage runtests: commands[0] | nosetests --with-xunit --with-xcoverage
...................................................................................................................................................................................
Name Stmts Miss Branch BrPart Cover Missing
-------------------------------------------------------------------------
transaction.py 14 0 0 0 100%
transaction/_compat.py 14 0 0 0 100%
transaction/_manager.py 124 0 56 1 99% 181->exit
transaction/_transaction.py 393 0 116 0 100%
transaction/interfaces.py 7 0 0 0 100%
transaction/weakset.py 22 0 4 0 100%
-------------------------------------------------------------------------
TOTAL 574 0 176 1 99%
nose.plugins.cover: ERROR: TOTAL Coverage did not reach minimum required: 100%
Doctest repr:
Document: convenience
---------------------
**********************************************************************
File "convenience.rst", line 30, in default
Failed example:
dm.last_note
Expected:
'test 3'
Got:
u'test 3'
**********************************************************************
1 items had failures:
1 of 8 in default
8 tests in 1 items.
7 passed and 1 failed.
***Test Failed*** 1 failures.
For read-only ZODB connections, I sometimes have the problem that I've accidentally modified the database, causing an error when the transaction commits. Tracking down the offending operation is easy enough, but it requires modifying either transaction
or ZODB
, which is somewhat inconvenient. It would be nice to either have a logger (i.e., as defined in the logging
module) that prints the stack when a transaction is joined. The logger could be disabled by default.
I'm not sure if this is a bug or intended behavior. But the attempts function does work as described in the example code in the documents:
for i in range(3):
try:
with transaction.manager:
... some something ...
except SomeTransientException:
contine
else:
break
Instead it works as:
for i in range(3):
try:
with transaction.manager:
... some something ...
except SomeTransientException:
contine
A successful attempt will just try again, as shown in the following:
import transaction.tests.savepointsample
import transaction
dm = transaction.tests.savepointsample.SampleSavepointDataManager()
with transaction.manager:
dm['ntry'] = 0
ntry = 0
import transaction.interfaces
class Retry(transaction.interfaces.TransientError):
pass
for attempt in transaction.manager.attempts(6):
with attempt as t:
t.note('test')
print("%s %s" % (dm['ntry'], ntry))
ntry += 1
dm['ntry'] = ntry
if ntry % 3:
raise Retry(ntry)
print('current ntry: %s' % dm['ntry'])
The result:
0 0
0 1
0 2
3 3
3 4
3 5
current ntry 6
While if the documentation is to be believed I'd expect a result as follows:
0 0
0 1
0 2
current ntry 3
Thus my question is really what is correct, the documentation or the implementation?
While trying to use transaction.manager as context manager, I hit a buffer instance error with traceback (this is reproducible just by issuing a transaction.commit, too):
Here is a complete example (requires sqlalchemy -reproducible at least with 0.9.4 and 0.7.8), zope-sqlalchemy and transaction:
from sqlalchemy import MetaData, Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
import transaction
from zope.sqlalchemy import ZopeTransactionExtension
S = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
M = MetaData()
B = declarative_base(metadata=M)
engine = create_engine('sqlite:///:memory:', echo=True)
S.configure(bind=engine)
M.bind = engine
class Example(B):
__tablename__ = "example"
id = Column(Integer, primary_key=True)
calc = Column(Integer)
M.create_all(engine)
with transaction.manager:
S.add_all([Example(id=1, calc=10), Example(id=1, calc=11)])
As you can see, we are trying to insert the same id in a primary key column, something that has to raise an error and it does:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-de8e5b3d6d89> in <module>()
1 with transaction.manager:
----> 2 S.add_all([Example(id=1, calc=10), Example(id=1, calc=11)])
3
/home/mariano/Code/SmD/env/local/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.pyc in __exit__(self, t, v, tb)
118 def __exit__(self, t, v, tb):
119 if v is None:
--> 120 self.commit()
121 else:
122 self.abort()
/home/mariano/Code/SmD/env/local/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.pyc in commit(self)
109 """ See ITransactionManager.
110 """
--> 111 return self.get().commit()
112
113 def abort(self):
/home/mariano/Code/SmD/env/local/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.pyc in commit(self)
276 tb = None
277 try:
--> 278 t, v, tb = self._saveAndGetCommitishError()
279 self._callAfterCommitHooks(status=False)
280 reraise(t, v, tb)
/home/mariano/Code/SmD/env/local/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.pyc in _saveAndGetCommitishError(self)
298 t, v, tb = sys.exc_info()
299 # Record how we got into commit().
--> 300 traceback.print_stack(sys._getframe(1), None, ft)
301 # Append the stack entries from here down to the exception.
302 traceback.print_tb(tb, None, ft)
/usr/lib/python2.7/traceback.pyc in print_stack(f, limit, file)
267 except ZeroDivisionError:
268 f = sys.exc_info()[2].tb_frame.f_back
--> 269 print_list(extract_stack(f, limit), file)
270
271 def format_stack(f=None, limit=None):
/usr/lib/python2.7/traceback.pyc in print_list(extracted_list, file)
23 ' File "%s", line %d, in %s' % (filename,lineno,name))
24 if line:
---> 25 _print(file, ' %s' % line.strip())
26
27 def format_list(extracted_list):
/usr/lib/python2.7/traceback.pyc in _print(file, str, terminator)
11
12 def _print(file, str='', terminator='\n'):
---> 13 file.write(str+terminator)
14
15
TypeError: 'unicode' does not have the buffer interface
There's a bunch of compatibility code supporting the old Transaction.register
and DataManager.prepare
APIs. That code is either documented as deprecated, commented as deprecated, or not documented at all. It hasn't been the supported protocol since transaction became its own distribution.
Removing it doesn't break any ZODB tests (except tests that are explicitly using testing the legacy functions).
This works:
>>> tx = tm.get()
>>> tx.status
'Active'
>>> tm.commit()
>>> tx.status
'Committed'
and this does’t:
>>> tx = tm.get()
>>> tx.status
'Active'
>>> tm.abort()
>>> tx.status
'Active'
Looking at the current status code definition:
transaction/transaction/_transaction.py
Lines 53 to 64 in 501a293
then an "Aborted"
status is missing, but I think would be quite sensible and useful to have.
And having said that, it would also make sense to either export the Status
publicly or add is_active()
(etc.) helper functions to the ITransaction interface so that the status of a transaction can be checked conveniently.
python-transaction fails to build on Fedora's python 3.13.
Fedora report:
https://bugzilla.redhat.com/show_bug.cgi?id=2245866
python-transaction fails to build with Python 3.13.0a1.
__________________________________ test_suite __________________________________
def test_suite():
> return unittest.makeSuite(WeakSetTests)
src/transaction/tests/test_weakset.py:140:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
name = 'makeSuite'
def __getattr__(name):
if name == 'IsolatedAsyncioTestCase':
global IsolatedAsyncioTestCase
from .async_case import IsolatedAsyncioTestCase
return IsolatedAsyncioTestCase
> raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
E AttributeError: module 'unittest' has no attribute 'makeSuite'
/usr/lib64/python3.13/unittest/__init__.py:80: AttributeError
=========================== short test summary info ============================
FAILED src/transaction/tests/test_weakset.py::test_suite - AttributeError: mo...
================== 1 failed, 176 passed, 2 warnings in 0.28s ===================
Fedora rawhide (Fedora 41), Python 3.13.0a1.
This is not an issue with transaction
per se, but an evil gotcha that might happen when one is using transaction
with other libraries.
gevent is a coroutine based Python networking library. gevent offers functionality to make stdlib more co-routine friendly by monkey patching stdlib:
>>> import threading
>>> threading.local
<class '_thread._local'>
>>> from gevent import monkey
>>> gevent.monkey.patch_all()
>>> threading.local
<class 'gevent.local.local'>
Transaction has ThreadTransactionManager
class that inherits from threading.local
. If used in an environment where something has run gevent.monkey.patch_all()
ThreadTransactionManager
ceases to function properly and shares transactions across thread boundaries. This may be subtle breakage; one might not run issues until some race condition timing occurs.
I am currently figuring out if gevent
can be made play nicely with transaction
if they co-exist within the same process.
In particular, the _before_commit
hooks are not expected to raise exceptions, and if they do so, leave the transaction in an ambiguous state (commit
not called, hooks not cleared, but one or more hooks may have completed).
See: Pylons/pyramid_tm#63
Somewhat related to #64. Transaction.join
functions the same whether a transaction is aborted or doomed or fully active. Should it allow that? What's the point of adding a resource manager to a transaction that can only be, or already has been, aborted?
On first glance this seems somewhat counterintuitive. I would have expected join
to raise an exception like DoomedTransaction
and AbortedTransaction
instead of just allowing it. Maybe resource managers have already acquired resources before they make the call to join
so it's important to be able to abort
them later? But you can't currently abort transactions more than once, so that seems like an opportunity for bad behivour and leaks. join
could automatically abort resource managers to cover that case.
def join(self, resource):
if self.isAborted() or self.isDoomed():
resource.abort()
raise AbortedTransaction if self.isAborted() else DoomedTransaction
# As before
...
The doom()
documentation says the transaction is otherwise like normal, so for backwards compatibility this might be best limited to aborted transactions.
The chapter called 'Writing a Resource Manager' actually gives an example of implementing an IDataManager
.
The chapter called "Transaction integrations / Data Manager Implementations" describes implementations of IDataManager
(that the previous chapter had called a resource manager).
The chapter called "Writing a Data Manager" I think is talking about ITransactionManager
(except it discusses a prepare()
method that should probably be begin()
).
A customer has an application using ZODB
and zope.sqlalchemy
running on transaction 2.4.0
. I wanted to upgrade it to 3.0
.
In the afterCommitHook
the relational database is accessed via zope.sqlalchemy
, changes are made and committed.
This worked in 2.4 because in tpc_finish
the database session is closed and self._free()
is called before calling the after commit hooks. This way the after commit hook had a clean state to start a new transaction.
transaction 3.0
moved the self._free()
call after calling the after commit hooks, see c76fd72#diff-b36b9bfe40a935a0303d1998fd1e0b8c1a062ced96eb4f92858e3b73409352c7L315-R323.
Using zope.sqlalcemy
in the after commit hook now requires it to join the transaction because in tpc_finish
it closed its session and forgot the knowledge about the zope transaction. But joining a transaction in the status committed
is prohibited.
zope.sqlalchemy
always seems to try to join the zope transaction. If that fails, it already had created the internal SQLAlchemy transaction. So using try-except in this case creates problems later on when SQLAlchemy thinks it does not have to join the zope transaction because it already has is internal transaction object initialized.
Blindly starting a new transaction in the application code used in the after commit hook is no solution as it is also used by code running inside the transaction.
self._free()
earlier intended to change a broken (?) behavior?transaction 2.4 -> 3.0
I am going to assign this to @d-maurer as the author of the change in transaction 3.0
but maybe others have an opinion, too.
It's not used by the transaction package and none of its concern.
I've noticed this in passing
.../.tox/py37/lib/python3.7/site-packages/transaction/_compat.py:48: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
from collections import MutableMapping
We should probably fix it.
I just discovered this in the past hour. I'm running an instance of PyPiCloud via waitress on python 2.7. Any time a request is received, this is the stack trace:
ERROR 2016-11-17 21:45:52,515 [waitress] Exception when serving /
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/waitress/channel.py", line 338, in service
task.service()
File "/usr/local/lib/python2.7/dist-packages/waitress/task.py", line 169, in service
self.execute()
File "/usr/local/lib/python2.7/dist-packages/waitress/task.py", line 399, in execute
app_iter = self.channel.server.application(env, start_response)
File "/usr/local/lib/python2.7/dist-packages/pyramid/router.py", line 236, in __call__
response = self.invoke_subrequest(request, use_tweens=True)
File "/usr/local/lib/python2.7/dist-packages/pyramid/router.py", line 211, in invoke_subrequest
response = handle_request(request)
File "/usr/local/lib/python2.7/dist-packages/pyramid/tweens.py", line 62, in excview_tween
reraise(*attrs['exc_info'])
File "/usr/local/lib/python2.7/dist-packages/pyramid/tweens.py", line 22, in excview_tween
response = handler(request)
File "/usr/local/lib/python2.7/dist-packages/pyramid_tm/__init__.py", line 109, in tm_tween
reraise(*exc_info)
File "/usr/local/lib/python2.7/dist-packages/pyramid_tm/__init__.py", line 85, in tm_tween
t.note(native_(request.path_info, 'utf-8'))
File "/usr/local/lib/python2.7/dist-packages/transaction/_transaction.py", line 539, in note
raise TypeError("Note must be text (unicode)")
TypeError: Note must be text (unicode)
Using version 2.0.2 works. I managed to track it down to a change made as part of the 2.0.3 release:
What would the following do:
with transaction.manager() as t1:
# some logic
with transaction.manager() as t2:
# some logic
The inner transaction.manager would create a nested transaction under t1
Would there be interest in adding support for QA tools (e.g. pylint or black), and perhaps some more CI and automation? See also the python-package-template repo for more details.
I also noticed that this package still has support for Python 3.5 and 3.6 both of which have reached EOL and are no longer supported. Should this package adjust accordingly?
Looks like it is some issue with way of using sphinx
+ /usr/bin/python3 setup.py build_sphinx -b man --build-dir build/sphinx
running build_sphinx
Running Sphinx v4.0.2
Extension error:
Could not import extension repoze.sphinx.autointerface (exception: cannot import name 'force_decode' from 'sphinx.util' (/usr/lib/python3.8/site-packages/sphinx/util/__init__.py))
I know this problem is not within the transaction module, but rather the in the stdlib, but it still may make sense to mitigate this, as this stdlib behavior is probably not going to change too soon. The setup to reproduce this is: Open a zodb server, connect a client, start a transaction, modify something, shutdown the server, try to commit the transaction.
/.../utils/database/database.py in close(self)
366 return
367 try:
--> 368 self.tm.commit()
369 except transaction.interfaces.TransactionFailedError:
370 pass
/usr/local/lib/python2.7/dist-packages/transaction/_manager.pyc in commit(self)
109 """ See ITransactionManager.
110 """
--> 111 return self.get().commit()
112
113 def abort(self):
/usr/local/lib/python2.7/dist-packages/transaction/_transaction.pyc in commit(self)
276 tb = None
277 try:
--> 278 t, v, tb = self._saveAndGetCommitishError()
279 self._callAfterCommitHooks(status=False)
280 reraise(t, v, tb)
/usr/local/lib/python2.7/dist-packages/transaction/_transaction.pyc in _saveAndGetCommitishError(self)
298 t, v, tb = sys.exc_info()
299 # Record how we got into commit().
--> 300 traceback.print_stack(sys._getframe(1), None, ft)
301 # Append the stack entries from here down to the exception.
302 traceback.print_tb(tb, None, ft)
/usr/lib/python2.7/traceback.pyc in print_stack(f, limit, file)
268 except ZeroDivisionError:
269 f = sys.exc_info()[2].tb_frame.f_back
--> 270 print_list(extract_stack(f, limit), file)
271
272 def format_stack(f=None, limit=None):
/usr/lib/python2.7/traceback.pyc in print_list(extracted_list, file)
23 ' File "%s", line %d, in %s' % (filename,lineno,name))
24 if line:
---> 25 _print(file, ' %s' % line.strip())
26
27 def format_list(extracted_list):
/usr/lib/python2.7/traceback.pyc in _print(file, str, terminator)
11
12 def _print(file, str='', terminator='\n'):
---> 13 file.write(str+terminator)
14
15
TypeError: 'unicode' does not have the buffer interface
Here str is a unicode line from the stack trace. Though I'm not totally sure why it is unicode at all.
As discussed in zopefoundation/ZODB#268 (comment), if, through any arbitrarily long chain of imports, a data manager implementation ultimately winds up importing the transaction
module, then when it joins the default transaction there will be a reference cycle (the data manager will reference the transaction
module through __globals__
, which references the transaction manager, which references the transaction which references the data manager).
I'm not sure what, if anything, can be done about this, but if anybody has an idea, that'd be great.
When the number of retries has been exceeded, we don't abort the transaction. We should always abort and then raise.
The run
implementation doesn't account for application code creating new transactions. This is an important use case. Consider an application that catches errors, and needs to record information about the errors transactionally. In this case, the application aborts the current transaction, uses a new transaction to record some information, or otherwise recover. The run
implementation should use the transaction manager methods to do it's work, rather than assuming it can interact with the initial transaction. Currently, an error is raised if the transaction changes.
Hello, can I somehow use transactions with shared transaction.manager
from multiple processes?
For example, I have two processes incrementing counter in for loop:
def worker():
for i in range(0, 10000):
counter.value += 1
And I want commit result of incrementation by transaction like this:
def worker():
with transaction.manager:
for i in range(0,10000):
counter.value += 1
transaction.commit()
But I think this code creates local transaction.manager
for each process. Can I somehow share it?
The order in which resources are committed during a commit can be important. For this reason, this order can be influenced by defining a method sortKey
on the resources. For abort
, the resources are aborted in registration order - independent of sortKey
.
However, the order in which resources are aborted can be important as well. In my case, a Plone portal should be created. During the operation, a ZODB connection and various collective.indexing.transactions.QueueTM
objects register with the transaction (in this order). An error during the portal creation causes a transaction.abort()
which fails, because the connection abort ghostifies the portal's component registry and the collective.indexing.transactions.QueueTM
abort depends on it.
It would be good to have control not only over the commit
order but also over the abort
order of resources. I assume that aborting resources in the inverse order of committing them would most likely give acceptable results.
The manager.run(attempts)
api is awesome. I'd like to use it in pyramid_tm which currently re-implements most of the logic itself. There's one feature that would be really useful which is a public api on the transaction (or the manager) to expose what's happening to underlying code.
Specifically we have some cases where we'd like to do things differently on the last attempt than on earlier attempts that raise retryable exceptions and there's no way to know that we're on the last attempt using the current run
api.
I'd propose something like txn.is_last_attempt
or txn.attempt_index
combined with txn.max_attempts
or txn.attempts_remaining
. This would allow underlying code to make decisions on what it does with retryable exceptions.
I'm happy to work on a PR for this but wanted to get some input on the API first. Personally I prefer txn.is_last_attempt
as I don't have an explicit need to expose the exact number of attempts to underlying code.
Hello,
Since upgrade to version 2.4, i have the following error "'ThreadTransactionManager' object has no attribute '_retryable'". I'm using pyramid, sqlalchemy and pyramid_tm. The problem occurs in my functional tests where I use ThreadTransactionManager instead of Zope transaction manager.
I think that the function "_retryable" has been removed in this pull request #68
Is it a bug or a mistake in the transaction lib or should I modify some thing in my sqlalchemy configuration ?
Thanks,
Julien
Currently there is isDoomed()
to check whether a transaction is doomed.
But there are no methods to check the other status (like Active
or Commit failed
).
To check for these status it is currently necessary to do something like this:
from transaction._transaction import Status
if transaction.get().status == Status.ACTIVE:
pass
It would be nice to
_transaction
(which feels like using internals instead of the API).What do you think?
transaction
currently supports (before and after) commit hooks. It should symmetrically support abort hooks as well.
I need this feature in dm.zodb.asynchronous
, more precisely in its TransactionalScheduler
. This scheduler allows to schedule a function call at transaction commit; it uses a commit hook for this. If the transaction is aborted (instead of committed), a cleanup becomes necessary. This requires a (transaction) abort hook.
I implement this feature currently in dm.transaction.aborthook
via monkey patching. If there is interest, I could implement it instead in transaction
.
What is the recommended way to check whether a custom data manager has joined a transaction’s set of registered data managers?
Currently I am using a transaction’s extensions dictionary, but that feels somewhat flimsy:
tm = transaction.manager # Get the thread-local transaction manager.
try:
tx = tm.get()
except Exception: # transaction.interfaces.NoTransaction
… # No transaction available, do something.
else:
if "SuperDataManager" not in tx.extension:
# See also https://github.com/zopefoundation/transaction/issues/47
dm = SuperDataManager(tm)
tx.join(dm)
tx.setExtendedInfo("SuperDataManager", dm)
else:
dm = tx.extension["SuperDataManager"]
dm.queue_my_data(some_data)
finally:
pass
It seems to me that data managers are stored in a private list:
transaction/transaction/_transaction.py
Line 89 in 501a293
def get_joined(data_manager_type):
"""
Return the data manager of type `data_manager_type` if it has joined this transaction,
or None if it hasn’t.
"""
return next((_ for _ in self._resources if isinstance(_, data_manager_type)), None)
which would simplify the above code:
dm = tx.get_joined(SuperDataManager)
if not dm:
# See also https://github.com/zopefoundation/transaction/issues/47
dm = SuperDataManager(tm)
tx.join(dm)
dm.queue_my_data(some_data)
If I was to make the SuperDataManager
thread-local like the default transaction manager, then I could also compare by the dm’s object id()
directly.
I’ve tried to figure out whether transaction could help me in one of my projects, and the documentation left me completely baffled. It seems to be mostly a bunch of maintainer notes?
Things I noticed as a first-time reader:
This transaction.tests.examples.DataManager class provides a trivial data-manager implementation and docstrings to illustrate the the protocol and to provide a tool for writing tests.
while there isn’t a single docstring beyond Used by the 'datamanager' chapter in the Sphinx docs.
. :)All that to say is that I care because I know that most of the zope libraries a best of breed and it would be a pity if people re-implemented their functionality (poorly), just because they couldn’t figure out how to use them.
I understand this issue is not just one issue, so feel free to handle however you want it.
Hi, I stumbled upon the following comment when digging around as to why I was getting an exception:
# TODO: Should it be possible to join a committing transaction?
# I think some users want it.
I don't know if this is the appropriate forum for this, but I am a user who wants this. I can elaborate on my use case if it matters.
AFAIK it is not possible to specify in setup.cfg in [build_sphinx]
section base path to module.
If I'm right this could be solved by move src/transaction/
to transaction/
+ /usr/bin/python3 setup.py build_sphinx -b man
running build_sphinx
Running Sphinx v4.0.2
Configuration error:
There is a programmable error in your configuration file:
Traceback (most recent call last):
File "/usr/lib/python3.8/site-packages/sphinx/config.py", line 323, in eval_config_file
exec(code, namespace)
File "/home/tkloczko/rpmbuild/BUILD/transaction-3.0.1/docs/conf.py", line 22, in <module>
rqmt = pkg_resources.require('transaction')[0]
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 901, in require
needed = self.resolve(parse_requirements(requirements))
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 787, in resolve
raise DistributionNotFound(req, requirers)
pkg_resources.DistributionNotFound: The 'transaction' distribution was not found and is required by the application
And after copy src/transaction*
to root directory
[tkloczko@barrel transaction-3.0.1]$ /usr/bin/python3 setup.py build_sphinx -b man
running build_sphinx
Running Sphinx v4.0.2
Extension error:
Could not import extension repoze.sphinx.autointerface (exception: No module named 'repoze')
And here is another esmall problem because repoze
module requires on testing transaction
and transaction
requires on building documentation repoze.sphinx.autointerface
(which is part of repoze
).
It is kind of cyrcular dependency :/
IMO it would be good to rewrite transaction
documentation using only plain sphinx
.
manager.begin()
will automatically abort an active transaction. The only way to determine if one is active is via manager._txn is None
. This private api is the only way right now to write a function that starts a new transaction only if one does not already exist. Similarly manager.get()
will create a transaction if one does not exist.
manager.is_active()
maybe?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.