Reads undone work from the datastore.

    If shard_id and num_shards are specified then this method will attempt
    to read undone work for shard with id shard_id. If no undone work was found
    then it will try to read shard (shard_id+1) and so on until either found
    shard with undone work or all shards are read.

    Args:
      shard_id: Id of the start shard
      num_shards: total number of shards

    Returns:
      id of the shard with undone work which was read. None means that work
      from all datastore was read.

def read_undone_from_datastore(self, shard_id=None, num_shards=None):
    """Reads undone work from the datastore.

    If shard_id and num_shards are specified then this method will attempt
    to read undone work for shard with id shard_id. If no undone work was found
    then it will try to read shard (shard_id+1) and so on until either found
    shard with undone work or all shards are read.

    Args:
      shard_id: Id of the start shard
      num_shards: total number of shards

    Returns:
      id of the shard with undone work which was read. None means that work
      from all datastore was read.
    """
    if shard_id is not None:
      shards_list = [(i + shard_id) % num_shards for i in range(num_shards)]
    else:
      shards_list = []
    shards_list.append(None)
    for shard in shards_list:
      self._read_undone_shard_from_datastore(shard)
      if self._work:
        return shard
    return None