# Copyright (c) testtools developers. See LICENSE for details. """Utilities for Deferreds.""" from functools import partial from testtools.content import TracebackContent class DeferredNotFired(Exception): """Raised when we extract a result from a Deferred that's not fired yet.""" def __init__(self, deferred): msg = "%r has not fired yet." % (deferred,) super(DeferredNotFired, self).__init__(msg) def extract_result(deferred): """Extract the result from a fired deferred. It can happen that you have an API that returns Deferreds for compatibility with Twisted code, but is in fact synchronous, i.e. the Deferreds it returns have always fired by the time it returns. In this case, you can use this function to convert the result back into the usual form for a synchronous API, i.e. the result itself or a raised exception. As a rule, this function should not be used when operating with asynchronous Deferreds (i.e. for normal use of Deferreds in application code). In those cases, it is better to add callbacks and errbacks as needed. """ failures = [] successes = [] deferred.addCallbacks(successes.append, failures.append) if len(failures) == 1: failures[0].raiseException() elif len(successes) == 1: return successes[0] else: raise DeferredNotFired(deferred) class ImpossibleDeferredError(Exception): """Raised if a Deferred somehow triggers both a success and a failure.""" def __init__(self, deferred, successes, failures): msg = ('Impossible condition on %r, got both success (%r) and ' 'failure (%r)') super(ImpossibleDeferredError, self).__init__( msg % (deferred, successes, failures)) def on_deferred_result(deferred, on_success, on_failure, on_no_result): """Handle the result of a synchronous ``Deferred``. If ``deferred`` has fire successfully, call ``on_success``. If ``deferred`` has failed, call ``on_failure``. If ``deferred`` has not yet fired, call ``on_no_result``. The value of ``deferred`` will be preserved, so that other callbacks and errbacks can be added to ``deferred``. :param Deferred[A] deferred: A synchronous Deferred. :param Callable[[Deferred[A], A], T] on_success: Called if the Deferred fires successfully. :param Callable[[Deferred[A], Failure], T] on_failure: Called if the Deferred fires unsuccessfully. :param Callable[[Deferred[A]], T] on_no_result: Called if the Deferred has not yet fired. :raises ImpossibleDeferredError: If the Deferred somehow triggers both a success and a failure. :raises TypeError: If the Deferred somehow triggers more than one success, or more than one failure. :return: Whatever is returned by the triggered callback. :rtype: ``T`` """ successes = [] failures = [] def capture(value, values): values.append(value) return value deferred.addCallbacks( partial(capture, values=successes), partial(capture, values=failures), ) if successes and failures: raise ImpossibleDeferredError(deferred, successes, failures) elif failures: [failure] = failures return on_failure(deferred, failure) elif successes: [result] = successes return on_success(deferred, result) else: return on_no_result(deferred) def failure_content(failure): """Create a Content object for a Failure. :param Failure failure: The failure to create content for. :rtype: ``Content`` """ return TracebackContent( (failure.type, failure.value, failure.getTracebackObject()), None, )