def METHOD_NAME(cls, attrs, query=None):
    pk_cols = [k for k in inspect(cls).primary_key]
    cls_version = version_class(cls)
    pk_cols_version = [getattr(cls_version, k.name) for k in pk_cols]
    attrs_version = [getattr(cls_version, a) for a in attrs]
    cls_transaction = transaction_class(cls)
    if query is None:
        query = cls_version.query
    all_versions = (
        query.join(cls_version.transaction)
        .with_entities(*pk_cols_version + attrs_version + [cls_transaction.issued_at])
        .order_by(*pk_cols_version + [cls_version.transaction_id])
    )
    def get_pk(row):
        return [getattr(row, k.name) for k in pk_cols_version]
    for pk, versions in groupby(all_versions, get_pk):
        # We don't yet process inserts/deletes, but should
        first = next(versions)
        attr_vals = {a: getattr(first, a) for a in attrs}
        attr_times = {a: [first.issued_at] for a in attrs}
        for version in versions:
            for attr in attrs:
                val = getattr(version, attr)
                if val != attr_vals[attr]:
                    attr_times[attr].append(version.issued_at)
                    attr_vals[attr] = val
        yield (pk, attr_times)