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
forwardsimplementation of the action. The first argument of theforwardsimplementation is a context object that can be used to share state between the forwards and backwards implementations. This argument is passed implicitly byreversibleand 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
.backwardsattribute of the decorated function can itself be used as a decorator to specify thebackwardsimplementation 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
forwardsandbackwardsimplementations will be called with the same arguments. Any information that needs to be sent fromforwardstobackwardsmust be added to the context object.The context object defaults to a dictionary. An alternative context constructor may be provided using the
context_classargument. 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
forwardsimplementation. - 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
forwardsand thebackwardsimplementations.
Returns: If
forwardswas given, a partially constructed action is returned. Thebackwardsmethod on that object can be used as a decorator to specify the rollback method for the action. Ifforwardswas omitted, a decorator that accepts theforwardsmethod is returned.- forwards – The function will be treated as the
-
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 viareversible.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.Returnexception, or if using Python 3.3 or newer, by simply using thereturnstatement.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
backwardsmethod and thebackwardsmethods of all actions executed so far will be called in reverse of the order in which theforwardsmethods were called.If any of the
backwardsmethods 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()andbackwards()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
forwardsmethod succeeds, the action is considered successful. If the method fails, thebackwardsmethod 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 thereversible.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 thebackwards()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 whoseforwardsand/orbackwardsmethods 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
forwardsand/orbackwardsmethods 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 ofreversible.tornado.gen().- function – The generator function. This generator must yield action objects. The
-
reversible.tornado.lift(future)¶ Returns the result of a Tornado Future inside a generator-based action.
Inside a
reversible.tornado.gen()context, the meaning ofyieldchanges 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, theliftmethod 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
forwardsand/orbackwardsmethods 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 ofexecute.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.