pyramid_tm-0.5/0000775000175000017500000000000011772424610014325 5ustar chrismchrism00000000000000pyramid_tm-0.5/CHANGES.txt0000664000175000017500000001036011772422576016147 0ustar chrismchrism000000000000000.5 (2012-06-26) ---------------- Bug Fixes ~~~~~~~~~ - When a non-retryable exception was raised as the result of a call to ``transaction.manager.commit``, the exception was not reraised properly. Symptom: an unrecoverable exception such as ``Unsupported: Storing blobs in is not supported.`` would be swallowed inappropriately. 0.4 (2012-03-28) ---------------- Bug Fixes ~~~~~~~~~ - Work around failure to retry ConflictError properly at commit time by the ``transaction`` 1.2.0 package. See https://mail.zope.org/pipermail/zodb-dev/2012-March/014603.html for details. Testing ~~~~~~~ - No longer tested under Python 2.5 by ``tox.ini`` (and therefore no longer tested under 2.5 by the Pylons Jenkins server). The package may still work under 2.5, but automated tests will no longer show breakage when it changes in ways that break 2.5 support. - Squash test deprecation warnings under Python 3.2. 0.3 (2011-09-27) ---------------- Features ~~~~~~~~ - The transaction manager has been converted to a Pyramid 1.2 "tween" (instead of an event subscriber). It will be slotted directly "below" the exception view handler, meaning it will have a chance to handle exceptions before they are turned into responses. This means it's best to "raise HTTPFound(...)" instead of "return HTTPFound(...)" if you want an HTTP exception to abort the transaction. - The transaction manager will now retry retryable exceptions (such as a ZODB conflict error) if ``tm.attempts`` is configured to be more than the default of ``1``. See the ``Retrying`` section of the documentation. - Python 3.2 compatibility (requires Pyramid 1.3dev+). Backwards Incompatibilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Incompatible with Pyramid < 1.2a1. Use ``pyramid_tm`` version 0.2 if you need compatibility with an older Pyramid installation. - The ``default_commit_veto`` commit veto callback is no longer configured into the system by default. Use ``tm.commit_veto = pyramid_tm.default_commit_veto`` in the deployment settings to add it. This is for parity with ``repoze.tm2``, which doesn't configure in a commit veto by default either. - The ``default_commit_veto`` no longer checks for the presence of the ``X-Tm-Abort`` header when attempting to figure out whether the transaction should be aborted (although it still checks for the ``X-Tm`` header). Use version 0.2 or a custom commit veto function if your application depends on the ``X-Tm-Abort`` header. - A commit veto is now called with two arguments: ``request`` and ``response``. The ``request`` is the webob request that caused the transaction manager to become active. The ``response`` is the response returned by the Pyramid application. This call signature is incompatible with older versions. The call signature of a ``pyramid_tm`` 0.2 and older commit veto accepted three arguments: ``environ``, ``status``, and ``headers``. If you're using a custom ``commit_veto`` function, you'll need to either convert your existing function to use the new calling convention or use a wrapper to make it compatible with the new calling convention. Here's a simple wrapper function (``bwcompat_commit_veto_wrapper``) that will allow you to use your existing custom commit veto function:: def bwcompat_commit_veto_wrapper(request, response): return my_custom_commit_veto(request.environ, response.status, response.headerlist) Deprecations ~~~~~~~~~~~~ - The ``pyramid_tm.commit_veto`` configuration setting is now canonically spelled as ``tm.commit_veto``. The older spelling will continue to work, but may raise a deprecation error when used. 0.2 (2011-07-18) ---------------- - A new header ``X-Tm`` is now honored by the ``default_commit_veto`` commit veto hook. If this header exists in the headerlist, its value must be a string. If its value is ``commit``, the transaction will be committed regardless of the status code or the value of ``X-Tm-Abort``. If the value of the ``X-Tm`` header is ``abort`` (or any other string value except ``commit``), the transaction will be aborted, regardless of the status code or the value of ``X-Tm-Abort``. 0.1 (2011-02-23) ---------------- - Initial release, based on repoze.tm2 pyramid_tm-0.5/tox.ini0000644000175000017500000000040311734532730015634 0ustar chrismchrism00000000000000[tox] envlist = py26,py27,py32,pypy,cover [testenv] commands = python setup.py test -q [testenv:cover] basepython = python2.6 commands = python setup.py nosetests --with-xunit --with-xcoverage deps = nose coverage nosexcover pyramid_tm-0.5/PKG-INFO0000664000175000017500000001457011772424610015431 0ustar chrismchrism00000000000000Metadata-Version: 1.1 Name: pyramid_tm Version: 0.5 Summary: A package which allows Pyramid requests to join the active transaction Home-page: http://docs.pylonsproject.org Author: Rocky Burt, Chris McDonough Author-email: pylons-devel@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: ``pyramid_tm`` ============== ``pyramid_tm`` is a package which allows Pyramid requests to join the active transaction as provided by the `transaction `_ package. See `http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/ `_ or ``docs/index.rst`` in this distribution for detailed documentation. 0.5 (2012-06-26) ---------------- Bug Fixes ~~~~~~~~~ - When a non-retryable exception was raised as the result of a call to ``transaction.manager.commit``, the exception was not reraised properly. Symptom: an unrecoverable exception such as ``Unsupported: Storing blobs in is not supported.`` would be swallowed inappropriately. 0.4 (2012-03-28) ---------------- Bug Fixes ~~~~~~~~~ - Work around failure to retry ConflictError properly at commit time by the ``transaction`` 1.2.0 package. See https://mail.zope.org/pipermail/zodb-dev/2012-March/014603.html for details. Testing ~~~~~~~ - No longer tested under Python 2.5 by ``tox.ini`` (and therefore no longer tested under 2.5 by the Pylons Jenkins server). The package may still work under 2.5, but automated tests will no longer show breakage when it changes in ways that break 2.5 support. - Squash test deprecation warnings under Python 3.2. 0.3 (2011-09-27) ---------------- Features ~~~~~~~~ - The transaction manager has been converted to a Pyramid 1.2 "tween" (instead of an event subscriber). It will be slotted directly "below" the exception view handler, meaning it will have a chance to handle exceptions before they are turned into responses. This means it's best to "raise HTTPFound(...)" instead of "return HTTPFound(...)" if you want an HTTP exception to abort the transaction. - The transaction manager will now retry retryable exceptions (such as a ZODB conflict error) if ``tm.attempts`` is configured to be more than the default of ``1``. See the ``Retrying`` section of the documentation. - Python 3.2 compatibility (requires Pyramid 1.3dev+). Backwards Incompatibilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Incompatible with Pyramid < 1.2a1. Use ``pyramid_tm`` version 0.2 if you need compatibility with an older Pyramid installation. - The ``default_commit_veto`` commit veto callback is no longer configured into the system by default. Use ``tm.commit_veto = pyramid_tm.default_commit_veto`` in the deployment settings to add it. This is for parity with ``repoze.tm2``, which doesn't configure in a commit veto by default either. - The ``default_commit_veto`` no longer checks for the presence of the ``X-Tm-Abort`` header when attempting to figure out whether the transaction should be aborted (although it still checks for the ``X-Tm`` header). Use version 0.2 or a custom commit veto function if your application depends on the ``X-Tm-Abort`` header. - A commit veto is now called with two arguments: ``request`` and ``response``. The ``request`` is the webob request that caused the transaction manager to become active. The ``response`` is the response returned by the Pyramid application. This call signature is incompatible with older versions. The call signature of a ``pyramid_tm`` 0.2 and older commit veto accepted three arguments: ``environ``, ``status``, and ``headers``. If you're using a custom ``commit_veto`` function, you'll need to either convert your existing function to use the new calling convention or use a wrapper to make it compatible with the new calling convention. Here's a simple wrapper function (``bwcompat_commit_veto_wrapper``) that will allow you to use your existing custom commit veto function:: def bwcompat_commit_veto_wrapper(request, response): return my_custom_commit_veto(request.environ, response.status, response.headerlist) Deprecations ~~~~~~~~~~~~ - The ``pyramid_tm.commit_veto`` configuration setting is now canonically spelled as ``tm.commit_veto``. The older spelling will continue to work, but may raise a deprecation error when used. 0.2 (2011-07-18) ---------------- - A new header ``X-Tm`` is now honored by the ``default_commit_veto`` commit veto hook. If this header exists in the headerlist, its value must be a string. If its value is ``commit``, the transaction will be committed regardless of the status code or the value of ``X-Tm-Abort``. If the value of the ``X-Tm`` header is ``abort`` (or any other string value except ``commit``), the transaction will be aborted, regardless of the status code or the value of ``X-Tm-Abort``. 0.1 (2011-02-23) ---------------- - Initial release, based on repoze.tm2 Keywords: wsgi pylons pyramid transaction Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Framework :: Pyramid Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: License :: Repoze Public License pyramid_tm-0.5/pyramid_tm.egg-info/0000775000175000017500000000000011772424610020164 5ustar chrismchrism00000000000000pyramid_tm-0.5/pyramid_tm.egg-info/not-zip-safe0000644000175000017500000000000111640303533022402 0ustar chrismchrism00000000000000 pyramid_tm-0.5/pyramid_tm.egg-info/SOURCES.txt0000644000175000017500000000071211772424610022046 0ustar chrismchrism00000000000000.gitignore CHANGES.txt CONTRIBUTORS.txt COPYRIGHT.txt LICENSE.txt README.rst setup.cfg setup.py tox.ini docs/.gitignore docs/Makefile docs/api.rst docs/conf.py docs/glossary.rst docs/index.rst pyramid_tm/__init__.py pyramid_tm/compat.py pyramid_tm/tests.py pyramid_tm.egg-info/PKG-INFO pyramid_tm.egg-info/SOURCES.txt pyramid_tm.egg-info/dependency_links.txt pyramid_tm.egg-info/not-zip-safe pyramid_tm.egg-info/requires.txt pyramid_tm.egg-info/top_level.txtpyramid_tm-0.5/pyramid_tm.egg-info/PKG-INFO0000644000175000017500000001457011772424607021274 0ustar chrismchrism00000000000000Metadata-Version: 1.1 Name: pyramid-tm Version: 0.5 Summary: A package which allows Pyramid requests to join the active transaction Home-page: http://docs.pylonsproject.org Author: Rocky Burt, Chris McDonough Author-email: pylons-devel@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: ``pyramid_tm`` ============== ``pyramid_tm`` is a package which allows Pyramid requests to join the active transaction as provided by the `transaction `_ package. See `http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/ `_ or ``docs/index.rst`` in this distribution for detailed documentation. 0.5 (2012-06-26) ---------------- Bug Fixes ~~~~~~~~~ - When a non-retryable exception was raised as the result of a call to ``transaction.manager.commit``, the exception was not reraised properly. Symptom: an unrecoverable exception such as ``Unsupported: Storing blobs in is not supported.`` would be swallowed inappropriately. 0.4 (2012-03-28) ---------------- Bug Fixes ~~~~~~~~~ - Work around failure to retry ConflictError properly at commit time by the ``transaction`` 1.2.0 package. See https://mail.zope.org/pipermail/zodb-dev/2012-March/014603.html for details. Testing ~~~~~~~ - No longer tested under Python 2.5 by ``tox.ini`` (and therefore no longer tested under 2.5 by the Pylons Jenkins server). The package may still work under 2.5, but automated tests will no longer show breakage when it changes in ways that break 2.5 support. - Squash test deprecation warnings under Python 3.2. 0.3 (2011-09-27) ---------------- Features ~~~~~~~~ - The transaction manager has been converted to a Pyramid 1.2 "tween" (instead of an event subscriber). It will be slotted directly "below" the exception view handler, meaning it will have a chance to handle exceptions before they are turned into responses. This means it's best to "raise HTTPFound(...)" instead of "return HTTPFound(...)" if you want an HTTP exception to abort the transaction. - The transaction manager will now retry retryable exceptions (such as a ZODB conflict error) if ``tm.attempts`` is configured to be more than the default of ``1``. See the ``Retrying`` section of the documentation. - Python 3.2 compatibility (requires Pyramid 1.3dev+). Backwards Incompatibilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Incompatible with Pyramid < 1.2a1. Use ``pyramid_tm`` version 0.2 if you need compatibility with an older Pyramid installation. - The ``default_commit_veto`` commit veto callback is no longer configured into the system by default. Use ``tm.commit_veto = pyramid_tm.default_commit_veto`` in the deployment settings to add it. This is for parity with ``repoze.tm2``, which doesn't configure in a commit veto by default either. - The ``default_commit_veto`` no longer checks for the presence of the ``X-Tm-Abort`` header when attempting to figure out whether the transaction should be aborted (although it still checks for the ``X-Tm`` header). Use version 0.2 or a custom commit veto function if your application depends on the ``X-Tm-Abort`` header. - A commit veto is now called with two arguments: ``request`` and ``response``. The ``request`` is the webob request that caused the transaction manager to become active. The ``response`` is the response returned by the Pyramid application. This call signature is incompatible with older versions. The call signature of a ``pyramid_tm`` 0.2 and older commit veto accepted three arguments: ``environ``, ``status``, and ``headers``. If you're using a custom ``commit_veto`` function, you'll need to either convert your existing function to use the new calling convention or use a wrapper to make it compatible with the new calling convention. Here's a simple wrapper function (``bwcompat_commit_veto_wrapper``) that will allow you to use your existing custom commit veto function:: def bwcompat_commit_veto_wrapper(request, response): return my_custom_commit_veto(request.environ, response.status, response.headerlist) Deprecations ~~~~~~~~~~~~ - The ``pyramid_tm.commit_veto`` configuration setting is now canonically spelled as ``tm.commit_veto``. The older spelling will continue to work, but may raise a deprecation error when used. 0.2 (2011-07-18) ---------------- - A new header ``X-Tm`` is now honored by the ``default_commit_veto`` commit veto hook. If this header exists in the headerlist, its value must be a string. If its value is ``commit``, the transaction will be committed regardless of the status code or the value of ``X-Tm-Abort``. If the value of the ``X-Tm`` header is ``abort`` (or any other string value except ``commit``), the transaction will be aborted, regardless of the status code or the value of ``X-Tm-Abort``. 0.1 (2011-02-23) ---------------- - Initial release, based on repoze.tm2 Keywords: wsgi pylons pyramid transaction Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Framework :: Pyramid Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: License :: Repoze Public License pyramid_tm-0.5/pyramid_tm.egg-info/top_level.txt0000644000175000017500000000001311772424607022714 0ustar chrismchrism00000000000000pyramid_tm pyramid_tm-0.5/pyramid_tm.egg-info/dependency_links.txt0000644000175000017500000000000111772424607024236 0ustar chrismchrism00000000000000 pyramid_tm-0.5/pyramid_tm.egg-info/requires.txt0000644000175000017500000000010311772424607022562 0ustar chrismchrism00000000000000pyramid>=1.2dev transaction [docs] Sphinx [testing] nose coveragepyramid_tm-0.5/LICENSE.txt0000644000175000017500000000326711530772254016160 0ustar chrismchrism00000000000000A copyright notice accompanies this license document that identifies the copyright holders. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyramid_tm-0.5/CONTRIBUTORS.txt0000644000175000017500000001072311530772254017026 0ustar chrismchrism00000000000000Pylons Project Contributor Agreement ==================================== The submitter agrees by adding his or her name within the section below named "Contributors" and submitting the resulting modified document to the canonical shared repository location for this software project (whether directly, as a user with "direct commit access", or via a "pull request"), he or she is signing a contract electronically. The submitter becomes a Contributor after a) he or she signs this document by adding their name beneath the "Contributors" section below, and b) the resulting document is accepted into the canonical version control repository. Treatment of Account --------------------- Contributor will not allow anyone other than the Contributor to use his or her username or source repository login to submit code to a Pylons Project source repository. Should Contributor become aware of any such use, Contributor will immediately by notifying Agendaless Consulting. Notification must be performed by sending an email to webmaster@agendaless.com. Until such notice is received, Contributor will be presumed to have taken all actions made through Contributor's account. If the Contributor has direct commit access, Agendaless Consulting will have complete control and discretion over capabilities assigned to Contributor's account, and may disable Contributor's account for any reason at any time. Legal Effect of Contribution ---------------------------- Upon submitting a change or new work to a Pylons Project source Repository (a "Contribution"), you agree to assign, and hereby do assign, a one-half interest of all right, title and interest in and to copyright and other intellectual property rights with respect to your new and original portions of the Contribution to Agendaless Consulting. You and Agendaless Consulting each agree that the other shall be free to exercise any and all exclusive rights in and to the Contribution, without accounting to one another, including without limitation, the right to license the Contribution to others under the Repoze Public License. This agreement shall run with title to the Contribution. Agendaless Consulting does not convey to you any right, title or interest in or to the Program or such portions of the Contribution that were taken from the Program. Your transmission of a submission to the Pylons Project source Repository and marks of identification concerning the Contribution itself constitute your intent to contribute and your assignment of the work in accordance with the provisions of this Agreement. License Terms ------------- Code committed to the Pylons Project source repository (Committed Code) must be governed by the Repoze Public License (http://repoze.org/LICENSE.txt, aka "the RPL") or another license acceptable to Agendaless Consulting. Until Agendaless Consulting declares in writing an acceptable license other than the RPL, only the RPL shall be used. A list of exceptions is detailed within the "Licensing Exceptions" section of this document, if one exists. Representations, Warranty, and Indemnification ---------------------------------------------- Contributor represents and warrants that the Committed Code does not violate the rights of any person or entity, and that the Contributor has legal authority to enter into this Agreement and legal authority over Contributed Code. Further, Contributor indemnifies Agendaless Consulting against violations. Cryptography ------------ Contributor understands that cryptographic code may be subject to government regulations with which Agendaless Consulting and/or entities using Committed Code must comply. Any code which contains any of the items listed below must not be checked-in until Agendaless Consulting staff has been notified and has approved such contribution in writing. - Cryptographic capabilities or features - Calls to cryptographic features - User interface elements which provide context relating to cryptography - Code which may, under casual inspection, appear to be cryptographic. Notices ------- Contributor confirms that any notices required will be included in any Committed Code. List of Contributors ==================== The below-signed are contributors to a code repository that is part of the project named "pyramid_tm". Each below-signed contributor has read, understand and agrees to the terms above in the section within this document entitled "Pylons Project Contributor Agreement" as of the date beside his or her name. Contributors ------------ - Rocky Burt, 2011/02/21 pyramid_tm-0.5/.gitignore0000664000175000017500000000017711772422450016322 0ustar chrismchrism00000000000000*.egg *.egg-info *.pyc *$py.class *.pt.py *.txt.py *~ .coverage build/ dist/ .tox/ nosetests.xml pyramid_tm/coverage.xml env*/ pyramid_tm-0.5/README.rst0000644000175000017500000000063111772423727016022 0ustar chrismchrism00000000000000``pyramid_tm`` ============== ``pyramid_tm`` is a package which allows Pyramid requests to join the active transaction as provided by the `transaction `_ package. See `http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/ `_ or ``docs/index.rst`` in this distribution for detailed documentation. pyramid_tm-0.5/pyramid_tm/0000775000175000017500000000000011772424610016472 5ustar chrismchrism00000000000000pyramid_tm-0.5/pyramid_tm/tests.py0000664000175000017500000002476511772422450020224 0ustar chrismchrism00000000000000import unittest from transaction import TransactionManager class TestDefaultCommitVeto(unittest.TestCase): def _callFUT(self, response, request=None): from pyramid_tm import default_commit_veto return default_commit_veto(request, response) def test_it_true_500(self): response = DummyResponse('500 Server Error') self.assertTrue(self._callFUT(response)) def test_it_true_503(self): response = DummyResponse('503 Service Unavailable') self.assertTrue(self._callFUT(response)) def test_it_true_400(self): response = DummyResponse('400 Bad Request') self.assertTrue(self._callFUT(response)) def test_it_true_411(self): response = DummyResponse('411 Length Required') self.assertTrue(self._callFUT(response)) def test_it_false_200(self): response = DummyResponse('200 OK') self.assertFalse(self._callFUT(response)) def test_it_false_201(self): response = DummyResponse('201 Created') self.assertFalse(self._callFUT(response)) def test_it_false_301(self): response = DummyResponse('301 Moved Permanently') self.assertFalse(self._callFUT(response)) def test_it_false_302(self): response = DummyResponse('302 Found') self.assertFalse(self._callFUT(response)) def test_it_false_x_tm_commit(self): response = DummyResponse('200 OK', {'x-tm':'commit'}) self.assertFalse(self._callFUT(response)) def test_it_true_x_tm_abort(self): response = DummyResponse('200 OK', {'x-tm':'abort'}) self.assertTrue(self._callFUT(response)) def test_it_true_x_tm_anythingelse(self): response = DummyResponse('200 OK', {'x-tm':''}) self.assertTrue(self._callFUT(response)) class Test_tm_tween_factory(unittest.TestCase): def setUp(self): self.txn = DummyTransaction() self.request = DummyRequest() self.response = DummyResponse() self.registry = DummyRegistry() def _callFUT(self, handler=None, registry=None, request=None, txn=None): if handler is None: def handler(request): return self.response if registry is None: registry = self.registry if request is None: request = self.request if txn is None: txn = self.txn from pyramid_tm import tm_tween_factory factory = tm_tween_factory(handler, registry, txn) return factory(request) def test_repoze_tm_active(self): request = DummyRequest() request.environ['repoze.tm.active'] = True result = self._callFUT(request=request) self.assertEqual(result, self.response) self.assertFalse(self.txn.began) def test_handler_exception(self): def handler(request): raise NotImplementedError self.assertRaises(NotImplementedError, self._callFUT, handler=handler) self.assertTrue(self.txn.began) self.assertTrue(self.txn.aborted) self.assertFalse(self.txn.committed) def test_handler_retryable_exception(self): from transaction.interfaces import TransientError class Conflict(TransientError): pass count = [] response = DummyResponse() self.registry.settings['tm.attempts'] = '3' def handler(request, count=count): count.append(True) if len(count) == 3: return response raise Conflict result = self._callFUT(handler=handler) self.assertTrue(self.txn.began) self.assertEqual(self.txn.committed, 1) self.assertEqual(self.txn.aborted, 2) self.assertEqual(self.request.made_seekable, 3) self.assertEqual(result, response) def test_handler_retryable_exception_defaults_to_1(self): from transaction.interfaces import TransientError class Conflict(TransientError): pass count = [] def handler(request, count=count): raise Conflict self.assertRaises(Conflict, self._callFUT, handler=handler) def test_handler_isdoomed(self): txn = DummyTransaction(True) self._callFUT(txn=txn) self.assertTrue(txn.began) self.assertTrue(txn.aborted) self.assertFalse(txn.committed) def test_500_without_commit_veto(self): response = DummyResponse() response.status = '500 Bad Request' def handler(request): return response result = self._callFUT(handler=handler) self.assertEqual(result, response) self.assertTrue(self.txn.began) self.assertFalse(self.txn.aborted) self.assertTrue(self.txn.committed) def test_500_with_default_commit_veto(self): settings = self.registry.settings settings['tm.commit_veto'] = 'pyramid_tm.default_commit_veto' response = DummyResponse() response.status = '500 Bad Request' def handler(request): return response result = self._callFUT(handler=handler) self.assertEqual(result, response) self.assertTrue(self.txn.began) self.assertTrue(self.txn.aborted) self.assertFalse(self.txn.committed) def test_null_commit_veto(self): response = DummyResponse() response.status = '500 Bad Request' def handler(request): return response registry = DummyRegistry({'tm.commit_veto':None}) result = self._callFUT(handler=handler, registry=registry) self.assertEqual(result, response) self.assertTrue(self.txn.began) self.assertFalse(self.txn.aborted) self.assertTrue(self.txn.committed) def test_commit_veto_true(self): registry = DummyRegistry( {'tm.commit_veto':'pyramid_tm.tests.veto_true'}) result = self._callFUT(registry=registry) self.assertEqual(result, self.response) self.assertTrue(self.txn.began) self.assertTrue(self.txn.aborted) self.assertFalse(self.txn.committed) def test_commit_veto_false(self): registry = DummyRegistry( {'tm.commit_veto':'pyramid_tm.tests.veto_false'}) result = self._callFUT(registry=registry) self.assertEqual(result, self.response) self.assertTrue(self.txn.began) self.assertFalse(self.txn.aborted) self.assertTrue(self.txn.committed) def test_commitonly(self): result = self._callFUT() self.assertEqual(result, self.response) self.assertTrue(self.txn.began) self.assertFalse(self.txn.aborted) self.assertTrue(self.txn.committed) def test_commit_veto_alias(self): registry = DummyRegistry( {'pyramid_tm.commit_veto':'pyramid_tm.tests.veto_true'}) result = self._callFUT(registry=registry) self.assertEqual(result, self.response) self.assertTrue(self.txn.began) self.assertTrue(self.txn.aborted) self.assertFalse(self.txn.committed) def veto_true(request, response): return True def veto_false(request, response): return False class Test_includeme(unittest.TestCase): def test_it(self): from pyramid.tweens import EXCVIEW from pyramid_tm import includeme config = DummyConfig() includeme(config) self.assertEqual(config.tweens, [('pyramid_tm.tm_tween_factory', EXCVIEW, None)]) class TestAttempt(unittest.TestCase): def _makeOne(self, manager): from pyramid_tm import Attempt return Attempt(manager) def test___enter__(self): manager = DummyManager() inst = self._makeOne(manager) self.assertTrue(inst.__enter__() is manager) def test_exit_v_is_not_None(self): manager = DummyManager(retryable='abc') inst = self._makeOne(manager) result = inst.__exit__(None, 'f', None) self.assertTrue(manager.aborted) self.assertEqual(result, 'abc') def test_exit_v_is_None_commit_does_not_raise(self): manager = DummyManager(retryable='abc') inst = self._makeOne(manager) result = inst.__exit__(None, None, None) self.assertTrue(manager.committed) self.assertEqual(result, None) def test_exit_v_is_None_commit_raises_retryable(self): manager = DummyManager(toraise=ValueError, retryable='abc') inst = self._makeOne(manager) result = inst.__exit__(None, None, None) self.assertTrue(manager.aborted) self.assertEqual(result, 'abc') def test_exit_v_is_None_commit_raises_nonretryable(self): manager = DummyManager(toraise=ValueError, retryable=False) inst = self._makeOne(manager) self.assertRaises(ValueError, inst.__exit__, None, None, None) self.assertTrue(manager.aborted) class DummyManager(object): def __init__(self, toraise=None, retryable=False): self.toraise = toraise self.retryable = retryable def __enter__(self): return self def commit(self): if self.toraise: raise self.toraise self.committed = True def abort(self): self.aborted = True def _retryable(self, t, v): return self.retryable class Dummy(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) class DummyRegistry(object): def __init__(self, settings=None): if settings is None: settings = {} self.settings = settings class DummyTransaction(TransactionManager): began = False committed = False aborted = False _resources = [] def __init__(self, doomed=False): self.doomed = doomed self.began = 0 self.committed = 0 self.aborted = 0 @property def manager(self): return self def isDoomed(self): return self.doomed def begin(self): self.began+=1 return self def commit(self): self.committed+=1 def abort(self): self.aborted+=1 class DummyRequest(object): def __init__(self): self.environ = {} self.made_seekable = 0 def make_body_seekable(self): self.made_seekable += 1 class DummyResponse(object): def __init__(self, status='200 OK', headers=None): self.status = status if headers is None: headers = {} self.headers = headers class DummyConfig(object): def __init__(self): self.registry = Dummy(settings={}) self.tweens = [] def add_tween(self, x, under=None, over=None): self.tweens.append((x, under, over)) pyramid_tm-0.5/pyramid_tm/__init__.py0000664000175000017500000001163411772422450020610 0ustar chrismchrism00000000000000from __future__ import with_statement import sys import transaction from pyramid.util import DottedNameResolver from pyramid.tweens import EXCVIEW from pyramid_tm.compat import reraise resolver = DottedNameResolver(None) def default_commit_veto(request, response): """ When used as a commit veto, the logic in this function will cause the transaction to be aborted if: - An ``X-Tm`` response header with the value ``abort`` (or any value other than ``commit``) exists. - The response status code starts with ``4`` or ``5``. Otherwise the transaction will be allowed to commit. """ xtm = response.headers.get('x-tm') if xtm is not None: if xtm == 'commit': return False return True status = response.status for bad in ('4', '5'): if status.startswith(bad): return True return False class AbortResponse(Exception): def __init__(self, response): self.response = response # work around broken "attempts" method of TransactionManager in transaction # 1.2.0 def _attempts(manager, number=3): assert number > 0 while number: number -= 1 if number: yield Attempt(manager) else: yield manager class Attempt(object): def __init__(self, manager): self.manager = manager def _retry_or_raise(self, t, v, tb): retry = self.manager._retryable(t, v) self.manager.abort() if retry: return retry # suppress the exception if necessary reraise(t, v, tb) # otherwise reraise the exception def __enter__(self): return self.manager.__enter__() def __exit__(self, t, v, tb): if v is None: try: self.manager.commit() except: # this is what transaction 1.2.0 doesn't do (it doesn't # suppress retryable exceptions raised by a commit) return self._retry_or_raise(*sys.exc_info()) else: return self._retry_or_raise(t, v, tb) def tm_tween_factory(handler, registry, transaction=transaction): # transaction parameterized for testing purposes old_commit_veto = registry.settings.get('pyramid_tm.commit_veto', None) commit_veto = registry.settings.get('tm.commit_veto', old_commit_veto) attempts = int(registry.settings.get('tm.attempts', 1)) if not commit_veto: commit_veto = None else: commit_veto = resolver.maybe_resolve(commit_veto) def tm_tween(request): if 'repoze.tm.active' in request.environ: # don't handle txn mgmt if repoze.tm is in the WSGI pipeline return handler(request) try: for attempt in _attempts(transaction.manager, attempts): with attempt as t: # make_body_seekable will copy wsgi.input if necessary, # otherwise it will rewind the copy to position zero if attempts != 1: request.make_body_seekable() response = handler(request) if t.isDoomed(): raise AbortResponse(response) if commit_veto is not None: veto = commit_veto(request, response) if veto: raise AbortResponse(response) return response except AbortResponse: e = sys.exc_info()[1] # py2.5-py3 compat return e.response return tm_tween def includeme(config): """ Set up am implicit 'tween' to do transaction management using the ``transaction`` package. The tween will be slotted between the main Pyramid app and the Pyramid exception view handler. For every request it handles, the tween will begin a transaction by calling ``transaction.begin()``, and will then call the downstream handler (usually the main Pyramid application request handler) to obtain a response. When attempting to call the downstream handler: - If an exception is raised by downstream handler while attempting to obtain a response, the transaction will be rolled back (``transaction.abort()`` will be called). - If no exception is raised by the downstream handler, but the transaction is doomed (``transaction.doom()`` has been called), the transaction will be rolled back. - If the deployment configuration specifies a ``tm.commit_veto`` setting, and the transaction management tween receives a response from the downstream handler, the commit veto hook will be called. If it returns True, the transaction will be rolled back. If it returns False, the transaction will be committed. - If none of the above conditions are True, the transaction will be committed (via ``transaction.commit()``). """ config.add_tween('pyramid_tm.tm_tween_factory', under=EXCVIEW) pyramid_tm-0.5/pyramid_tm/compat.py0000664000175000017500000000135311772422450020331 0ustar chrismchrism00000000000000import sys PY3 = sys.version_info[0] == 3 if PY3: # pragma: no cover import builtins exec_ = getattr(builtins, "exec") def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value else: # pragma: no cover def exec_(code, globs=None, locs=None): """Execute code in a namespace.""" if globs is None: frame = sys._getframe(1) globs = frame.f_globals if locs is None: locs = frame.f_locals del frame elif locs is None: locs = globs exec("""exec code in globs, locs""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) pyramid_tm-0.5/setup.py0000664000175000017500000000440411772422673016050 0ustar chrismchrism00000000000000############################################################################## # # Copyright (c) 2008-2011 Agendaless Consulting and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE # ############################################################################## import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) try: README = open(os.path.join(here, 'README.rst')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() except IOError: README = CHANGES = '' install_requires = [ 'pyramid>=1.2dev', 'transaction', ] testing_extras = ['nose', 'coverage'] docs_extras = ['Sphinx'] setup(name='pyramid_tm', version='0.5', description=('A package which allows Pyramid requests to join the ' 'active transaction'), long_description=README + '\n\n' + CHANGES, classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP :: WSGI", "License :: Repoze Public License", ], keywords='wsgi pylons pyramid transaction', author="Rocky Burt, Chris McDonough", author_email="pylons-devel@googlegroups.com", url="http://docs.pylonsproject.org", license="BSD-derived (http://www.repoze.org/LICENSE.txt)", packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=install_requires, tests_require=install_requires, test_suite="pyramid_tm", entry_points='', extras_require = { 'testing':testing_extras, 'docs':docs_extras, }, ) pyramid_tm-0.5/COPYRIGHT.txt0000644000175000017500000000016111530772254016434 0ustar chrismchrism00000000000000Copyright (c) 2008-2011 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved pyramid_tm-0.5/setup.cfg0000644000175000017500000000045011772424610016143 0ustar chrismchrism00000000000000[easy_install] zip_ok = false [nosetests] match = ^test where = pyramid_tm nocapture = 1 cover-package = pyramid_tm cover-erase = 1 [aliases] dev = develop easy_install pyramid_tm[testing] docs = develop easy_install pyramid_tm[docs] [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pyramid_tm-0.5/docs/0000775000175000017500000000000011772424610015255 5ustar chrismchrism00000000000000pyramid_tm-0.5/docs/glossary.rst0000644000175000017500000000056111530772254017654 0ustar chrismchrism00000000000000.. _glossary: Glossary ======== .. glossary:: :sorted: Pyramid A `web framework `_. transaction A database transaction comprises a unit of work performed within a database management system. Within this context it is also the name of a `Python distribution `_. pyramid_tm-0.5/docs/Makefile0000644000175000017500000000521511734533512016716 0ustar chrismchrism00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -W SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files (usable by e.g. sphinx-web)" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf _build/* html: mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." text: mkdir -p _build/text _build/doctrees $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text @echo @echo "Build finished. The HTML pages are in _build/text." pickle: mkdir -p _build/pickle _build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files or run" @echo " sphinx-web _build/pickle" @echo "to start the sphinx-web server." web: pickle htmlhelp: mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." latex: mkdir -p _build/latex _build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex cp _static/*.png _build/latex ./convert_images.sh cp _static/latex-warning.png _build/latex cp _static/latex-note.png _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p _build/changes _build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: mkdir -p _build/linkcheck _build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub @echo @echo "Build finished. The epub file is in _build/epub." pyramid_tm-0.5/docs/conf.py0000644000175000017500000001562411772422702016562 0ustar chrismchrism00000000000000# -*- coding: utf-8 -*- # # pyramid_tm documentation build configuration file # # This file is execfile()d with the current directory set to its containing # dir. # # The contents of this file are pickled, so don't put values in the # namespace that aren't pickleable (module imports are okay, they're # removed automatically). # # All configuration values have a default value; values that are commented # out serve to show the default value. # If your extensions are in another directory, add it here. If the # directory is relative to the documentation root, use os.path.abspath to # make it absolute, like shown here. #sys.path.append(os.path.abspath('some/directory')) import sys, os # Add and use Pylons theme if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers from subprocess import call, Popen, PIPE p = Popen('which git', shell=True, stdout=PIPE) git = p.stdout.read().strip() cwd = os.getcwd() _themes = os.path.join(cwd, '_themes') if not os.path.isdir(_themes): call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git', '_themes']) else: os.chdir(_themes) call([git, 'checkout', 'master']) call([git, 'pull']) os.chdir(cwd) sys.path.append(os.path.abspath('_themes')) parent = os.path.dirname(os.path.dirname(__file__)) sys.path.append(os.path.abspath(parent)) wd = os.getcwd() os.chdir(parent) os.system('%s setup.py test -q' % sys.executable) os.chdir(wd) for item in os.listdir(parent): if item.endswith('.egg'): sys.path.append(os.path.join(parent, item)) html_theme_path = ['_themes'] html_theme = 'pyramid' html_theme_options = dict(github_url='http://github.com/Pylons/pyramid_tm') # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', ] # Looks for pyramid's objects intersphinx_mapping = { 'pyramid': ('http://docs.pylonsproject.org/projects/pyramid/dev/', None)} # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General substitutions. project = 'pyramid_tm' copyright = '2011, Agendaless Consulting ' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. release = version # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directories, that shouldn't be # searched for source files. #exclude_dirs = [] exclude_patterns = ['_themes/README.rst',] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # Add and use Pylons theme # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. # html_style = 'repoze.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. #html_short_title = None # The name of an image file (within the static path) to place at the top of # the sidebar. # html_logo = '.static/logo_hi.gif' # The name of an image file (within the static path) to use as favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or # 32x32 pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". #html_static_path = ['.static'] # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as # _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option must # be the base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'atemplatedoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, document class [howto/manual]). latex_documents = [ ('index', 'pyramid_tm.tex', 'pyramid_tm Documentation', 'Repoze Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the # top of the title page. latex_logo = '.static/logo_hi.gif' # For "manual" documents, if this is true, then toplevel headings are # parts, not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True pyramid_tm-0.5/docs/.gitignore0000644000175000017500000000003111734534002017230 0ustar chrismchrism00000000000000_build/ _themes/ .static pyramid_tm-0.5/docs/index.rst0000644000175000017500000001734011636453703017125 0ustar chrismchrism00000000000000pyramid_tm ========== Overview -------- ``pyramid_tm`` is a package which allows :term:`Pyramid` requests to join the active :term:`transaction` as provided by the :term:`transaction` package. Installation ------------ Install using setuptools, e.g. (within a virtualenv):: $ easy_install pyramid_tm Setup ----- Once ``pyramid_tm`` is installed, you must use the ``config.include`` mechanism to include it into your Pyramid project's configuration. In your Pyramid project's ``__init__.py``: .. code-block:: python :linenos: config = Configurator(.....) config.include('pyramid_tm') Or use the ``pyramid.includes`` configuration setting in your ``.ini`` file: .. code-block:: ini :linenos: [app:myapp] pyramid.includes = pyramid_tm After the package is included, whenever a new request enters the application, a new transaction is associated with that request. .. note:: When the ``repoze.tm`` or ``repoze.tm2`` middleware is in the WSGI pipeline, ``pyramid_tm`` becomes inactive. :term:`transaction` Usage ------------------------- At the beginning of a request a new :term:`transaction` is started using the ``transaction.begin()`` function. Once the request has finished all of it's works (ie views have finished running), a few checks are tested: 1) Did some a transaction.doom() cause the transaction to become "doomed"? if so, ``transaction.abort()``. 2) Did an exception occur in the underlying code? if so, ``transaction.abort()`` 3) If the ``tm.commit_veto`` configuration setting was used, did the commit veto callback, called with the response generated by the application, return a result that evaluates to ``True``? if so, ``transaction.abort()``. If none of these checks calls ``transaction.abort()`` then the transaction is instead committed using ``transaction.commit()``. By itself, this :term:`transaction` machinery doesn't do much. It is up to third-party code to *join* the active transaction to benefit. See `repoze.filesafe `_ for an example of how files creation can be committed or rolled back based on :term:`transaction` and the `pyramid_mailer `_ package to see how you can prevent emails from being sent until a transaction succeeeds. ZODB database connections are automatically joined to the transaction, as well as SQLAlchemy connections which are configured with the ``ZopeTransactionExtension`` extension. Adding a Commit Veto Hook ------------------------- It is possible to configure ``pyramid_tm`` with a "commit veto" hook. The commit veto hook receives the request and the response. It can examine both of them, and return ``True`` if the transaction should be vetoed. If the transaction is vetoed, it will be aborted instead of committed. By default, ``pyramid_tm`` does not configure a commit veto into the system; you must do it explicitly. :mod:`pyramid_tm` contains a :func:`pyramid_tm.default_commit_veto` that is suitable for use when you want to abort when the response's status code indicates non-success or if you'd like to signal that the transaction should be aborted or committed using a response header. The default commit veto vetoes a commit if the status code starts with ``4`` or ``5`` or there is a ``X-Tm`` response header with a value that does not equal ``commit``. .. code-block:: python :linenos: def default_commit_veto(request, response): xtm = response.headers.get('x-tm') if xtm is not None: if xtm == 'commit': return False return True status = response.status for bad in ('4', '5'): if status.startswith(bad): return True return False If you'd like to use this commit veto in your system, you can do it via Python: .. code-block:: python :linenos: from pyramid.config import Configurator def app(global_conf, settings): settings['tm.commit_veto'] = 'pyramid_tm.default_commit_veto' config = Configurator(settings=settings) config.include('pyramid_tm') Or via PasteDeploy: .. code-block:: ini :linenos: [app:myapp] tm.commit_veto = pyramid_tm.default_commit_veto If you'd like to use a different "commit veto" callback, create a function with the same signature (``request``, ``response``) and return value (``True`` or ``False``), then pass a ``tm.commit_veto`` key/value pair in your settings which points at the Python dotted name of this commit veto. Via Python: .. code-block:: python :linenos: from pyramid.config import Configurator def app(global_conf, settings): settings['tm.commit_veto'] = 'my.package.commit_veto' config = Configurator(settings=settings) config.include('pyramid_tm') Via PasteDeploy: .. code-block:: ini :linenos: [app:myapp] tm.commit_veto = my.package.commit_veto In the PasteDeploy example, the path is a Python dotted name, where the dots separate module and package names, and the colon separates a module from its contents. In the above example, the code would be implemented as a "commit_veto" function which lives in the "package" submodule of the "my" package. Retrying -------- When the transaction manager calls the downstream handler, if the handler raises a "retryable" exception, the transaction manager can be configured to attempt to call the downstream handler again with the same request, in effect "replaying" the request. By default, retrying is turned off. To turn it on, use the ``tm.attempts`` configuration setting. By default this setting is ``1``, meaning only one attempt will be tried, and no retry will happen even if a retryable error is raised by the handler. But if the value, for example, is set to ``3``, the following set of events might happen. - The first attempt to call the handler raises a retryable exception; a second attempt will be tried. - The second attempt raises a retryable exception, the transaction manager will try the request again one more time. - The third attempt also raises a retryable exception, at this point all attempts are used up and the "retryable" exception will be raised to its caller. Or this might happen: - The first attempt to call the handler raises a retryable exception; a second attempt will be tried. - The second attempt returns a response without raising any exception. - The response is returned to the caller. Retryable exceptions include ```ZODB.POSException.ConflictError``, and certain exceptions raised by various data managers, such as ``psycopg2.extensions.TransactionRollbackError``, ``cx_Oracle.DatabaseError`` where the exception's code is 8877. Any exception which inherits from ``transaction.interfaces.TransientError`` will be treated with retry behavior. Explicit Tween Configuration ---------------------------- Note that the transaction manager is a Pyramid "tween", and it can be used in the explicit tween list if its implicit position in the tween chain is incorrect (see the output of ``paster ptweens``):: [app:myapp] pyramid.tweens = someothertween pyramid.tweens.excview_tween_factory pyramid_tm.tm_tween_factory It usually belongs directly above the "MAIN" entry in the ``paster ptweens`` output, and will attempt to sort there by default as the result of having ``include('pyramid_tm')`` invoked. More Information ---------------- .. toctree:: :maxdepth: 1 api.rst glossary.rst Reporting Bugs / Development Versions ------------------------------------- Visit http://github.com/Pylons/pyramid_tm to download development or tagged versions. Visit http://github.com/Pylons/pyramid_tm/issues to report bugs. Indices and tables ------------------ * :ref:`glossary` * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyramid_tm-0.5/docs/api.rst0000644000175000017500000000031411636453703016560 0ustar chrismchrism00000000000000.. _pyramid_tm_api: :mod:`pyramid_tm` API --------------------------- .. automodule:: pyramid_tm .. autofunction:: includeme .. autofunction:: default_commit_veto .. autofunction:: tm_tween_factory