fix syntax

This commit is contained in:
kandrusyak
2026-07-25 15:47:36 +03:00
parent 3f63d0305c
commit c04fb9480b
8 changed files with 56 additions and 32 deletions

View File

@@ -148,16 +148,18 @@ async def private_voice(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N
)
status_message = await update.message.reply_text("Распознаю голосовое сообщение...")
temp_path: Path | None = None
transcript = ""
try:
telegram_file = await context.bot.get_file(voice.file_id)
file_descriptor, raw_path = tempfile.mkstemp(suffix=".ogg")
os.close(file_descriptor)
temp_path = Path(raw_path)
await telegram_file.download_to_drive(custom_path=temp_path)
voice_path = Path(raw_path)
temp_path = voice_path
await telegram_file.download_to_drive(custom_path=voice_path)
transcript = await asyncio.to_thread(
get_speech_recognizer(context).transcribe,
temp_path,
voice_path,
)
except (SpeechRecognitionError, TelegramError):
logger.exception("Failed to transcribe Telegram voice message")
@@ -364,7 +366,7 @@ async def remind_command(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
reminder_id = get_storage(context).add_reminder(
user_id=user_id,
chat_id=int(update.effective_chat.id),
chat_id=int(update.message.chat_id),
text=parsed.text,
remind_at_utc=parsed.remind_at_utc,
)

View File

@@ -2,7 +2,7 @@ import sys
import threading
import warnings
from pathlib import Path
from typing import Any, Callable, Protocol
from typing import Any, Callable, Protocol, cast
class SpeechRecognitionError(RuntimeError):
@@ -21,9 +21,9 @@ def load_whisper_model_class() -> Any:
missing = object()
loaded_torch = sys.modules.get("torch", missing)
if loaded_torch is missing:
sys.modules["torch"] = None
sys.modules["torch"] = cast(Any, None)
try:
with warnings.catch_warnings():
with warnings.catch_warnings(record=False):
warnings.filterwarnings(
"ignore",
message="pkg_resources is deprecated as an API.*",

View File

@@ -9,6 +9,17 @@ from .config import get_default_model
from .time_utils import to_utc_iso, utc_now
def row_to_dict(row: sqlite3.Row) -> dict[str, Any]:
return {str(key): row[key] for key in row.keys()}
def required_lastrowid(cursor: sqlite3.Cursor) -> int:
lastrowid = cursor.lastrowid
if lastrowid is None:
raise RuntimeError("SQLite insert did not return a row ID")
return lastrowid
class AssistantStorage:
def __init__(self, path: Path) -> None:
self.path = path
@@ -162,7 +173,7 @@ class AssistantStorage:
""",
(user_id, chat_id, started_after_id, limit),
).fetchall()
return [dict(row) for row in reversed(rows)]
return [row_to_dict(row) for row in reversed(rows)]
def start_new_conversation(self, user_id: int, chat_id: int) -> None:
now = to_utc_iso(utc_now())
@@ -230,7 +241,7 @@ class AssistantStorage:
age = max(0, newest_id - int(row["id"]))
recency_weight = 1.0 / (1.0 + age / 50.0)
score = (coverage + phrase_bonus) * (0.5 + 0.5 * recency_weight)
item = dict(row)
item = row_to_dict(row)
item["score"] = round(score, 4)
matches.append((score, item))
@@ -266,7 +277,7 @@ class AssistantStorage:
(user_id, chat_id, message_id, radius),
).fetchall()
rows = [*reversed(before), *after]
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def get_user_model(self, user_id: int, provider: str = "local") -> str:
column = self._model_column(provider)
@@ -315,7 +326,7 @@ class AssistantStorage:
"INSERT INTO memories(user_id, text, created_at) VALUES (?, ?, ?)",
(user_id, text, to_utc_iso(utc_now())),
)
return int(cursor.lastrowid)
return required_lastrowid(cursor)
def list_memories(self, user_id: int, limit: int = 20) -> list[dict[str, Any]]:
with self._connection() as connection:
@@ -329,7 +340,7 @@ class AssistantStorage:
""",
(user_id, limit),
).fetchall()
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def delete_memory(self, user_id: int, memory_id: int) -> bool:
with self._connection() as connection:
@@ -345,7 +356,7 @@ class AssistantStorage:
"INSERT INTO notes(user_id, text, created_at) VALUES (?, ?, ?)",
(user_id, text, to_utc_iso(utc_now())),
)
return int(cursor.lastrowid)
return required_lastrowid(cursor)
def list_notes(self, user_id: int, limit: int = 20) -> list[dict[str, Any]]:
with self._connection() as connection:
@@ -359,7 +370,7 @@ class AssistantStorage:
""",
(user_id, limit),
).fetchall()
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def delete_note(self, user_id: int, note_id: int) -> bool:
with self._connection() as connection:
@@ -390,7 +401,7 @@ class AssistantStorage:
to_utc_iso(utc_now()),
),
)
return int(cursor.lastrowid)
return required_lastrowid(cursor)
def list_reminders(self, user_id: int, limit: int = 20) -> list[dict[str, Any]]:
with self._connection() as connection:
@@ -404,7 +415,7 @@ class AssistantStorage:
""",
(user_id, limit),
).fetchall()
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def cancel_reminder(self, user_id: int, reminder_id: int) -> bool:
with self._connection() as connection:
@@ -430,7 +441,7 @@ class AssistantStorage:
""",
(to_utc_iso(now_utc), limit),
).fetchall()
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def mark_reminder_sent(self, reminder_id: int) -> None:
with self._connection() as connection:
@@ -453,7 +464,7 @@ class AssistantStorage:
""",
(user_id, title, status, now, now),
)
return int(cursor.lastrowid)
return required_lastrowid(cursor)
def list_tracked_items(self, user_id: int, limit: int = 30) -> list[dict[str, Any]]:
with self._connection() as connection:
@@ -467,7 +478,7 @@ class AssistantStorage:
""",
(user_id, limit),
).fetchall()
return [dict(row) for row in rows]
return [row_to_dict(row) for row in rows]
def set_tracked_status(self, user_id: int, item_id: int, status: str) -> bool:
with self._connection() as connection:

View File

@@ -57,12 +57,13 @@ def get_voice_max_duration(context: ContextTypes.DEFAULT_TYPE) -> int:
def command_text(context: ContextTypes.DEFAULT_TYPE) -> str:
return " ".join(context.args).strip()
return " ".join(context.args or []).strip()
def require_user_id(update: Update) -> int | None:
if update.effective_user:
return int(update.effective_user.id)
user = update.effective_user
if user:
return int(user.id)
return None

View File

@@ -13,7 +13,10 @@ from assistant_bot.storage import AssistantStorage
class ReminderParserTests(unittest.TestCase):
def test_supported_relative_unit(self) -> None:
self.assertEqual(unit_to_timedelta(2, "часа").total_seconds(), 7200)
delta = unit_to_timedelta(2, "часа")
self.assertIsNotNone(delta)
assert delta is not None
self.assertEqual(delta.total_seconds(), 7200)
def test_absolute_datetime(self) -> None:
result = parse_reminder(

View File

@@ -1,6 +1,7 @@
import asyncio
import unittest
from types import SimpleNamespace
from typing import Any
from unittest.mock import patch
from assistant_bot.jobs import post_init, post_shutdown
@@ -10,11 +11,11 @@ class ReminderJobLifecycleTests(unittest.IsolatedAsyncioTestCase):
async def test_reminder_task_is_cancelled_on_shutdown(self) -> None:
started = asyncio.Event()
async def fake_reminder_loop(application) -> None:
async def fake_reminder_loop(_application) -> None:
started.set()
await asyncio.Event().wait()
application = SimpleNamespace(bot_data={})
application: Any = SimpleNamespace(bot_data={})
with patch("assistant_bot.jobs.reminder_loop", fake_reminder_loop):
await post_init(application)
await started.wait()

View File

@@ -1,6 +1,7 @@
import unittest
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from assistant_bot.handlers import private_voice
@@ -15,7 +16,7 @@ class VoiceHandlerTests(unittest.IsolatedAsyncioTestCase):
voice=SimpleNamespace(duration=duration, file_id="voice-file-id"),
reply_text=AsyncMock(return_value=status_message),
)
update = SimpleNamespace(message=message)
update: Any = SimpleNamespace(message=message)
telegram_file = SimpleNamespace(download_to_drive=AsyncMock())
bot = SimpleNamespace(
get_file=AsyncMock(return_value=telegram_file),
@@ -23,7 +24,7 @@ class VoiceHandlerTests(unittest.IsolatedAsyncioTestCase):
)
recognizer = MagicMock()
recognizer.transcribe.return_value = transcript
context = SimpleNamespace(
context: Any = SimpleNamespace(
bot=bot,
application=SimpleNamespace(
bot_data={

View File

@@ -38,6 +38,15 @@ class FakeChatCompletions:
return self.completion
class FakeHTTPError(RuntimeError):
def __init__(self) -> None:
super().__init__("forbidden")
self.response = SimpleNamespace(
status_code=403,
text='{"error":{"message":"folder mismatch"}}',
)
class YandexAIClientTests(unittest.IsolatedAsyncioTestCase):
def test_multiple_system_messages_are_merged_at_the_beginning(self) -> None:
self.assertEqual(
@@ -62,11 +71,7 @@ class YandexAIClientTests(unittest.IsolatedAsyncioTestCase):
)
def test_http_error_includes_safe_response_body(self) -> None:
error = RuntimeError("forbidden")
error.response = SimpleNamespace(
status_code=403,
text='{"error":{"message":"folder mismatch"}}',
)
error = FakeHTTPError()
self.assertEqual(
describe_yandex_error(error),