fix(error-handling): replace bare except clauses with specific exceptions

- qb_downloader.py: catch httpx network errors in logout() and rename_file()
- user.py: log exception details when querying users table fails
- download_client.py: log exception when category creation fails
- title_parser.py: catch specific exceptions (ValueError, AttributeError, TypeError)
  instead of broad Exception in raw_parser()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
EstrellaXD
2026-01-26 14:51:44 +01:00
parent d6e89f62ed
commit 0c8ebb70a3
4 changed files with 61 additions and 26 deletions

View File

@@ -73,10 +73,15 @@ class UserDatabase:
try:
result = self.session.exec(statement)
users = list(result.all())
except Exception:
except Exception as e:
# Table may not exist yet during initial setup
logger.debug(
f"[Database] Could not query users table (may not exist yet): {e}"
)
users = []
if len(users) != 0:
return
user = User(username="admin", password=get_password_hash("adminadmin"))
self.session.add(user)
self.session.commit()
logger.info("[Database] Created default admin user")

View File

@@ -26,9 +26,7 @@ class QbDownloader:
async def auth(self, retry=3):
times = 0
timeout = httpx.Timeout(connect=3.1, read=10.0, write=10.0, pool=10.0)
self._client = httpx.AsyncClient(
timeout=timeout, verify=self.ssl
)
self._client = httpx.AsyncClient(timeout=timeout, verify=self.ssl)
while times < retry:
try:
resp = await self._client.post(
@@ -61,8 +59,12 @@ class QbDownloader:
if self._client:
try:
await self._client.post(self._url("auth/logout"))
except Exception:
pass
except (
httpx.ConnectError,
httpx.RequestError,
httpx.TimeoutException,
) as e:
logger.debug(f"[Downloader] Logout request failed (non-critical): {e}")
await self._client.aclose()
self._client = None
@@ -114,7 +116,9 @@ class QbDownloader:
)
return resp.json()
async def add_torrents(self, torrent_urls, torrent_files, save_path, category, tags=None):
async def add_torrents(
self, torrent_urls, torrent_files, save_path, category, tags=None
):
data = {
"savepath": save_path,
"category": category,
@@ -133,9 +137,17 @@ class QbDownloader:
if torrent_files:
if isinstance(torrent_files, list):
for i, f in enumerate(torrent_files):
files[f"torrents_{i}"] = (f"torrent_{i}.torrent", f, "application/x-bittorrent")
files[f"torrents_{i}"] = (
f"torrent_{i}.torrent",
f,
"application/x-bittorrent",
)
else:
files["torrents"] = ("torrent.torrent", torrent_files, "application/x-bittorrent")
files["torrents"] = (
"torrent.torrent",
torrent_files,
"application/x-bittorrent",
)
max_retries = 3
for attempt in range(max_retries):
@@ -153,14 +165,14 @@ class QbDownloader:
)
await asyncio.sleep(2)
else:
logger.error(f"[Downloader] Failed to add torrent after {max_retries} attempts: {e}")
logger.error(
f"[Downloader] Failed to add torrent after {max_retries} attempts: {e}"
)
raise
async def get_torrents_by_tag(self, tag: str) -> list[dict]:
"""Get all torrents with a specific tag."""
resp = await self._client.get(
self._url("torrents/info"), params={"tag": tag}
)
resp = await self._client.get(self._url("torrents/info"), params={"tag": tag})
return resp.json()
async def torrents_delete(self, hash, delete_files: bool = True):
@@ -191,7 +203,8 @@ class QbDownloader:
logger.debug(f"Conflict409Error: {old_path} >> {new_path}")
return False
return resp.status_code == 200
except Exception:
except (httpx.ConnectError, httpx.RequestError, httpx.TimeoutException) as e:
logger.warning(f"[Downloader] Failed to rename file {old_path}: {e}")
return False
async def rss_add_feed(self, url, item_path):
@@ -216,6 +229,7 @@ class QbDownloader:
async def rss_set_rule(self, rule_name, rule_def):
import json
await self._client.post(
self._url("rss/setRule"),
data={"ruleName": rule_name, "ruleDef": json.dumps(rule_def)},

View File

@@ -70,10 +70,13 @@ class DownloadClient(TorrentPath):
"rss_refresh_interval": 30,
}
await self.client.prefs_init(prefs=prefs)
# Category creation may fail if it already exists (HTTP 409) or network issues
try:
await self.client.add_category("BangumiCollection")
except Exception:
logger.debug("[Downloader] Cannot add new category, maybe already exists.")
except Exception as e:
logger.debug(
f"[Downloader] Could not add category (may already exist): {e}"
)
if settings.downloader.path == "":
prefs = await self.client.get_app_prefs()
settings.downloader.path = self._join_path(prefs["save_path"], "Bangumi")
@@ -107,7 +110,9 @@ class DownloadClient(TorrentPath):
await asyncio.gather(*[self.set_rule(info) for info in bangumi_info])
logger.debug("[Downloader] Finished.")
async def get_torrent_info(self, category="Bangumi", status_filter="completed", tag=None):
async def get_torrent_info(
self, category="Bangumi", status_filter="completed", tag=None
):
return await self.client.torrents_info(
status_filter=status_filter, category=category, tag=tag
)
@@ -137,7 +142,9 @@ class DownloadClient(TorrentPath):
async with RequestContent() as req:
if isinstance(torrent, list):
if len(torrent) == 0:
logger.debug(f"[Downloader] No torrent found: {bangumi.official_title}")
logger.debug(
f"[Downloader] No torrent found: {bangumi.official_title}"
)
return False
if "magnet" in torrent[0].url:
torrent_url = [t.url for t in torrent]
@@ -149,7 +156,9 @@ class DownloadClient(TorrentPath):
# Filter out None values (failed fetches)
torrent_file = [f for f in torrent_file if f is not None]
if not torrent_file:
logger.warning(f"[Downloader] Failed to fetch torrent files for: {bangumi.official_title}")
logger.warning(
f"[Downloader] Failed to fetch torrent files for: {bangumi.official_title}"
)
return False
torrent_url = None
else:
@@ -159,7 +168,9 @@ class DownloadClient(TorrentPath):
else:
torrent_file = await req.get_content(torrent.url)
if torrent_file is None:
logger.warning(f"[Downloader] Failed to fetch torrent file for: {bangumi.official_title}")
logger.warning(
f"[Downloader] Failed to fetch torrent file for: {bangumi.official_title}"
)
return False
torrent_url = None
# Create tag with bangumi_id for offset lookup during rename
@@ -175,10 +186,14 @@ class DownloadClient(TorrentPath):
logger.debug(f"[Downloader] Add torrent: {bangumi.official_title}")
return True
else:
logger.debug(f"[Downloader] Torrent added before: {bangumi.official_title}")
logger.debug(
f"[Downloader] Torrent added before: {bangumi.official_title}"
)
return False
except Exception as e:
logger.error(f"[Downloader] Failed to add torrent for {bangumi.official_title}: {e}")
logger.error(
f"[Downloader] Failed to add torrent for {bangumi.official_title}: {e}"
)
return False
async def move_torrent(self, hashes, location):

View File

@@ -44,7 +44,9 @@ class TitleParser:
@staticmethod
async def tmdb_poster_parser(bangumi: Bangumi):
tmdb_info = await tmdb_parser(bangumi.official_title, settings.rss_parser.language)
tmdb_info = await tmdb_parser(
bangumi.official_title, settings.rss_parser.language
)
if tmdb_info:
logger.debug(f"TMDB Matched, official title is {tmdb_info.title}")
bangumi.poster_link = tmdb_info.poster_link
@@ -98,9 +100,8 @@ class TitleParser:
offset=0,
filter=",".join(settings.rss_parser.filter),
)
except Exception as e:
logger.debug(e)
logger.warning(f"Cannot parse {raw}.")
except (ValueError, AttributeError, TypeError) as e:
logger.warning(f"Cannot parse '{raw}': {type(e).__name__}: {e}")
return None
@staticmethod