API Reference

Construction and composition

reversible.action(forwards=None, context_class=None)

Decorator to build functions.

This decorator can be applied to a function to build actions. The decorated function becomes the forwards implementation of the action. The first argument of the forwards implementation is a context object that can be used to share state between the forwards and backwards implementations. This argument is passed implicitly by reversible and callers of the function shouldn’t provide it.

@reversible.action
def create_order(context, order_details):
    order_id = OrderStore.put(order_details)
    context['order_id'] = order_id
    return order_id

The .backwards attribute of the decorated function can itself be used as a decorator to specify the backwards implementation of the action.

@create_order.backwards
def delete_order(context, order_details):
    if 'order_id' in context:
        # order_id will be absent if create_order failed
        OrderStore.delete(context['order_id'])

# Note that the context argument was not provided here. It's added
# implicitly by the library.
action = create_order(order_details)
order_id = reversible.execute(action)

Both, the forwards and backwards implementations will be called with the same arguments. Any information that needs to be sent from forwards to backwards must be added to the context object.

The context object defaults to a dictionary. An alternative context constructor may be provided using the context_class argument. It will be called with no arguments to construct the context object.

@reversible.action(context_class=UserInfo)
def create_user(user_info, user_details):
    user_info.user_id = UserStore.put(user_details)
    return user_info

Note that a backwards action is required. Attempts to use the action without specifying a way to roll back will fail.

Parameters:
  • forwards – The function will be treated as the forwards implementation.
  • context_class – Constructor for context objects. A single action call will have its own context object and that object will be implictly passed as the first argument to both, the forwards and the backwards implementations.
Returns:

If forwards was given, a partially constructed action is returned. The backwards method on that object can be used as a decorator to specify the rollback method for the action. If forwards was omitted, a decorator that accepts the forwards method is returned.

reversible.gen(function)

Allows using a generator to chain together reversible actions.

This decorator may be added to a generator that yields reversible actions (any object with a .forwards() and .backwards() method). These may be constructed manually or via reversible.action(). The decorated function, when called, will return another reversible action that runs all yielded actions and if any of them fails, rolls back all actions that had been executed in the reverse order.

Values can be returned by raising the reversible.Return exception, or if using Python 3.3 or newer, by simply using the return statement.

For example,

@reversible.gen
def submit_order(order):
    # CreateOrder is a class that declares a forwards() and
    # backwards() method. The forwards() method returns the
    # order_id. It is propagated back to the yield point.
    order_id = yield CreateOrder(order.cart)

    # If get_payment_info throws an exception, the order will
    # be deleted and the exeception will be re-raised to the
    # caller.
    payment_info = PaymentStore.get_payment_info(order.user_id)

    try:
        # charge_order is a function that returns an action.
        # It is easy to create such a function by using
        # reversible.action as a decorator.
        total = yield charge_order(payment_info, order_id)
    except InsufficientFundsException:
        # Exceptions thrown by a dependency's forwards()
        # method are propagated at the yield point. It's
        # possible to handle them and prevent rollback for
        # everything else.
        send_insufficient_funds_email(order_id, order.user_id)
    else:
        yield update_receipt(order_id, total)
        send_receipt(order_id)

    # The order ID is the result of this action.
    raise reversible.Return(order_id)

order_id = reversible.execute(submit_order(order))

# If another action based on reversible.gen calls
# submit_order, it can simply do:
#
#    order_id = yield submit_order(order_details)

When an action fails, its backwards method and the backwards methods of all actions executed so far will be called in reverse of the order in which the forwards methods were called.

If any of the backwards methods fail, rollback will be aborted.

Parameters:function – The generator function. This generator must yield action objects.
Returns:A function that, when called, produces an action object that executes actions and functions as yielded by the generator.

Execution

reversible.execute(action)

Execute the given action.

An action is any object with a forwards() and backwards() method.

class CreateUser(object):

    def __init__(self, userinfo):
        self.userinfo = userinfo
        self.user_id  = None

    def forwards(self):
        self.user_id = UserStore.create(userinfo)
        return self.user_id

    def backwards(self):
        if self.user_id is not None:
            # user_id will be None if creation failed
            UserStore.delete(self.user_id)

If the forwards method succeeds, the action is considered successful. If the method fails, the backwards method is called to revert any effect it might have had on the system.

In addition to defining classes, actions may be built using the reversible.action() decorator. Actions may be composed together using the reversible.gen() decorator.

Parameters:action – The action to execute.
Returns:The value returned by the forwards() method of the action.
Raises:The exception raised by the forwards() method if rollback succeeded. Otherwise, the exception raised by the backwards() method is raised.

Types

class reversible.Return(value)

Used to return values from reversible.gen().

Generators that use reversible.gen() to compose actions can throw this exception to return a result.

@reversible.gen
def foo():
    a = yield f()
    b = yield g()
    raise reversible.Return(a + b)

This exception is not needed with Python 3.3 or newer; a return statement is enough.

Tornado Support

Construction and composition

reversible.tornado.action(forwards=None, context_class=None)

Decorator to build functions. See reversible.action() for details.

reversible.tornado.gen(function, io_loop=None)

Allows using a generator to chain together reversible actions.

This function is very similar to reversible.gen() except that it may be used with actions whose forwards and/or backwards methods are couroutines. Specifically, if either of those methods return futures the generated action will stop execution until the result of the future is available.

@reversible.tornado.gen
@tornado.gen.coroutine
def save_comment(ctx, comment):
    ctx['comment_id'] = yield async_http_client.fetch(
        # ...
    )
    raise tornado.gen.Return(ctx['comment_id'])

@save_comment.backwards
def delete_comment(ctx, comment):
    # returns a Future
    return async_http_client.fetch(...)

@reversible.tornado.gen
def post_comment(post, comment, client):
    try:
        comment_id = yield save_comment(comment)
    except CommentStoreException:
        # Exceptions thrown by actions may be caught by the
        # action.
        yield queue_save_comment_request(comment)
    else:
        yield update_comment_count(post)
        update_cache()
Parameters:
  • function – The generator function. This generator must yield action objects. The forwards and/or backwards methods on the action may be asynchronous operations returning coroutines.
  • io_loop – IOLoop used to execute asynchronous operations. Defaults to the current IOLoop if omitted.
Returns:

An action executable via reversible.tornado.execute() and yieldable in other instances of reversible.tornado.gen().

reversible.tornado.lift(future)

Returns the result of a Tornado Future inside a generator-based action.

Inside a reversible.tornado.gen() context, the meaning of yield changes to “execute this possibly asynchronous action and return the result.” However sometimes it is necessary to execute a standard Tornado coroutine. To make this possible, the lift method is made available.

@reversible.tornado.gen
def my_action():
    request = yield build_request_action()
    try:
        response = yield reversible.tornado.lift(
            AsyncHTTPClient().fetch(request)
        )
    except HTTPError:
        # ...

    raise reversible.tornado.Return(response)

Note that operations executed through lift are assumed to be non-reversible. If the operations are intended to be reversible, a reversible action must be constructed.

Parameters:future – Tornado future whose result is required. When the returned object is yielded, action execution will stop until the future’s result is available or the future fails. If the future fails, its exception will be propagated back at the yield point.
Returns:An action yieldable inside a reversible.tornado.gen() context.

Execution

reversible.tornado.execute(action, io_loop=None)

Execute the given action and return a Future with the result.

The forwards and/or backwards methods for the action may be synchronous or asynchronous. If asynchronous, that method must return a Future that will resolve to its result.

See reversible.execute() for more details on the behavior of execute.

Parameters:
  • action – The action to execute.
  • io_loop – IOLoop through which asynchronous operations will be executed. If omitted, the current IOLoop is used.
Returns:

A future containing the result of executing the action.

Types

class reversible.tornado.Return

Used to return values from reversible.tornado.gen() generators in versions of Python older than 3.3.

See also reversible.Return.