load authors by followers fix
Some checks failed
Deploy on push / deploy (push) Failing after 3m33s

This commit is contained in:
2025-08-26 14:12:49 +03:00
parent 2a6fcc3f45
commit 90aece7a60
11 changed files with 253 additions and 194 deletions

View File

@@ -115,168 +115,189 @@ async def get_authors_with_stats(
"""
Выполняет запрос к базе данных для получения авторов со статистикой.
"""
logger.debug(f"Выполняем запрос на получение авторов со статистикой: limit={limit}, offset={offset}, by={by}")
try:
with local_session() as session:
# Базовый запрос для получения авторов
base_query = select(Author).where(Author.deleted_at.is_(None))
with local_session() as session:
# Базовый запрос для получения авторов
base_query = select(Author).where(Author.deleted_at.is_(None))
# vars for statistics sorting
stats_sort_field = None
default_sort_applied = False
# vars for statistics sorting
stats_sort_field = None
default_sort_applied = False
if by:
if "order" in by:
order_value = by["order"]
logger.debug(f"Found order field with value: {order_value}")
if order_value in ["shouts", "followers", "rating", "comments"]:
stats_sort_field = order_value
logger.debug(f"Applying statistics-based sorting by: {stats_sort_field}")
# Не применяем другую сортировку, так как будем использовать stats_sort_field
default_sort_applied = True
elif order_value == "name":
# Sorting by name in ascending order
base_query = base_query.order_by(asc(Author.name))
logger.debug("Applying alphabetical sorting by name")
default_sort_applied = True
else:
# If order is not a stats field, treat it as a regular field
column = getattr(Author, order_value or "", "")
if column:
base_query = base_query.order_by(sql_desc(column))
logger.debug(f"Applying sorting by column: {order_value}")
if by:
if "order" in by:
order_value = by["order"]
logger.debug(f"Found order field with value: {order_value}")
if order_value in ["shouts", "followers", "rating", "comments"]:
stats_sort_field = order_value
logger.debug(f"Applying statistics-based sorting by: {stats_sort_field}")
# Не применяем другую сортировку, так как будем использовать stats_sort_field
default_sort_applied = True
elif order_value == "name":
# Sorting by name in ascending order
base_query = base_query.order_by(asc(Author.name))
logger.debug("Applying alphabetical sorting by name")
default_sort_applied = True
else:
logger.warning(f"Unknown order field: {order_value}")
else:
# Regular sorting by fields
for field, direction in by.items():
if field is None:
continue
column = getattr(Author, field, None)
if column:
if isinstance(direction, str) and direction.lower() == "desc":
# If order is not a stats field, treat it as a regular field
column = getattr(Author, order_value or "", "")
if column:
base_query = base_query.order_by(sql_desc(column))
logger.debug(f"Applying sorting by column: {order_value}")
default_sort_applied = True
else:
base_query = base_query.order_by(column)
logger.debug(f"Applying sorting by field: {field}, direction: {direction}")
default_sort_applied = True
else:
logger.warning(f"Unknown field: {field}")
logger.warning(f"Unknown order field: {order_value}")
else:
# Regular sorting by fields
for field, direction in by.items():
if field is None:
continue
column = getattr(Author, field, None)
if column:
if isinstance(direction, str) and direction.lower() == "desc":
base_query = base_query.order_by(sql_desc(column))
else:
base_query = base_query.order_by(column)
logger.debug(f"Applying sorting by field: {field}, direction: {direction}")
default_sort_applied = True
else:
logger.warning(f"Unknown field: {field}")
# Если сортировка еще не применена, используем сортировку по умолчанию
if not default_sort_applied and not stats_sort_field:
base_query = base_query.order_by(sql_desc(Author.created_at))
logger.debug("Applying default sorting by created_at (no by parameter)")
# Если сортировка еще не применена, используем сортировку по умолчанию
if not default_sort_applied and not stats_sort_field:
base_query = base_query.order_by(sql_desc(Author.created_at))
logger.debug("Applying default sorting by created_at (no by parameter)")
# If sorting by statistics, modify the query
if stats_sort_field == "shouts":
# Sorting by the number of shouts
logger.debug("Building subquery for shouts sorting")
subquery = (
select(ShoutAuthor.author, func.count(func.distinct(Shout.id)).label("shouts_count"))
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
.group_by(ShoutAuthor.author)
.subquery()
)
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.shouts_count, 0))
)
logger.debug("Applied sorting by shouts count")
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for shouts sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
elif stats_sort_field == "followers":
# Sorting by the number of followers
logger.debug("Building subquery for followers sorting")
subquery = (
select(
AuthorFollower.following,
func.count(func.distinct(AuthorFollower.follower)).label("followers_count"),
# If sorting by statistics, modify the query
if stats_sort_field == "shouts":
# Sorting by the number of shouts
logger.debug("Building subquery for shouts sorting")
subquery = (
select(ShoutAuthor.author, func.count(func.distinct(Shout.id)).label("shouts_count"))
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
.group_by(ShoutAuthor.author)
.subquery()
)
.select_from(AuthorFollower)
.group_by(AuthorFollower.following)
.subquery()
)
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.followers_count, 0))
)
logger.debug("Applied sorting by followers count")
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.shouts_count, 0))
)
logger.debug("Applied sorting by shouts count")
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for followers sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for shouts sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
elif stats_sort_field == "followers":
# Sorting by the number of followers
logger.debug("Building subquery for followers sorting")
subquery = (
select(
AuthorFollower.following,
func.count(func.distinct(AuthorFollower.follower)).label("followers_count"),
)
.select_from(AuthorFollower)
.group_by(AuthorFollower.following)
.subquery()
)
# Применяем лимит и смещение
base_query = base_query.limit(limit).offset(offset)
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.following).order_by(
sql_desc(func.coalesce(subquery.c.followers_count, 0))
)
logger.debug("Applied sorting by followers count")
# Получаем авторов
authors = session.execute(base_query).scalars().unique().all()
author_ids = [author.id for author in authors]
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for followers sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
if not author_ids:
return []
# Применяем лимит и смещение
base_query = base_query.limit(limit).offset(offset)
# Логирование результатов для отладки сортировки
if stats_sort_field:
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
# Получаем авторов
logger.debug("Executing main query for authors")
authors = session.execute(base_query).scalars().unique().all()
author_ids = [author.id for author in authors]
logger.debug(f"Retrieved {len(authors)} authors with IDs: {author_ids}")
# Оптимизированный запрос для получения статистики по публикациям для авторов
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
shouts_stats_query = f"""
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
FROM shout_author sa
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
WHERE sa.author IN ({placeholders})
GROUP BY sa.author
"""
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
if not author_ids:
logger.debug("No authors found, returning empty list")
return []
# Запрос на получение статистики по подписчикам для авторов
followers_stats_query = f"""
SELECT following, COUNT(DISTINCT follower) as followers_count
FROM author_follower
WHERE following IN ({placeholders})
GROUP BY following
"""
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
# Логирование результатов для отладки сортировки
if stats_sort_field:
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
# Формируем результат с добавлением статистики
result = []
for author in authors:
# Получаем словарь с учетом прав доступа
author_dict = author.dict()
author_dict["stat"] = {
"shouts": shouts_stats.get(author.id, 0),
"followers": followers_stats.get(author.id, 0),
}
# Оптимизированный запрос для получения статистики по публикациям для авторов
logger.debug("Executing shouts statistics query")
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
shouts_stats_query = f"""
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
FROM shout_author sa
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
WHERE sa.author IN ({placeholders})
GROUP BY sa.author
"""
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
logger.debug(f"Shouts stats retrieved: {shouts_stats}")
result.append(author_dict)
# Запрос на получение статистики по подписчикам для авторов
logger.debug("Executing followers statistics query")
followers_stats_query = f"""
SELECT following, COUNT(DISTINCT follower) as followers_count
FROM author_follower
WHERE following IN ({placeholders})
GROUP BY following
"""
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
logger.debug(f"Followers stats retrieved: {followers_stats}")
# Кешируем каждого автора отдельно для использования в других функциях
# Важно: кэшируем полный словарь для админов
await cache_author(author.dict())
# Формируем результат с добавлением статистики
logger.debug("Building final result with statistics")
result = []
for author in authors:
try:
# Получаем словарь с учетом прав доступа
author_dict = author.dict()
author_dict["stat"] = {
"shouts": shouts_stats.get(author.id, 0),
"followers": followers_stats.get(author.id, 0),
}
return result
result.append(author_dict)
# Кешируем каждого автора отдельно для использования в других функциях
# Важно: кэшируем полный словарь для админов
logger.debug(f"Caching author {author.id}")
await cache_author(author.dict())
except Exception as e:
logger.error(f"Error processing author {getattr(author, 'id', 'unknown')}: {e}")
# Продолжаем обработку других авторов
continue
logger.debug(f"Successfully processed {len(result)} authors")
return result
except Exception as e:
logger.error(f"Error in fetch_authors_with_stats: {e}")
logger.error(f"Traceback: {traceback.format_exc()}")
raise
# Используем универсальную функцию для кеширования запросов
return await cached_query(cache_key, fetch_authors_with_stats)
cached_result = await cached_query(cache_key, fetch_authors_with_stats)
logger.debug(f"Cached result: {cached_result}")
return cached_result
# Функция для инвалидации кеша авторов
@@ -285,8 +306,7 @@ async def invalidate_authors_cache(author_id=None) -> None:
Инвалидирует кеши авторов при изменении данных.
Args:
author_id: Опциональный ID автора для точечной инвалидации.
Если не указан, инвалидируются все кеши авторов.
author_id: Опциональный ID автора для точечной инвалидации. Если не указан, инвалидируются все кеши авторов.
"""
if author_id:
# Точечная инвалидация конкретного автора