fix syntax
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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.*",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user