def _advance_tasklet(self, send_value=None, error=None): """Advance a tasklet one step by sending in a value or error.""" try: with self.context.use(): # Send the next value or exception into the generator if error: self.generator.throw(type(error), error) # send_value will be None if this is the first time yielded = self.generator.send(send_value) # Context may have changed in tasklet self.context = context_module.get_context() except StopIteration as stop: # Generator has signalled exit, get the return value. This tasklet # has finished. self.set_result(_get_return_value(stop)) return except Exception as error: # An error has occurred in the tasklet. This tasklet has finished. self.set_exception(error) return # This tasklet has yielded a value. We expect this to be a future # object (either NDB or gRPC) or a sequence of futures, in the case of # parallel yield. def done_callback(yielded): # To be called when a future dependency has completed. Advance the # tasklet with the yielded value or error. # # It might be worth noting that legacy NDB added a callback to the # event loop which, in turn, called _help_tasklet_along. I don't # see a compelling reason not to go ahead and call _advance_tasklet # immediately here, rather than queue it up to be called soon by # the event loop. This is subject to change if the reason for the # indirection in the original implementation becomes apparent. error = yielded.exception() if error: self._advance_tasklet(error=error) else: self._advance_tasklet(yielded.result()) if isinstance(yielded, Future): yielded.add_done_callback(done_callback) elif isinstance(yielded, _remote.RemoteCall): _eventloop.queue_rpc(yielded, done_callback) elif isinstance(yielded, (list, tuple)): future = _MultiFuture(yielded) future.add_done_callback(done_callback) else: raise RuntimeError( "A tasklet yielded an illegal value: {!r}".format(yielded))
def _advance_tasklet(self, send_value=None, error=None): """Advance a tasklet one step by sending in a value or error.""" try: with self.context.use(): # Send the next value or exception into the generator if error: self.generator.throw(type(error), error) # send_value will be None if this is the first time yielded = self.generator.send(send_value) # Context may have changed in tasklet self.context = context_module.get_context() except StopIteration as stop: # Generator has signalled exit, get the return value. This tasklet # has finished. self.set_result(_get_return_value(stop)) return except Exception as error: # An error has occurred in the tasklet. This tasklet has finished. self.set_exception(error) return # This tasklet has yielded a value. We expect this to be a future # object (either NDB or gRPC) or a sequence of futures, in the case of # parallel yield. def done_callback(yielded): # To be called when a future dependency has completed. Advance the # tasklet with the yielded value or error. # # It was tempting to call `_advance_tasklet` (`_help_tasklet_along` # in Legacy) directly. Doing so, it has been found, can lead to # exceeding the maximum recursion depth. Queing it up to run on the # event loop avoids this issue by keeping the call stack shallow. error = yielded.exception() if error: _eventloop.call_soon(self._advance_tasklet, error=error) else: _eventloop.call_soon(self._advance_tasklet, yielded.result()) if isinstance(yielded, Future): yielded.add_done_callback(done_callback) elif isinstance(yielded, _remote.RemoteCall): _eventloop.queue_rpc(yielded, done_callback) elif isinstance(yielded, (list, tuple)): future = _MultiFuture(yielded) future.add_done_callback(done_callback) else: raise RuntimeError( "A tasklet yielded an illegal value: {!r}".format(yielded))
def idle_callback(self): """Perform a Datastore Lookup on all batched Lookup requests.""" keys = [] for todo_key in self.todo.keys(): key_pb = entity_pb2.Key() key_pb.ParseFromString(todo_key) keys.append(key_pb) read_options = _get_read_options(self.options) rpc = _datastore_lookup(keys, read_options) _eventloop.queue_rpc(rpc, self.lookup_callback)
def _perform_batch_lookup(): """Perform a Datastore Lookup on all batched Lookup requests. Meant to be used as an idle callback, so that calls to lookup entities can be batched into a single request to the back end service as soon as running code has need of one of the results. """ state = _runstate.current() batch = state.batches.pop(_BATCH_LOOKUP, None) if batch is None: return rpc = _datastore_lookup(batch.keys()) _eventloop.queue_rpc(rpc, BatchLookupCallback(batch))
def _advance_tasklet(self, send_value=None, error=None): """Advance a tasklet one step by sending in a value or error.""" try: # Send the next value or exception into the generator if error: self.generator.throw(type(error), error) # send_value will be None if this is the first time yielded = self.generator.send(send_value) except StopIteration as stop: # Generator has signalled exit, get the return value. This tasklet # has finished. self.set_result(_get_return_value(stop)) return except Exception as error: # An error has occurred in the tasklet. This tasklet has finished. self.set_exception(error) return # This tasklet has yielded a value. We expect this to be a future # object (either NDB or gRPC) or a sequence of futures, in the case of # parallel yield. def done_callback(yielded): # To be called when a future dependency has completed. # Advance the tasklet with the yielded value or error. error = yielded.exception() if error: self._advance_tasklet(error=error) else: self._advance_tasklet(yielded.result()) if isinstance(yielded, Future): yielded.add_done_callback(done_callback) elif isinstance(yielded, grpc.Future): _eventloop.queue_rpc(yielded, done_callback) elif isinstance(yielded, (list, tuple)): future = MultiFuture(yielded) future.add_done_callback(done_callback) else: raise RuntimeError( "A tasklet yielded an illegal value: {!r}".format(yielded))
def test_queue_rpc(context): loop = mock.Mock(spec=("run", "queue_rpc")) with context.new(eventloop=loop).use(): _eventloop.queue_rpc("foo", "bar") loop.queue_rpc.assert_called_once_with("foo", "bar")
def test_queue_rpc(EventLoop): EventLoop.return_value = loop = unittest.mock.Mock(spec=("run", "queue_rpc")) with _runstate.state_context(None): _eventloop.queue_rpc("foo", "bar") loop.queue_rpc.assert_called_once_with("foo", "bar")
def test_queue_rpc(): with pytest.raises(NotImplementedError): eventloop.queue_rpc()
def idle_callback(self): """Send the commit for this batch to Datastore.""" rpc = _datastore_commit(self.mutations, _get_transaction(self.options)) _eventloop.queue_rpc(rpc, self.commit_callback)
def test_queue_rpc(): with pytest.raises(NotImplementedError): _eventloop.queue_rpc()