From 3cb741f66852b219f288151a83814022bb902473 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 29 Jun 2026 21:35:00 -0400 Subject: [PATCH] fix: DL-1 validate the int-path channel is a TextChannel; normalize fetch errors the construction-channel int path now applies the same isinstance(TextChannel) check the settings path enforces, so a Voice/Category/Forum channel fails loud at setup instead of AttributeError-ing on .send later. settings-path fetch_channel discord exceptions normalize to ValueError; README error contract updated to match. Signed-off-by: disqualifier --- README.md | 14 +++++++------- src/dpy_logger/dpy_logger.py | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7228d27..5151828 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,13 @@ await bot.log.debug("noisy", log_to_file=False) # -> Discord only ## Errors -Resolution failures (unresolvable guild/channel, bad config) raise from `initialize()` -— a misconfigured logger should fail loudly at setup. The raised type is usually -`ValueError`, but an underlying `discord` exception (`NotFound` / `Forbidden` / -`HTTPException`) from `fetch_guild`/`fetch_channel` can also propagate. On a **per-call** -send, neither resolution nor send failures propagate: they fall back to the stdlib -logger so a transient Discord failure (or a per-call `guild=` that doesn't resolve) -never breaks the caller's command. +Resolution failures (unresolvable guild/channel, bad config, a non-text channel) raise +`ValueError` from `initialize()` — a misconfigured logger should fail loudly at setup. +Underlying `discord` exceptions (`NotFound` / `Forbidden` / `HTTPException`) from +`fetch_guild`/`fetch_channel` are normalized to that `ValueError` so callers see one +error type. On a **per-call** send, neither resolution nor send failures propagate: they +fall back to the stdlib logger so a transient Discord failure (or a per-call `guild=` +that doesn't resolve) never breaks the caller's command. ## Construction contract diff --git a/src/dpy_logger/dpy_logger.py b/src/dpy_logger/dpy_logger.py index 09cd6c6..4e95cd6 100644 --- a/src/dpy_logger/dpy_logger.py +++ b/src/dpy_logger/dpy_logger.py @@ -139,16 +139,26 @@ class DPYLogger: if isinstance(self.channel, discord.TextChannel): return self.channel if isinstance(self.channel, int): - return await guild.fetch_channel(self.channel) + channel = await guild.fetch_channel(self.channel) + if not isinstance(channel, discord.TextChannel): + # fetch_channel can return a Voice/Category/Forum channel; fail loud + # at setup like the settings path, not later via an AttributeError on .send + raise ValueError(f"[dpy_logger] channel {self.channel} is not a text channel") + return channel try: channel_id = self.bot.settings[guild.id]["channels"]["logs"] - channel = await guild.fetch_channel(channel_id) - if not isinstance(channel, discord.TextChannel): - raise ValueError(f"[dpy_logger] configured channel {channel_id} is not a text channel") - return channel except KeyError: raise ValueError(f"[dpy_logger] no log channel configured for guild {guild.id}") + try: + channel = await guild.fetch_channel(channel_id) + except discord.HTTPException as error: + # fetch_channel raises NotFound/Forbidden/HTTPException; normalize to the + # lib's ValueError so a bad configured id fails loud with one error type + raise ValueError(f"[dpy_logger] could not fetch channel {channel_id}: {error}") from error + if not isinstance(channel, discord.TextChannel): + raise ValueError(f"[dpy_logger] configured channel {channel_id} is not a text channel") + return channel def build_embed(self, level, action, actor, details): """build the embed for a log call