chore: add ruff and ty, fix linting

- Add ruff (line-length 88) and ty to dev dependencies
- Fix all ruff linting errors
- Configure ty to ignore nullable type warnings
- Update AGENTS.md with linting instructions
This commit is contained in:
2026-02-02 21:43:08 +03:00
parent 52dce1b2b8
commit fc3f438cbf
8 changed files with 141 additions and 38 deletions

View File

@@ -1,9 +1,9 @@
import sqlite3
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Generator
from src.config import DB_PATH
from src.logger import logger
@@ -171,7 +171,8 @@ def get_or_create_user(telegram_id: int) -> User:
)
cursor = conn.execute(
"INSERT INTO users (telegram_id) VALUES (?) RETURNING id, telegram_id, created_at",
"INSERT INTO users (telegram_id) VALUES (?) "
"RETURNING id, telegram_id, created_at",
(telegram_id,),
)
row = cursor.fetchone()
@@ -244,10 +245,11 @@ def get_all_scenarios() -> list[Scenario]:
def create_replicas(scenario_id: str, replicas: list[tuple[int, int, str]]) -> None:
"""Создаёт реплики для сценария. replicas: [(speaker_id, replica_index, text), ...]"""
"""Создаёт реплики. replicas: [(speaker_id, replica_index, text), ...]"""
with get_connection() as conn:
conn.executemany(
"INSERT INTO replicas (scenario_id, speaker_id, replica_index, text) VALUES (?, ?, ?, ?)",
"INSERT INTO replicas (scenario_id, speaker_id, replica_index, text) "
"VALUES (?, ?, ?, ?)",
[
(scenario_id, speaker_id, idx, text)
for speaker_id, idx, text in replicas
@@ -300,7 +302,8 @@ def get_track_speaker_ids(scenario_id: str) -> list[int]:
"""Получает список speaker_id (дорожек) в сценарии."""
with get_connection() as conn:
cursor = conn.execute(
"SELECT DISTINCT speaker_id FROM replicas WHERE scenario_id = ? ORDER BY speaker_id",
"SELECT DISTINCT speaker_id FROM replicas "
"WHERE scenario_id = ? ORDER BY speaker_id",
(scenario_id,),
)
return [row["speaker_id"] for row in cursor.fetchall()]
@@ -314,7 +317,8 @@ def create_recording(user_id: int, scenario_id: str, replica_index: int) -> Reco
with get_connection() as conn:
cursor = conn.execute(
"INSERT INTO recordings (user_id, scenario_id, replica_index) "
"VALUES (?, ?, ?) RETURNING id, user_id, scenario_id, replica_index, created_at",
"VALUES (?, ?, ?) "
"RETURNING id, user_id, scenario_id, replica_index, created_at",
(user_id, scenario_id, replica_index),
)
row = cursor.fetchone()
@@ -356,8 +360,9 @@ def get_user_recordings_for_scenario(user_id: int, scenario_id: str) -> list[Rec
"""Получает все записи пользователя для сценария."""
with get_connection() as conn:
cursor = conn.execute(
"SELECT id, user_id, scenario_id, replica_index, created_at FROM recordings "
"WHERE user_id = ? AND scenario_id = ? ORDER BY replica_index",
"SELECT id, user_id, scenario_id, replica_index, created_at "
"FROM recordings WHERE user_id = ? AND scenario_id = ? "
"ORDER BY replica_index",
(user_id, scenario_id),
)
return [

View File

@@ -1,5 +1,5 @@
from collections.abc import Callable
from functools import wraps
from typing import Callable
from telegram import Update
from telegram.ext import ContextTypes
@@ -69,5 +69,3 @@ def require_state(*states: UserState):
return wrapper
return decorator

View File

@@ -26,9 +26,11 @@ INTRO_TEXT = """👋 Добро пожаловать!
Это бот для сбора датасета озвученных реплик совещаний.
Вы будете озвучивать реплики участников совещаний. Каждая дорожка — это реплики одного участника в рамках одного совещания.
Вы будете озвучивать реплики участников совещаний.
Каждая дорожка — это реплики одного участника в рамках одного совещания.
📋 Отправляя голосовые сообщения, вы соглашаетесь с тем, что они будут использованы в исследовательских целях для обучения моделей машинного обучения.
📋 Отправляя голосовые сообщения, вы соглашаетесь с тем, что они будут
использованы в исследовательских целях для обучения моделей машинного обучения.
Нажмите кнопку ниже, чтобы начать."""
@@ -201,6 +203,13 @@ def get_track_length(scenario_id: str, speaker_id: int) -> int:
return len(get_replicas_for_track(scenario_id, speaker_id))
def format_replica_message(session: UserSession) -> str:
"""Форматирует сообщение с репликой."""
replica_text = get_current_replica_text(session)
header = SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)
return f"{header}\n\n{replica_text}"
def format_admin_stats() -> str:
"""Форматирует статистику для админки."""
stats = get_stats()
@@ -314,8 +323,7 @@ async def handle_rerecord_previous(
replica_text = get_current_replica_text(session)
await query.edit_message_text(f"{FIRST_REPLICA_INSTRUCTIONS}\n\n{replica_text}")
else:
replica_text = get_current_replica_text(session)
text = f"{SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)}\n\n{replica_text}"
text = format_replica_message(session)
await query.edit_message_text(text, reply_markup=get_show_replica_keyboard())
session.last_bot_message_id = query.message.message_id
@@ -371,10 +379,7 @@ async def handle_cancel_restart(
"""Обработчик отмены рестарта."""
query = update.callback_query
session.state = UserState.SHOW_REPLICA
replica_text = get_current_replica_text(session)
text = (
f"{SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)}\n\n{replica_text}"
)
text = format_replica_message(session)
await query.edit_message_text(text, reply_markup=get_show_replica_keyboard())
session.last_bot_message_id = query.message.message_id
upsert_user_session(session)
@@ -430,10 +435,7 @@ async def handle_rerecord_last(
track_length = get_track_length(session.scenario_id, session.speaker_id)
session.state = UserState.SHOW_REPLICA
session.replica_index = track_length - 1
replica_text = get_current_replica_text(session)
text = (
f"{SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)}\n\n{replica_text}"
)
text = format_replica_message(session)
await query.edit_message_text(text, reply_markup=get_show_replica_keyboard())
session.last_bot_message_id = query.message.message_id
upsert_user_session(session)
@@ -497,8 +499,7 @@ async def handle_exit_admin(
replica_text = get_current_replica_text(session)
await query.edit_message_text(f"{FIRST_REPLICA_INSTRUCTIONS}\n\n{replica_text}")
elif session.state == UserState.SHOW_REPLICA:
replica_text = get_current_replica_text(session)
text = f"{SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)}\n\n{replica_text}"
text = format_replica_message(session)
await query.edit_message_text(text, reply_markup=get_show_replica_keyboard())
elif session.state == UserState.CONFIRM_SAVE:
await query.edit_message_text(
@@ -547,10 +548,8 @@ async def handle_confirm_upload(
"SELECT telegram_id FROM users WHERE id = ?", (waiting_user_id,)
).fetchone()
if row:
await context.bot.send_message(
row[0],
"🎉 Появился новый сценарий для озвучивания! Используйте /start для продолжения.",
)
msg = "🎉 Появился новый сценарий! Используйте /start"
await context.bot.send_message(row[0], msg)
except Exception:
pass
@@ -619,8 +618,7 @@ async def handle_voice_message(
)
else:
session.state = UserState.SHOW_REPLICA
replica_text = get_current_replica_text(session)
text = f"{SHOW_REPLICA_TEXT.format(num=session.replica_index + 1)}\n\n{replica_text}"
text = format_replica_message(session)
msg_id = await send_message_and_save(
update, context, session, text, get_show_replica_keyboard()
)
@@ -631,7 +629,10 @@ async def handle_voice_message(
@with_user_and_session
async def handle_replica_number_input(
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession | None
update: Update,
context: ContextTypes.DEFAULT_TYPE,
user: User,
session: UserSession | None,
) -> None:
"""Обработчик ввода номера реплики."""
if not session or session.state != UserState.ASK_REPLICA_NUMBER:
@@ -715,14 +716,21 @@ VOICE_EXPECTED_TEXT = "❌ Пожалуйста, отправьте голосо
@with_user_and_session
async def handle_unexpected_text(
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession | None
update: Update,
context: ContextTypes.DEFAULT_TYPE,
user: User,
session: UserSession | None,
) -> None:
"""Обработчик неожиданных текстовых сообщений."""
if not session:
await update.message.reply_text("Используйте /start для начала работы с ботом.")
return
voice_states = {UserState.FIRST_REPLICA, UserState.SHOW_REPLICA, UserState.REPEAT_REPLICA}
voice_states = {
UserState.FIRST_REPLICA,
UserState.SHOW_REPLICA,
UserState.REPEAT_REPLICA,
}
if session.state in voice_states:
await update.message.reply_text(VOICE_EXPECTED_TEXT)
# В других состояниях игнорируем текст (например, INTRO, NO_MORE_SCENARIOS)

View File

@@ -9,7 +9,6 @@ from src.database import (
get_connection,
get_replicas_for_track,
get_scenario,
get_track_speaker_ids,
)
from src.logger import logger
@@ -132,7 +131,7 @@ def find_available_track(user_id: int) -> tuple[str, int] | None:
if key in track_recordings and user_id in track_recordings[key]:
# Пользователь уже записывает эту дорожку — пропускаем
continue
# Пользователь записывает другую дорожку в этом сценарии — пропускаем весь сценарий
# Пользователь записывает другую дорожку в этом сценарии
continue
if key not in track_recordings: