Edit me on GitHub

pyramid_tm

Overview

pyramid_tm is a package which allows Pyramid requests to join the active transaction as provided by the Python transaction package. (See the documentation for the transaction package for an explanation of what “joining the active transaction” means).

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:

1
2
config = Configurator(.....)
config.include('pyramid_tm')

Or use the pyramid.includes configuration setting in your .ini file:

1
2
[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.

transaction Usage

At the beginning of a request a new transaction is started using the transaction.begin() function. Once the request has finished all of its 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 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 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.

pyramid_tm contains a 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.

1
2
3
4
5
def default_commit_veto(request, response):
    xtm = response.headers.get('x-tm')
    if xtm is not None:
        return xtm != 'commit'
    return response.status.startswith(('4', '5'))

If you’d like to use this commit veto in your system, you can do it via Python:

1
2
3
4
5
6
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:

1
2
[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:

1
2
3
4
5
6
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:

1
2
[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

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.