refactor: extract common handler logic into decorators

- Add @answer_callback for auto-answering callback queries
- Add @with_user_and_session for injecting user/session
- Add @require_state for state validation
- Reduce handlers.py from ~850 to ~540 lines
This commit is contained in:
2026-02-02 21:27:26 +03:00
parent 8fecb3d543
commit 613e492b2d
2 changed files with 523 additions and 587 deletions

76
src/decorators.py Normal file
View File

@@ -0,0 +1,76 @@
from functools import wraps
from typing import Callable
from telegram import Update
from telegram.ext import ContextTypes
from src.database import (
User,
UserSession,
UserState,
get_or_create_user,
get_user_session,
)
INVALID_STATE_TEXT = "❌ Некорректное действие. Используйте /start для начала."
def answer_callback(func: Callable) -> Callable:
"""Автоматически отвечает на callback query."""
@wraps(func)
async def wrapper(
update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs
):
if update.callback_query:
await update.callback_query.answer()
return await func(update, context, *args, **kwargs)
return wrapper
def with_user_and_session(func: Callable) -> Callable:
"""Добавляет user и session в kwargs."""
@wraps(func)
async def wrapper(
update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs
):
user = get_or_create_user(update.effective_user.id)
session = get_user_session(user.id)
return await func(update, context, *args, user=user, session=session, **kwargs)
return wrapper
def require_state(*states: UserState):
"""Проверяет, что сессия в одном из указанных состояний."""
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
*args,
user: User,
session: UserSession | None,
**kwargs,
):
if not session or session.state not in states:
if update.callback_query:
await update.callback_query.edit_message_text(INVALID_STATE_TEXT)
elif update.message:
await update.message.reply_text(INVALID_STATE_TEXT)
return
return await func(
update, context, *args, user=user, session=session, **kwargs
)
return wrapper
return decorator
def require_states(*states: UserState):
"""Алиас для require_state с несколькими состояниями."""
return require_state(*states)

File diff suppressed because it is too large Load Diff