@@ -415,9 +415,19 @@ async def lock_user_for_update(db: AsyncSession, user: User) -> User:
|
||||
|
||||
Returns the refreshed user object with current DB values.
|
||||
Must be called within an active transaction before modifying balance_kopeks.
|
||||
Eagerly loads key relationships to avoid MissingGreenlet in async context.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User).where(User.id == user.id).with_for_update().execution_options(populate_existing=True)
|
||||
select(User)
|
||||
.where(User.id == user.id)
|
||||
.options(
|
||||
selectinload(User.subscription),
|
||||
selectinload(User.user_promo_groups).selectinload(UserPromoGroup.promo_group),
|
||||
selectinload(User.promo_group),
|
||||
selectinload(User.referrer),
|
||||
)
|
||||
.with_for_update()
|
||||
.execution_options(populate_existing=True)
|
||||
)
|
||||
return result.scalar_one()
|
||||
|
||||
@@ -434,8 +444,18 @@ async def add_user_balance(
|
||||
) -> bool:
|
||||
try:
|
||||
# Lock the user row to prevent concurrent balance race conditions
|
||||
# Eagerly load key relationships to avoid MissingGreenlet in async context
|
||||
locked_result = await db.execute(
|
||||
select(User).where(User.id == user.id).with_for_update().execution_options(populate_existing=True)
|
||||
select(User)
|
||||
.where(User.id == user.id)
|
||||
.options(
|
||||
selectinload(User.subscription),
|
||||
selectinload(User.user_promo_groups).selectinload(UserPromoGroup.promo_group),
|
||||
selectinload(User.promo_group),
|
||||
selectinload(User.referrer),
|
||||
)
|
||||
.with_for_update()
|
||||
.execution_options(populate_existing=True)
|
||||
)
|
||||
user = locked_result.scalar_one()
|
||||
|
||||
@@ -534,8 +554,18 @@ async def subtract_user_balance(
|
||||
)
|
||||
|
||||
# Lock the user row to prevent concurrent balance race conditions
|
||||
# Eagerly load key relationships to avoid MissingGreenlet in async context
|
||||
locked_result = await db.execute(
|
||||
select(User).where(User.id == user.id).with_for_update().execution_options(populate_existing=True)
|
||||
select(User)
|
||||
.where(User.id == user.id)
|
||||
.options(
|
||||
selectinload(User.subscription),
|
||||
selectinload(User.user_promo_groups).selectinload(UserPromoGroup.promo_group),
|
||||
selectinload(User.promo_group),
|
||||
selectinload(User.referrer),
|
||||
)
|
||||
.with_for_update()
|
||||
.execution_options(populate_existing=True)
|
||||
)
|
||||
user = locked_result.scalar_one()
|
||||
|
||||
|
||||
@@ -274,8 +274,9 @@ class ChannelSubscriptionService:
|
||||
)
|
||||
return False # Fail-closed -- bot cannot verify membership
|
||||
except TelegramBadRequest as e:
|
||||
if 'user not found' in str(e).lower():
|
||||
return False # User never interacted with bot in that context
|
||||
err_msg = str(e).lower()
|
||||
if 'user not found' in err_msg or 'participant_id_invalid' in err_msg:
|
||||
return False # User never interacted with bot/channel
|
||||
logger.error('Bad request checking channel', channel_id=channel_id, error=str(e))
|
||||
return False # Fail-closed
|
||||
except TelegramNetworkError:
|
||||
|
||||
@@ -24,6 +24,8 @@ def format_referrer_info(user: User) -> str:
|
||||
|
||||
try:
|
||||
# Проверяем, является ли referrer обычным объектом или InstrumentedList
|
||||
# getattr default does NOT catch MissingGreenlet (not an AttributeError),
|
||||
# so we wrap in try/except to handle lazy-load failures in async context.
|
||||
referrer = getattr(user, 'referrer', None)
|
||||
|
||||
# Если referrer это InstrumentedList или None, то возвращаем информацию по ID
|
||||
@@ -39,8 +41,9 @@ def format_referrer_info(user: User) -> str:
|
||||
|
||||
return f'ID {referrer_telegram_id or referred_by_id}'
|
||||
|
||||
except (AttributeError, TypeError):
|
||||
# Если возникла ошибка при обращении к атрибутам, просто возвращаем ID
|
||||
except Exception:
|
||||
# MissingGreenlet is not a subclass of AttributeError/TypeError,
|
||||
# so we must catch broadly to handle lazy-load failures in async context.
|
||||
return f'ID {referred_by_id} (ошибка загрузки)'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user