From a01ca30f5ba1e58de1e67211a42bb0c3bc88b063 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 7 Aug 2024 14:02:36 +0300 Subject: [PATCH] stat-docs-reactions-apifix --- resolvers/reaction.py | 7 +-- resolvers/stat.py | 137 +++++++++++++++++++++++++++++++++++------- 2 files changed, 117 insertions(+), 27 deletions(-) diff --git a/resolvers/reaction.py b/resolvers/reaction.py index 5bcd06c9..72bd49ba 100644 --- a/resolvers/reaction.py +++ b/resolvers/reaction.py @@ -418,17 +418,14 @@ async def load_reactions_by(_, info, by, limit=50, offset=0): reaction, author, shout, - reacted_stat, commented_stat, - likes_stat, - dislikes_stat, + rating_stat, last_reacted_at, ] in result_rows: reaction.created_by = author reaction.shout = shout reaction.stat = { - "rating": int(likes_stat or 0) - int(dislikes_stat or 0), - "reacted": reacted_stat, + "rating": rating_stat, "commented": commented_stat, "last_reacted_at": last_reacted_at, } diff --git a/resolvers/stat.py b/resolvers/stat.py index 6d27b8b2..488271ca 100644 --- a/resolvers/stat.py +++ b/resolvers/stat.py @@ -13,12 +13,19 @@ from utils.logger import root_logger as logger def add_topic_stat_columns(q): + """ + Добавляет статистические колонки к запросу тем. + + :param q: SQL-запрос для получения тем. + :return: Запрос с добавленными колонками статистики. + """ + # Создаем алиасы для предотвращения конфликтов имен aliased_shout = aliased(ShoutTopic) - # Create a new query object + # Создаем новый объект запроса для тем new_q = select(Topic) - # Apply the necessary filters to the new query object + # Применяем необходимые фильтры и добавляем колонки статистики new_q = ( new_q.join( aliased_shout, @@ -31,26 +38,35 @@ def add_topic_stat_columns(q): Shout.deleted_at.is_(None), ), ) - .add_columns(func.count(distinct(aliased_shout.shout)).label("shouts_stat")) + .add_columns(func.count(distinct(aliased_shout.shout)).label("shouts_stat")) # Подсчет уникальных публикаций для темы ) aliased_follower = aliased(TopicFollower) + # Добавляем количество подписчиков темы new_q = new_q.outerjoin(aliased_follower, aliased_follower.topic == Topic.id).add_columns( func.count(distinct(aliased_follower.follower)).label("followers_stat") ) + # Группировка по идентификатору темы new_q = new_q.group_by(Topic.id) return new_q def add_author_stat_columns(q): + """ + Добавляет статистические колонки к запросу авторов. + + :param q: SQL-запрос для получения авторов. + :return: Запрос с добавленными колонками статистики. + """ # Алиасирование таблиц для предотвращения конфликтов имен aliased_shout_author = aliased(ShoutAuthor) aliased_shout = aliased(Shout) aliased_author_follower = aliased(AuthorFollower) + # Применение фильтров и добавление колонок статистики q = ( q.select_from(Author) .join( @@ -64,19 +80,27 @@ def add_author_stat_columns(q): aliased_shout.deleted_at.is_(None), ), ) - .add_columns(func.count(distinct(aliased_shout.id)).label("shouts_stat")) + .add_columns(func.count(distinct(aliased_shout.id)).label("shouts_stat")) # Подсчет уникальных публикаций автора ) + # Добавляем количество подписчиков автора q = q.outerjoin(aliased_author_follower, aliased_author_follower.author == Author.id).add_columns( func.count(distinct(aliased_author_follower.follower)).label("followers_stat") ) + # Группировка по идентификатору автора q = q.group_by(Author.id) return q -def get_topic_shouts_stat(topic_id: int): +def get_topic_shouts_stat(topic_id: int) -> int: + """ + Получает количество публикаций для указанной темы. + + :param topic_id: Идентификатор темы. + :return: Количество уникальных публикаций для темы. + """ q = ( select(func.count(distinct(ShoutTopic.shout))) .select_from(join(ShoutTopic, Shout, ShoutTopic.shout == Shout.id)) @@ -88,12 +112,19 @@ def get_topic_shouts_stat(topic_id: int): ) ) ) + # Выполнение запроса и получение результата with local_session() as session: result = session.execute(q).first() return result[0] if result else 0 -def get_topic_authors_stat(topic_id: int): +def get_topic_authors_stat(topic_id: int) -> int: + """ + Получает количество уникальных авторов для указанной темы. + + :param topic_id: Идентификатор темы. + :return: Количество уникальных авторов, связанных с темой. + """ count_query = ( select(func.count(distinct(ShoutAuthor.author))) .select_from(join(ShoutTopic, Shout, ShoutTopic.shout == Shout.id)) @@ -107,13 +138,19 @@ def get_topic_authors_stat(topic_id: int): ) ) - # Выполняем запрос и получаем результат + # Выполнение запроса и получение результата with local_session() as session: result = session.execute(count_query).first() return result[0] if result else 0 -def get_topic_followers_stat(topic_id: int): +def get_topic_followers_stat(topic_id: int) -> int: + """ + Получает количество подписчиков для указанной темы. + + :param topic_id: Идентификатор темы. + :return: Количество уникальных подписчиков темы. + """ aliased_followers = aliased(TopicFollower) q = select(func.count(distinct(aliased_followers.follower))).filter(aliased_followers.topic == topic_id) with local_session() as session: @@ -121,7 +158,14 @@ def get_topic_followers_stat(topic_id: int): return result[0] if result else 0 -def get_topic_comments_stat(topic_id: int): +def get_topic_comments_stat(topic_id: int) -> int: + """ + Получает количество комментариев для всех публикаций в указанной теме. + + :param topic_id: Идентификатор темы. + :return: Общее количество комментариев к публикациям темы. + """ + # Подзапрос для получения количества комментариев для каждой публикации sub_comments = ( select( Shout.id.label("shout_id"), @@ -140,6 +184,7 @@ def get_topic_comments_stat(topic_id: int): .group_by(Shout.id) .subquery() ) + # Запрос для суммирования количества комментариев по теме q = select(func.coalesce(func.sum(sub_comments.c.comments_count), 0)).filter(ShoutTopic.topic == topic_id) q = q.outerjoin(sub_comments, ShoutTopic.shout == sub_comments.c.shout_id) with local_session() as session: @@ -147,7 +192,13 @@ def get_topic_comments_stat(topic_id: int): return result[0] if result else 0 -def get_author_shouts_stat(author_id: int): +def get_author_shouts_stat(author_id: int) -> int: + """ + Получает количество публикаций для указанного автора. + + :param author_id: Идентификатор автора. + :return: Количество уникальных публикаций автора. + """ aliased_shout_author = aliased(ShoutAuthor) aliased_shout = aliased(Shout) @@ -169,7 +220,13 @@ def get_author_shouts_stat(author_id: int): return result[0] if result else 0 -def get_author_authors_stat(author_id: int): +def get_author_authors_stat(author_id: int) -> int: + """ + Получает количество авторов, на которых подписан указанный автор. + + :param author_id: Идентификатор автора. + :return: Количество уникальных авторов, на которых подписан автор. + """ aliased_authors = aliased(AuthorFollower) q = select(func.count(distinct(aliased_authors.author))).filter( and_( @@ -182,7 +239,13 @@ def get_author_authors_stat(author_id: int): return result[0] if result else 0 -def get_author_followers_stat(author_id: int): +def get_author_followers_stat(author_id: int) -> int: + """ + Получает количество подписчиков для указанного автора. + + :param author_id: Идентификатор автора. + :return: Количество уникальных подписчиков автора. + """ aliased_followers = aliased(AuthorFollower) q = select(func.count(distinct(aliased_followers.follower))).filter(aliased_followers.author == author_id) with local_session() as session: @@ -190,7 +253,14 @@ def get_author_followers_stat(author_id: int): return result[0] if result else 0 -def get_author_comments_stat(author_id: int): +def get_author_comments_stat(author_id: int) -> int: + """ + Получает количество комментариев, оставленных указанным автором. + + :param author_id: Идентификатор автора. + :return: Количество комментариев, оставленных автором. + """ + # Подзапрос для получения количества комментариев, оставленных автором sub_comments = ( select(Author.id, func.coalesce(func.count(Reaction.id)).label("comments_count")) .select_from(Author) # явно указываем левый элемент join'а @@ -212,28 +282,34 @@ def get_author_comments_stat(author_id: int): def get_with_stat(q): + """ + Выполняет запрос с добавлением статистики. + + :param q: SQL-запрос для выполнения. + :return: Список объектов с добавленной статистикой. + """ records = [] try: with local_session() as session: - # detect author + # Определяем, является ли запрос запросом авторов author_prefixes = ("select author", "select * from author") is_author = f"{q}".lower().startswith(author_prefixes) - # Add stat columns to the query + # Добавляем колонки статистики в запрос q = add_author_stat_columns(q) if is_author else add_topic_stat_columns(q) - # execute query + # Выполняем запрос result = session.execute(q) for cols in result: entity = cols[0] stat = dict() - stat["shouts"] = cols[1] - stat["followers"] = cols[2] + stat["shouts"] = cols[1] # Статистика по публикациям + stat["followers"] = cols[2] # Статистика по подписчикам if is_author: - stat["authors"] = get_author_authors_stat(entity.id) - stat["comments"] = get_author_comments_stat(entity.id) + stat["authors"] = get_author_authors_stat(entity.id) # Статистика по подпискам на авторов + stat["comments"] = get_author_comments_stat(entity.id) # Статистика по комментариям else: - stat["authors"] = get_topic_authors_stat(entity.id) + stat["authors"] = get_topic_authors_stat(entity.id) # Статистика по авторам темы entity.stat = stat records.append(entity) except Exception as exc: @@ -246,6 +322,12 @@ def get_with_stat(q): def author_follows_authors(author_id: int): + """ + Получает список авторов, на которых подписан указанный автор. + + :param author_id: Идентификатор автора. + :return: Список авторов с добавленной статистикой. + """ af = aliased(AuthorFollower, name="af") author_follows_authors_query = ( select(Author).select_from(join(Author, af, Author.id == af.author)).where(af.follower == author_id) @@ -254,6 +336,12 @@ def author_follows_authors(author_id: int): def author_follows_topics(author_id: int): + """ + Получает список тем, на которые подписан указанный автор. + + :param author_id: Идентификатор автора. + :return: Список тем с добавленной статистикой. + """ author_follows_topics_query = ( select(Topic) .select_from(join(Topic, TopicFollower, Topic.id == TopicFollower.topic)) @@ -263,6 +351,11 @@ def author_follows_topics(author_id: int): def update_author_stat(author_id: int): + """ + Обновляет статистику для указанного автора и сохраняет её в кэше. + + :param author_id: Идентификатор автора. + """ author_query = select(Author).where(Author.id == author_id) try: result = get_with_stat(author_query) @@ -270,7 +363,7 @@ def update_author_stat(author_id: int): author_with_stat = result[0] if isinstance(author_with_stat, Author): author_dict = author_with_stat.dict() - # await cache_author(author_dict) + # Асинхронное кэширование данных автора asyncio.create_task(cache_author(author_dict)) except Exception as exc: logger.error(exc, exc_info=True)