def test_transient_error_callback_is_tasklet(core_retry, sleep): """Regression test for #519 https://github.com/googleapis/python-ndb/issues/519 """ core_retry.exponential_sleep_generator.return_value = itertools.count() core_retry.if_transient_error.return_value = True sleep_future = tasklets.Future("sleep") sleep.return_value = sleep_future callback = mock.Mock(side_effect=[ utils.future_exception(Exception("Spurious error.")), utils.future_result("foo"), ]) retry = _retry.retry_async(callback) future = retry() # This is the important check for the bug in #519. We need to make sure # that we're waiting for the sleep future to complete before moving on. assert future.running() # Finish sleeping sleep_future.set_result(None) assert future.result() == "foo" sleep.assert_called_once_with(0)
def transaction_async( callback, retries=_retry._DEFAULT_RETRIES, read_only=False, xg=True, propagation=None, ): """Run a callback in a transaction. This is the asynchronous version of :func:`transaction`. """ if propagation is not None: raise exceptions.NoLongerImplementedError() # Keep transaction propagation simple: don't do it. context = context_module.get_context() if context.transaction: raise NotImplementedError( "Can't start a transaction during a transaction.") tasklet = functools.partial(_transaction_async, context, callback, read_only=read_only) if retries: tasklet = _retry.retry_async(tasklet, retries=retries) return tasklet()
def make_call(rpc_name, request, retries=None): """Make a call to the Datastore API. Args: rpc_name (str): Name of the remote procedure to call on Datastore. request (Any): An appropriate request object for the call, eg, `entity_pb2.LookupRequest` for calling ``Lookup``. retries (int): Number of times to potentially retry the call. If :data:`None` is passed, will use :data:`_retry._DEFAULT_RETRIES`. If :data:`0` is passed, the call is attempted only once. Returns: tasklets.Future: Future for the eventual response for the API call. """ api = stub() method = getattr(api, rpc_name) if retries is None: retries = _retry._DEFAULT_RETRIES @tasklets.tasklet def rpc_call(): rpc = _remote.RemoteCall(method.future(request), "{}({})".format(rpc_name, request)) log.debug(rpc) result = yield rpc return result if retries: rpc_call = _retry.retry_async(rpc_call, retries=retries) return rpc_call()
def test_unhandled_error(): error = Exception("Spurious error") def callback(): raise error retry = _retry.retry_async(callback) assert retry().exception() is error
def callback(): def nested_callback(): return "bar" nested = _retry.retry_async(nested_callback) assert nested().result() == "bar" return "foo"
def test_success_callback_is_tasklet(): tasklet_future = tasklets.Future() @tasklets.tasklet def callback(): result = yield tasklet_future raise tasklets.Return(result) retry = _retry.retry_async(callback) tasklet_future.set_result("foo") assert retry().result() == "foo"
def test_transient_error(core_retry, sleep): core_retry.exponential_sleep_generator.return_value = itertools.count() core_retry.if_transient_error.return_value = True sleep_future = tasklets.Future("sleep") sleep.return_value = sleep_future callback = mock.Mock(side_effect=[Exception("Spurious error."), "foo"]) retry = _retry.retry_async(callback) sleep_future.set_result(None) assert retry().result() == "foo" sleep.assert_called_once_with(0)
def test_nested_retry_with_exception(): error = Exception("Fail") def callback(): def nested_callback(): raise error nested = _retry.retry_async(nested_callback, retries=1) return nested() with pytest.raises(core_exceptions.RetryError): retry = _retry.retry_async(callback, retries=1) retry().result()
def make_call(rpc_name, request, retries=None, timeout=None): """Make a call to the Datastore API. Args: rpc_name (str): Name of the remote procedure to call on Datastore. request (Any): An appropriate request object for the call, eg, `entity_pb2.LookupRequest` for calling ``Lookup``. retries (int): Number of times to potentially retry the call. If :data:`None` is passed, will use :data:`_retry._DEFAULT_RETRIES`. If :data:`0` is passed, the call is attempted only once. timeout (float): Timeout, in seconds, to pass to gRPC call. If :data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`. Returns: tasklets.Future: Future for the eventual response for the API call. """ api = stub() method = getattr(api, rpc_name) if retries is None: retries = _retry._DEFAULT_RETRIES if timeout is None: timeout = _DEFAULT_TIMEOUT @tasklets.tasklet def rpc_call(): context = context_module.get_toplevel_context() call = method.future(request, timeout=timeout) rpc = _remote.RemoteCall(call, rpc_name) utils.logging_debug(log, rpc) utils.logging_debug(log, "timeout={}", timeout) utils.logging_debug(log, request) try: result = yield rpc except Exception as error: if isinstance(error, grpc.Call): error = core_exceptions.from_grpc_error(error) raise error finally: context.rpc_time += rpc.elapsed_time raise tasklets.Return(result) if retries: rpc_call = _retry.retry_async(rpc_call, retries=retries) return rpc_call()
def test_too_many_transient_errors(core_retry, sleep): core_retry.exponential_sleep_generator.return_value = itertools.count() core_retry.if_transient_error.return_value = True sleep_future = tasklets.Future("sleep") sleep.return_value = sleep_future sleep_future.set_result(None) error = Exception("Spurious error") def callback(): raise error retry = _retry.retry_async(callback) with pytest.raises(core_exceptions.RetryError) as error_context: retry().check_success() assert error_context.value.cause is error assert sleep.call_count == 4 assert sleep.call_args[0][0] == 3
def transaction_async( callback, retries=_retry._DEFAULT_RETRIES, read_only=False, join=False, xg=True, propagation=None, ): """Run a callback in a transaction. This is the asynchronous version of :func:`transaction`. """ # Avoid circular import in Python 2.7 from google.cloud.ndb import context as context_module if propagation is not None: raise exceptions.NoLongerImplementedError() context = context_module.get_context() if context.transaction: if join: result = callback() if not isinstance(result, tasklets.Future): future = tasklets.Future() future.set_result(result) result = future return result else: raise NotImplementedError( "Transactions may not be nested. Pass 'join=True' in order to " "join an already running transaction.") tasklet = functools.partial(_transaction_async, context, callback, read_only=read_only) if retries: tasklet = _retry.retry_async(tasklet, retries=retries) return tasklet()
def test_success(): def callback(): return "foo" retry = _retry.retry_async(callback) assert retry().result() == "foo"
def callback(): def nested_callback(): raise error nested = _retry.retry_async(nested_callback, retries=1) return nested()