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 theforwards
implementation is a context object that can be used to share state between the forwards and backwards implementations. This argument is passed implicitly byreversible
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 thebackwards
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
andbackwards
implementations will be called with the same arguments. Any information that needs to be sent fromforwards
tobackwards
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 thebackwards
implementations.
Returns: If
forwards
was given, a partially constructed action is returned. Thebackwards
method on that object can be used as a decorator to specify the rollback method for the action. Ifforwards
was omitted, a decorator that accepts theforwards
method 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.Return
exception, or if using Python 3.3 or newer, by simply using thereturn
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 thebackwards
methods of all actions executed so far will be called in reverse of the order in which theforwards
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()
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
forwards
method succeeds, the action is considered successful. If the method fails, thebackwards
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 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 whoseforwards
and/orbackwards
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/orbackwards
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 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 ofyield
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, thelift
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/orbackwards
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 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
.