113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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,
 | 
						|
    )
 |