stat-refactored
All checks were successful
Deploy to core / deploy (push) Successful in 1m42s

This commit is contained in:
Untone 2024-02-23 02:08:43 +03:00
parent b0e2551e9b
commit 3d34c6c540
8 changed files with 87 additions and 208 deletions

View File

@ -10,7 +10,7 @@ from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.topic import Topic
from resolvers.follower import query_follows
from resolvers.stat import get_authors_from_query, add_author_stat_columns
from resolvers.stat import add_stat_columns, get_with_stat, unpack_stat
from services.auth import login_required
from services.db import local_session
from services.rediscache import redis
@ -20,7 +20,7 @@ from services.logger import root_logger as logger
@mutation.field('update_author')
@login_required
async def update_author(_, info, profile):
def update_author(_, info, profile):
user_id = info.context['user_id']
with local_session() as session:
author = session.query(Author).where(Author.user == user_id).first()
@ -32,7 +32,7 @@ async def update_author(_, info, profile):
# TODO: caching query
@query.field('get_authors_all')
async def get_authors_all(_, _info):
def get_authors_all(_, _info):
authors = []
with local_session() as session:
authors = session.query(Author).all()
@ -97,10 +97,8 @@ def count_author_shouts_rating(session, author_id) -> int:
return shouts_likes - shouts_dislikes
async def load_author_with_stats(q):
q = add_author_stat_columns(q)
result = await get_authors_from_query(q)
def load_author_with_stats(q):
result = get_with_stat(q, Author, AuthorFollower)
if result:
[author] = result
@ -140,7 +138,7 @@ async def load_author_with_stats(q):
@query.field('get_author')
async def get_author(_, _info, slug='', author_id=None):
def get_author(_, _info, slug='', author_id=None):
q = None
if slug or author_id:
if bool(slug):
@ -148,7 +146,7 @@ async def get_author(_, _info, slug='', author_id=None):
if author_id:
q = select(Author).where(Author.id == author_id)
return await load_author_with_stats(q)
return load_author_with_stats(q)
async def get_author_by_user_id(user_id: str):
@ -162,7 +160,7 @@ async def get_author_by_user_id(user_id: str):
logger.info(f'getting author id for {user_id}')
q = select(Author).filter(Author.user == user_id)
author = await load_author_with_stats(q)
author = load_author_with_stats(q)
if author:
update_author(author)
return author
@ -174,9 +172,8 @@ async def get_author_id(_, _info, user: str):
@query.field('load_authors_by')
async def load_authors_by(_, _info, by, limit, offset):
def load_authors_by(_, _info, by, limit, offset):
q = select(Author)
q = add_author_stat_columns(q)
if by.get('slug'):
q = q.filter(Author.slug.ilike(f"%{by['slug']}%"))
elif by.get('name'):
@ -201,14 +198,14 @@ async def load_authors_by(_, _info, by, limit, offset):
q = q.order_by(desc(f'{order}_stat'))
q = q.limit(limit).offset(offset)
authors = await get_authors_from_query(q)
q = q.group_by(Author.id)
authors = get_with_stat(q, Author, AuthorFollower)
return authors
@query.field('get_author_follows')
async def get_author_follows(
def get_author_follows(
_, _info, slug='', user=None, author_id=None
) -> List[Author]:
with local_session() as session:
@ -228,7 +225,7 @@ async def get_author_follows(
@mutation.field('rate_author')
@login_required
async def rate_author(_, info, rated_slug, value):
def rate_author(_, info, rated_slug, value):
user_id = info.context['user_id']
with local_session() as session:
@ -262,7 +259,7 @@ async def rate_author(_, info, rated_slug, value):
return {}
async def create_author(user_id: str, slug: str, name: str = ''):
def create_author(user_id: str, slug: str, name: str = ''):
with local_session() as session:
new_author = Author(user=user_id, slug=slug, name=name)
session.add(new_author)
@ -271,15 +268,15 @@ async def create_author(user_id: str, slug: str, name: str = ''):
@query.field('get_author_followers')
async def get_author_followers(_, _info, slug) -> List[Author]:
q = select(Author)
q = add_author_stat_columns(q)
def get_author_followers(_, _info, slug) -> List[Author]:
aliased_author = aliased(Author)
q = select(aliased_author)
q = (
q.join(AuthorFollower, AuthorFollower.follower == Author.id)
.join(aliased_author, aliased_author.id == AuthorFollower.author)
.where(aliased_author.slug == slug)
)
return await get_authors_from_query(q)
q = add_stat_columns(q, aliased_author, AuthorFollower)
q = q.group_by(aliased_author.id)
return unpack_stat(q)

View File

@ -14,7 +14,7 @@ from orm.shout import Shout, ShoutReactionsFollower
from orm.topic import Topic, TopicFollower
from resolvers.community import community_follow, community_unfollow
from resolvers.topic import topic_follow, topic_unfollow
from resolvers.stat import add_topic_stat_columns, get_topics_from_query, add_author_stat_columns
from resolvers.stat import add_stat_columns, unpack_stat
from services.auth import login_required
from services.db import local_session
from services.follows import DEFAULT_FOLLOWS
@ -110,55 +110,14 @@ def query_follows(user_id: str):
.filter(TopicFollower.topic == Topic.id)
)
authors_query = add_author_stat_columns(authors_query)
topics_query = add_topic_stat_columns(topics_query)
authors = [
{
'id': author_id,
'name': author.name,
'slug': author.slug,
'pic': author.pic,
'bio': author.bio,
'last_seen': author.last_seen or int(time.time()),
'stat': {
'shouts': shouts_stat,
'followers': followers_stat,
'followings': followings_stat, # TODO: rename to authors to
# TODO: use graphql to reserve universal type Stat { authors shouts followers views comments }
},
}
for [
author,
shouts_stat,
followers_stat,
followings_stat,
] in session.execute(authors_query)
]
topics = [
{
'id': topic.id,
'title': topic.title,
'slug': topic.slug,
'body': topic.body,
'stat': {
'shouts': shouts_stat,
'authors': authors_stat,
'followers': followers_stat,
},
}
for [
topic,
shouts_stat,
authors_stat,
followers_stat,
] in session.execute(topics_query)
]
authors_query = add_stat_columns(authors_query, aliased_author, AuthorFollower)
authors = unpack_stat(authors_query)
topics_query = add_stat_columns(topics_query, aliased_author, TopicFollower)
authors = unpack_stat(topics_query)
return {
'topics': topics,
'authors': authors,
# Include other results (e.g., shouts) if needed
'communities': [{'id': 1, 'name': 'Дискурс', 'slug': 'discours'}],
}
@ -260,17 +219,17 @@ def author_unfollow(follower_id, slug):
@query.field('get_topic_followers')
async def get_topic_followers(_, _info, slug: str, topic_id: int) -> List[Author]:
def get_topic_followers(_, _info, slug: str, topic_id: int) -> List[Author]:
q = select(Author)
q = add_topic_stat_columns(q)
q = (
q.join(TopicFollower, TopicFollower.follower == Author.id)
.join(Topic, Topic.id == TopicFollower.topic)
.filter(or_(Topic.slug == slug, Topic.id == topic_id))
)
q = add_stat_columns(q, Author, TopicFollower)
q = q.group_by(Author.id)
return await get_topics_from_query(q)
return unpack_stat(q)
@query.field('get_shout_followers')

View File

@ -19,7 +19,7 @@ from services.viewed import ViewedStorage
from services.logger import root_logger as logger
def add_stat_columns(q, aliased_reaction):
def add_reaction_stat_columns(q, aliased_reaction):
q = q.outerjoin(aliased_reaction).add_columns(
func.sum(aliased_reaction.id).label('reacted_stat'),
func.sum(
@ -229,7 +229,7 @@ async def update_reaction(_, info, reaction):
with local_session() as session:
reaction_query = select(Reaction).filter(Reaction.id == int(rid))
aliased_reaction = aliased(Reaction)
reaction_query = add_stat_columns(reaction_query, aliased_reaction)
reaction_query = add_reaction_stat_columns(reaction_query, aliased_reaction)
reaction_query = reaction_query.group_by(Reaction.id)
try:
@ -358,7 +358,7 @@ async def load_reactions_by(_, info, by, limit=50, offset=0):
# calculate counters
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
# filter
q = apply_reaction_filters(by, q)
@ -425,7 +425,7 @@ async def reacted_shouts_updates(follower_id: int, limit=50, offset=0) -> List[S
.outerjoin(Author, Shout.authors.any(id=follower_id))
.options(joinedload(Shout.reactions), joinedload(Shout.authors))
)
q1 = add_stat_columns(q1, aliased(Reaction))
q1 = add_reaction_stat_columns(q1, aliased(Reaction))
q1 = q1.filter(Author.id == follower_id).group_by(Shout.id)
# Shouts where follower reacted
@ -436,7 +436,7 @@ async def reacted_shouts_updates(follower_id: int, limit=50, offset=0) -> List[S
.filter(Reaction.created_by == follower_id)
.group_by(Shout.id)
)
q2 = add_stat_columns(q2, aliased(Reaction))
q2 = add_reaction_stat_columns(q2, aliased(Reaction))
# Sort shouts by the `last_comment` field
combined_query = (

View File

@ -7,7 +7,7 @@ from orm.author import Author, AuthorFollower
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.topic import Topic, TopicFollower
from resolvers.reaction import add_stat_columns
from resolvers.reaction import add_reaction_stat_columns
from resolvers.topic import get_random_topic
from services.auth import login_required
from services.db import local_session
@ -46,7 +46,7 @@ async def get_shout(_, _info, slug=None, shout_id=None):
with local_session() as session:
q = select(Shout).options(joinedload(Shout.authors), joinedload(Shout.topics))
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
if slug is not None:
q = q.filter(Shout.slug == slug)
@ -133,7 +133,7 @@ async def load_shouts_by(_, _info, options):
# stats
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
# filters
filters = options.get('filters', {})
@ -269,7 +269,7 @@ async def load_shouts_feed(_, info, options):
)
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
filters = options.get('filters', {})
q = apply_filters(q, filters, reader.id)
@ -366,7 +366,7 @@ async def load_shouts_unrated(_, info, limit: int = 50, offset: int = 0):
q = q.having(func.count(distinct(Reaction.id)) <= 4)
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
q = q.group_by(Shout.id).order_by(func.random()).limit(limit).offset(offset)
user_id = info.context.get('user_id')
@ -450,7 +450,7 @@ async def load_shouts_random_top(_, _info, options):
.where(Shout.id.in_(subquery))
)
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
limit = options.get('limit', 10)
q = q.group_by(Shout.id).order_by(func.random()).limit(limit)
@ -486,7 +486,7 @@ def fetch_shouts_by_topic(topic, limit):
)
aliased_reaction = aliased(Reaction)
q = add_stat_columns(q, aliased_reaction)
q = add_reaction_stat_columns(q, aliased_reaction)
q = q.group_by(Shout.id).order_by(desc(Shout.created_at)).limit(limit)

View File

@ -1,81 +1,36 @@
from sqlalchemy import func, distinct
from sqlalchemy.orm import aliased
from orm.author import Author, AuthorFollower
from orm.shout import ShoutAuthor, ShoutTopic
from orm.topic import Topic, TopicFollower
from services.db import local_session
# from services.viewed import ViewedStorage
from orm.author import AuthorFollower
from orm.shout import ShoutTopic, ShoutAuthor
def add_author_stat_columns(q):
shout_author_aliased = aliased(ShoutAuthor)
q = q.outerjoin(shout_author_aliased).add_columns(
func.count(distinct(shout_author_aliased.shout)).label('shouts_stat')
)
authors_table = aliased(AuthorFollower)
def add_stat_columns(q, author_alias, follower_model_alias):
shouts_stat_model = ShoutAuthor if isinstance(follower_model_alias, AuthorFollower) else ShoutTopic
q = q.outerjoin(shouts_stat_model).add_columns(func.count(distinct(shouts_stat_model.shout)).label('shouts_stat'))
q = q.outerjoin(
authors_table, authors_table.follower == Author.id
).add_columns(func.count(distinct(authors_table.author)).label('authors_stat'))
followers_table = aliased(AuthorFollower)
q = q.outerjoin(followers_table, followers_table.author == Author.id).add_columns(
func.count(distinct(followers_table.follower)).label('followers_stat')
follower_model_alias, follower_model_alias.follower == author_alias.id
).add_columns(func.count(distinct(follower_model_alias.author)).label('authors_stat'))
q = q.outerjoin(follower_model_alias, follower_model_alias.author == author_alias.id).add_columns(
func.count(distinct(follower_model_alias.follower)).label('followers_stat')
)
q = q.group_by(Author.id)
return q
async def get_authors_from_query(q):
authors = []
def unpack_stat(q):
records = []
with local_session() as session:
for [author, shouts_stat, authors_stat, followers_stat] in session.execute(q):
author.stat = {
'shouts': shouts_stat,
'followers': followers_stat,
'followings': authors_stat,
# viewed
}
authors.append(author)
return authors
def add_topic_stat_columns(q):
aliased_shout_author = aliased(ShoutAuthor)
aliased_topic_follower = aliased(TopicFollower)
q = (
q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic)
.add_columns(func.count(distinct(ShoutTopic.shout)).label('shouts_stat'))
.outerjoin(aliased_shout_author, ShoutTopic.shout == aliased_shout_author.shout)
.add_columns(
func.count(distinct(aliased_shout_author.author)).label('authors_stat')
)
.outerjoin(aliased_topic_follower)
.add_columns(
func.count(distinct(aliased_topic_follower.follower)).label(
'followers_stat'
)
)
)
q = q.group_by(Topic.id)
return q
async def get_topics_from_query(q):
topics = []
with local_session() as session:
for [topic, shouts_stat, authors_stat, followers_stat] in session.execute(q):
topic.stat = {
for [entity, shouts_stat, authors_stat, followers_stat] in session.execute(q):
entity.stat = {
'shouts': shouts_stat,
'authors': authors_stat,
'followers': followers_stat,
# 'viewed': await ViewedStorage.get_topic(topic.slug),
'followers': followers_stat
}
topics.append(topic)
records.append(entity)
return topics
return records
def get_with_stat(q, author_alias, follower_model_alias):
q = add_stat_columns(q, author_alias, follower_model_alias)
return unpack_stat(q)

View File

@ -3,7 +3,7 @@ from sqlalchemy import and_, distinct, func, select
from orm.author import Author
from orm.shout import ShoutTopic
from orm.topic import Topic, TopicFollower
from resolvers.stat import add_topic_stat_columns, get_topics_from_query
from resolvers.stat import get_with_stat
from services.auth import login_required
from services.db import local_session
from services.schema import mutation, query
@ -11,33 +11,24 @@ from services.logger import root_logger as logger
@query.field('get_topics_all')
async def get_topics_all(_, _info):
def get_topics_all(_, _info):
q = select(Topic)
q = add_topic_stat_columns(q)
return await get_topics_from_query(q)
async def topics_followed_by(author_id):
q = select(Topic, TopicFollower)
q = add_topic_stat_columns(q)
q = q.join(TopicFollower).where(TopicFollower.follower == author_id)
return await get_topics_from_query(q)
q = q.group_by(Topic.id)
topics = get_with_stat(q, Author, TopicFollower)
return topics
@query.field('get_topics_by_community')
async def get_topics_by_community(_, _info, community_id: int):
q = select(Topic).where(Topic.community == community_id)
q = add_topic_stat_columns(q)
return await get_topics_from_query(q)
q = q.group_by(Topic.id)
topics = await get_with_stat(q, Author, TopicFollower)
return topics
@query.field('get_topics_by_author')
async def get_topics_by_author(_, _info, author_id=None, slug='', user=''):
q = select(Topic)
q = add_topic_stat_columns(q)
if author_id:
q = q.join(Author).where(Author.id == author_id)
elif slug:
@ -45,15 +36,16 @@ async def get_topics_by_author(_, _info, author_id=None, slug='', user=''):
elif user:
q = q.join(Author).where(Author.user == user)
return await get_topics_from_query(q)
q = q.group_by(Topic.id)
topics = await get_with_stat(q, Author, TopicFollower)
return topics
@query.field('get_topic')
async def get_topic(_, _info, slug):
q = select(Topic).where(Topic.slug == slug)
q = add_topic_stat_columns(q)
topics = await get_topics_from_query(q)
q = select(Topic).filter(Topic.slug == slug)
q = q.group_by(Topic.id)
topics = await get_with_stat(q, Author, TopicFollower)
if topics:
return topics[0]

View File

@ -1,14 +1,6 @@
type AuthorFollowings {
unread: Int
topics: [String]
authors: [String]
reactions: [Int]
communities: [String]
}
type AuthorStat {
shouts: Int
followings: Int
authors: Int
followers: Int
rating: Int
rating_shouts: Int

View File

@ -5,7 +5,7 @@ import json
from orm.author import Author, AuthorFollower
from orm.topic import Topic, TopicFollower
from resolvers.stat import add_author_stat_columns, add_topic_stat_columns
from resolvers.stat import get_with_stat
from services.rediscache import redis
@ -17,6 +17,7 @@ DEFAULT_FOLLOWS = {
],
}
async def update_author(author: Author, ttl = 25 * 60 * 60):
redis_key = f'user:{author.user}:author'
await redis.execute('SETEX', redis_key, ttl, json.dumps(author.dict()))
@ -77,23 +78,15 @@ async def update_follows_for_user(
async def handle_author_follower_change(connection, author_id, follower_id, is_insert):
q = select(Author).filter(Author.id == author_id)
q = add_author_stat_columns(q)
authors = get_with_stat(q, Author, AuthorFollower)
author = authors[0]
async with connection.begin() as conn:
[author, shouts_stat, followers_stat, followings_stat] = await conn.execute(
q
).first()
author.stat = {
'shouts': shouts_stat,
# 'viewed': await ViewedStorage.get_author(author.slug),
'followers': followers_stat,
'followings': followings_stat,
}
follower = await conn.execute(
select(Author).filter(Author.id == follower_id)
).first()
if follower and author:
await update_follows_for_user(
connection,
conn,
follower.user,
'author',
{
@ -110,23 +103,14 @@ async def handle_author_follower_change(connection, author_id, follower_id, is_i
async def handle_topic_follower_change(connection, topic_id, follower_id, is_insert):
q = select(Topic).filter(Topic.id == topic_id)
q = add_topic_stat_columns(q)
topics = get_with_stat(q, Author, TopicFollower)
topic = topics[0]
async with connection.begin() as conn:
[topic, shouts_stat, authors_stat, followers_stat] = await conn.execute(
q
).first()
topic.stat = {
'shouts': shouts_stat,
'authors': authors_stat,
'followers': followers_stat,
# 'viewed': await ViewedStorage.get_topic(topic.slug),
}
follower = connection.execute(
select(Author).filter(Author.id == follower_id)
).first()
follower = conn.execute(select(Author).filter(Author.id == follower_id)).first()
if follower and topic:
await update_follows_for_user(
connection,
conn,
follower.user,
'topic',
{