refactor: replace try-except blocks with contextlib.suppress for cleaner error handling across multiple files
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
@@ -564,10 +565,8 @@ class DiscordChannel(BaseChannel):
|
||||
# Delayed working indicator (cosmetic — not tied to subagent lifecycle)
|
||||
async def _delayed_working_emoji() -> None:
|
||||
await asyncio.sleep(self.config.working_emoji_delay)
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await message.add_reaction(self.config.working_emoji)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._working_emoji_tasks[channel_id] = asyncio.create_task(_delayed_working_emoji())
|
||||
|
||||
@@ -771,10 +770,8 @@ class DiscordChannel(BaseChannel):
|
||||
if task is None:
|
||||
return
|
||||
task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def _clear_reactions(self, chat_id: str) -> None:
|
||||
"""Remove all pending reactions after bot replies."""
|
||||
@@ -788,10 +785,8 @@ class DiscordChannel(BaseChannel):
|
||||
return
|
||||
bot_user = self._client.user if self._client else None
|
||||
for emoji in (self.config.read_receipt_emoji, self.config.working_emoji):
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await msg_obj.remove_reaction(emoji, bot_user)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _cancel_all_typing(self) -> None:
|
||||
"""Stop all typing tasks."""
|
||||
|
||||
@@ -6,6 +6,7 @@ import imaplib
|
||||
import re
|
||||
import smtplib
|
||||
import ssl
|
||||
from contextlib import suppress
|
||||
from datetime import date
|
||||
from email import policy
|
||||
from email.header import decode_header, make_header
|
||||
@@ -460,10 +461,8 @@ class EmailChannel(BaseChannel):
|
||||
if mark_seen:
|
||||
client.store(imap_id, "+FLAGS", "\\Seen")
|
||||
finally:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
client.logout()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _collect_self_addresses(self) -> set[str]:
|
||||
"""Return normalized email addresses owned by this channel instance."""
|
||||
|
||||
@@ -9,6 +9,7 @@ import threading
|
||||
import time
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal
|
||||
|
||||
@@ -612,12 +613,11 @@ class FeishuChannel(BaseChannel):
|
||||
"""Callback: store reaction_id after background add-reaction completes."""
|
||||
if task.cancelled():
|
||||
return
|
||||
try:
|
||||
# Failures already logged by _on_background_task_done.
|
||||
with suppress(Exception):
|
||||
reaction_id = task.result()
|
||||
if reaction_id:
|
||||
self._reaction_ids[message_id] = reaction_id
|
||||
except Exception:
|
||||
pass # already logged by _on_background_task_done
|
||||
# Trim cache to prevent unbounded growth
|
||||
if len(self._reaction_ids) > 500:
|
||||
self._reaction_ids.pop(next(iter(self._reaction_ids)))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@@ -220,10 +221,8 @@ class ChannelManager:
|
||||
# Stop dispatcher
|
||||
if self._dispatch_task:
|
||||
self._dispatch_task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self._dispatch_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# Stop all channels
|
||||
for name, channel in self.channels.items():
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, TypeAlias
|
||||
@@ -341,10 +342,8 @@ class MatrixChannel(BaseChannel):
|
||||
timeout=self.config.sync_stop_grace_seconds)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
self._sync_task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self._sync_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if self.client:
|
||||
await self.client.close()
|
||||
|
||||
@@ -609,13 +608,11 @@ class MatrixChannel(BaseChannel):
|
||||
"""Best-effort typing indicator update."""
|
||||
if not self.client:
|
||||
return
|
||||
try:
|
||||
with suppress(Exception):
|
||||
response = await self.client.room_typing(room_id=room_id, typing_state=typing,
|
||||
timeout=TYPING_NOTICE_TIMEOUT_MS)
|
||||
if isinstance(response, RoomTypingError):
|
||||
logger.debug("Matrix typing failed for {}: {}", room_id, response)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _start_typing_keepalive(self, room_id: str) -> None:
|
||||
"""Start periodic typing refresh (spec-recommended keepalive)."""
|
||||
@@ -625,22 +622,18 @@ class MatrixChannel(BaseChannel):
|
||||
return
|
||||
|
||||
async def loop() -> None:
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
while self._running:
|
||||
await asyncio.sleep(TYPING_KEEPALIVE_INTERVAL_MS / 1000)
|
||||
await self._set_typing(room_id, True)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
self._typing_tasks[room_id] = asyncio.create_task(loop())
|
||||
|
||||
async def _stop_typing_keepalive(self, room_id: str, *, clear_typing: bool) -> None:
|
||||
if task := self._typing_tasks.pop(room_id, None):
|
||||
task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if clear_typing:
|
||||
await self._set_typing(room_id, False)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
@@ -330,10 +331,8 @@ class MochatChannel(BaseChannel):
|
||||
await self._cancel_delay_timers()
|
||||
|
||||
if self._socket:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._socket.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
self._socket = None
|
||||
|
||||
if self._cursor_save_task:
|
||||
@@ -460,10 +459,8 @@ class MochatChannel(BaseChannel):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Failed to connect Mochat websocket: {}", e)
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await client.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
self._socket = None
|
||||
return False
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import re
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from contextlib import contextmanager, suppress
|
||||
from dataclasses import dataclass
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -712,10 +712,8 @@ class MSTeamsChannel(BaseChannel):
|
||||
os.replace(tmp_path, path)
|
||||
finally:
|
||||
if tmp_path and os.path.exists(tmp_path):
|
||||
try:
|
||||
with suppress(OSError):
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _save_refs_locked(self, *, prune: bool = True) -> None:
|
||||
"""Persist conversation references (caller must hold _refs_guard)."""
|
||||
|
||||
@@ -25,6 +25,7 @@ import os
|
||||
import re
|
||||
import time
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
from urllib.parse import unquote, urlparse
|
||||
@@ -221,17 +222,13 @@ class QQChannel(BaseChannel):
|
||||
"""Stop bot and cleanup resources."""
|
||||
self._running = False
|
||||
if self._client:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._client.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._client = None
|
||||
|
||||
if self._http:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._http.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._http = None
|
||||
|
||||
logger.info("QQ bot stopped")
|
||||
@@ -683,7 +680,5 @@ class QQChannel(BaseChannel):
|
||||
finally:
|
||||
# Cleanup partial file
|
||||
if tmp_path is not None:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
tmp_path.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,7 @@ import asyncio
|
||||
import re
|
||||
import time
|
||||
import unicodedata
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
@@ -462,10 +463,8 @@ class TelegramChannel(BaseChannel):
|
||||
if not msg.metadata.get("_progress", False):
|
||||
self._stop_typing(msg.chat_id)
|
||||
if reply_to_message_id := msg.metadata.get("message_id"):
|
||||
try:
|
||||
with suppress(ValueError):
|
||||
await self._remove_reaction(msg.chat_id, int(reply_to_message_id))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
chat_id = int(msg.chat_id)
|
||||
@@ -642,10 +641,8 @@ class TelegramChannel(BaseChannel):
|
||||
return
|
||||
self._stop_typing(chat_id)
|
||||
if reply_to_message_id := meta.get("message_id"):
|
||||
try:
|
||||
with suppress(ValueError):
|
||||
await self._remove_reaction(chat_id, int(reply_to_message_id))
|
||||
except ValueError:
|
||||
pass
|
||||
thread_kwargs = {}
|
||||
if message_thread_id := meta.get("message_thread_id"):
|
||||
thread_kwargs["message_thread_id"] = message_thread_id
|
||||
@@ -1162,11 +1159,10 @@ class TelegramChannel(BaseChannel):
|
||||
async def _typing_loop(self, chat_id: str) -> None:
|
||||
"""Repeatedly send 'typing' action until cancelled."""
|
||||
try:
|
||||
while self._app:
|
||||
await self._app.bot.send_chat_action(chat_id=int(chat_id), action="typing")
|
||||
await asyncio.sleep(4)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
with suppress(asyncio.CancelledError):
|
||||
while self._app:
|
||||
await self._app.bot.send_chat_action(chat_id=int(chat_id), action="typing")
|
||||
await asyncio.sleep(4)
|
||||
except Exception as e:
|
||||
logger.debug("Typing indicator stopped for {}: {}", chat_id, e)
|
||||
|
||||
@@ -1265,10 +1261,8 @@ class TelegramChannel(BaseChannel):
|
||||
button_label = query.data or ""
|
||||
await query.answer()
|
||||
if query.message:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await query.message.edit_reply_markup(reply_markup=None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Inline button tap from {}: {}", sender_id, button_label)
|
||||
self._start_typing(str(chat_id))
|
||||
await self._handle_message(
|
||||
|
||||
+12
-33
@@ -19,6 +19,7 @@ import re
|
||||
import time
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
@@ -211,7 +212,7 @@ class WeixinChannel(BaseChannel):
|
||||
|
||||
def _save_state(self) -> None:
|
||||
state_file = self._get_state_dir() / "account.json"
|
||||
try:
|
||||
with suppress(Exception):
|
||||
data = {
|
||||
"token": self._token,
|
||||
"get_updates_buf": self._get_updates_buf,
|
||||
@@ -220,8 +221,6 @@ class WeixinChannel(BaseChannel):
|
||||
"base_url": self.config.base_url,
|
||||
}
|
||||
state_file.write_text(json.dumps(data, ensure_ascii=False))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# HTTP helpers (matches api.ts buildHeaders / apiFetch)
|
||||
@@ -576,10 +575,8 @@ class WeixinChannel(BaseChannel):
|
||||
# Process messages (WeixinMessage[] from types.ts)
|
||||
msgs: list[dict] = data.get("msgs", []) or []
|
||||
for msg in msgs:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._process_message(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Inbound message processing (matches inbound.ts + process-message.ts)
|
||||
@@ -932,10 +929,8 @@ class WeixinChannel(BaseChannel):
|
||||
await asyncio.sleep(TYPING_KEEPALIVE_INTERVAL_S)
|
||||
if stop_event.is_set():
|
||||
break
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._send_typing(user_id, typing_ticket, TYPING_STATUS_TYPING)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
@@ -962,16 +957,12 @@ class WeixinChannel(BaseChannel):
|
||||
return
|
||||
|
||||
typing_ticket = ""
|
||||
try:
|
||||
with suppress(Exception):
|
||||
typing_ticket = await self._get_typing_ticket(msg.chat_id, ctx_token)
|
||||
except Exception:
|
||||
typing_ticket = ""
|
||||
|
||||
if typing_ticket:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._send_typing(msg.chat_id, typing_ticket, TYPING_STATUS_TYPING)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
typing_keepalive_stop = asyncio.Event()
|
||||
typing_keepalive_task: asyncio.Task | None = None
|
||||
@@ -1043,16 +1034,12 @@ class WeixinChannel(BaseChannel):
|
||||
if typing_keepalive_task:
|
||||
typing_keepalive_stop.set()
|
||||
typing_keepalive_task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await typing_keepalive_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
if typing_ticket and not is_progress:
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._send_typing(msg.chat_id, typing_ticket, TYPING_STATUS_CANCEL)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _start_typing(self, chat_id: str, context_token: str = "") -> None:
|
||||
"""Start typing indicator immediately when a message is received."""
|
||||
@@ -1076,10 +1063,8 @@ class WeixinChannel(BaseChannel):
|
||||
await asyncio.sleep(TYPING_KEEPALIVE_INTERVAL_S)
|
||||
if stop_event.is_set():
|
||||
break
|
||||
try:
|
||||
with suppress(Exception):
|
||||
await self._send_typing(chat_id, ticket, TYPING_STATUS_TYPING)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
@@ -1095,10 +1080,8 @@ class WeixinChannel(BaseChannel):
|
||||
if stop_event:
|
||||
stop_event.set()
|
||||
task.cancel()
|
||||
try:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if not clear_remote:
|
||||
return
|
||||
entry = self._typing_tickets.get(chat_id)
|
||||
@@ -1339,13 +1322,11 @@ def _encrypt_aes_ecb(data: bytes, aes_key_b64: str) -> bytes:
|
||||
pad_len = 16 - len(data) % 16
|
||||
padded = data + bytes([pad_len] * pad_len)
|
||||
|
||||
try:
|
||||
with suppress(ImportError):
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
return cipher.encrypt(padded)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
@@ -1371,13 +1352,11 @@ def _decrypt_aes_ecb(data: bytes, aes_key_b64: str) -> bytes:
|
||||
|
||||
decrypted: bytes | None = None
|
||||
|
||||
try:
|
||||
with suppress(ImportError):
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = cipher.decrypt(data)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if decrypted is None:
|
||||
try:
|
||||
|
||||
@@ -8,6 +8,7 @@ import os
|
||||
import secrets
|
||||
import shutil
|
||||
import subprocess
|
||||
from contextlib import suppress
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
@@ -47,10 +48,8 @@ def _load_or_create_bridge_token(path: Path) -> str:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
token = secrets.token_urlsafe(32)
|
||||
path.write_text(token, encoding="utf-8")
|
||||
try:
|
||||
with suppress(OSError):
|
||||
path.chmod(0o600)
|
||||
except OSError:
|
||||
pass
|
||||
return token
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user