'Get results from multiple rows matching multiple attributes.
We could implement this using read_rows, but it is a table scan. Our current
data model makes that slow because it is a directory hierarchy that includes
entries for subdirectories interleaved. So if you want all the results for a
directory you need to skip those in the scan.
Instead we make an RPC for each subject all at once using a threadpool. We
pay more in RPC overhead but we get to do it concurrently.
Args:
subjects: A list of subjects.
attribute_prefix: The attribute prefix.
timestamp: A range of times for consideration (In
microseconds). Can be a constant such as ALL_TIMESTAMPS or
NEWEST_TIMESTAMP or a tuple of ints (start, end).
limit: The total number of result values to return.
token: An ACL token.
Yields:
A list of tuples:
(subject, [(attribute, value string, timestamp)])
that can be simply converted to a dict.
Values with the same attribute (happens when timestamp is not
NEWEST_TIMESTAMP, but ALL_TIMESTAMPS or time range) are guaranteed
to be ordered in the decreasing timestamp order.
Raises:
AccessError: if anything goes wrong.
ValueError: if we get a string instead of a list of subjects.'

def MultiResolvePrefix(self, subjects, attribute_prefix, timestamp=None, limit=None, token=None):

    if isinstance(subjects, basestring):
        raise ValueError(('Expected    list    of    subjects,    got    string:    %s' % subjects))
    if isinstance(attribute_prefix, basestring):
        attribute_prefix_list = [utils.SmartStr(attribute_prefix)]
    else:
        attribute_prefix_list = [utils.SmartStr(x) for x in attribute_prefix]
    timestamp_filter = self._TimestampToFilter(timestamp)
    filter_union = []
    for attribute_prefix in attribute_prefix_list:
        (family, column) = self.GetFamilyColumn(attribute_prefix)
        family_filter = row_filters.FamilyNameRegexFilter(family)
        row_filter_list = [family_filter]
        if column:
            column += '.*'
            col_filter = row_filters.ColumnQualifierRegexFilter(column)
            row_filter_list.append(col_filter)
        if timestamp_filter:
            row_filter_list.append(timestamp_filter)
        if (len(row_filter_list) > 1):
            row_filter = row_filters.RowFilterChain(filters=row_filter_list)
        else:
            row_filter = row_filter_list[0]
        filter_union.append(row_filter)
    if (len(filter_union) > 1):
        attribute_filter = row_filters.RowFilterUnion(filters=filter_union)
    else:
        attribute_filter = filter_union[0]
    pool_args = []
    original_subject_map = {}
    for subject in subjects:
        pool_args.append(((self.table.read_row, 'read', utils.SmartStr(subject)), {'filter_': attribute_filter}))
        original_subject_map[utils.SmartStr(subject)] = subject
    max_results = (limit or (2 ** 64))
    for result in self.pool.imap_unordered(self._WrapCallWithRetry, pool_args):
        if (max_results <= 0):
            break
        if result:
            (subject_results, max_results) = self._GetSubjectResults(result, max_results)
            (yield (original_subject_map[result.row_key], self._SortResultsByAttrTimestampValue(subject_results)))
